DÍA 135 DE 365

Fase 3: Comunicaciones y Redes Industriales

📖 Teoría a Estudiar

El Búfer Circular Ininterrumpido (Continuous Ring Buffer)

Si no podemos apagar el DMA, ¿cómo leemos los datos sin que se mezclen? Utilizando la teoría de persecución (Chasing Pointers). Imaginaremos nuestro arreglo rx_dma_buffer como una pista circular infinita.

Cuando el bus RS485 guarda silencio, la bandera IDLE se dispara. En ese momento, tu Súper Bucle mira dónde está la Cabeza y persigue todos los bytes faltantes desde la Cola hasta alcanzar a la Cabeza. Los transfiere a un búfer lineal temporal de procesamiento, sin jamás decirle al DMA que se detenga.

El Problema del Wrap-Around (Salto de Límite)

¿Qué pasa si tienes un búfer de 256 bytes, la Cola está en la posición 250, y llega un paquete de 10 bytes? La Cabeza escribirá hasta la posición 255, dará la vuelta (Wrap-around) mágicamente por hardware, y escribirá los últimos 4 bytes en las posiciones 0, 1, 2 y 3. Nuestro software de extracción debe usar el operador módulo (%) para seguir la misma trayectoria circular y no leer basura fuera del arreglo.

CORTE DE TRAMA VARIABLE: RING BUFFER Y ARITMÉTICA DE PUNTEROS rx_dma_buffer[256] Dato [0..3] . . . (Espacio Libre) . . . Dato [250..255] Idx: 0 Idx: 255 TAIL (250) HEAD (4) Paquete de 10 bytes: Trama salta de 255 a 0 automáticamente (Modo CIRC) Algoritmo de Extracción Segura (En el Súper Bucle) head = RX_BUFFER_SIZE - DMA1_Stream5->NDTR; // Calcula posición actual del DMA while (tail != head) { paquete_temp[i++] = rx_dma_buffer[tail]; tail = (tail + 1) % RX_BUFFER_SIZE; }

⚙️ Ejercicios Prácticos

Ejercicio 1: Modificaremos el driver del Día 134. El DMA1 quedará encendido perpetuamente (Jamás tocaremos el bit EN de nuevo). La interrupción IDLE simplemente levantará una bandera bandera_trama_lista = true. En el main(), crearemos la función extraer_trama_dma() que calculará el puntero Head, copiará los datos sorteando el límite circular usando aritmética %, y procesará el paquete Modbus o JSON resultante.


/**
 * @file uart_dma_circular.c
 * @brief DMA RX Circular Continuo + Procesamiento de Trama Variable
 */

#include "stm32f401xe.h"
#include <stdbool.h>
#include <string.h>

#define RX_BUFFER_SIZE 256

// Búfer Circular Físico (El DMA escribe aquí infinitamente)
volatile uint8_t rx_dma_buffer[RX_BUFFER_SIZE];

// Variables de Control (Aritmética de Punteros)
volatile uint16_t head_ptr = 0;
uint16_t tail_ptr = 0;

volatile bool bandera_trama_lista = false;

// Búfer Lineal de Software (Para analizar el comando tranquilamente)
uint8_t paquete_comando[RX_BUFFER_SIZE];

void inicializar_usart2_dma_circular(void) {
    
    // (Asumimos inicialización de Relojes, Pines PA2/PA3 y USART2 igual al Día 134)
    
    // ====================================================
    // CONFIGURACIÓN DEL DMA1 (Stream 5, Canal 4)
    // ====================================================
    DMA1_Stream5->CR &= ~DMA_SxCR_EN;
    while(DMA1_Stream5->CR & DMA_SxCR_EN); 
    
    DMA1_Stream5->CR |= (4 << DMA_SxCR_CHSEL_Pos);
    DMA1_Stream5->PAR = (uint32_t)&USART2->DR;
    DMA1_Stream5->M0AR = (uint32_t)rx_dma_buffer;
    DMA1_Stream5->NDTR = RX_BUFFER_SIZE; // Contador inicial
    
    // MODO CIRCULAR OBLIGATORIO: Cuando NDTR llega a 0, se recarga a 256 y el 
    // puntero M0AR vuelve al índice 0 mágicamente por hardware.
    DMA1_Stream5->CR |= DMA_SxCR_MINC | DMA_SxCR_CIRC;
    
    // ENCENDIDO DEFINITIVO DEL DMA (NUNCA MÁS SE APAGARÁ)
    DMA1_Stream5->CR |= DMA_SxCR_EN;
    
    // ====================================================
    // CONFIGURACIÓN DEL USART2
    // ====================================================
    USART2->CR1 = 0;
    USART2->BRR = 0x016D; // 115200
    
    // Habilitar RX, TX y IDLE Interrupt (¡Sin RXNEIE!)
    USART2->CR1 |= USART_CR1_TE | USART_CR1_RE | USART_CR1_IDLEIE;
    USART2->CR3 |= USART_CR3_DMAR; // Conectar USART a DMA
    
    NVIC_SetPriority(USART2_IRQn, 4);
    NVIC_EnableIRQ(USART2_IRQn);
    
    USART2->CR1 |= USART_CR1_UE;
}

