En el Día 116 utilizamos el DMA para mover datos del ADC. Hoy usaremos el DMA1 para la recepción del USART2. Le diremos al hardware del UART: "En lugar de levantar la bandera RXNE para molestar al CPU, envíale una Petición de Transferencia (DMA Request) directa al DMA1".
El DMA atrapará silenciosamente cada byte a la velocidad de la luz y lo escribirá secuencialmente en un enorme arreglo Circular en la memoria RAM. El procesador ARM Cortex-M ni siquiera se enterará de que se están descargando kilobytes de datos.
Si el DMA maneja los bytes en silencio, ¿cómo sabe nuestro Súper Bucle (main) que ya llegó un paquete completo para analizarlo? En el esquema clásico, buscábamos la letra `\n`. Pero el DMA es "tonto"; solo mueve bytes, no sabe leerlos ni parsearlos.
El hardware del STM32 tiene un as bajo la manga: IDLE Line Detection (Detección de Línea Inactiva). Si el Servidor Central envía un paquete de 2,000 letras, los bytes entran uno pegado al otro por el cable. Cuando el servidor termina, el cable se queda en estado de reposo (Alto / 3.3V). Si el USART detecta silencio durante el equivalente a 1 marco de bits (aprox. 86 µs), levanta la bandera IDLE y dispara una única interrupción. ¡Esa es nuestra señal! Significa que la ráfaga de datos ha terminado, y podemos ir a la RAM a leer lo que el DMA dejó ahí.
Ejercicio 1: Desactivaremos la interrupción `RXNEIE` del Día 132. En su lugar, activaremos el bit DMAR del USART2 para que delegue la recepción al DMA1 (Stream 5, Canal 4). Activaremos la interrupción IDLEIE. Cuando ocurra el IDLE, el procesador detendrá el DMA momentáneamente para analizar cuántos bytes llegaron, copiará el paquete y reiniciará el escáner.
/**
* @file uart_dma_idle.c
* @brief Recepción Masiva y Eficiente: UART RX vía DMA + IDLE Line Detection
*/
#include "stm32f401xe.h"
#include <stdbool.h>
#include <string.h>
#define RX_BUFFER_SIZE 256
// Búfer físico donde el DMA escribirá los bytes a velocidad de hardware
volatile uint8_t rx_dma_buffer[RX_BUFFER_SIZE];
// Búfer lógico donde copiaremos el paquete seguro
uint8_t paquete_recibido[RX_BUFFER_SIZE];
volatile uint16_t longitud_paquete = 0;
volatile bool paquete_listo = false;
void inicializar_usart2_dma_rx(void) {
// 1. RELOJES (USART2, GPIOA y DMA1)
RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN;
RCC->AHB1ENR |= RCC_AHB1ENR_DMA1EN;
RCC->APB1ENR |= RCC_APB1ENR_USART2EN;
// Pines PA2 (TX) y PA3 (RX) en AF7 (Omitido por brevedad, igual al Día 130)
GPIOA->MODER &= ~(GPIO_MODER_MODE2 | GPIO_MODER_MODE3);
GPIOA->MODER |= (GPIO_MODER_MODE2_1 | GPIO_MODER_MODE3_1);
GPIOA->AFR[0] &= ~(0xFF << 8);
GPIOA->AFR[0] |= (0x77 << 8);
// ====================================================
// 2. CONFIGURACIÓN DEL DMA1 (Stream 5, Channel 4 para USART2_RX)
// ====================================================
DMA1_Stream5->CR &= ~DMA_SxCR_EN;
while(DMA1_Stream5->CR & DMA_SxCR_EN);
DMA1_Stream5->CR |= (4 << DMA_SxCR_CHSEL_Pos); // Seleccionar Canal 4
// Direcciones
DMA1_Stream5->PAR = (uint32_t)&USART2->DR; // Origen: UART Data Register
DMA1_Stream5->M0AR = (uint32_t)rx_dma_buffer; // Destino: Nuestro Array en RAM
DMA1_Stream5->NDTR = RX_BUFFER_SIZE; // Cantidad máxima a transferir
// Modo: Incremento en memoria (MINC) y Circular (CIRC)
DMA1_Stream5->CR |= DMA_SxCR_MINC | DMA_SxCR_CIRC;
DMA1_Stream5->CR |= DMA_SxCR_EN; // Iniciar el motor del DMA
// ====================================================
// 3. CONFIGURACIÓN DEL USART2
// ====================================================
USART2->CR1 = 0;
USART2->BRR = 0x016D; // 115200 Baudios a 42MHz
// Habilitar RX, TX y la Interrupción de Línea Inactiva (IDLE)
// ADVERTENCIA: NO activamos RXNEIE, el CPU ignorará los bytes individuales.
USART2->CR1 |= USART_CR1_TE | USART_CR1_RE | USART_CR1_IDLEIE;
// MAGIA: Habilitar DMA Receiver Mode en el registro CR3
USART2->CR3 |= USART_CR3_DMAR;
// Habilitar la interrupción global del USART en el NVIC
NVIC_SetPriority(USART2_IRQn, 4);
NVIC_EnableIRQ(USART2_IRQn);
USART2->CR1 |= USART_CR1_UE; // Start!
}
/**
* @brief Interrupción disparada ÚNICAMENTE cuando la ráfaga de datos ha terminado.
*/
void USART2_IRQHandler(void) {
// Si la causa fue la detección de línea inactiva (IDLE)
if (USART2->SR & USART_SR_IDLE) {
// Secuencia oficial para limpiar la bandera IDLE: Leer SR, luego leer DR
volatile uint32_t limpieza = USART2->SR;
limpieza = USART2->DR;
(void)limpieza;
// Calcular cuántos bytes recibió el DMA
// NDTR guarda los bytes "que faltan". Lo restamos del tamaño total.
uint16_t bytes_recibidos = RX_BUFFER_SIZE - DMA1_Stream5->NDTR;
// Detenemos el DMA para reiniciar la escritura segura en el índice 0
DMA1_Stream5->CR &= ~DMA_SxCR_EN;
while(DMA1_Stream5->CR & DMA_SxCR_EN);
// Copiamos el paquete a un búfer seguro para que el Main lo procese
// (Usamos memcpy para mover el bloque de memoria de golpe)
memcpy(paquete_recibido, (const void*)rx_dma_buffer, bytes_recibidos);
paquete_recibido[bytes_recibidos] = '\0'; // Terminador
longitud_paquete = bytes_recibidos;
paquete_listo = true;
// Reiniciamos el contador NDTR y reactivamos el DMA para la siguiente ráfaga
DMA1_Stream5->NDTR = RX_BUFFER_SIZE;
DMA1_Stream5->CR |= DMA_SxCR_EN;
}
}
int main(void) {
// inicializar_relojes();
inicializar_usart2_dma_rx();
while(1) {
// --- PROCESAMIENTO ASÍNCRONO DEL PAQUETE ---
if (paquete_listo) {
// El Súper Bucle analiza todo el paquete JSON o Modbus de una vez
// procesar_json_configuracion(paquete_recibido, longitud_paquete);
// Reiniciar bandera
paquete_listo = false;
}
// El CPU está 100% libre para medir el fluido.
// No hay interrupciones por byte, cero sobrecarga.
}
return 0;
}
Objetivo del día: Soporte para Actualizaciones OTA (Over The Air) y JSONs Masivos.
La tecnología avanza, y los estándares de comunicación gasera también. Hoy en día, muchas estaciones configuran sus equipos enviando extensas tramas JSON de configuración (ej. precios del día, factores de corrección, listas de empleados autorizados, etc.) a través de la red de la gasolinera.
Si tuvieras la arquitectura vieja (Interrupción RXNE), descargar un JSON de 2,000 caracteres habría interrumpido al CPU 2,000 veces. Al conectar el DMA1 al USART2 y usar la interrupción IDLE, tu microcontrolador actúa como una esponja automática. Absorbe toda la ráfaga de datos en la memoria RAM en segundo plano. Cuando el router Wi-Fi o el bus RS485 guarda silencio durante más de 86 microsegundos, el CPU recibe un toque en el hombro: "Tienes un archivo de 2 KB listo en la RAM". Ahora tu Despachador puede recibir actualizaciones de parámetros masivas sin perder ni un solo mililitro en el cálculo de caudalometría.
DR del USART a un Canal del Controlador DMA.RX se mantiene en estado alto lógico (Silencio) por un marco de tiempo equivalente a un carácter, indicando el "Fin de Ráfaga".SR) seguida inmediatamente de una lectura del registro de datos (DR).