DÍA 134 DE 365

Fase 3: Comunicaciones y Redes Industriales

📖 Teoría a Estudiar

Uniendo el DMA y el USART

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.

El Problema del "Fin del Mensaje" y la Interrupción IDLE

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í.

RECEPCIÓN MASIVA: DMA + IDLE LINE DETECTION Línea RX ... (2000 bytes) ... Silencio = Línea IDLE (Inactiva) El DMA1 mueve bytes a RAM silenciosamente (Sin generar interrupciones RXNE al CPU) IDLE_IRQ Disparado (1 sola vez por paquete) CPU Libre (100% Procesando Metrología) No se pierde ni un pulso de caudal CPU lee RAM (Procesa el paquete entero) Carga de interrupciones reducida un 99%

⚙️ Ejercicios Prácticos

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;
}
        

🚀 Avance del Proyecto Tesis: Despachador de Gas LP

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.

📝 Resumen del Día