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.
NDTR (Number of Data to Transfer Register), que es una cuenta regresiva.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.
¿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.
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;
}
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.
EN=0) en sistemas de comunicaciones críticas genera Puntos Ciegos (Blind Spots), ocasionando la pérdida de tramas si una señal llega inmediatamente después de un apagado.NDTR funciona como una cuenta regresiva que nos permite deducir exactamente en qué dirección de RAM está apuntando el hardware (Head Pointer).% SIZE), se garantiza que el software recorra el anillo de memoria envolviéndose suavemente de regreso al índice 0 sin violar los límites de los arreglos.main().