DÍA 251 DE 365

Fase 5: Diseño Industrial y Proyecto Final

📖 Teoría a Estudiar

Direct Memory Access (DMA): El Mensajero de Hardware

El DMA es un coprocesador independiente dentro del STM32. Su única función es copiar bloques de bytes desde la memoria RAM (tus arreglos) hacia un periférico (como el UART o SPI) sin que la CPU principal tenga que hacer nada. Le das una dirección de inicio, un tamaño y él se encarga del resto.

El problema surge porque la memoria RAM es compartida. Si la CPU ordena al DMA enviar el arreglo buffer_venta, y un microsegundo después la CPU modifica ese mismo arreglo para otra venta, el DMA enviará una mezcla corrupta de datos (Data Race). Para evitarlo, la Tarea que invoca al DMA debe esperar a que este termine antes de volver a tocar el buffer.

Semáforos Binarios vs Task Notifications

Para bloquear una Tarea en FreeRTOS hasta que un hardware responda, los novatos usan Semáforos Binarios. Sin embargo, FreeRTOS introdujo las Task Notifications (Notificaciones de Tarea). Funcionan igual que un semáforo, pero no requieren alojar objetos en RAM y son un 45% más rápidas en realizar el cambio de contexto (Context Switch). Son la herramienta definitiva para acoplar Rutinas de Interrupción de Hardware (ISR) con el Sistema Operativo.

ARQUITECTURA DE SUSPENSIÓN: RTOS TASK NOTIFICATIONS Y DMA TAREA RTOS (Task_Telemetry) OTRAS TAREAS (Metrología, RFID) HARDWARE DMA Coprocesador STM32 HARDWARE ISR (DMA_TxCpltCallback) 1. HAL_UART_Tx_DMA() Delega 200 bytes 2. ulTaskNotifyTake() TAREA DORMIDA 3. CPU LIBRE Calculando Gas LP... Transfiriendo Bytes a la UART sin CPU 4. DMA Termina (Dispara Interrupt IRQ) 5. vTaskNotifyGiveFromISR() 6. TAREA DESPIERTA (Reclama el buffer) CONCLUSIÓN: Los 17 milisegundos de transmisión costaron 0% de uso de CPU.

⚙️ Ejercicios Prácticos

Context Switch desde una ISR: Cuando una interrupción de hardware (ISR) despierta a una Tarea de FreeRTOS, esta última no asume el control inmediatamente; la CPU terminará la ISR y volverá a lo que estaba haciendo antes (probablemente una tarea de baja prioridad). Para forzar al procesador a saltar directamente a la Tarea recién despertada en el mismo microsegundo en que sale de la ISR, se debe ejecutar la sagrada macro portYIELD_FROM_ISR().

Ejercicio 1: Transmisión UART Asíncrona. Programaremos la Tarea que envía la telemetría a la nube, y la rutina de Callback del hardware DMA que libera el candado lógico.


/**
 * @file uart_dma_rtos.c
 * @brief Offloading de Transmisión UART con RTOS Task Notifications (MISRA-C)
 */

#include "stm32f401xe.h"
#include "cmsis_os2.h"
#include "FreeRTOS.h"
#include "task.h"
#include <stdint.h>
#include <stdbool.h>

// =========================================================================
// DEFINICIONES GLOBALES
// =========================================================================
extern UART_HandleTypeDef huart1;

// Handler o "Puntero" de la tarea que maneja la telemetría.
// Lo necesitamos para que la ISR sepa a quién debe despertar.
TaskHandle_t Tarea_Telemetria_Handle = NULL;

// Buffer protegido de la Tarea
static uint8_t buffer_tx_uart[256];

