Una Cola de Mensajes (Message Queue) es un tubo o banda transportadora creada en la memoria RAM y administrada directamente por el Kernel del RTOS. Funciona bajo el principio FIFO: el primer dato que entra por un lado, es el primero que sale por el otro.
A diferencia de las variables globales, la Cola actúa como un buzón asíncrono. Un hilo Productor (ej. el que maneja el teclado o el WiFi) empuja un "Sobre" dentro de la cola. El hilo Consumidor (ej. el controlador de válvulas) saca el "Sobre", lo abre y lo procesa. Si el Productor empuja datos más rápido de lo que el Consumidor los puede leer, no pasa nada: los sobres se forman en la cola pacientemente.
La característica más poderosa de las colas en FreeRTOS es que los datos se pasan por copia. Cuando la Tarea A mete un struct en la cola, el RTOS copia cada byte literal a su memoria interna. Inmediatamente después, la Tarea A puede borrar o modificar su estructura local sin afectar al mensaje que ya va viajando por la cola hacia la Tarea B. ¡Condiciones de carrera matemáticamente imposibles!
Si la Tarea B intenta sacar un mensaje pero la cola está vacía, el RTOS no la deja esperando inútilmente (Busy-Wait). La transfiere automáticamente al estado BLOCKED. La Tarea B dormirá profundamente sin gastar CPU hasta el microsegundo exacto en que la Tarea A meta un mensaje en la cola. La cola funciona simultáneamente como canal de datos y mecanismo de sincronización.
Ejercicio 1: Vamos a estructurar las comunicaciones del Despachador usando CMSIS-OS v2. Crearemos un struct (nuestro "Sobre") que contendrá un Comando de Operación y un Parámetro numérico. El Hilo WiFi (nuestro Servidor IoT) inyectará comandos en la Cola, y el Hilo de Metrología dormirá esperando a que aparezca un mensaje nuevo para actuar.
/**
* @file colas_rtos.c
* @brief Enrutamiento de Comandos IoT a Metrología mediante Queues CMSIS-OS v2
*/
#include "stm32f401xe.h"
#include "cmsis_os2.h"
// =======================================================
// 1. DEFINICIÓN DEL PROTOCOLO DE MENSAJES
// =======================================================
typedef enum {
CMD_NULO = 0,
CMD_INICIAR_VENTA,
CMD_PAUSAR_VENTA,
CMD_CERRAR_EMERGENCIA,
CMD_CAMBIAR_PRECIO
} ComandoOperacion_t;
// Este es el "Sobre" que viajará por la Cola
typedef struct {
ComandoOperacion_t comando; // 4 bytes
float parametro; // 4 bytes (Ej. Litros a despachar, o Nuevo Precio)
} MensajeVenta_t; // Total: 8 bytes por mensaje
// ID Global de la Cola
osMessageQueueId_t cola_comandos_id;
// =======================================================
// HILO PRODUCTOR: RED WIFI (IoT AWS)
// =======================================================
void Thread_WiFi_Cloud(void *argument) {
MensajeVenta_t msg_tx; // Variable local en RAM de esta tarea
for (;;) {
// Simulamos recibir una instrucción desde el servidor AWS...
if (AWS_Hay_Nuevo_Comando()) {
// Empaquetamos el mensaje
msg_tx.comando = CMD_INICIAR_VENTA;
msg_tx.parametro = 50.0f; // El cliente compró 50 litros por la App
// MAGIA RTOS: Ponemos la COPIA del mensaje en la cola
// Parámetro 3: Prioridad del mensaje (0 = Normal)
// Parámetro 4: Timeout. Si la cola está llena, no esperamos (0).
osMessageQueuePut(cola_comandos_id, &msg_tx, 0, 0U);
// ¡Seguridad! Como la Cola copió los datos, podemos reusar/borrar 'msg_tx'
// inmediatamente sin dañar el mensaje que ya va en camino.
}
osDelay(100);
}
}
// =======================================================
// HILO CONSUMIDOR: METROLOGÍA DE VÁLVULAS
// =======================================================
void Thread_Metrologia(void *argument) {
MensajeVenta_t msg_rx; // Estructura local donde recibiremos el correo
osStatus_t status;
for (;;) {
// MAGIA RTOS: Bloqueo Inteligente
// La tarea se queda "dormida" (BLOCKED) aquí. No avanza ni gasta CPU.
// Solo despertará en el microsegundo exacto en que haya un mensaje.
// 'osWaitForever' significa que esperaremos indefinidamente.
status = osMessageQueueGet(cola_comandos_id, &msg_rx, NULL, osWaitForever);
if (status == osOK) {
// ¡Ha llegado correo! Extraemos el comando y actuamos.
switch (msg_rx.comando) {
case CMD_INICIAR_VENTA:
Valvula_Preparar_Carga(msg_rx.parametro); // Ej. 50 Lts
Valvula_SetApertura(100.0f);
break;
case CMD_PAUSAR_VENTA:
Valvula_SetApertura(0.0f);
break;
case CMD_CAMBIAR_PRECIO:
Metro_ActualizarPrecioLocal(msg_rx.parametro);
break;
default:
break;
}
}
}
}
// =======================================================
// INICIALIZACIÓN
// =======================================================
int main(void) {
SystemClock_Config();
osKernelInitialize();
// CREAR LA COLA DE MENSAJES ANTES QUE LAS TAREAS
// Argumento 1: Capacidad (¿Cuántos mensajes caben formados?) -> 10 mensajes.
// Argumento 2: Tamaño físico de cada mensaje -> sizeof(MensajeVenta_t) (8 bytes).
cola_comandos_id = osMessageQueueNew(10, sizeof(MensajeVenta_t), NULL);
if (cola_comandos_id == NULL) {
// Error: RAM del Kernel (Heap) insuficiente
Error_Handler();
}
// Crear Hilos
osThreadNew(Thread_WiFi_Cloud, NULL, &attr_wifi);
osThreadNew(Thread_Metrologia, NULL, &attr_metro);
osKernelStart();
while (1);
}
Objetivo del día: Desacoplamiento de Subsistemas (Decoupling) y Arquitectura Escalable.
Un sistema de software mal diseñado se parece a un plato de espagueti: si mueves un fideo de un lado (el WiFi), otro fideo del lado opuesto se mueve (la Válvula) porque comparten docenas de variables globales enmarañadas. Si el día de mañana la estación de gas decide cambiar su sistema AWS por un servidor interno Modbus, tendrías que reescribir toda la lógica de la válvula para entender las nuevas variables globales de Modbus.
Al implementar el sistema nervioso central de tu Despachador utilizando **Colas de Mensajes (Message Queues)**, has logrado el **Desacoplamiento Total**. A la Tarea de Metrología no le importa si el comando `CMD_INICIAR_VENTA` provino de la nube WiFi, del teclado físico, de una tarjeta RFID o del puerto Modbus. Su única responsabilidad es dormir, abrir el buzón, leer el sobre estandarizado `MensajeVenta_t`, y ejecutar. Puedes añadir módulos IoT, cambiarlos o borrarlos, y tu código metrológico jamás tendrá que ser modificado o certificado de nuevo.
osMessageQueueGet), si ésta se encuentra vacía, la tarea consumidora es colocada inmediatamente en el estado **BLOCKED**. Esto provee una sincronización perfecta y latencia cero sin derrochar ciclos de CPU, esperando ciegamente la inyección del próximo paquete de datos.