En el mundo estricto de FreeRTOS, un Mailbox no es una estructura de datos exótica; es simplemente una Cola de Mensajes (Message Queue) de Longitud 1 combinada con una política de sobrescritura.
Ayer vimos que una Cola convencional es como un tubo largo. Si la tarea emisora envía los comandos A, B y C, y el receptor está dormido, los tres comandos se formarán. Cuando el receptor despierte, leerá A primero. Esto es vital para eventos discretos donde cada instrucción importa.
El Mailbox es para Estados Continuos (Telemetría: temperatura, presión, voltaje). El sensor mide la presión 100 PSI, luego 105 PSI, y luego 110 PSI. Si el buzón ya está lleno con 100 PSI, la tarea del sensor no debe bloquearse a esperar, ni debe formarlo. El sensor debe tirar a la basura el 100 y sobrescribirlo forzosamente con el 110. Así, cuando la Pantalla despierte, siempre leerá la verdad más absoluta y reciente.
Al igual que la Cola normal, el Mailbox mantiene la ventaja del RTOS: Si la Pantalla intenta leer la presión y el buzón está vacío (porque el ADC aún no arranca), la Pantalla pasa a estado BLOCKED y no gasta CPU. En cuanto el sensor deposite la primera lectura, la Pantalla despertará de inmediato.
Ejercicio 1: CMSIS-OS v2 no tiene una función nativa llamada osMailboxOverwrite. Utiliza la estructura base osMessageQueue. Para simular el patrón Mailbox de forma robusta y Thread-Safe, crearemos una función "Envoltorio" (Wrapper). Si intentamos meter la lectura del sensor en la cola y nos arroja un error porque está llena, nuestra función extraerá la lectura vieja a la basura y volverá a inyectar la lectura nueva.
/**
* @file telemetria_mailbox.c
* @brief Implementación del Patrón Mailbox para Telemetría de Presión usando CMSIS-OS v2
*/
#include "stm32f401xe.h"
#include "cmsis_os2.h"
// =======================================================
// ESTRUCTURAS Y VARIABLES GLOBALES
// =======================================================
// La estructura de nuestro mensaje de estado
typedef struct {
float presion_psi;
uint32_t timestamp_ms; // Para saber qué tan "fresca" es la lectura
} TelemetriaPresion_t;
// Identificador del Mailbox (Que en el fondo es una cola de tamaño 1)
osMessageQueueId_t mailbox_presion;
// =======================================================
// EL WRAPPER DEL PATRÓN MAILBOX (Sobrescritura Segura)
// =======================================================
void Mailbox_Update_Presion(TelemetriaPresion_t *nueva_lectura) {
// 1. Intentamos inyectar el mensaje sin esperar (Timeout = 0)
osStatus_t status = osMessageQueuePut(mailbox_presion, nueva_lectura, 0, 0);
// 2. Si falló (osErrorResource), significa que la cola de 1 espacio está llena.
// Esto es normal: el ADC mide más rápido de lo que la Pantalla lee.
if (status == osErrorResource) {
// a) Sacamos la basura (leemos el dato viejo para vaciar el slot)
TelemetriaPresion_t basura;
osMessageQueueGet(mailbox_presion, &basura, NULL, 0);
// b) Volvemos a inyectar la nueva lectura fresca.
// Ahora sí entrará con éxito garantizado.
osMessageQueuePut(mailbox_presion, nueva_lectura, 0, 0);
}
}
// =======================================================
// HILO PRODUCTOR: SENSOR ADC (Alta Frecuencia)
// =======================================================
void Thread_Sensor_Presion(void *argument) {
TelemetriaPresion_t lectura_actual;
for (;;) {
// Lee el ADC del hardware (Ej. 100 veces por segundo)
lectura_actual.presion_psi = ADC_Leer_Fisico();
lectura_actual.timestamp_ms = osKernelGetTickCount();
// Actualiza el Mailbox sin bloquear a nadie
Mailbox_Update_Presion(&lectura_actual);
osDelay(10); // Duerme 10ms (100 Hz)
}
}
// =======================================================
// HILO CONSUMIDOR: PANTALLA LCD (Baja Frecuencia)
// =======================================================
void Thread_Pantalla_UI(void *argument) {
TelemetriaPresion_t lectura_recibida;
for (;;) {
// La pantalla solo se dibuja 2 veces por segundo (Timeout = osWaitForever)
// Extrae la última lectura reportada por el sensor.
osMessageQueueGet(mailbox_presion, &lectura_recibida, NULL, osWaitForever);
// Dibuja la presión actual real
UI_Dibujar_Barra_Presion(lectura_recibida.presion_psi);
// Rendimos la CPU por 500ms
osDelay(500);
}
}
// =======================================================
// INICIALIZACIÓN
// =======================================================
int main(void) {
SystemClock_Config();
osKernelInitialize();
// CREAR EL MAILBOX
// ¡CRÍTICO! El tamaño máximo de mensajes debe ser ESTRICTAMENTE 1.
mailbox_presion = osMessageQueueNew(1, sizeof(TelemetriaPresion_t), NULL);
// Crear Hilos
osThreadNew(Thread_Sensor_Presion, NULL, &attr_sensor);
osThreadNew(Thread_Pantalla_UI, NULL, &attr_ui);
osKernelStart();
while (1);
}
Objetivo del día: Telemetría de Latencia Cero sin Saturación de RAM.
La presión del gas dentro del tanque estacionario de tu gasolinera es una variable viva y peligrosa. Para vigilarla, programaste un conversor Analógico a Digital (ADC) que lee el sensor a altísima velocidad (100 Hz). Si usaras una "Cola de Mensajes" estándar (FIFO) de 50 espacios para enviar las presiones hacia el Hilo del Servidor WiFi (que envía datos a la nube cada 2 segundos), la cola se llenaría en medio segundo. Los siguientes 1.5 segundos de presión crítica se perderían (Dropped Packets), o peor, cuando el WiFi despierte, leería 50 datos obsoletos, generando un retraso artificial gravísimo en los paneles de control de la nube de AWS.
Al implementar el patrón **Mailbox**, transformaste el flujo de la información. El Sensor de Presión es un dictador que sobrescribe el buzón continuamente. Al servidor WiFi no le importan las 99 lecturas intermedias que ocurrieron mientras dormía; cuando despierta y saca el dato del buzón, tiene la certeza matemática de que ese número representa la presión del Gas LP de *este exacto milisegundo*. Lograste información en tiempo real, utilizando solo el equivalente en RAM a 1 solo mensaje (8 bytes).