En Bare-Metal, cuando una interrupción de hardware ocurría, solíamos encender una variable booleana (flag_pulso = true;). Luego, en el Súper Bucle, escribíamos if(flag_pulso == true) { hacer_algo(); }.
En un RTOS, hacer esto es desastroso. Significa que la Tarea debe estar constantemente girando en un bucle preguntando "¿Ya ocurrió? ¿Ya ocurrió?", desperdiciando el 100% del procesador y causando inanición a las tareas menores.
Un Semáforo Binario es una bandera administrada directamente por el Kernel del Sistema Operativo. Funciona como una "Ficha" o "Token" que solo puede valer 0 o 1.
osSemaphoreAcquire(). Si el semáforo está vacío (0), el RTOS arranca a la Tarea de la CPU y la pone en estado BLOCKED (coma inducido). Consumo de CPU: 0%.osSemaphoreRelease(). Esto inyecta el Token (1) en el semáforo.Regla de Oro: Los Semáforos Binarios se usan para Sincronización (Signaling) de un evento. No los confundas con los Mutexes (Día 190), que se usan para proteger variables compartidas.
Ejercicio 1: Reestructuraremos el sensor de flujo de gas. Quitaremos toda la matemática de la interrupción (ISR) y la moveremos a un Hilo de Alta Prioridad. Usaremos osSemaphoreNew para crear el candado, osSemaphoreRelease en la ISR para liberar el pulso, y osSemaphoreAcquire(id, osWaitForever) en la Tarea para obligarla a dormir para siempre hasta que el hardware avise.
/**
* @file sensor_hall_rtos.c
* @brief Sincronización Hardware-Software mediante Semáforos Binarios
*/
#include "stm32f401xe.h"
#include "cmsis_os2.h"
// ID Global de nuestro Semáforo
osSemaphoreId_t sem_pulso_caudalimetro;
// Variables protegidas de la aplicación
float litros_despachados = 0.0f;
const float LITROS_POR_PULSO = 0.025f; // 25ml por pulso del Hall
// =======================================================
// 1. LA INTERRUPCIÓN HARDWARE (ISR) - Rápida y Estúpida
// =======================================================
// Se dispara cuando el imán del caudalímetro pasa por el sensor (Pin PB0)
void EXTI0_IRQHandler(void) {
// 1. Limpiar bandera de interrupción del hardware
EXTI->PR |= (1 << 0);
// 2. MAGIA RTOS: Soltar el semáforo (Dar el Token)
// En CMSIS-OS v2, esta función es segura para usarse desde ISR.
// El RTOS sabrá que, al terminar esta ISR, debe forzar un Cambio de
// Contexto inmediato a la Tarea que estaba esperando este semáforo.
osSemaphoreRelease(sem_pulso_caudalimetro);
// ¡FIN! Cero matemáticas, cero latencia. Salimos en microsegundos.
}
// =======================================================
// 2. EL HILO DE SOFTWARE (Tarea de Alta Prioridad)
// =======================================================
void Thread_Procesar_Caudal(void *argument) {
for (;;) {
// 1. DORMIR PROFUNDAMENTE HASTA EL EVENTO
// 'osWaitForever' asegura que si no hay pulsos, la tarea consumirá 0% de CPU.
// Cuando la ISR haga Release, esta función despertará y consumirá el Token.
osSemaphoreAcquire(sem_pulso_caudalimetro, osWaitForever);
// --- A PARTIR DE AQUÍ ESTAMOS EN LA TAREA (No en la ISR) ---
// 2. Ejecutar matemática de punto flotante pesada con calma
litros_despachados += LITROS_POR_PULSO;
// 3. Evaluar límites y accionar válvulas si es necesario
if (litros_despachados >= LIMITES_DE_VENTA) {
Valvula_Cerrar();
}
// Al terminar el bucle, vuelve arriba y se Bloquea otra vez.
}
}
// =======================================================
// INICIALIZACIÓN
// =======================================================
int main(void) {
SystemClock_Config();
osKernelInitialize();
// Configurar el Pin B0 como entrada con interrupción (EXTI)
// Hardware_EXTI_Init();
// CREACIÓN DEL SEMÁFORO BINARIO
// Parámetro 1: Cuenta máxima (1 = Binario).
// Parámetro 2: Cuenta inicial (0 = Empieza vacío, bloqueando a quien lo pida).
sem_pulso_caudalimetro = osSemaphoreNew(1, 0, NULL);
// Crear el Hilo con Alta Prioridad
const osThreadAttr_t attr_caudal = {
.name = "FlowTask",
.stack_size = 512,
.priority = osPriorityHigh,
};
osThreadNew(Thread_Procesar_Caudal, NULL, &attr_caudal);
osKernelStart();
while (1);
}
Objetivo del día: Sincronización Diferida (Deferred Interrupt Processing).
En el Bare-Metal de la Fase 2, programamos la función matemática `Calcular_Precio_y_Flujo()` directamente dentro de la rutina de interrupción `EXTI0_IRQHandler`. En aquel entonces funcionaba. Pero ahora, con el procesador ejecutando un RTOS, protocolos de red WiFi e interfaces gráficas complejas, dejar matemática de punto flotante dentro de una ISR es un pecado arquitectónico. Las ISR bloquean al RTOS entero; si una ISR tarda mucho, los Ticks del sistema se pierden y la máquina entera se desincroniza.
Al implementar **Semáforos Binarios**, hemos separado el "Aviso" del "Trabajo". El hardware avisa instantáneamente y huye de la ISR. La matemática pesada se realiza en `Thread_Procesar_Caudal`. Esta técnica profesional se llama **Procesamiento de Interrupción Diferida**. Mantiene el Kernel sano, los Ticks precisos y garantiza que jamás perderás un solo pulso del sensor, incluso bombeando Gas LP a máxima presión.
osSemaphoreAcquire(). Si el semáforo es 0, el Kernel pone a la tarea en estado **BLOCKED** sin consumir tiempo de CPU. La ISR usa osSemaphoreRelease(), inyectando un 1 y despertando a la Tarea inmediatamente.