// =========================================================================
// TAREA RTOS (HILO PRODUCTOR)
// =========================================================================
void Task_Telemetria_Cloud(void *argument) {
    
    // 1. Guardamos nuestra propia "identidad" en la variable global
    Tarea_Telemetria_Handle = xTaskGetCurrentTaskHandle();
    
    for(;;) {
        // ... (Aquí se empaca la venta en el buffer_tx_uart) ...
        uint16_t longitud_paquete = 150U;
        
        // 2. DISPARO DEL HARDWARE DMA
        // El STM32 configura el DMA y retorna instantáneamente. La CPU sigue viva.
        if (HAL_UART_Transmit_DMA(&huart1, buffer_tx_uart, longitud_paquete) == HAL_OK) {
            
            // 3. BLOQUEO VOLUNTARIO (Dormir la tarea)
            // 'ulTaskNotifyTake' bloquea la tarea hasta que reciba un "Give".
            // pdTRUE significa que se limpia la bandera (como un semáforo binario).
            // Timeout de 100ms. Si el DMA falla por hardware roto, no nos colgamos para siempre.
            uint32_t notificacion_rx = ulTaskNotifyTake(pdTRUE, pdMS_TO_TICKS(100U));
            
            if (notificacion_rx > 0U) {
                // EXITO: El DMA terminó exitosamente y nos despertó.
                // Ya es seguro reutilizar 'buffer_tx_uart'.
            } else {
                // FALLA (Timeout): Pasaron 100ms y la interrupción nunca llegó.
                // Acciones de mitigación: Reiniciar periférico UART/DMA.
                HAL_UART_AbortTransmit(&huart1);
            }
        }
        
        // Dormir un tiempo antes de mandar el siguiente reporte
        osDelay(pdMS_TO_TICKS(5000U)); 
    }
}

// =========================================================================
// HARDWARE ISR (RUTINA DE INTERRUPCIÓN DEL DMA)
// =========================================================================
/**
 * @brief Callback invocado por los drivers de ST cuando el DMA termina 
 *        de escupir el último byte del arreglo hacia el silicio del UART.
 *        ¡ADVERTENCIA! Este código corre en el contexto de Interrupción, NO en el RTOS.
 */
void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart) {
    
    if (huart->Instance == USART1) {
        
        // Variable local requerida por FreeRTOS para saber si despertó 
        // a una tarea que tiene mayor prioridad que la tarea actualmente en ejecución.
        BaseType_t requiere_cambio_contexto = pdFALSE;
        
        // 4. EL DESPERTADOR LÓGICO
        // Emitimos la notificación directamente hacia el Handle de la tarea.
        // Al terminar, la API modifica 'requiere_cambio_contexto' si es necesario.
        if (Tarea_Telemetria_Handle != NULL) {
            vTaskNotifyGiveFromISR(Tarea_Telemetria_Handle, &requiere_cambio_contexto);
            
            // 5. CAMBIO DE CONTEXTO OBLIGATORIO (YIELD)
            // Si la Tarea_Telemetria_Cloud tiene prioridad "High", y el RTOS estaba
            // corriendo la tarea "Idle" (Low), portYIELD fuerza a que la CPU
            // abandone todo y salte a la Tarea High de inmediato al salir de esta ISR.
            portYIELD_FROM_ISR(requiere_cambio_contexto);
        }
    }
}
        

🚀 Avance del Proyecto Tesis: Despachador de Gas LP

Objetivo del día: Sincronización Asíncrona Extrema y Protección del Determinismo Metrológico.

Imagina este escenario industrial: Un cliente está llenando su tanque. El Gas LP fluye a 80 litros por minuto, generando interrupciones del caudalímetro cada pocos milisegundos. Exactamente al mismo tiempo, el administrador central de la planta envía un comando remoto por AWS solicitando a tu máquina descargar una actualización de configuración de red (3 Kilobytes de datos).

Si tu código usara rutinas bloqueantes (`HAL_UART_Transmit`), el microcontrolador le diría al fluido de gas: *"Pausa, estoy enviando datos al módem"*. Pero el gas presurizado no sabe de informática, seguirá fluyendo y la venta no se contabilizará.

Al implementar el patrón **DMA + Task Notifications**, dividiste tu microcontrolador en dos cerebros. El coprocesador interno de silicio (DMA) toma el archivo pesado y se encarga de lidiar con el módem en el fondo. La CPU principal de 84 MHz entra en reposo (Block State) y cede toda su potencia a la Tarea de Metrología. En el instante exacto en que el Módem termina de recibir los 3 KB, el silicio dispara una señal eléctrica (ISR) que invoca el comando `portYIELD_FROM_ISR`. Tu Tarea de Comunicaciones resucita instantáneamente de entre los muertos, cierra el archivo y continúa su trabajo. Acabas de crear un entorno **Hard Real-Time**, donde la comunicación de red jamás afectará el conteo de dinero de tu cliente. Esto es la cima del desarrollo embebido.

📝 Resumen del Día