/**
 * @brief Interrupción disparada al detectar silencio en la línea (Fin de Paquete)
 */
void USART2_IRQHandler(void) {
    
    // Si la línea se quedó en silencio (IDLE)
    if (USART2->SR & USART_SR_IDLE) {
        
        // Purgar bandera IDLE
        volatile uint32_t limpieza = USART2->SR;
        limpieza = USART2->DR;
        (void)limpieza;
        
        // Simplemente avisamos al Súper Bucle que hay una trama completa esperando
        // NO APAGAMOS EL DMA. Si otro paquete llega en el siguiente microsegundo, 
        // el DMA lo atrapará sin problema.
        bandera_trama_lista = true;
    }
}

/**
 * @brief Extrae los datos frescos del Ring Buffer y rearma el paquete.
 *        Debe ser llamado en el Súper Bucle.
 */
void procesar_tramas_pendientes(void) {
    
    // 1. Calcular dónde está la cabeza (Puntero del DMA)
    // Si NDTR = 256, Head = 0. Si NDTR = 200, Head = 56.
    head_ptr = RX_BUFFER_SIZE - DMA1_Stream5->NDTR;
    
    // 2. Extraer todo lo que haya desde la 'Cola' hasta la 'Cabeza'
    // La cola persigue a la cabeza.
    uint16_t i = 0;
    
    while (tail_ptr != head_ptr) {
        // Copiamos el byte actual al búfer seguro
        paquete_comando[i++] = rx_dma_buffer[tail_ptr];
        
        // Avanzamos la cola, aplicando el Wrap-Around circular
        tail_ptr = (tail_ptr + 1) % RX_BUFFER_SIZE;
        
        // Prevención de desbordamiento de seguridad (Por si llega un paquete gigante)
        if (i >= RX_BUFFER_SIZE) break; 
    }
    
    // Solo si realmente extrajimos algo
    if (i > 0) {
        paquete_comando[i] = '\0'; // Terminador de C
        
        // ¡Magia! Aquí tienes tu cadena variable (JSON o Comando) perfecta
        // ejecutar_comando_json(paquete_comando, i);
    }
}

int main(void) {
    
    // inicializar_relojes();
    inicializar_usart2_dma_circular();
    
    while(1) {
        
        // Tareas Metrológicas Críticas...
        // control_valvulas();
        
        // Procesamiento Asíncrono de Red
        if (bandera_trama_lista) {
            bandera_trama_lista = false;
            
            // Extraer y analizar sin detener jamás la recepción física
            procesar_tramas_pendientes();
        }
        
    }
    
    return 0;
}
        

🚀 Avance del Proyecto Tesis: Despachador de Gas LP

Objetivo del día: Recepción Tolerante a Alta Concurrencia (Zero-Blind-Spot).

Imagina que tu Despachador está enlazado a un bus Modbus RS485 con otros 10 Despachadores. El Servidor Central "grita" preguntas y comandos a todos los equipos constantemente. El tráfico de red es masivo. Si detuvieras tu sistema de recepción (DMA) para leer un paquete, quedarías sordo ante el siguiente paquete que podría ir dirigido a ti.

Al migrar a la **Extracción por Aritmética de Punteros en Anillo (Ring Buffer)**, has convertido la memoria RAM de tu microcontrolador en un "cántaro sin fondo". El hardware del DMA sigue apilando letras girando en círculos incansablemente. Cuando hay silencio (IDLE), tu Súper Bucle simplemente extrae los datos calculando la diferencia entre el inicio (`tail_ptr`) y la posición actual del hardware (`head_ptr` deducido del `NDTR`). Tu Tesis ahora tiene una capa de comunicaciones de grado aeroespacial: **Cero pérdida de paquetes (Zero Packet Loss)** y **Cero interrupciones de procesamiento (Zero CPU Overhead)** durante el tráfico denso.

📝 Resumen del Día