Migrando para um RTOS no RP2040
(blog.brixit.nl)- À medida que um controlador de equipamentos de vídeo baseado em Raspberry Pi Pico passou a exigir muitas tarefas simultâneas, a estrutura de software ficou difícil demais de sustentar apenas com o pico-sdk e a separação entre dois núcleos
- O controlador lida ao mesmo tempo com VISCA via RS-485, Ethernet, 9 botões RGB, joystick e display, além de precisar tratar DHCP, mDNS e o protocolo ATEM baseado em UDP
- O FreeRTOS permitiu implementar parte disso com escalonador e comunicação entre tarefas, mas problemas de travamento com
printfe a falta de abstração de hardware dificultaram a depuração e a reutilização de código - O Apache NuttX oferecia shell, sistema de arquivos, abstração de dispositivos como
/dev/i2c0e configuração via Kconfig, mas os problemas com I2C depois foram corrigidos como erro de configuração, então boa parte da avaliação feita na época pode ser inválida - O Zephyr esbarrou em um repositório de 5 GB, SDK, definições de placa e complexidade de build, e no fim a decisão tendeu a continuar tentando com o FreeRTOS, que era o mais simples de integrar ao ambiente já existente
Por que um RTOS passou a ser necessário
- Vários pequenos projetos com microcontroladores estão sendo criados em torno da placa Raspberry Pi Pico
- O Pico facilita a integração com IDE graças a um SDK adequado, hardware barato e suporte a depuração com gdb/openocd
- O projeto atual é um controlador de hardware para controlar vários equipamentos de vídeo
- Os dispositivos controlados são duas câmeras PTZ motorizadas, uma câmera fixa e um equipamento de comutação de vídeo conectado
- O controlador PTZ existente é um painel sem marca parecido com o Marshall VS-PTC-200
- Há alguns anos ele custava €650, mas o toque dos botões e a qualidade do joystick analógico deixam a desejar
- Muitos botões não funcionam com as câmeras usadas atualmente, e ele parece ter sido otimizado para câmeras de segurança
- A conexão com as câmeras é feita por um barramento RS-485
- O controle do switcher de vídeo ATEM hoje é feito apenas pelo painel de software em um computador
- Os painéis de hardware da Blackmagic Design são muito caros
Composição do hardware do controlador
- O projeto mínimo foi definido com 9 botões, um joystick e um display para a interface do usuário
- Depois de várias iterações de hardware ao longo de um ano, a PCB ficou pronta
- 9 botões RGB
- Um joystick de $10 que também estava no painel clonado da Marshall
- TP8485E para comunicação RS-485 com as câmeras PTZ
- Módulo Wiznet W5500 para comunicação com o switcher de vídeo via Ethernet
- Depois de corrigir a placa, todos os componentes de hardware passaram a funcionar, mas a parte mais difícil acabou sendo o software
Quando o pico-sdk sozinho deixou de dar conta
- O projeto começou como outros projetos para RP2040, trazendo o pico-sdk em um projeto cmake
- Para chegar a algo funcional, o segundo núcleo do Pico foi dedicado ao módulo Wiznet, enquanto o primeiro cuidava da E/S da interface do usuário
- Foi possível implementar até o piscar de LED e um cliente DHCP rodando no segundo núcleo
- O restante do sistema ficou muito mais complexo
- A quantidade de tarefas que precisavam rodar ao mesmo tempo cresceu rapidamente
- Desenhar uma interface de usuário razoavelmente fluida no display
- Enviar comandos VISCA pela interface RS-485
- Responder à entrada dos botões
- Manter a pilha de rede com várias conexões
- A parte de rede exigia tarefas de fundo separadas
- O suporte correto a DHCP precisa acompanhar tempos de expiração e falar ocasionalmente com o servidor DHCP para manter o lease
- mDNS é necessário para descobrir automaticamente o IP do switcher de vídeo ATEM, e seria útil também anunciar a presença do painel de controle
- O protocolo ATEM é simples, mas às vezes recebe dados maiores que o buffer do módulo Wiznet, e se o envio de datagramas UDP parar, o timeout para desconexão é muito curto
- Nessas condições, parecia mais apropriado dividir o trabalho com um RTOS do que continuar empilhando loops manualmente
FreeRTOS: simples, mas com pouca abstração
- O FreeRTOS tecnicamente já está incluído no pico-sdk, mas os tutoriais usavam o método de baixar uma nova cópia, e foi isso que acabou sendo seguido
- Entre os RTOS avaliados, ele parecia o mais simples, oferecendo principalmente um escalonador e comunicação entre tarefas
- A estrutura consiste em criar tarefas com
xTaskCreatee iniciar o escalonador comvTaskStartScheduler - Também é possível usar IPC com filas, por exemplo para passar o estado dos botões para uma tarefa de LEDs
- A estrutura consiste em criar tarefas com
- Depois de alguns dias de uso, a base de código já estava dividida em várias tarefas, mesmo ainda tendo pouca funcionalidade real
buttonsTask: faz polling do expansor GPIO via I2C para verificar entradas dos botões e coloca mensagens na fila de botõesledTask: define a cor RGB de um botão específico com base em mensagens deledQueuemainTask: executa o loop principal que atualiza o estado do projeto conforme a entrada dos botõesnetworkTask: se comunica com o módulo WiznetdhcpTask: criada pornetworkTaskquando o cabo de rede é conectadomdnsTask: criada pordhcpTaskdepois que um endereço IP é obtidoatemTask: criada quando o mDNS recebe resposta de um dispositivo ATEMviscaTask: ainda não faz nada, mas precisa enviar dados pela porta RS-485
- Mesmo sem fazer praticamente nada além de aparecer na rede, o hardware já acumulava muitas tarefas
- O problema mais incômodo era que o
printftravava o sistema o tempo todo- O depurador gdb funcionava, mas não era adequado para verificar tráfego DHCP imprimindo logs
- O FreeRTOS não oferece abstração de hardware, então o código escrito para se comunicar com vários chips não é fácil de reutilizar
- Mais tarde houve a tentativa de criar um novo projeto FreeRTOS limpo e migrar as funcionalidades, mas a necessidade de depurar às cegas, sem saída serial, levou à busca por outras opções
Apache NuttX: estrutura ao estilo Unix e erros de configuração
- O Apache NuttX parece mais próximo de um sistema operacional tradicional, tratando o microcontrolador como um sistema Unix
- O tutorial orientava baixar o pico-sdk e configurar variáveis de ambiente
- O SDK já estava em
/usr/sharee as variáveis também já existiam, mas o NuttX tentou sobrescrever o arquivoversion.hdo pico-sdk, e o build falhou por problema de permissão
- O SDK já estava em
- Depois de compilar o firmware mínimo do NuttX e conectar à porta serial, apareceu um shell de verdade
- Comandos como
uptime,unameeuname -afuncionavam - A versão mostrada era
NuttX 12.5.1e o alvo eraarm raspberrypi-pico
- Comandos como
- Como segue a abordagem Unix, parecia possível escrever uma aplicação para executar automaticamente no boot
- Há um sistema de arquivos, e o hardware é exposto com abstrações como
/dev/i2c0e/dev/adc0
- Há um sistema de arquivos, e o hardware é exposto com abstrações como
- Um ponto positivo foi a configuração baseada em menuconfig/Kconfig
- É uma abordagem familiar para quem vem do desenvolvimento Linux
- Existe um sistema real de drivers de hardware, e o driver do chip expansor GPIO usado nos botões já estava disponível
- A configuração de pin mux do RP2040 também pode ser definida no menuconfig, sem precisar manter constantes separadas de número de pino ou escrever muito código de inicialização do barramento I2C
- Utilitários de teste de I2C também podem ser incluídos no firmware
- No começo, pareceu que o funcionamento básico de I2C não operava
- Em uma atualização posterior, isso foi corrigido: na verdade funcionava, e o barramento I2C havia sido quebrado por erro de configuração
- O texto deixa claro que, por isso, grande parte da avaliação do NuttX nessa seção pode ser inválida
- Na época, ainda não estava claro como informar ao NuttX que os botões GPIO ficavam atrás de um expansor GPIO, nem como ligar esse expansor ao barramento I2C
- Depois que o
configure.shfalhou, o repositório ficou em estado inconsistente e foi preciso clonar o repositório do NuttX várias vezes- Em alguns casos, até o
distcleantambém não funcionava direito pelo mesmo motivo
- Em alguns casos, até o
- A abordagem Unix-like pareceu interessante no início, mas não havia vontade de lidar com caminhos de sistema de arquivos falsos em um microcontrolador
- Um shell também não é necessário em um sistema de produção; basta o código rodar
Zephyr: SDK grande e barreira das definições de placa
- A escolha seguinte foi o Zephyr, que oferece utilitários em Python para configuração do projeto
- Logo no primeiro passo, ele baixa cerca de 5 GB de repositórios Git
- Isso inclui bibliotecas HAL para muitos chips
- E também afeta a configuração global de cmake do usuário
- O tutorial exige instalar o SDK do Zephyr
- O SDK do Zephyr inclui toolchains, compiladores, assembler, linker e ferramentas de build para cada arquitetura suportada
- Também inclui ferramentas de host como QEMU e OpenOCD
- Como já existiam várias toolchains ARM, não havia interesse em compilar ou baixar binários prontos de compiladores para todas as arquiteturas
- Para compilar sem o SDK do Zephyr, foram definidas opções de cross-compilation
ZEPHYR_TOOLCHAIN_VARIANT=cross-compileCROSS_COMPILE=/usr/bin/arm-none-eabi-west build -p always -b sparkfun_pro_micro_rp2040 samples/basic/blinky
- O Raspberry Pi Pico na prática não é suportado, e só há suporte para outras placas com o mesmo SoC
- Como o SoC era o mesmo, a ideia foi seguir em frente assumindo que a placa seria parecida na prática
- Logo surgiu um problema: a demo
blinkynem sequer compilava- A demo exige a definição de
led0, que representa o LED que deve piscar - A Sparkfun Pro Micro RP2040 não tem um LED GPIO simples, e sim um LED endereçável WS2812B
- A demo exige a definição de
- Depois disso, foi seguido o manual de placa customizada copiando a definição de outra placa, mas mesmo após corrigir erros e avisos de build, não foi possível gerar um build com sucesso para a placa alvo
No fim, de volta ao FreeRTOS
- Entre os três RTOS, o único com o qual de fato foi possível compilar parte da aplicação real foi o FreeRTOS
- O problema com
printfprovavelmente terá de ser resolvido substituindo a implementação de printf e chamando outra função onde for necessário, como sugerem orientações encontradas online - O plano agora é continuar tentando com o FreeRTOS
- Entre as opções avaliadas, ele foi a única que conseguiu se integrar de forma relativamente simples ao ambiente já existente, em vez de exigir que todo o ambiente de desenvolvimento se adaptasse ao RTOS
1 comentários
Opiniões do Hacker News
Parece que o autor vê um RTOS como algo igual ao ambiente Arduino, ou como algo em que dá para mexer por tentativa e erro e esperar que funcione. Na maioria das vezes, não é assim
Hoje em dia, muitos Arduinos têm mbed ou FreeRTOS por baixo, e há formas de expor isso, então talvez esse caminho combinasse melhor com o estilo do autor
O Zephyr é fácil de usar e tem bom suporte no CLion, mas não dá para esperar que tudo funcione sem instalar a toolchain. Ele também dá suporte claro ao Pi Pico e, quando usei diretamente, não tive problemas
Resumindo: o FreeRTOS é suportado em praticamente todo lugar, mas os drivers em geral são específicos de SoC/dispositivo, o que dá trabalho, e a API também não é muito amigável, embora dê para se acostumar. Se quiser usar Bluetooth, é preciso procurar uma stack por conta própria
O Zephyr oferece abstração real de hardware e suporta a maioria dos SoCs, mas pode ser necessário fazer algum trabalho na placa. A stack Bluetooth é fornecida, e talvez seja preciso adicionar um pouco de suporte a HCI
O NuttX não tem um suporte excelente, mas, se você conseguir fazê-lo funcionar, é uma opção bem interessante. O suporte da indústria ainda não é forte. Também existe o mbed, mas vou deixar de lado aqui
No mundo real de RTOS, normalmente se escolhe o que o fornecedor do SoC suporta. Se for Nordic, Zephyr; se for NXP, FreeRTOS, e assim por diante, porque é assim que se consegue bom suporte
Nunca vi, em um projeto de firmware para dispositivo OEM, desenvolvedores não sofrerem com Zephyr. Também ainda não encontrei nenhum desenvolvedor criando firmware real para um produto lançado que ache que a abstração de hardware do Zephyr ajuda
Não quer dizer que essas pessoas não existam, mas nos últimos cinco anos, mais ou menos, não cruzei com nenhuma
Para mim, o Zephyr tem materiais de marketing muito bonitos. Mas por trás desse brilho há inchaço excessivo, compilação muito lenta e um ambiente difícil de começar a usar
Instalar a toolchain no sistema inteiro, no estilo tradicional do UNIX, é doloroso e, sinceramente, nem é uma abordagem muito inteligente
Se tudo funciona bem para uma pessoa só, ótimo, mas, quando vários desenvolvedores lidam com vários projetos com alvos diferentes, muito tempo acaba sendo gasto para entender problemas de build e configuração
Continuar usando Python nas ferramentas também não ajuda. Não entendo por que insistir em usar uma linguagem que traz seus próprios problemas de versão e se comporta de forma diferente em cada máquina de desenvolvedor
Faço desenvolvimento embarcado como hobby e profissão há cerca de 10 anos, e não consigo entender a postura de gastar uma semana igualando o ambiente de todo mundo em um projeto e ainda assim não ver isso como um problema
Isso é um problema real, irritante, desperdiça tempo e é desnecessário
As ferramentas deveriam ser binários linkados estaticamente. Não importa se são escritas em Rust, Go, C ou C++, mas eu gostaria que se priorizassem ferramentas robustas, nas quais se possa confiar que vão funcionar da mesma forma independentemente do que está instalado no computador, em vez de desenvolvimento com gambiarras
Python não entrega isso, e ficar irritado e defensivo em vez de levar esse problema mais a sério também não ajuda
Ainda assim, coisas como o PlatformIO vão na direção certa. Sei que é um projeto em Python e que isso às vezes causa problemas, mas menos do que outras ferramentas, e a ideia está correta
Toolchains, SDKs e bibliotecas precisam ser gerenciados, a configuração do projeto deve ser simples, e o build deve ser reproduzível em qualquer lugar, sempre
Eu gostaria que a indústria de embarcados enxergasse melhor o valor de um esforço comum de estruturação. Conheço muita gente que trabalha em grandes fabricantes de MCUs, e em geral eles estão tão ocupados resolvendo os próprios problemas imediatos que parecem ter uma visão estreita das necessidades dos desenvolvedores, o que é sempre decepcionante
Tenho um projeto de teclado que roda no RP2040, e o firmware é em Rust. Partindo apenas do repositório, sem a toolchain Rust instalada, o procedimento para gravar é o seguinte
(instalar o rustup)
$ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh$ rustup target add thumbv6m-none-eabi$ cargo install elf2uf2-rs* colocar o teclado em modo bootloader *$ cargo run --releaseAqui, rustup e Cargo cuidam da maior parte do gerenciamento da toolchain e do build, e os dois são realmente excelentes. Projetos em Python nem chegam perto
Basta executar um único comando e, seja localmente ou no CI, tudo é configurado e funciona exatamente da mesma forma, com as mesmas versões das ferramentas de build e das bibliotecas
Por isso, toda vez que volto a lidar com CMake e Make e a brigar para instalar bibliotecas, sofro. É muito diferente de algo como
compile 'library-name-here'Em vez disso, escolhem por causa do ecossistema de bibliotecas. Em análise de dados, visualização e computação científica, não há opções comparáveis em outras linguagens
Essas são áreas que ninguém quer reimplementar do zero. É fácil introduzir bugs em coisas como estabilidade numérica, fazendo com que 99% dos resultados estejam corretos e 1% pareça plausível, mas esteja completamente errado
Pessoalmente, comecei a migrar meus projetos com RP2040 para Rust e Embassy
Rust exigiu um pouco de adaptação, mas gostei bastante. Não é um RTOS, mas atende a muitas das necessidades que levam alguém a precisar de um RTOS
Rust e Cargo eliminam o sofrimento de compilar e gravar firmware para RP2040 ou STM32. Foi o ambiente embarcado mais agradável que já usei até agora
Muitas aplicações não usam MPU. Some a isso a segurança de memória do Rust e a menor complexidade geral do firmware sem um RTOS
O assíncrono do executor do Embassy também funciona muito bem, aliviando bastante a dor do processo de projetar com RTOS
Quando se adiciona até a interface PIO para o RP2040, o código fica muito simples e elegante, em uma configuração difícil de alcançar com outros processadores
O Pi Pico é 100% suportado pelo Zephyr. Será que não verificaram a documentação? https://github.com/zephyrproject-rtos/zephyr/tree/main/board... https://docs.zephyrproject.org/latest/boards/raspberrypi/rpi...
Além disso, em muitas situações, o uso pretendido não é ter uma única instalação “main” do Zephyr, mas incluir os módulos externos necessários no
west.ymldo projetoVários projetos compartilharem a mesma instalação do Zephyr é outra discussão, mas instalar todas as toolchains e HALs possíveis não é o único caminho
O ThreadX, que hoje é open source, não foi mencionado
https://github.com/eclipse-threadx/threadx/
https://github.com/RIOT-OS/RIOT
Gostei da comparação das opções de RTOS
Pessoalmente, vejo o MicroPython como um caminho mais fácil. A multitarefa cooperativa baseada em
async/awaitfunciona bem para mimEm um projeto recente, controlei 6 motores de passo, vários LEDs e varredura de botões; para o usuário, parecia tempo real
Ainda me surpreende que muita gente não entenda de fato quão poucos recursos tínhamos naquela época e, mesmo assim, usávamos linguagens de alto nível
Quando usei MicroPython no ESP32, gostei do suporte a manipuladores de interrupção de um jeito bem “Pythonico”. Imagino que o RP2040 tenha suporte semelhante
No meu microscópio, uma placa FluidNC cuida do controle dos motores de passo quase em tempo real, e eu controlo tudo por um protocolo serial leve
Porém, estou olhando https://pypi.org/project/micropython-stepper/, e isso parece usar temporizadores de hardware e código Python
Quero muito experimentar o Hubris em um projeto de verdade (https://hubris.oxide.computer/reference/)
A abordagem de arquitetura combina bastante com o que eu busco na área embarcada. Só que eu faço isso de um jeito mais sofrido, em C
Também não é tão diferente da forma como isso é feito com Erlang/Elixir em ambientes hospedados
Em situações com memória mais limitada, em que não dá para bancar várias pilhas, o Embassy parece ser uma boa opção
Todo novo projeto embarcado deve sempre, sempre mesmo, começar em uma máquina virtual. Não se deve misturar ferramentas no mesmo sistema
No meu projeto comercial, essa foi a principal causa de problemas de qualidade
Se você vai começar um projeto com um novo chipset ou um novo fornecedor, crie uma VM nova, instale nela apenas as ferramentas do fornecedor e compile por lá
Desenvolvimento experimental pode ser feito na sua máquina local não virtualizada. Mas releases precisam obrigatoriamente ser feitos na VM. E, por favor, mantenha a VM e a estação de trabalho de desenvolvimento sincronizadas
No momento, estou sofrendo muito para lidar com uma build de firmware que precisa ser corrigida enquanto o desenvolvedor original está de férias. Ninguém consegue acessar a estação de trabalho dele, a VM preparada está 6 meses defasada, e o cliente está perguntando por que deveria pagar pelo custo de uma equipe inteira para algo que, aparentemente, só um programador especial no planeta consegue fazer
Acho que releases devem ser compilados pelo sistema de CI. Ao marcar uma tag de release no git, os testes devem passar e então sair um binário
Ao escolher FreeRTOS, é difícil errar feio. A esta altura, ele é praticamente o padrão da indústria
printf()funcionar?Parece ser bem trabalhoso
Tive uma experiência parecida com a do post original
Por isso, criei por conta própria um timer simples de green threads
Ele não oferece gerenciamento real de processos como um kernel de verdade, nem dá qualquer garantia, mas me permitiu ir além do agendamento bare-metal e evitar a bagunça que é um RTOS
Pense nele como callbacks de timer em JavaScript que recebem uma struct de contexto opcional em C
Foi possível consultar vários sensores, processar sinais de entrada, tomar decisões de controle e emitir comandos, cada um em frequências diferentes
Antes de arruinar a vida com uma arquitetura lenta e abstrata dessas, recomendo fortemente tentar primeiro esse tipo de abordagem