DÍA 116 DE 365

Fase 2: Fundamentos de Hardware y Electrónica

📖 Teoría a Estudiar

El Acceso Directo a Memoria (DMA)

El DMA es un coprocesador de hardware integrado en el STM32. Su única función en la vida es mover bytes de un lugar a otro a una velocidad absurda. Puedes decirle: "DMA, cada vez que el ADC termine de leer, toma el dato de su registro, colócalo en el array 'buffer_sensores' de la memoria RAM, e incrementa el puntero. No me molestes hasta que hayas movido 1000 datos".

Mientras el DMA hace este trabajo de mensajería (que tomaría miles de ciclos de reloj usando interrupciones en el CPU), el núcleo ARM Cortex-M puede dedicarse al 100% a calcular logaritmos, compensaciones térmicas o enviar datos por Wi-Fi.

La Matriz de Buses Multicapa (Bus Matrix)

Podrías pensar: *"Si el DMA está moviendo datos a la RAM, el bus está ocupado. ¿El CPU no tiene que hacer fila para usarlo?"*. La respuesta es no. La arquitectura moderna utiliza una Multi-layer AHB Bus Matrix (Matriz de conmutación cruzada).

Imagina una autopista con múltiples puentes elevados. El DMA puede estar usando la ruta "Periférico ➡️ SRAM", mientras simultáneamente el CPU utiliza la ruta "Flash ➡️ Núcleo" para leer las instrucciones de tu programa C. Como son vías distintas dentro del silicio, operan al mismo tiempo con cero colisiones y latencia nula.

ARQUITECTURA CONCURRENTE: DMA Y LA MATRIZ DE BUSES Master 1 CPU (Cortex-M) Master 2 DMA2 Controller AHB Multi-Layer Bus Matrix Slave 1: Flash Instrucciones C Slave 2: SRAM Variables / Arreglos Slave 3: APB2 ADC1 (Registro DR) Lectura RUTA 1: El CPU ejecuta código desde la memoria Flash RUTA 2 (SIMULTÁNEA): DMA mueve ADC->DR directo a RAM

⚙️ Ejercicios Prácticos

Ejercicio 1: En el Día 114 utilizamos el Modo Scan del ADC mediante la interrupción ADC_IRQHandler. Vamos a eliminar esa interrupción. Configuraremos el DMA2 (Stream 0, Canal 0), el cual está cableado físicamente al ADC1 en el chip STM32F4. Le diremos que transfiera automáticamente el registro ADC1->DR hacia nuestro arreglo adc_buffer en la SRAM y que opere en Modo Circular (Bucle infinito).


/**
 * @file adc_dma_driver.c
 * @brief Adquisición multi-canal por Hardware puro (ADC + DMA Circular)
 */

#include "stm32f401xe.h"

// Memoria destino (SRAM). El DMA escribirá directamente aquí sin avisarle al CPU.
volatile uint16_t adc_buffer[2] = {0, 0}; 

