Resumen | Este codelab fue creado para estudiar los mecanismos de comunicación entre tareas en FreeRTOS. Con este recurso se espera que usted al finalizar esté en capacidad de:
|
Fecha de Creación: | 2024/03/01 |
Última Actualización: | 2024/03/01 |
Requisitos Previos: | |
Adaptado de: | |
Referencias: | |
Escrito por: | Fredy Segura-Quijano |
En FreeRTOS, la sincronización y la comunicación entre tareas son aspectos importantes para el funcionamiento correcto de sistemas concurrentes y la gestión eficiente de recursos compartidos. FreeRTOS proporciona varias herramientas (funciones) y mecanismos para lograr esto, incluyendo colas, semáforos, mutexes, y notificaciones de tareas. Estos mecanismos pueden evitar Condiciones de Carrera y garantizar la integridad de los datos.
Una Condición de Carrera (race condition) es un comportamiento indeseable que ocurre en sistemas concurrentes cuando el resultado o el comportamiento del sistema depende del orden o la temporización de las ejecuciones de múltiples tareas o hilos. Las Condiciones de Carrera pueden llevar a errores difíciles de reproducir y diagnosticar, ya que dependen de la interleavación específica (ejecución entrelazada de múltiples tareas) de operaciones concurrentes.
Por ejemplo cuando dos tareas (o hilos) intentan incrementar una variable global compartida, puede pasar que si ambas tareas leen la variable al mismo tiempo, la incrementan y escriben el resultado de vuelta, es posible que una actualización se pierda, resultando en un valor incorrecto.
int counter = 0;
void task1(void *pvParameters) {
for (;;) {
counter++;
vTaskDelay(pdMS_TO_TICKS(100));
}
}
void task2(void *pvParameters) {
for (;;) {
counter++;
vTaskDelay(pdMS_TO_TICKS(100));
}
}
En este ejemplo, task1 y task2 incrementan counter independientemente. Si ambas tareas leen el valor de counter al mismo tiempo, incrementan el valor y escriben el nuevo valor de vuelta, una de las actualizaciones se perderá, resultando en un incremento incorrecto. Sin embargo es bueno entender el detalle de lo que puede ocurrir, dado que las tareas se asume que tienen prioridad y no se ejecutan al mismo tiempo.
Aunque las tareas parecen ejecutarse independientemente, el problema surge cuando ambas acceden y modifican la variable contador casi al mismo tiempo. Un posible escenario sería:
Este problema ocurre debido a que la lectura, modificación y escritura de la variable no son operaciones atómicas (no se ejecutan como una sola operación indivisible).
Una posible solución a las Condiciones de Carrera es el uso de mecanismos de sincronización como semáforos o mutex. A continuación se presenta el mismo código, con la propuesta de usar un Mutex (es un tipo de semáforo). Aunque el detalle de mutex lo estudiaremos más adelante, el propósito es dar una idea de su funcionalidad.
#include <Arduino.h>
int contador = 0;
SemaphoreHandle_t xMutex;
void tarea1(void *pvParameters) {
for (;;) {
if (xSemaphoreTake(xMutex, portMAX_DELAY)) {
contador++;
Serial.print("Tarea 1 contador: ");
Serial.println(contador);
xSemaphoreGive(xMutex);
}
vTaskDelay(pdMS_TO_TICKS(1000));
}
}
void tarea2(void *pvParameters) {
for (;;) {
if (xSemaphoreTake(xMutex, portMAX_DELAY)) {
contador++;
Serial.print("Tarea 2 contador: ");
Serial.println(contador);
xSemaphoreGive(xMutex);
}
vTaskDelay(pdMS_TO_TICKS(1500));
}
}
void setup() {
Serial.begin(115200);
// Crear el mutex antes de usarlo
xMutex = xSemaphoreCreateMutex();
if (xMutex != NULL) {
xTaskCreate(tarea1, "Tarea 1", 1000, NULL, 1, NULL);
xTaskCreate(tarea2, "Tarea 2", 1000, NULL, 1, NULL);
} else {
Serial.println("Error al crear el mutex.");
}
}
void loop() {
// No hacer nada aquí
}
Explicación del Código:
De esta forma los mutex aseguran que solo una tarea pueda acceder a la sección crítica del código a la vez. Los mutex son de fácil implementación y proveen un método seguro para la sincronización de tareas en sistemas multitarea.
Las colas (queues) en FreeRTOS son estructuras de datos utilizadas para la comunicación y sincronización entre tareas. Permiten que una tarea envíe datos a otra tarea de manera segura y eficiente, garantizando que los datos se entreguen en el orden en que se enviaron. Una cola es un sistema FIFO simple con lecturas y escrituras atómicas. Las "operaciones atómicas" son aquellas que no pueden ser interrumpidas por otras tareas durante su ejecución. Esto garantiza que otra tarea no pueda sobrescribir los datos, mientras que la tarea anterior no termine de realizar su funcionalidad completa.
Dentro de las ventajas que tiene el uso de colas están:
Dentro de las desventajas que tiene el uso de colas están:
El objetivo del siguiente ejemplo usando FreeRTOS es crear dos tareas y dos colas. Las tareas deben realizar las siguientes funcionalidades:
Tarea A:
Tarea B:
Librerías:
#include <Arduino.h>
Configuración del Núcleo:
#if CONFIG_FREERTOS_UNICORE
static const BaseType_t app_cpu = 0;
#else
static const BaseType_t app_cpu = 1;
#endif
Configuración de Parámetros y Pines:
static const uint8_t buf_len = 255;
static const char command[] = "delay ";
static const int delay_queue_len = 5;
static const int msg_queue_len = 5;
static const uint8_t blink_max = 100;
static const int led_pin = LED_BUILTIN;
Estructura de Mensaje:
typedef struct Message {
char body[20];
int count;
} Message;
Variables Globales:
static QueueHandle_t delay_queue;
static QueueHandle_t msg_queue;
Tarea CLI:
void doCLI(void *parameters) {
Message rcv_msg;
char c;
char buf[buf_len];
uint8_t idx = 0;
uint8_t cmd_len = strlen(command);
int led_delay;
memset(buf, 0, buf_len);
while (1) {
if (xQueueReceive(msg_queue, (void *)&rcv_msg, 0) == pdTRUE) {
Serial.print("Received message: ");
Serial.print(rcv_msg.body);
Serial.println(rcv_msg.count);
}
if (Serial.available() > 0) {
c = Serial.read();
if (idx < buf_len - 1) {
buf[idx] = c;
idx++;
} else {
Serial.println("Buffer overflow, resetting buffer");
memset(buf, 0, buf_len);
idx = 0;
}
if ((c == '\n') || (c == '\r')) {
Serial.print("\r\n");
if (memcmp(buf, command, cmd_len) == 0) {
char* tail = buf + cmd_len;
led_delay = atoi(tail);
led_delay = abs(led_delay);
if (xQueueSend(delay_queue, (void *)&led_delay, 10) != pdTRUE) {
Serial.println("ERROR: Could not put item on delay queue.");
}
}
memset(buf, 0, buf_len);
idx = 0;
} else {
Serial.print(c);
}
}
}
}
Tarea de Parpadeo de LED:
void blinkLED(void *parameters) {
Message msg;
int led_delay = 500;
uint8_t counter = 0;
pinMode(LED_BUILTIN, OUTPUT);
while (1) {
if (xQueueReceive(delay_queue, (void *)&led_delay, 0) == pdTRUE) {
strcpy(msg.body, "Message received ");
msg.count = 1;
xQueueSend(msg_queue, (void *)&msg, 10);
}
digitalWrite(led_pin, HIGH);
vTaskDelay(led_delay / portTICK_PERIOD_MS);
digitalWrite(led_pin, LOW);
vTaskDelay(led_delay / portTICK_PERIOD_MS);
counter++;
if (counter >= blink_max) {
strcpy(msg.body, "Blinked: ");
msg.count = counter;
if (xQueueSend(msg_queue, (void *)&msg, 10) != pdTRUE) {
Serial.println("ERROR: Could not put item on msg queue.");
} else {
Serial.println("Message sent to msg_queue.");
}
counter = 0;
}
}
}
Configuración Inicial:
void setup() {
Serial.begin(115200);
vTaskDelay(1000 / portTICK_PERIOD_MS);
Serial.println();
Serial.println("---FreeRTOS Queue Solution---");
Serial.println("Enter the command 'delay xxx' where xxx is your desired ");
Serial.println("LED blink delay time in milliseconds");
delay_queue = xQueueCreate(delay_queue_len, sizeof(int));
msg_queue = xQueueCreate(msg_queue_len, sizeof(Message));
if (delay_queue == NULL || msg_queue == NULL) {
Serial.println("ERROR: Could not create one or more queues.");
while (true); // Halt execution if queues are not created
}
xTaskCreatePinnedToCore(doCLI,
"CLI",
2048,
NULL,
1,
NULL,
app_cpu);
xTaskCreatePinnedToCore(blinkLED,
"Blink LED",
2048,
NULL,
1,
NULL,
app_cpu);
vTaskDelete(NULL);
}
Función loop:
void loop() {
// No hacer nada
// setup() y loop() se ejecutan en su propia tarea con prioridad 1 en el núcleo 1
// en ESP32
}
Código completo: A continuación se presenta el código completo del uso de colas; para que se pueda validar en VSCode y programar la ESP32. No olvide ver el comportamiento del led de la tarjeta y el "serial monitor"; para validar funcionalidad. Active en el monitor serial la función "line ending=LF"; para que se generen los finales de línea y envíe datos del monitor serial a la tarjeta ESP32.
/*######################################################################
# C CODE.
######################################################################
# Copyright (C) 2024. F.E.Segura-Quijano (FES) fsegura@uniandes.edu.co
#
# Este trabajo está licenciado bajo la Licencia:
# Creative Commons Atribución-NoComercial 4.0 Internacional.
# Para ver una copia de esta licencia, visita
# http://creativecommons.org/licenses/by-nc/4.0/ o envía una carta
# a Creative Commons, PO Box 1866, Mountain View, CA 94042, USA.
######################################################################*/
/*######################################################################
# Parte de este código se basa en los ejemplos FreeRTOS de:
# https://github.com/ShawnHymel/introduction-to-rtos/tree/main
# Parte de este código fue generado con la asistencia de ChatGPT de OpenAI.
######################################################################*/
/*######################################################################
# LIBRARIES.
######################################################################*/
#include <Arduino.h>
// Use only core 1 for demo purposes
#if CONFIG_FREERTOS_UNICORE
static const BaseType_t app_cpu = 0;
#else
static const BaseType_t app_cpu = 1;
#endif
// Settings
static const uint8_t buf_len = 255; // Size of buffer to look for command
static const char command[] = "delay "; // Note the space!
static const int delay_queue_len = 5; // Size of delay_queue
static const int msg_queue_len = 5; // Size of msg_queue
static const uint8_t blink_max = 100; // Num times to blink before message
// Pins (change this if your Arduino board does not have LED_BUILTIN defined)
static const int led_pin = LED_BUILTIN;
// Message struct: used to wrap strings (not necessary, but it's useful to see
// how to use structs here)
typedef struct Message {
char body[20];
int count;
} Message;
// Globals
static QueueHandle_t delay_queue;
static QueueHandle_t msg_queue;
/*######################################################################
# TASKs.
######################################################################*/
// Task: command line interface (CLI)
void doCLI(void *parameters) {
Message rcv_msg;
char c;
char buf[buf_len];
uint8_t idx = 0;
uint8_t cmd_len = strlen(command);
int led_delay;
// Clear whole buffer
memset(buf, 0, buf_len);
// Loop forever
while (1) {
// See if there's a message in the queue (do not block)
if (xQueueReceive(msg_queue, (void *)&rcv_msg, 0) == pdTRUE) {
Serial.print("Received message: ");
Serial.print(rcv_msg.body);
Serial.println(rcv_msg.count);
}
// Read characters from serial
if (Serial.available() > 0) {
c = Serial.read();
// Store received character to buffer if not over buffer limit
if (idx < buf_len - 1) {
buf[idx] = c;
idx++;
} else {
// If buffer limit is reached, reset the buffer to prevent overflow
Serial.println("Buffer overflow, resetting buffer");
memset(buf, 0, buf_len);
idx = 0;
}
// Print newline and check input on 'enter'
if ((c == '\n') || (c == '\r')) {
// Print newline to terminal
Serial.print("\r\n");
// Check if the first 6 characters are "delay "
if (memcmp(buf, command, cmd_len) == 0) {
// Convert last part to positive integer (negative int crashes)
char* tail = buf + cmd_len; // <--- Corrección aplicada aquí
led_delay = atoi(tail);
led_delay = abs(led_delay);
// Send integer to other task via queue
if (xQueueSend(delay_queue, (void *)&led_delay, 10) != pdTRUE) {
Serial.println("ERROR: Could not put item on delay queue.");
}
}
// Reset receive buffer and index counter
memset(buf, 0, buf_len);
idx = 0;
// Otherwise, echo character back to serial terminal
} else {
Serial.print(c);
}
}
}
}
// Task: flash LED based on delay provided, notify other task every 20 blinks
void blinkLED(void *parameters) {
Message msg;
int led_delay = 500;
uint8_t counter = 0;
// Set up pin
pinMode(LED_BUILTIN, OUTPUT);
// Loop forever
while (1) {
// See if there's a message in the queue (do not block)
if (xQueueReceive(delay_queue, (void *)&led_delay, 0) == pdTRUE) {
// Best practice: use only one task to manage serial comms
strcpy(msg.body, "Message received ");
msg.count = 1;
xQueueSend(msg_queue, (void *)&msg, 10);
}
// Blink
digitalWrite(led_pin, HIGH);
vTaskDelay(led_delay / portTICK_PERIOD_MS);
digitalWrite(led_pin, LOW);
vTaskDelay(led_delay / portTICK_PERIOD_MS);
// If we've blinked 100 times, send a message to the other task
counter++;
if (counter >= blink_max) {
// Construct message and send
strcpy(msg.body, "Blinked: ");
msg.count = counter;
if (xQueueSend(msg_queue, (void *)&msg, 10) != pdTRUE) {
Serial.println("ERROR: Could not put item on msg queue.");
} else {
Serial.println("Message sent to msg_queue.");
}
// Reset counter
counter = 0;
}
}
}
/*######################################################################
# SETUP.
######################################################################*/
void setup() {
// Configure Serial
Serial.begin(115200);
// Wait a moment to start (so we don't miss Serial output)
vTaskDelay(1000 / portTICK_PERIOD_MS);
Serial.println();
Serial.println("---FreeRTOS Queue Solution---");
Serial.println("Enter the command 'delay xxx' where xxx is your desired ");
Serial.println("LED blink delay time in milliseconds");
// Create queues
delay_queue = xQueueCreate(delay_queue_len, sizeof(int));
msg_queue = xQueueCreate(msg_queue_len, sizeof(Message));
// Check if the queues were created successfully
if (delay_queue == NULL || msg_queue == NULL) {
Serial.println("ERROR: Could not create one or more queues.");
while (true); // Halt execution if queues are not created
}
// Start CLI task
xTaskCreatePinnedToCore(doCLI,
"CLI",
2048,
NULL,
1,
NULL,
app_cpu);
// Start blink task
xTaskCreatePinnedToCore(blinkLED,
"Blink LED",
2048,
NULL,
1,
NULL,
app_cpu);
// Delete "setup and loop" task
vTaskDelete(NULL);
}
/*######################################################################
# LOOP.
######################################################################*/
void loop() {
// Do nothing
// setup() and loop() run in their own task with priority 1 in core 1
// on ESP32
}
A continuación presentamos una captura de pantalla del monitor serial donde se puede ver la interacción con el programa del ejemplo:
[Programación y pruebas con el código completo ejemplo uso de colas.]
En FreeRTOS, los semáforos son herramientas de sincronización utilizadas para controlar el acceso a recursos compartidos y coordinar la ejecución de tareas. Los semáforos pueden ser binarios (que solo tienen dos estados, tomado y disponible) o de conteo (que permiten contar eventos o recursos disponibles). También existen los mutexes, que son un tipo especial de semáforo utilizado para exclusión mutua.
Dentro de las ventajas que tiene el uso de semáforos están:
Dentro de las desventajas que tiene el uso de semáforos están:
Para controlar dos tareas que acceden a un recurso compartido, como un archivo o una sección de memoria, se puede usar un mutex para asegurar de que solo una tarea puede acceder al recurso a la vez.
#include <FreeRTOS.h>
#include <task.h>
#include <semphr.h>
#include <Arduino.h>
SemaphoreHandle_t xMutex;
void vTask1(void *pvParameters) {
while (1) {
if (xSemaphoreTake(xMutex, portMAX_DELAY) == pdTRUE) {
// Acceder al recurso compartido
Serial.println("Tarea 1 accediendo al recurso compartido");
vTaskDelay(pdMS_TO_TICKS(500)); // Simular trabajo
Serial.println("Tarea 1 liberando el recurso compartido");
xSemaphoreGive(xMutex);
vTaskDelay(pdMS_TO_TICKS(1000)); // Esperar antes de intentar acceder de nuevo
}
}
}
void vTask2(void *pvParameters) {
while (1) {
if (xSemaphoreTake(xMutex, portMAX_DELAY) == pdTRUE) {
// Acceder al recurso compartido
Serial.println("Tarea 2 accediendo al recurso compartido");
vTaskDelay(pdMS_TO_TICKS(500)); // Simular trabajo
Serial.println("Tarea 2 liberando el recurso compartido");
xSemaphoreGive(xMutex);
vTaskDelay(pdMS_TO_TICKS(1500)); // Esperar antes de intentar acceder de nuevo
}
}
}
void setup() {
Serial.begin(115200);
// Crear el mutex antes de usarlo
xMutex = xSemaphoreCreateMutex();
if (xMutex != NULL) {
xTaskCreate(vTask1, "Tarea 1", 1000, NULL, 1, NULL);
xTaskCreate(vTask2, "Tarea 2", 1000, NULL, 1, NULL);
}
// Iniciar el scheduler
vTaskStartScheduler();
}
void loop() {
// No hacer nada aquí
}
Note que este ejemplo es similar al explicado previamente con las Condiciones de Carrera.
Explicación del Ejemplo
En el siguiente ejemplo se busca lograr una coordinación de Tareas con Semáforos Binarios y de Semáforos de Conteo. Así, se proponen tres tareas:
Los semáforos a utilizar se proponen así: un semáforo binario se usa para señalar la disponibilidad de datos en el buffer, y un semáforo de conteo se usa para llevar la cuenta de la cantidad de datos procesados.
A continuación se presenta una posible implementación para resolver el ejemplo:
/*######################################################################
# C CODE.
######################################################################
# Copyright (C) 2024. F.E.Segura-Quijano (FES) fsegura@uniandes.edu.co
#
# Este trabajo está licenciado bajo la Licencia:
# Creative Commons Atribución-NoComercial 4.0 Internacional.
# Para ver una copia de esta licencia, visita
# http://creativecommons.org/licenses/by-nc/4.0/ o envía una carta
# a Creative Commons, PO Box 1866, Mountain View, CA 94042, USA.
######################################################################*/
/*######################################################################
# Parte de este código se basa en los ejemplos FreeRTOS de:
# https://github.com/ShawnHymel/introduction-to-rtos/tree/main
# Parte de este código fue generado con la asistencia de ChatGPT de OpenAI.
######################################################################*/
/*######################################################################
# LIBRARIES.
######################################################################*/
#include <Arduino.h>
// Use only core 1 for demo purposes
#if CONFIG_FREERTOS_UNICORE
static const BaseType_t app_cpu = 0;
#else
static const BaseType_t app_cpu = 1;
#endif
#define BUFFER_SIZE 10
// Buffers and indices
int buffer[BUFFER_SIZE];
int bufferIndex = 0;
// Semaphores
SemaphoreHandle_t xDataSemaphore;
SemaphoreHandle_t xCountSemaphore;
int dataCount = 0;
/*######################################################################
# TASKs.
######################################################################*/
// Producer Task
void vProducerTask(void *pvParameters) {
while (1) {
// Produce data
buffer[bufferIndex] = random(100);
Serial.print("Produced: ");
Serial.println(buffer[bufferIndex]);
// Signal that data is available
xSemaphoreGive(xDataSemaphore);
// Increment buffer index
bufferIndex = (bufferIndex + 1) % BUFFER_SIZE;
// Delay for a random time to simulate data production
vTaskDelay(pdMS_TO_TICKS(random(500, 1000)));
}
}
// Consumer Task 1
void vConsumerTask1(void *pvParameters) {
int data;
while (1) {
// Wait for data to be available
if (xSemaphoreTake(xDataSemaphore, portMAX_DELAY) == pdTRUE) {
// Consume data
bufferIndex = (bufferIndex == 0) ? BUFFER_SIZE - 1 : bufferIndex - 1;
data = buffer[bufferIndex];
Serial.print("Consumer 1 processed: ");
Serial.println(data);
// Signal that data has been processed
xSemaphoreGive(xCountSemaphore);
}
}
}
// Consumer Task 2
void vConsumerTask2(void *pvParameters) {
int data;
while (1) {
// Wait for data to be available
if (xSemaphoreTake(xDataSemaphore, portMAX_DELAY) == pdTRUE) {
// Consume data
bufferIndex = (bufferIndex == 0) ? BUFFER_SIZE - 1 : bufferIndex - 1;
data = buffer[bufferIndex];
Serial.print("Consumer 2 processed: ");
Serial.println(data);
// Signal that data has been processed
xSemaphoreGive(xCountSemaphore);
}
}
}
/*######################################################################
# SETUP.
######################################################################*/
void setup() {
// Configure Serial
Serial.begin(115200);
// Create the semaphores
xDataSemaphore = xSemaphoreCreateBinary();
xCountSemaphore = xSemaphoreCreateCounting(BUFFER_SIZE, 0);
// Check if the semaphores were created successfully
if (xDataSemaphore != NULL && xCountSemaphore != NULL) {
// Create tasks
xTaskCreate(vProducerTask, "Producer", 1000, NULL, 1, NULL);
xTaskCreate(vConsumerTask1, "Consumer 1", 1000, NULL, 1, NULL);
xTaskCreate(vConsumerTask2, "Consumer 2", 1000, NULL, 1, NULL);
} else {
Serial.println("Error creating semaphores.");
}
}
/*######################################################################
# LOOP.
######################################################################*/
void loop() {
// Do nothing
// setup() and loop() run in their own task with priority 1 in core 1
// on ESP32
}
Explicación del Código:
Definición de Semáforos:
Tareas:
Control de Buffer:
Creación de Semáforos y Tareas:
A continuación presentamos una captura de pantalla del monitor serial donde se puede ver la interacción con el programa del ejemplo:
Un mutex es una herramienta de sincronización que asegura que solo una tarea (o hilo) pueda acceder a un recurso compartido en un momento dado. El propósito principal de un mutex es la exclusión mutua. Un mutex tiene un concepto de propiedad, es decir que la tarea que toma el mutex debe ser la misma que lo libera. Garantiza que solo una tarea tenga acceso al recurso protegido por el mutex a la vez. Un mutex es ideal para proteger recursos críticos, como variables globales, secciones de código o dispositivos de hardware. Un mutex puede soportar mecanismos de herencia de prioridad para evitar la inversión de prioridad.
Un semáforo binario es una herramienta de sincronización que puede tener solo dos estados: tomado (1) o disponible (0). Se usa principalmente para la señalización y sincronización entre tareas o entre tareas e interrupciones. A diferencia de un mutex, un semáforo binario no tiene un concepto de propiedad, es decir que cualquier tarea puede tomar (decrementar) o dar (incrementar) el semáforo. Es útil para la sincronización simple entre tareas o para señalizar eventos. Aunque puede usarse para exclusión mutua, no es su propósito principal y no tiene soporte para herencia de prioridad. Un semáforo binario aunque puede usarse para exclusión mutua, no es su propósito principal y no tiene soporte para herencia de prioridad.
La herencia de prioridad es un mecanismo utilizado en sistemas de tiempo real para evitar un problema conocido como inversión de prioridad. Este mecanismo permite que una tarea con alta prioridad "herede" temporalmente la prioridad de una tarea con menor prioridad que posee un mutex, con el fin de prevenir bloqueos o retardos innecesarios en el sistema.
La inversión de prioridad ocurre cuando una tarea de alta prioridad se ve bloqueada esperando un recurso que está siendo utilizado por una tarea de baja prioridad, y una tarea de prioridad media impide que la tarea de baja prioridad libere el recurso. Esto puede llevar a que la tarea de alta prioridad no cumpla sus plazos de tiempo real, lo cual es crítico en sistemas de tiempo real.
Ejemplo de Inversión de Prioridad:
Herencia de Prioridad: La herencia de prioridad resuelve este problema al elevar temporalmente la prioridad de la tarea que posee el mutex (Tarea A) al nivel de la tarea más alta que está esperando por el mutex (Tarea B). Esto asegura que Tarea A se ejecute y libere el mutex lo antes posible, permitiendo que Tarea B continúe su ejecución. En FreeRTOS, los mutexes soportan herencia de prioridad de manera automática.
Las Notificaciones de Tareas en FreeRTOS son un mecanismo ligero y eficiente para la comunicación y sincronización entre tareas. Cada tarea tiene un array de valores de notificación que otras tareas pueden utilizar para enviarle datos o señales. Las notificaciones de tareas pueden considerarse una alternativa más rápida y sencilla a las colas y semáforos para ciertas aplicaciones.
Dentro de las ventajas que tiene el uso de notificación de tareas están:
Dentro de las desventajas que tiene el uso de notificación de tareas están:
#include <Arduino.h>
// Handles de las tareas
TaskHandle_t xProducerTaskHandle = NULL;
TaskHandle_t xConsumerTaskHandle = NULL;
void vProducerTask(void *pvParameters) {
uint32_t ulValueToNotify = 0;
while (1) {
// Produce un valor de datos
ulValueToNotify++;
// Notifica a la tarea consumidora
xTaskNotifyGive(xConsumerTaskHandle);
// Simula un retardo en la producción de datos
vTaskDelay(pdMS_TO_TICKS(1000));
}
}
void vConsumerTask(void *pvParameters) {
uint32_t ulNotificationValue;
while (1) {
// Espera la notificación de la tarea productora
ulNotificationValue = ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
// Procesa los datos recibidos
Serial.print("Dato recibido: ");
Serial.println(ulNotificationValue);
}
}
void setup() {
Serial.begin(115200);
// Crear las tareas
xTaskCreate(vProducerTask, "Productor", 1000, NULL, 1, &xProducerTaskHandle);
xTaskCreate(vConsumerTask, "Consumidor", 1000, NULL, 1, &xConsumerTaskHandle);
}
void loop() {
// No hacer nada aquí
}
Explicación del Código
Definición de las Tareas:
Notificaciones de Tareas:
Configuración y Ejecución: