Los microcontroladores modernos tienen un rendimiento bastante alto y esto les da a muchos programadores la oportunidad de pensar aproximadamente en la siguiente línea: - “Está bien si el 1-5% del rendimiento se destina al mantenimiento del sistema operativo. ¡Pero mi código será fácilmente depurado y explícito! Estos pensamientos están respaldados por una gran cantidad de memoria no volátil (flash) para almacenar el código del sistema operativo y la memoria operativa (RAM / SRAM) para asignar su propia pila para cada tarea. Sin embargo, en la mayoría de los casos, esta idea es errónea. Y en este artículo te diré por qué.
Sobre los proyectos con los que trabajo
En mi práctica, a menudo tengo que trabajar con un "diseñador". Describí este enfoque en detalle en mi artículo anterior sobre el
uso de C ++ en microcontroladores . Entonces no dije lo más importante. La mayoría de los "bloques" de este "constructor" están de alguna manera vinculados a un sistema operativo en tiempo real. La mayoría de los "bloques" tienen su propio flujo (tarea, en términos del sistema operativo en tiempo real FreeRTOS utilizado). Y eso, en promedio, el proyecto tiene alrededor de 10-15 tareas. A veces este valor alcanza 35-40.
Donde tanto?
Aquí hay una breve lista de tareas que se encuentran
en cada proyecto:
- Mantenimiento de ADC (cada módulo es atendido por su propio flujo);
- mantenimiento de wdt (si el sistema operativo falla, la tarea no lo restablecerá y el dispositivo se reiniciará);
- trabajar con páginas de configuración (una secuencia separada controla el trabajo con memoria flash);
- mantenimiento del protocolo de interacción con el mundo exterior (aguas abajo de la interfaz. Por ejemplo, uart);
Luego, ya hay cosas específicas para cada dispositivo, como un flujo para dar servicio a los termistores (recibir datos del flujo de medición ADC y convertir estos datos a temperatura), sondear los periféricos externos, etc.
Aparente simplicidad
A pesar del hecho de que hay muchas tareas en el proyecto, cada una de ellas está "oculta" dentro de un objeto de la clase correspondiente (recuerde que el constructor está en C ++, pero esto también se puede imitar en C usando "programación en C en un estilo orientado a objetos". Pero
es mejor no hacerlo necesario ). Dado que los objetos de este "constructor" son globales y FreeRTOS 9 se usa en proyectos, lo que permite la creación de sus propias entidades en buffers asignados por el usuario, el uso de la memoria puede controlarse incluso en la etapa de construcción. Entonces, desde el punto de vista del monitoreo de pérdidas de memoria, todo es más o menos normal. Pero hay los siguientes matices:
- debe entenderse claramente cuánto se necesita una pila para cada subproceso. En este caso:
- los casos críticos deben tenerse en cuenta (por ejemplo, anidando con un determinado comportamiento);
- si se utilizan funciones de bibliotecas estándar, también sepa cómo están organizadas, o al menos tenga una idea de cuánto consumirán la pila;
Además de este hecho, parece que usar el sistema operativo solo mejorará la lógica del código y lo hará más claro.
Abuso de la funcionalidad del sistema operativo
Los principales problemas comienzan en el momento en que comienzas a olvidar lo que estás escribiendo específicamente para el microcontrolador. El sistema operativo impone sus costos al trabajar con sus propias entidades (como semáforos, mutexes, colas). Aquí hay un ejemplo de una
clase UART para implementar una función de terminal . En la interrupción, se recibe un byte, después del cual, si pasa el rango por caracteres de entrada válidos, se agrega a la cola con los reemplazos correspondientes (por ejemplo, '\ n' cambia a la secuencia "\ n \ r"). Esto se hizo para asegurar el puerto para el envío (ya que el puerto puede funcionar no solo como terminal. Los datos de registro también se pueden enviar a través de él). Por un lado, esto asegura que la respuesta se enviará lo antes posible y no interferirá con el envío de datos de mayor prioridad (además, mientras se envían datos de mayor prioridad, se acumula en el búfer, lo que permite que se use DMA para enviar la respuesta). Sin embargo, a partir de este momento te encuentras en una pista resbaladiza. En lugar de escribir un montón a través de la cola, uno podría configurar correctamente la interrupción en un búfer no vacío que actualmente no funciona en el UART y cuando finaliza el DMA. Este enfoque requiere una comprensión clara de cómo funcionan los periféricos. Sin embargo, reduce los costos a un mínimo absoluto, lo que hace que la necesidad de tal solución sea cero.
Ignorando la funcionalidad del hardware del microcontrolador
En mi práctica, conocí un proyecto con 18 temporizadores de software del sistema operativo sintonizados a la misma frecuencia. Al mismo tiempo, había alrededor de 10 temporizadores en el microcontrolador, de los cuales solo se usaba systic. Para cronometrar el planificador del sistema operativo. Esta decisión fue explicada por la falta de deseo de "meterse con el hardware" del microcontrolador. Al mismo tiempo, se asignaron aproximadamente 10 kb a la pila para la función llamada por el temporizador de software. De hecho, se usó aproximadamente 1 kb (corto). Esto se debió a la "ambigüedad de lo que está sucediendo dentro de las bibliotecas llamadas".
En este caso, fue posible seleccionar TIM6 de manera segura (en el caso de usar stm32f4), lo que generaría una interrupción a una frecuencia dada y dentro de ella simplemente llamaría a las funciones requeridas.
Usar un bucle infinito en lugar de una máquina de estados
Como columna separada, destacaría la incapacidad de algunos programadores para escribir máquinas compactas de estados finitos, y en su lugar crearía una secuencia en la que hay un bucle infinito que comienza su trabajo al obtener algo de la cola. Curiosamente, en
este artículo se escribe cómo crear máquinas compactas de estados finitos mediante el lenguaje en sí.
Ignorando el "planificador de hardware"
Muchos microcontroladores de treinta y dos bits tienen un controlador de interrupción bien pensado con un sistema de prioridad personalizable. En el caso de stm32f4, tiene el nombre NVIC y tiene la capacidad de establecer prioridades de interrupción con 16 niveles (sin considerar los subniveles).
La mayoría de las aplicaciones bajo FreeRTOS con las que tuve que lidiar podrían escribirse como máquinas de estado llamadas en interrupciones con prioridades configuradas correctamente. Y en caso de que el procesador vuelva a la "ejecución normal", vaya a "suspensión". En este caso, no habría necesidad de bloquear el acceso a la mayoría de los recursos (variables y otros). Las aplicaciones perderían un nivel extra de abstracción. Y en este caso, lejos de ser gratuito. Sin embargo, este enfoque requiere una planificación cuidadosa de la arquitectura para cada proyecto. En los proyectos, "diseñadores": todas las interrupciones tienen una prioridad y, de hecho, son necesarias para "filtrar" los datos. Luego, coloque las sobras en la cola, desde donde las recogerá el flujo de objetos de la clase correspondiente.
Resumen
En este artículo hablé sobre los problemas básicos que tiene que enfrentar al usar el sistema operativo en proyectos para microcontroladores, y también examiné casos comunes de uso del sistema operativo cuando esto podría haberse evitado sin perder la legibilidad y la lógica del código.