Transición de RP2040 a un RTOS
(blog.brixit.nl)- A medida que un controlador de equipos de video basado en Raspberry Pi Pico empezó a exigir muchas tareas concurrentes, la estructura de software se volvió difícil de sostener solo con pico-sdk y la separación de doble núcleo
- El controlador maneja en conjunto RS-485 VISCA, Ethernet, 9 botones RGB, un joystick y una pantalla, y además necesita procesar DHCP, mDNS y el protocolo ATEM basado en UDP
- FreeRTOS permitió implementar algunas partes con un scheduler y comunicación entre tareas, pero los cuelgues con printf y la falta de abstracción de hardware dificultaron la depuración y la reutilización de código
- Apache NuttX ofrecía una shell, sistema de archivos, abstracciones de dispositivos como
/dev/i2c0y configuración con Kconfig, pero el problema de I2C luego se corrigió como un error de configuración, por lo que buena parte de la evaluación de ese momento podría no ser válida - Zephyr quedó bloqueado por un repositorio y SDK de 5 GB, definiciones de placa y complejidad de compilación; al final, la decisión se inclinó por seguir intentando con FreeRTOS, que era lo más simple de integrar al entorno existente
Por qué hizo falta un RTOS
- Varios proyectos pequeños con microcontroladores se están construyendo alrededor de la placa Raspberry Pi Pico
- La Pico facilita la integración con IDE gracias a un SDK adecuado, hardware barato y soporte de depuración con gdb/openocd
- El proyecto actual es un controlador de hardware para controlar varios equipos de video
- Los dispositivos a controlar son 2 cámaras PTZ motorizadas, 1 cámara fija y un equipo de conmutación de video conectado
- El controlador de cámaras PTZ existente es un panel sin marca parecido al Marshall VS-PTC-200
- Hace unos años costaba €650, pero la sensación de los botones y la calidad del joystick analógico no son buenas
- Muchos botones no funcionan con las cámaras que se usan actualmente, y parece estar optimizado para cámaras de seguridad
- Se conecta con las cámaras mediante un bus RS-485
- El control del switcher de video ATEM actualmente se hace solo con un panel de software en una computadora
- Los paneles de hardware de Blackmagic Design son muy caros
Configuración de hardware del controlador
- El diseño mínimo quedó definido como 9 botones, un joystick y una pantalla para la interfaz de usuario
- Después de varias iteraciones de diseño de hardware durante 1 año, se obtuvo una PCB
- 9 botones RGB
- Un joystick de $10 que también estaba en el panel clon de Marshall
- TP8485E para comunicarse por RS-485 con las cámaras PTZ
- Un módulo Wiznet W5500 para comunicarse por Ethernet con el switcher de video
- Tras las modificaciones de la placa, todos los componentes de hardware quedaron funcionando, pero la parte más difícil fue el software
Software que se volvió demasiado pesado solo con pico-sdk
- Como en proyectos anteriores con RP2040, se empezó trayendo pico-sdk dentro de un proyecto cmake
- Para llegar a algo funcional, se asignó el segundo núcleo de la Pico al manejo del módulo Wiznet, y el primer núcleo al I/O de la interfaz de usuario
- Se logró hacer parpadear un LED e implementar un cliente DHCP ejecutándose en el segundo núcleo
- Implementar el resto del sistema se volvió mucho más complejo
- Las tareas que debían procesarse en simultáneo crecieron rápidamente
- Dibujar una interfaz de usuario más o menos fluida en la pantalla
- Enviar comandos VISCA por la interfaz RS-485
- Reaccionar a las entradas de los botones
- Mantener una pila de red con varias conexiones
- La parte de red requiere trabajo adicional en segundo plano
- Un soporte DHCP conforme al estándar debe rastrear tiempos de expiración y comunicarse de vez en cuando con el servidor DHCP para mantener el estado del lease
- mDNS es necesario para descubrir automáticamente la IP del switcher de video ATEM, y sería bueno también poder anunciar la presencia del panel de control
- El protocolo ATEM es simple, pero a veces recibe datos que superan el tamaño del búfer del módulo Wiznet, y si se detiene el envío de datagramas UDP, el timeout de desconexión es muy corto
- En estas condiciones, parecía más adecuado dividir el trabajo con un RTOS que seguir apilando loops propios
FreeRTOS: simple, pero con poca abstracción
- FreeRTOS está técnicamente incluido en pico-sdk, pero los tutoriales usan el método de descargar una copia nueva, así que se siguió ese camino
- De los RTOS revisados, parecía el más simple, y principalmente ofrece un scheduler y comunicación entre tareas
- La estructura consiste en crear tareas con
xTaskCreatee iniciar el scheduler convTaskStartScheduler - Se puede usar IPC con colas, por ejemplo para pasar el estado de los botones a una tarea de LEDs
- La estructura consiste en crear tareas con
- Después de usarlo algunos días, la base de código quedó dividida en varias tareas, aunque todavía tenía poca funcionalidad real
buttonsTask: sondea el expansor GPIO por I2C para revisar entradas de botones y coloca mensajes en la cola de botonesledTask: establece el color RGB de un botón específico a partir de mensajes deledQueuemainTask: ejecuta el loop principal que actualiza el estado del proyecto según las entradas de botonesnetworkTask: se comunica con el módulo WiznetdhcpTask: la creanetworkTaskcuando se conecta el cable de redmdnsTask: la creadhcpTaskdespués de obtener una dirección IPatemTask: se crea cuando mDNS recibe una respuesta de un dispositivo ATEMviscaTask: todavía no hace nada, pero debería enviar datos por el puerto RS-485
- Aunque el hardware todavía no hace nada más que aparecer en la red, la cantidad de tareas ya creció bastante
- El problema más molesto es que printf se cuelga cada vez
- El depurador gdb funciona, pero no es adecuado para revisar imprimiendo tráfico DHCP
- FreeRTOS no ofrece abstracción de hardware, por lo que es difícil reutilizar fácilmente el código escrito para comunicarse con varios chips
- Más adelante se intentó crear un proyecto FreeRTOS limpio y mover las funcionalidades, pero por la incomodidad de depurar a ciegas sin salida serial, se exploraron otras opciones
Apache NuttX: estructura estilo Unix y un error de configuración
- Apache NuttX se parece más a un sistema operativo general, y trata a los microcontroladores como sistemas Unix
- El tutorial indica traer pico-sdk y configurar variables de entorno
- Aunque ya había un SDK en
/usr/sharey también estaban las variables de entorno, NuttX intentó sobrescribir el archivoversion.hde pico-sdk, y la compilación falló por un problema de permisos
- Aunque ya había un SDK en
- Después de compilar un firmware mínimo de NuttX y conectarse al puerto serial, apareció una shell real
- Comandos como
uptime,unameyuname -afuncionaban - La versión mostrada era
NuttX 12.5.1y el destino eraarm raspberrypi-pico
- Comandos como
- Como sigue el enfoque Unix, parecía posible escribir una aplicación y ejecutarla automáticamente al arrancar
- Hay sistema de archivos y el hardware se expone mediante abstracciones como
/dev/i2c0y/dev/adc0
- Hay sistema de archivos y el hardware se expone mediante abstracciones como
- Una parte que gustó fue la configuración basada en menuconfig/Kconfig
- Es un método familiar para quien desarrolla en Linux
- Hay un sistema real de drivers de hardware, y ya existe un driver para el chip expansor GPIO usado en los botones
- La configuración de mux de pines del RP2040 también puede definirse en menuconfig, así que no hace falta mantener constantes de números de pin por separado ni escribir mucho código de inicialización del bus I2C
- También se puede incluir en el firmware una utilidad de prueba de I2C
- Al principio parecía que la operación básica de I2C no funcionaba
- En una actualización posterior se corrigió que en realidad funcionaba bien, y que el bus I2C se había roto por un error de configuración
- Se indicó que probablemente la mayor parte del resto de la evaluación de esa sección sobre NuttX no era válida
- En ese momento no se logró entender cómo decirle a NuttX que los botones GPIO estaban detrás de un expansor GPIO, ni cómo conectar el expansor GPIO con el bus I2C
- Tras fallar
configure.sh, el repositorio quedó en un estado inconsistente, por lo que hubo que volver a clonar el repositorio de NuttX varias veces- También hubo situaciones en las que
distcleanno funcionaba correctamente por el mismo motivo
- También hubo situaciones en las que
- El enfoque Unix-like parecía bueno al principio, pero no se quería lidiar con rutas de sistema de archivos falsas en un microcontrolador
- En un sistema de producción tampoco se necesita una shell; basta con que se ejecute el código
Zephyr: un SDK grande y la barrera de las definiciones de placa
- La siguiente opción fue Zephyr, que ofrece una utilidad en Python para configurar proyectos
- En el primer paso trae un repositorio Git de unos 5 GB
- Incluye bibliotecas HAL para muchos chips
- También afecta la configuración global de cmake del usuario
- El tutorial exige instalar el Zephyr SDK
- El Zephyr SDK incluye toolchains, compiladores, ensambladores, linkers y herramientas de build para cada arquitectura soportada
- También incluye herramientas host como QEMU y OpenOCD
- Como ya había varias toolchains de ARM, no se quería compilar ni descargar compiladores precompilados para todas las arquitecturas
- Para compilar sin el Zephyr SDK, se configuraron opciones 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
- La Raspberry Pi Pico en realidad no está soportada, solo otras placas con el mismo SoC
- Como usan el mismo SoC, se siguió adelante considerándolas prácticamente similares
- De inmediato surgió un problema: la demo
blinkyno compilaba- La demo requiere una definición de
led0, que es el LED a hacer parpadear - La Sparkfun Pro Micro RP2040 no tiene un LED GPIO simple, sino un LED direccionable WS2812B
- La demo requiere una definición de
- Se siguió el manual de placas personalizadas copiando la definición de otra placa, pero incluso después de corregir errores y advertencias de compilación no se logró compilar para la placa objetivo
Al final, de vuelta a FreeRTOS
- De los tres RTOS, FreeRTOS fue el único con el que se compiló parte de la aplicación real
- Es probable que el problema de printf deba resolverse reemplazándolo por otra implementación de printf, como sugieren guías en línea, y llamando a otra función donde haga falta
- De ahora en adelante, el plan es seguir intentando con FreeRTOS
- Porque fue la única opción que podía integrarse de forma relativamente simple al entorno existente, en vez de adaptar el entorno de desarrollo al RTOS como con los otros RTOS
1 comentarios
Opiniones de Hacker News
Parece que el autor veía a RTOS como algo igual al entorno de Arduino, o como algo que uno puede trastear un poco y esperar que funcione. En la mayoría de los casos no es así.
Hoy en día muchos Arduino llevan mbed o FreeRTOS por debajo, y también hay formas de exponerlos, así que para el estilo del autor quizá eso habría sido mejor.
Zephyr es fácil de usar y tiene buen soporte en CLion, pero no se puede esperar que todo funcione sin instalar la cadena de herramientas. También soporta claramente la Pi Pico, y cuando la usé directamente no tuve problemas.
En resumen: FreeRTOS tiene soporte casi en todas partes, pero los drivers suelen ser específicos de cada SoC/dispositivo, así que es engorroso, y la API tampoco es muy amigable, aunque uno puede acostumbrarse. Si quieres usar Bluetooth, tienes que buscar el stack por tu cuenta.
Zephyr soporta una abstracción real del hardware y la mayoría de los SoC, pero puede que tengas que trabajar un poco en la placa. Incluye un stack de Bluetooth, aunque quizá tengas que añadir algo de soporte HCI.
NuttX no tiene un soporte excelente, pero si logras hacerlo funcionar, es una opción bastante interesante. Todavía no tiene un apoyo fuerte de la industria. También está mbed, pero lo omito aquí.
En el mundo real de RTOS, normalmente se elige lo que soporte el proveedor del SoC. Si es Nordic, Zephyr; si es NXP, FreeRTOS, y así, porque de esa forma puedes recibir buen soporte.
Nunca he visto un proyecto de firmware para un dispositivo OEM en el que los desarrolladores no sufrieran con Zephyr. Tampoco he conocido todavía a ningún desarrollador que haga firmware real para un producto lanzado al mercado y piense que la abstracción de hardware de Zephyr le ayuda.
No digo que esa gente no exista, pero en los últimos cinco años más o menos no me he cruzado con nadie así.
A mi parecer, Zephyr tiene materiales de marketing muy bonitos. Pero detrás de ese brillo hay una hinchazón excesiva, compilaciones muy lentas y un entorno difícil para empezar.
Instalar la cadena de herramientas en todo el sistema, al estilo tradicional de UNIX, es doloroso y, sinceramente, tampoco es un enfoque muy inteligente.
Si trabajas solo puede estar bien, pero cuando varios desarrolladores manejan varios proyectos con distintos objetivos, se pierde mucho tiempo entendiendo problemas de compilación y configuración.
Seguir usando Python para las herramientas tampoco ayuda. No entiendo por qué insistir en un lenguaje que trae sus propios problemas de versiones y se comporta distinto en cada computadora de desarrollador.
Llevo unos 10 años haciendo desarrollo embebido como hobby y como trabajo, y no entiendo la actitud de dedicar una semana a igualar el entorno de todos en un proyecto y aun así no verlo como un problema.
Esto es un problema real, es molesto, hace perder tiempo y es innecesario.
Las herramientas deberían ser binarios enlazados estáticamente. No importa si están escritas en Rust, Go, C o C++, pero me gustaría que se prioricen herramientas sólidas en las que puedas confiar para que funcionen igual sin depender de lo que esté instalado en la computadora, en vez de desarrollo con parches improvisados.
Python no te da eso, y tampoco ayuda enojarse y ponerse a la defensiva en vez de tomarse este problema más en serio.
Aun así, cosas como PlatformIO van en la dirección correcta. Sé que es un proyecto en Python y a veces causa problemas, pero menos que otras herramientas, y la idea es correcta.
Las cadenas de herramientas, los SDK y las bibliotecas deben gestionarse; la configuración del proyecto debe ser simple; y las compilaciones deben ser reproducibles en cualquier lugar y en cualquier momento.
Ojalá la industria embebida despertara más al valor de un esfuerzo común de estructuración. Conozco a mucha gente que trabaja en grandes fabricantes de MCU, y en general me decepciona ver que están tan ocupados resolviendo sus problemas inmediatos que parecen tener una visión estrecha sobre las necesidades de los desarrolladores.
Tengo un proyecto de teclado que funciona en RP2040 y cuyo firmware está hecho en Rust. Si tienes solo el repositorio y no tienes la cadena de herramientas de Rust, el procedimiento para flashearlo es este:
(instalar rustup)
$ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh$ rustup target add thumbv6m-none-eabi$ cargo install elf2uf2-rs* poner el teclado en modo bootloader *$ cargo run --releaseAquí, rustup y Cargo se encargan de la mayor parte de la gestión de la cadena de herramientas y de la compilación, y ambos son realmente excelentes. Los proyectos en Python ni se les acercan.
Con ejecutar un solo comando, tanto en local como en CI, todo se configura y funciona exactamente igual, con la misma versión de las herramientas de compilación y de las bibliotecas.
Por eso sufro cada vez que vuelvo a lidiar con CMake y Make y a pelearme para instalar bibliotecas. Es muy distinto de algo como
compile 'library-name-here'.Más bien lo eligen por su ecosistema de bibliotecas. En análisis de datos, visualización y computación científica no hay alternativas comparables en otros lenguajes.
Esas son áreas que uno no quiere reimplementar desde cero. Es fácil introducir bugs en cosas como la estabilidad numérica, y puedes obtener resultados correctos el 99% de las veces y, el 1% restante, resultados plausibles pero completamente equivocados.
Personalmente, empecé a migrar mis proyectos con RP2040 a Rust y Embassy
Rust me tomó algo de adaptación, pero me gusta bastante. No es un RTOS, pero cubre muchas de las necesidades que hacen que uno termine necesitando un RTOS
Rust y Cargo eliminan el sufrimiento de compilar y flashear para RP2040 o STM32. Ha sido el entorno embebido más cómodo que he usado hasta ahora
Muchas aplicaciones no usan MPU. A eso súmale la seguridad de memoria de Rust y la menor complejidad general del firmware al no usar RTOS
La asincronía del executor de Embassy también funciona muy bien y quita mucho del dolor del proceso de diseñar con RTOS
Si además le sumas la interfaz PIO para RP2040, el código queda muy simple y elegante, y se vuelve una configuración difícil de lograr con otros procesadores
La Pi Pico está soportada al 100% en Zephyr. ¿Será que no revisaron la documentación? https://github.com/zephyrproject-rtos/zephyr/tree/main/board... https://docs.zephyrproject.org/latest/boards/raspberrypi/rpi...
Además, en muchas situaciones, el uso previsto no es tener una sola instalación “main” de Zephyr, sino incluir los módulos externos necesarios en el
west.ymldel proyectoQue varios proyectos compartan la misma instalación de Zephyr es otra discusión, pero instalar todas las toolchains y HAL posibles no es la única forma de hacerlo
No se mencionó ThreadX, que ahora es open source
https://github.com/eclipse-threadx/threadx/
https://github.com/RIOT-OS/RIOT
Me gustó la comparación de opciones de RTOS
Personalmente, creo que MicroPython es un camino más fácil. La multitarea cooperativa basada en
async/awaitme funciona bienEn un proyecto reciente manejé 6 motores paso a paso, varios LED y el escaneo de botones, y para el usuario se veía como tiempo real
Me sorprende que mucha gente todavía no entienda bien lo pocos recursos que teníamos entonces y que, aun así, usábamos lenguajes de alto nivel
Cuando usé MicroPython en ESP32, me gustó que soportara los manejadores de interrupciones de una forma muy propia de Python. Supongo que el RP2040 ofrece algo similar
En mi microscopio, una placa FluidNC se encarga del control de motores paso a paso casi en tiempo real, y yo la controlo con un protocolo serie liviano
Aunque estoy viendo https://pypi.org/project/micropython-stepper/, y esto parece usar temporizadores de hardware y código Python
Me gustaría probar Hubris en un proyecto de verdad (https://hubris.oxide.computer/reference/)
Su enfoque arquitectónico encaja bastante bien con lo que busco en el mundo embebido. Aunque yo lo estoy haciendo de forma más dolorosa en C
Tampoco es muy distinto de cómo se hace con Erlang/Elixir en entornos alojados
En situaciones donde la memoria es más limitada y no se pueden sostener varios stacks, Embassy parece una buena opción
Un proyecto embebido nuevo siempre, pero siempre, debería empezar en una máquina virtual. No hay que mezclar herramientas en el mismo sistema
En mis proyectos comerciales, esta fue la causa número uno de problemas de calidad
Si vas a iniciar un proyecto con un chipset nuevo o un proveedor nuevo, crea una VM nueva, instala ahí solo las herramientas del proveedor y compila desde ahí
El desarrollo experimental puede hacerse en la máquina local sin VM. Pero los releases deben hacerse obligatoriamente en una VM. Y por favor, hay que mantener sincronizadas la VM y la estación de trabajo de desarrollo
Ahora mismo estoy sufriendo muchísimo para manejar una compilación de firmware que hay que corregir sí o sí mientras el desarrollador original está de vacaciones. Nadie tiene acceso a su estación de trabajo, la VM preparada está atrasada 6 meses, y el cliente pregunta por qué tiene que pagar por todo un equipo para algo que, al parecer, solo puede hacer un programador especial en todo el planeta
Creo que los releases debería compilarlos el sistema de CI. Al poner una etiqueta de release en git, después de pasar las pruebas debería salir el binario
Si eliges FreeRTOS, es difícil equivocarse demasiado. A esta altura, es prácticamente el estándar de la industria
printf()funcione?Parece bastante complicado
Tuve una experiencia similar a la del artículo original
Por eso hice yo mismo un temporizador simple con green threads
No admite gestión real de procesos como un kernel de verdad ni ofrece ninguna garantía, pero me permitió llegar más lejos que con la planificación bare-metal y evitar el caos de un RTOS
Se puede ver como algo parecido a callbacks de temporizadores de JavaScript que reciben una estructura de contexto opcional en C
Pude manejar en distintos ciclos tareas como consultar varios sensores, procesar señales entrantes, tomar decisiones de control y emitir comandos
Antes de arruinarte la vida con una arquitectura tan lenta y abstracta, recomiendo encarecidamente probar primero este enfoque