Los microcontroladores están limitados a ciertas tareas y normalmente no tienen capacidades de subprocesos múltiples, lo que permitiría más de un usuario o tarea al mismo tiempo sin requerir múltiples copias de un programa o computadora. Generalmente, los microcontroladores no pueden dividir aplicaciones más grandes o complejas en múltiples subprocesos. Se ocupan de secuencias simples de código en tiempo real, con excepción de bucles de programación, condiciones específicas e interrupciones.
Las interrupciones son una forma de resolver tareas urgentes en dispositivos integrados. Sin embargo, las interrupciones siguen dependiendo de circuitos externos (en el caso de interrupciones de hardware) o de periféricos integrados (en el caso de interrupciones de temporizador).
Tenga en cuenta que las interrupciones tienen como objetivo hacer que los microcontroladores respondan a eventos específicos en tiempo real y no a plazos estrictos. Como resultado, no hay garantía de ejecución oportuna de tareas críticas porque las interrupciones en los microcontroladores tienen inherentemente prioridad. Además, a pesar de las mejores prácticas de codificación, una rutina de servicio de interrupción puede ser muy larga. También suspende el código de ejecución principal, que puede incluir otras tareas urgentes.
¿La solución? Se puede utilizar un sistema operativo en tiempo real (RTOS) para cumplir con plazos operativos estrictos. Hay varios RTOS disponibles para microcontroladores y el RTOS gratuito es popular entre las versiones comerciales disponibles.
Técnicamente, FreeRTOS no es un sistema operativo en tiempo real completo, sino un programador en tiempo real diseñado para microcontroladores. Con el soporte de FreeRTOS, puede priorizar las tareas integradas para que se ejecuten dentro de plazos estrictos.
En este proyecto, cargaremos FreeRTOS en Arduino UNO para aprender cómo programa las tareas integradas. En este caso será para encender LEDs. Este proyecto también se puede ejecutar en ESP8266, ESP32 o cualquier otra placa de microcontrolador compatible con Arduino.
Componentes necesarios
1. Arduino UNO x1
2. LEDx4
3. Resistencias 330Ω x4
4. Tablero de prueba
5. Cables de puente/cables de conexión
Cómo funciona FreeRTOS
Los microcontroladores tienen un único núcleo, lo que significa que sólo pueden realizar una tarea simultáneamente. FreeRTOS permite la programación de tareas, por lo que parece que se ejecutan varios eventos simultáneamente. Esto se llama programación en tiempo real. En realidad, en los microcontroladores de un solo núcleo solo hay una tarea activa a la vez y el resto permanece inactiva.
FreeRTOS divide las tareas inactivas en tres estados...
1. Estado listo: las tareas en estado listo están disponibles para programarse a través del programador FreeRTOS. A estas tareas se les asignan recursos de memoria y procesador de acuerdo con el algoritmo de programación. El algoritmo establece el cronograma para las tareas en estado listo en función de la prioridad que se les asigna. Estas tareas no están bloqueadas ni suspendidas. Entonces no están corriendo, sino esperando para correr.
2. Estado bloqueado: las tareas en estado bloqueado no están en la cola del programador. En cambio, el programador los bloquea y permanecen inactivos indefinidamente. El estado bloqueado depende de una interrupción externa o de un recurso como un mutex, un semáforo o un semáforo de conteo. Otra razón para una tarea bloqueada e inactiva podría ser su naturaleza periódica, es decir, que se retrasa periódicamente en el bucle principal.
3. Estado suspendido: las tareas inactivas en estado bloqueado solo se pueden ejecutar cuando se recibe una interrupción respectiva o se utiliza un recurso requerido. Pero el desarrollador retrasa explícitamente algunas tareas inactivas y luego las coloca en una cola. A menudo, estas tareas se programan cuando se cumplen condiciones específicas o después de que se haya ejecutado otra tarea. Por ejemplo, un desarrollador podría suspender la impresión de un valor de lectura de sensor en una unidad de visualización hasta que el ADC lo lea y lo convierta.
La API de FreeRTOS ofrece una función vTaskSuspend que suspende explícitamente una tarea y una función vTaskResume que reanuda la tarea.
Una tarea puede pasar por un estado bloqueado, listo, suspendido y en ejecución varias veces durante su vida útil multitarea, como se muestra en el diagrama de flujo a continuación.
En Arduino, cada tarea se define como un bloque de código envuelto en su propia función. FreeRTOS permite la creación de tareas que llaman a una función respectiva. A cada tarea se le da una prioridad. FreeRTOS define automáticamente el algoritmo de programación de tareas durante el tiempo de ejecución, según las prioridades asignadas y la disponibilidad de recursos de procesamiento.
Instalación de FreeRTOS
FreeRTOS está disponible en la biblioteca Arduino. Para incorporar FreeRTOS en una aplicación Arduino, simplemente incluya la biblioteca en el boceto. Pero primero, instálalo en el IDE de Arduino.
Para hacer esto, navegue hasta Herramientas->Administrar bibliotecas y busque FreeRTOS. Vaya a la biblioteca FreeRTOS escrita por Phillip Steven. Instale la biblioteca.
Ahora, puede incluir fácilmente FreeRTOS en una aplicación Arduino importando la biblioteca al boceto siguiendo estas instrucciones...
#includeArduino_FreeRTOS.h>
Crear y priorizar tareas
La API de FreeRTOS proporciona una función xTaskCreate para crear tareas. La función tiene este prototipo…
BaseType_t xTaskCreate(
TaskFunction_t pvTaskCode,
carácter constante * nombre_pc constante,
configSTACK_DEPTH_TYPE usStackDepth,
void *pvParametros,
UBaseType_t uxPrioridad,
TaskHandle_t *pxCreatedTask
);
Se necesitan los siguientes argumentos...
- pvTaskCode: el puntero a la función definida de la tarea. La tarea se implementa como un bucle infinito. El puntero se pasa como argumento a la función. La tarea también se puede eliminar.
- pcname: el nombre de la función de devolución de llamada que realiza la tarea. La función no debe devolver un valor ni salir.
- usStackDepth: el tamaño de la pila en la cantidad de palabras asignadas a la tarea. En un microcontrolador de 32 bits, cada palabra tiene cuatro bytes. Por lo tanto, si el tamaño de la pila se establece en 100, se asignarán 400 bytes a la tarea.
- Parámetros pv: el valor del parámetro, que se pasa como argumento a la función de tarea.
- uxPriority: la prioridad establecida para la tarea, que puede ser un número entero positivo o -1. Si se establece en -1, la prioridad se establecerá en indefinida. Cuanto mayor sea el número entero (que se pasa como argumento), mayor será la prioridad de la tarea. Por ejemplo, una tarea con prioridad 3 tiene una prioridad más alta que una tarea con prioridad 2. La prioridad más baja es 0.
- pxCreatedTask: el identificador de rol de la tarea. El identificador se puede utilizar para eliminar la tarea.
Configurar la secuencia de ejecución de la tarea
FreeRTOS ejecuta primero la tarea de mayor prioridad. Las tareas restantes se realizan cuando hay recursos disponibles o en turnos periódicos. Normalmente, es necesario definir la secuencia inicial de tareas. Esto se aplica a la realización de tareas iniciales. Si las tareas deben ejecutarse en un orden predeterminado, esto se puede definir mediante la función vTaskDelayUntil de la API de FreeRTOS. Esta función le permite establecer un retraso antes de que la tarea se ejecute por primera vez.
Conexiones de circuito
Para demostrar FreeRTOS como programador de tareas, encenderemos LED en Arduino. Para este proyecto, utilizamos cuatro LED y los conectamos con GPIO8, GPIO9, GPIO10 y GPIO11 de Arduino.
Los LED están interconectados para que se iluminen cuando el GPIO envía una salida de señal BAJA.
Después de realizar las conexiones del circuito, cargue el siguiente boceto en Arduino.
Como funciona
FreeRTOS se utiliza para realizar cuatro tareas diferentes. Cada tarea enciende una luz LED y apaga los demás LED. Se imprime un mensaje en el monitor serie indicando la ejecución de una determinada tarea.
- La tarea que enciende el LED conectado a GPIO8 tiene la prioridad más alta de 3. Está programada para ejecutarse después de 100 milisegundos en la primera ejecución.
- La tarea que enciende el LED conectado a GPIO9 tiene prioridad 2. Está programada para ejecutarse después de 110 milisegundos en la primera ejecución.
- La tarea que enciende el LED conectado al GPIO10 tiene prioridad 1. Está programada para ejecutarse después de 120 milisegundos en la primera ejecución.
- La tarea que enciende el LED conectado a GPIO11 tiene la prioridad más baja, 0. Está programada para ejecutarse con un retraso de 50 milisegundos.
Inicialmente, las tareas deben realizarse en este orden predefinido. Luego, las tareas se ejecutan según su prioridad y disponibilidad de recursos de procesamiento.
Código
El boceto comienza importando la biblioteca Arduino_FreeRTOS.h. En la función de configuración, la velocidad de transmisión para comunicar mensajes al monitor serie es de 9600 bps. La ejecución de la función de configuración se indica imprimiendo un mensaje en el monitor serie.
Los LED están conectados a pines Arduino que se configuran como salidas digitales mediante la función pinMode. Las tareas se crean con las prioridades 3, 2, 1 y 0 mediante la función xTaskCreate de la API de FreeRTOS. Todas las tareas reciben una pila de 100 palabras. Las tareas están vinculadas a las funciones de devolución de llamada MyTask1, MyTask2, MyTask3 e IdleTask.
La función de bucle está vacía porque las tareas integradas serán ejecutadas por el programador FreeRTOS. La primera tarea está definida en la función MyTask1. Enciende el LED que está conectado a GPIO8, apagando todos los demás LED. Después de la ejecución, 'Tarea1' se imprime en el monitor serie. La tarea está programada para ejecutarse después de 100 milisegundos usando la función vTaskDelay cuando se ejecuta por primera vez.
La segunda tarea está definida en la función MyTask2. Enciende el LED que está conectado a GPIO9, apagando todos los demás LED. Después de la ejecución, 'Tarea2' se imprime en el monitor serie. La tarea está programada para ejecutarse después de 110 milisegundos usando la función vTaskDelay cuando se ejecuta por primera vez.
La tercera tarea está definida en la función MyTask3. Enciende el LED que está conectado al GPIO10, apagando todos los demás LED. Después de la ejecución, 'Task3' se imprime en el monitor serie. La tarea está programada para ejecutarse después de 120 milisegundos usando la función vTaskDelay cuando se ejecuta por primera vez.
La última tarea se define en la función MyIdleTask. Enciende el LED que está conectado a GPIO11 (LED amarillo), apagando todos los demás LED. Después de la ejecución, se imprime el 'Estado inactivo' en el monitor serie. La tarea está programada para ejecutarse periódicamente después de 50 milisegundos.
Esta tarea sólo se realiza cuando el microcontrolador no está ocupado realizando otras tareas. Dado que tiene un retraso de 50 milisegundos, debe ejecutarse periódicamente.
Resultados
El siguiente registro de ejecución de tareas de FreeRTOS se muestra en el monitor serie.
Los LED se alternan mediante el programador FreeRTOS.
(Enlace al vídeo de demostración del P35-DV)
Conclusión
El registro del monitor serie indica que las tareas se ejecutaron inicialmente en el patrón predefinido definido por la función vTaskDelay. Posteriormente, las tareas se ejecutan según su prioridad y disponibilidad de recursos de procesamiento.
Dado que la tarea inactiva se ejecuta periódicamente cada 50 milisegundos, el LED conectado a GPIO11 parece permanecer encendido. El LED conectado al GPIO8 permanece encendido por más tiempo porque la tarea responsable de encenderlo tiene la mayor prioridad y dura más. El LED conectado a GPIO9 se enciende de forma intermitente porque la tarea responsable de encenderlo lo hace siempre que la tarea 1 no se está ejecutando. El LED conectado al GPIO10 aparece apagado porque la tarea responsable de encenderlo rara vez tiene una oportunidad.