1 pontos por GN⁺ 2024-07-07 | 1 comentários | Compartilhar no WhatsApp
  • À 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 printf e 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/i2c0 e 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 xTaskCreate e iniciar o escalonador com vTaskStartScheduler
    • Também é possível usar IPC com filas, por exemplo para passar o estado dos botões para uma tarefa de LEDs
  • 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ões
    • ledTask: define a cor RGB de um botão específico com base em mensagens de ledQueue
    • mainTask: executa o loop principal que atualiza o estado do projeto conforme a entrada dos botões
    • networkTask: se comunica com o módulo Wiznet
    • dhcpTask: criada por networkTask quando o cabo de rede é conectado
    • mdnsTask: criada por dhcpTask depois que um endereço IP é obtido
    • atemTask: criada quando o mDNS recebe resposta de um dispositivo ATEM
    • viscaTask: 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 printf travava 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/share e as variáveis também já existiam, mas o NuttX tentou sobrescrever o arquivo version.h do pico-sdk, e o build falhou por problema de permissão
  • Depois de compilar o firmware mínimo do NuttX e conectar à porta serial, apareceu um shell de verdade
    • Comandos como uptime, uname e uname -a funcionavam
    • A versão mostrada era NuttX 12.5.1 e o alvo era arm raspberrypi-pico
  • 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/i2c0 e /dev/adc0
  • 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.sh falhou, o repositório ficou em estado inconsistente e foi preciso clonar o repositório do NuttX várias vezes
    • Em alguns casos, até o distclean também não funcionava direito pelo mesmo motivo
  • 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-compile
    • CROSS_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 blinky nem 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
  • 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 printf provavelmente 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

 
GN⁺ 2024-07-07
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

    • Perguntando rapidamente: estou curioso se o desenvolvimento é para uma placa de prototipagem ou se é para criar firmware de um dispositivo OEM
      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
    • “Zephyr é fácil de usar” não bate com a minha experiência
      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
    • Por que deixar o mbed de fora? Essa é a parte “melhor”
  • 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

    • Hoje em dia, não uso nada que envolva ferramentas em Python. É raro demais funcionar bem na primeira tentativa
      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 --release
      Aqui, 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
    • Docker ou outras ferramentas de contêiner não seriam bons candidatos? Em vez de instalar a toolchain localmente, basta fazer o build em um contêiner baseado em uma imagem com a toolchain exata necessária
    • Depois de desenvolver apps Android, fiquei exigente nessa parte
      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'
    • Acho que as pessoas não escolhem Python por suas qualidades como linguagem para ferramentas. Desse ponto de vista, ele não é excelente, e binários linkados estaticamente são melhores
      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
    • Tenho uma ferramenta de linha de comando escrita em C# em 2011 que ainda simplesmente funciona bem
  • 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

    • Se for seguir esse caminho, recomendo começar com o crate rp2040-hal e, quando começar a sentir dor para gerenciar várias tarefas, dar uma olhada no Embassy ou no RTIC
      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
    • Vejo de forma parecida. Em geral, as pessoas migram para um RTOS por causa de bibliotecas e gerenciamento de dependências; com Rust, Cargo e crates.io, isso vem praticamente de fábrica
      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
    • Concordo totalmente. No geral, Rust embarcado tem sido uma ótima experiência para mim
      O assíncrono do executor do Embassy também funciona muito bem, aliviando bastante a dor do processo de projetar com RTOS
    • Concordo 100%. O Embassy é excelente e eu gosto muito dele
      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.yml do projeto
    Vá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

    • Se a pessoa não queria instalar o Zephyr SDK, compilar com a toolchain GNU Arm Embedded também deveria ser algo trivial; não sei bem por que não deu certo
  • O ThreadX, que hoje é open source, não foi mencionado
    https://github.com/eclipse-threadx/threadx/

    • O RIOT também pode ser outra alternativa
      https://github.com/RIOT-OS/RIOT
    • Não sei por que o ThreadX não aparece com mais frequência nesse tipo de lista. Ele é relativamente simples de usar e entender
  • 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/await funciona bem para mim
    Em um projeto recente, controlei 6 motores de passo, vários LEDs e varredura de botões; para o usuário, parecia tempo real

    • Esses minicomputadores são muito mais potentes que os computadores domésticos de 8 e 16 bits, então o MicroPython consegue muito bem substituir os casos de uso de BASIC dos sistemas iniciais
      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
    • Em casos sem requisitos fortes de tempo real, como um controlador PTZ, e com baixo uso de memória pela aplicação, o MicroPython parece uma escolha produtiva
      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
    • Nesse caso, não seria melhor assumir que os sinais dos motores de passo não são gerados diretamente pelo código Python, mas por periféricos de hardware, como o RMT do ESP32?
      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
    • Sim, isso parece ser um projeto simples em MicroPython
  • 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

    • Melhor ainda é usar contêineres. Não uso máquinas virtuais há anos e não sinto a menor falta
    • Nix e flakes ajudariam a ter ambientes de build reproduzíveis?
    • Sinto essa dor
      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

    • Que soluções existem para fazer 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