Tradicionalmente, si el ADC tiene una lectura lista, la CPU debe detener lo que está haciendo, leer el registro de datos del ADC y guardarlo en una variable. Este movimiento de información cuesta ciclos de CPU valiosos. En arquitecturas modernas, el DMA funciona como un "puente de hardware" independiente que transfiere información de un Periférico a la Memoria, o de Memoria a Periférico, sin intervención de la CPU.
Configurar el DMA en C puro (Bare-Metal) es como darle instrucciones a un cartero robótico. Le entregas tres cosas esenciales:
El modo circular es la joya de la corona para el monitoreo de biometría o de fluidos. Una vez que el DMA llena el arreglo de memoria con 10 lecturas de presión del ADC, en lugar de detenerse, vuelve mágicamente al índice 0 y comienza a sobrescribir. Así, el Súper Bucle siempre tendrá a su disposición un "búfer fresco" con el promedio de presión de los últimos milisegundos, con 0% de uso de CPU.
Ejercicio 1: Programa los registros base de un canal DMA genérico (estilo ARM Cortex-M) para transferir los datos desde el registro físico del ADC hacia un arreglo en tu memoria RAM. Aplicaremos el modificador volatile (visto en el Día 26) porque la memoria será actualizada silenciosamente por el hardware.
#include <stdint.h>
// --- 1. ABSTRACCIÓN DE REGISTROS (Pseudo-STM32) ---
// En el hardware real, estas direcciones están en tu Datasheet (Memory Map)
#define ADC1_DR_ADDR 0x4001244C // Dirección física del Registro de Datos del ADC
#define DMA1_CH1_BASE 0x40020000
// Apuntadores directos a los registros de configuración del DMA
#define DMA_CCR (*((volatile uint32_t *)(DMA1_CH1_BASE + 0x08))) // Control Register
#define DMA_CNDTR (*((volatile uint16_t *)(DMA1_CH1_BASE + 0x0C))) // Num de Datos
#define DMA_CPAR (*((volatile uint32_t *)(DMA1_CH1_BASE + 0x10))) // Dir. Periférico
#define DMA_CMAR (*((volatile uint32_t *)(DMA1_CH1_BASE + 0x14))) // Dir. Memoria
// Máscaras de Configuración Bitwise
#define DMA_CCR_MINC (1 << 7) // Memory Increment Mode (Avanzar el índice del arreglo)
#define DMA_CCR_CIRC (1 << 5) // Circular Mode (Infinito)
#define DMA_CCR_DIR_P2M (0 << 4) // Direction: Periphery to Memory
#define DMA_CCR_PSIZE_16 (1 << 8) // Tamaño de dato del periférico: 16-bits
#define DMA_CCR_MSIZE_16 (1 << 10) // Tamaño de dato en RAM: 16-bits
#define DMA_CCR_EN (1 << 0) // Enable DMA
// --- 2. BÚFER DE MEMORIA RAM ---
// ⚠️ IMPORTANTE: 'volatile' es obligatorio. El compilador GCC debe saber que
// los valores de este arreglo cambiarán solos, o de lo contrario cacheará ceros.
#define NUM_MUESTRAS 10
volatile uint16_t buffer_presion_adc[NUM_MUESTRAS];
// --- 3. FUNCIÓN DE INICIALIZACIÓN BARE-METAL ---
void DMA_Configurar_Canal_ADC(void) {
// 1. Apuntar a la fuente física (El ADC)
// El DMA irá al ADC1_DR a recoger los datos.
DMA_CPAR = (uint32_t)ADC1_DR_ADDR;
// 2. Apuntar al destino en SRAM
// Hacemos un "cast" del puntero de nuestro arreglo a un uint32_t crudo
// porque el registro del DMA requiere una dirección absoluta, no un puntero de C.
DMA_CMAR = (uint32_t)&buffer_presion_adc[0];
// 3. Cantidad de transferencias por ciclo
DMA_CNDTR = NUM_MUESTRAS;
// 4. Inyectar configuración
// Circular, Incremento en Memoria, de Periférico a Memoria, datos de 16 bits.
DMA_CCR |= (DMA_CCR_MINC | DMA_CCR_CIRC | DMA_CCR_PSIZE_16 | DMA_CCR_MSIZE_16);
DMA_CCR &= ~DMA_CCR_DIR_P2M; // Aseguramos que sea 0 (Lectura)
// 5. ¡Encender el coprocesador!
DMA_CCR |= DMA_CCR_EN;
}
// =====================================================================
// APLICACIÓN EN EL DESPACHADOR LP
// =====================================================================
uint32_t Obtener_Promedio_Presion(void) {
uint32_t suma = 0;
// El DMA está llenando 'buffer_presion_adc' silenciosamente de fondo.
// Nosotros solo tenemos que iterarlo cuando queramos, sin bloqueos.
for(uint8_t i = 0; i < NUM_MUESTRAS; i++) {
suma += buffer_presion_adc[i];
}
return (suma / NUM_MUESTRAS); // Retorna el ADC suavizado (Filtro Pasa-Bajas simple)
}
Objetivo del día: Adquisición Continua y Hardware Offloading.
Un despachador de grado industrial requiere vigilancia extrema de la presión interna. Si un cliente cierra la válvula del cilindro abruptamente, la presión en la manguera se dispara a niveles peligrosos en milisegundos. Usando ADC en modo continuo combinado con un canal DMA en Modo Circular, tu STM32 tendrá un búfer en RAM (SRAM) con las lecturas analógicas actualizándose perpetuamente en el fondo (hardware). Tu Matriz de Seguridad simplemente tiene que llamar a Obtener_Promedio_Presion() en su tiempo libre, logrando un filtrado analógico con un 0% de sobrecarga de procesamiento.
volatile para evitar que el compilador optimice y destruya la lectura en el Súper Bucle (Día 26).