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.
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.
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);
}
}
}
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.
ulTaskNotifyTake y xTaskNotifyGive), las cuales son mucho más ligeras en RAM y rápidas en ejecución que los Semáforos Binarios tradicionales.TxCpltCallback). Todo llamado a la API del RTOS desde aquí debe usar versiones específicas con el sufijo FromISR.portYIELD_FROM_ISR() al finalizar la interrupción. Esto le indica al Scheduler de FreeRTOS que si la tarea que acaba de despertar tiene mayor prioridad que la tarea que estaba corriendo antes de la interrupción, debe realizar el cambio de contexto (Context Switch) inmediatamente, sin esperar al siguiente ciclo (Tick) del sistema.