Una gran variedad de respuestas aquí ... principalmente abordando el problema de varias maneras.
He estado escribiendo software y firmware integrados de bajo nivel durante más de 25 años en una variedad de lenguajes, principalmente C (pero con desviaciones en Ada, Occam2, PL / M y una variedad de ensambladores en el camino).
Después de un largo período de reflexión y prueba y error, me he decidido por un método que obtiene resultados con bastante rapidez y es bastante fácil de crear envolturas y arneses de prueba (¡donde AGREGAN VALOR!)
El método es más o menos así:
Escriba un controlador o una unidad de código de abstracción de hardware para cada periférico principal que desee usar. También escriba uno para inicializar el procesador y configurar todo (esto hace que el entorno sea amigable). Por lo general, en pequeños procesadores integrados, su AVR es un ejemplo, puede haber entre 10 y 20 unidades, todas pequeñas. Estas pueden ser unidades para inicialización, conversión A / D a memorias intermedias de memoria sin escala, salida bit a bit, entrada de botón pulsador (sin rebote solo muestreado), controladores de modulación de ancho de pulso, UART / controladores seriales simples que interrumpen el uso y pequeñas memorias intermedias de E / S. Puede haber algunos más, por ejemplo, controladores I2C o SPI para EEPROM, EPROM u otros dispositivos I2C / SPI.
Para cada una de las unidades de abstracción de hardware (HAL) / controlador, entonces escribo un programa de prueba. Esto se basa en un puerto serie (UART) y un procesador init, por lo que el primer programa de prueba usa esas 2 unidades solamente y solo realiza algunas entradas y salidas básicas. Esto me permite probar que puedo iniciar el procesador y que tengo soporte básico de depuración de E / S en serie funcionando. Una vez que eso funciona (y solo entonces), desarrollo los otros programas de prueba HAL, construyéndolos sobre las unidades UART e INIT buenas conocidas. Por lo tanto, podría tener programas de prueba para leer las entradas bit a bit y mostrarlas en una forma agradable (hexadecimal, decimal, lo que sea) en mi terminal de depuración en serie. Luego puedo pasar a cosas más grandes y complejas, como los programas de prueba EEPROM o EPROM: hago que la mayoría de estos menús funcionen para poder seleccionar una prueba para ejecutar, ejecutarla y ver el resultado. No puedo ESCRIBIRLO, pero generalmente no lo hago
Una vez que tengo todo mi HAL funcionando, entonces encuentro la manera de obtener un tic del temporizador regular. Esto suele ser a una velocidad entre 4 y 20 ms. Esto debe ser regular, generado en una interrupción. El rollover / overflow de los contadores suele ser cómo se puede hacer esto. El manejador de interrupciones INCREMENTA un "semáforo" de tamaño de byte. En este punto, también puede jugar con la administración de energía si es necesario. La idea del semáforo es que si su valor es> 0, debe ejecutar el "bucle principal".
El EJECUTIVO ejecuta el bucle principal. Prácticamente solo espera que ese semáforo se convierta en no 0 (abstraigo este detalle). En este punto, puede jugar con contadores para contar estos ticks (porque conoce la tasa de ticks) y así puede establecer indicadores que muestren si el tick ejecutivo actual es para un intervalo de 1 segundo, 1 minuto y otros intervalos comunes que podría querer usar. Una vez que el ejecutivo sabe que el semáforo es> 0, ejecuta una sola pasada a través de cada función de "actualización" de los procesos de "aplicación".
Los procesos de la aplicación se sientan uno al lado del otro y se ejecutan regularmente mediante una marca de "actualización". Esta es solo una función convocada por el ejecutivo. Esta es efectivamente la multitarea de los hombres pobres con un RTOS local muy simple que se basa en todas las aplicaciones que ingresan, realizan un pequeño trabajo y salen. Las aplicaciones necesitan mantener sus propias variables de estado y no pueden hacer cálculos de ejecución prolongada porque no hay un sistema operativo preventivo para forzar la imparcialidad. OBVIAMENTE, el tiempo de ejecución de las aplicaciones (acumulativamente) debe ser menor que el período de tick principal.
El enfoque anterior se amplía fácilmente para que pueda agregar cosas como pilas de comunicación que se ejecutan de forma asincrónica y luego se pueden enviar mensajes de comunicación a las aplicaciones (agrega una nueva función a cada uno que es el "rx_message_handler" y escribe un despachador de mensajes que figura a qué aplicación enviar).
Este enfoque funciona para casi cualquier sistema de comunicación que desee nombrar: puede (y lo ha hecho) funcionar para muchos sistemas patentados, sistemas de comunicaciones de estándares abiertos, incluso funciona para pilas TCP / IP.
También tiene la ventaja de estar construido en piezas modulares con interfaces bien definidas. Puede introducir y extraer piezas en cualquier momento, sustituir diferentes piezas. En cada punto del camino, puede agregar arneses de prueba o controladores que se basan en las partes conocidas de la capa inferior (las cosas a continuación). He descubierto que aproximadamente del 30% al 50% de un diseño puede beneficiarse al agregar pruebas unitarias especialmente escritas que generalmente se agregan con bastante facilidad.
Llevé todo esto un paso más allá (una idea que descubrí de otra persona que ha hecho esto) y reemplacé la capa HAL con un equivalente para PC. Entonces, por ejemplo, puede usar C / C ++ y winforms o similar en una PC y al escribir el código CUIDADOSAMENTE puede emular cada interfaz (por ejemplo, EEPROM = un archivo de disco leído en la memoria de la PC) y luego ejecutar la aplicación integrada completa en una PC. La capacidad de utilizar un entorno de depuración amigable puede ahorrar una gran cantidad de tiempo y esfuerzo. Solo los proyectos realmente grandes pueden justificar esta cantidad de esfuerzo.
La descripción anterior es algo que no es exclusivo de cómo hago las cosas en plataformas integradas: me he encontrado con numerosas organizaciones comerciales que hacen cosas similares. La forma en que se hace suele ser muy diferente en la implementación, pero los principios son con frecuencia muy similares.
Espero que lo anterior dé un poco de sabor ... este enfoque funciona para pequeños sistemas integrados que se ejecutan en unos pocos KB con gestión agresiva de la batería hasta monstruos de 100K o más líneas de origen que funcionan con alimentación permanente. Si ejecuta "incrustado" en un sistema operativo grande como Windows CE, etc., todo lo anterior es completamente irrelevante. Pero eso no es una programación REAL REAL, de todos modos.