void inicializar_adc_con_dma(void) {
    
    // 1. ACTIVAR RELOJES (ADC, GPIO y DMA2)
    // El ADC1 pertenece a la jurisdicción del DMA2 en el bus AHB1
    RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN;
    RCC->APB2ENR |= RCC_APB2ENR_ADC1EN;
    RCC->AHB1ENR |= RCC_AHB1ENR_DMA2EN;
    
    // Pines PA1 y PA2 en Modo Analógico (Omitido por brevedad, igual al Día 114)
    GPIOA->MODER |= GPIO_MODER_MODE1 | GPIO_MODER_MODE2;
    
    // ====================================================
    // CONFIGURACIÓN DEL COPOCRESADOR DMA2 (Stream 0)
    // ====================================================
    
    // Desactivar el stream antes de configurarlo
    DMA2_Stream0->CR &= ~DMA_SxCR_EN;
    while(DMA2_Stream0->CR & DMA_SxCR_EN); 
    
    // Seleccionar Canal 0 (Mapeo físico del ADC1 en el F4)
    DMA2_Stream0->CR &= ~DMA_SxCR_CHSEL; 
    
    // Direcciones de Memoria
    // Origen: La dirección física del Registro de Datos del ADC
    DMA2_Stream0->PAR = (uint32_t)&ADC1->DR;
    
    // Destino: La dirección de nuestro arreglo en la memoria RAM
    DMA2_Stream0->M0AR = (uint32_t)&adc_buffer[0];
    
    // Cantidad de datos a mover por cada ciclo (Presión y Temp = 2 datos)
    DMA2_Stream0->NDTR = 2;
    
    // Configuración del Comportamiento del DMA:
    // - MINC: Memory Increment Mode (Mover el puntero del arreglo: buffer[0] -> buffer[1])
    // - CIRC: Circular Mode (Al llegar a 2, reiniciar a 0 automáticamente)
    // - MSIZE & PSIZE: Tamaño de 16-bits (Half-Word, valor 01)
    // - DIR: Peripheral to Memory (Dirección de flujo, valor por defecto 00)
    DMA2_Stream0->CR |= DMA_SxCR_MINC | 
                        DMA_SxCR_CIRC | 
                        (1 << DMA_SxCR_MSIZE_Pos) | 
                        (1 << DMA_SxCR_PSIZE_Pos);
                        
    // Encender el Stream del DMA
    DMA2_Stream0->CR |= DMA_SxCR_EN;
    
    // ====================================================
    // CONFIGURACIÓN DEL ADC1 PARA HABLAR CON EL DMA
    // ====================================================
    ADC1->CR2 &= ~ADC_CR2_ADON;
    ADC1->CR1 |= ADC_CR1_SCAN; // Modo secuencia activa
    
    // Secuenciador (SQR): 2 canales. CH1(PA1) luego CH2(PA2)
    ADC1->SQR1 |= (1 << ADC_SQR1_L_Pos); 
    ADC1->SQR3 |= (1 << 0) | (2 << 5);
    
    // MAGIA DMA: Activar peticiones DMA en el ADC (Bit DMA)
    // Y el bit DDS (DMA Disable Selection) en 1 para emitir peticiones en bucle infinito
    ADC1->CR2 |= ADC_CR2_DMA | ADC_CR2_DDS;
    
    // Encender ADC
    ADC1->CR2 |= ADC_CR2_ADON;
    for(volatile int i=0; i<10000; i++); // Retardo de estabilización
}

int main(void) {
    
    inicializar_adc_con_dma();
    
    // Opcional: Configurar un Hardware Timer para disparar el ADC automáticamente 
    // en vez de usar SWSTART manual. ¡Sistema 100% autónomo!
    ADC1->CR2 |= ADC_CR2_SWSTART;
    
    while(1) {
        
        // ¡El código principal es inmaculado!
        // No hay interrupciones del ADC, no hay banderas EOC que limpiar, no hay polling.
        // La memoria RAM contiene Mágicamente los valores actualizados de los sensores.
        
        uint16_t presion = adc_buffer[0];
        uint16_t temperatura = adc_buffer[1];
        
        // Procesamos la compensación y RS485 usando el 100% de la capacidad del CPU
        // compensar_volumen_gas(presion, temperatura);
    }
    
    return 0;
}
        

🚀 Avance del Proyecto Tesis: Despachador de Gas LP

Objetivo del día: Liberación total de recursos computacionales (Zero-Overhead HMI).

Si la pantalla LCD conectada por I2C (que veremos más adelante) o la red RS485 del Despachador consumen todo el tiempo del procesador, bajo el enfoque anterior habríamos perdido las lecturas de presión y temperatura, lo cual es inaceptable metrológicamente.

Al implementar el DMA Circular en conjunto con el modo Scan, has construido un sistema "Set-and-Forget" (Configurar y Olvidar). Le dijiste al hardware de silicio: "Hazte cargo tú de los sensores". Las variables globales adc_buffer[0] y [1] son como "espejos físicos" del mundo real; se actualizan solas miles de veces por segundo en el fondo. El código C de tu procesador Cortex-M ahora navega con un 0% de carga (Overhead) dedicado a la lectura de sensores, permitiéndote escalar el firmware de tu Tesis para manejar bases de datos de clientes, criptografía y comunicaciones de alta velocidad de forma simultánea y sin bloqueos.

📝 Resumen del Día