Los periféricos en la arquitectura ARM Cortex-M están interconectados por debajo de la mesa. En lugar de que tu código C ejecute ADC1->CR2 |= SWSTART para pedirle una lectura al ADC, podemos configurar el ADC para que reaccione ciegamente a una señal externa, llamada EXTSEL (External Event Select).
Esa señal puede venir de un pin físico, pero lo más poderoso es conectarla a un Hardware Timer (como el TIM3). Configuramos el Timer para que genere un evento llamado TRGO (Trigger Output) cada vez que su contador se reinicia. De esta forma, el Timer marca el compás, el ADC ejecuta el baile y el DMA recoge los boletos, todo en hardware puro.
TRGO interno.TRGO. Despierta, lee el pin PA1 (Presión) y luego el pin PA2 (Temp).ADC1->DR y lo mueve directamente a nuestro arreglo en memoria RAM estática.Ejercicio 1: Modifica la estructura del Día 116. Esta vez, en lugar de usar SWSTART en el main, configuraremos el TIM3 para que genere un evento TRGO cada milisegundo (1 kHz). Luego, configuraremos el ADC1 para que escuche ese evento usando los bits EXTEN y EXTSEL.
/**
* @file adc_dma_timer.c
* @brief Adquisición 100% Autónoma (ADC + DMA + TIM3)
*/
#include "stm32f401xe.h"
// Búfer en RAM para Presión y Temperatura
volatile uint16_t adc_buffer[2] = {0, 0};
void inicializar_adquisicion_autonoma(void) {
// --- 1. ACTIVAR RELOJES (GPIO, ADC, DMA2, TIM3) ---
RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN;
RCC->APB2ENR |= RCC_APB2ENR_ADC1EN;
RCC->AHB1ENR |= RCC_AHB1ENR_DMA2EN;
RCC->APB1ENR |= RCC_APB1ENR_TIM3EN;
// Configurar PA1 (Presión) y PA2 (Temp) en Modo Analógico
GPIOA->MODER |= GPIO_MODER_MODE1 | GPIO_MODER_MODE2;
GPIOA->PUPDR &= ~(GPIO_PUPDR_PUPD1 | GPIO_PUPDR_PUPD2);
// ====================================================
// 2. CONFIGURACIÓN DEL TIM3 (Trigger Maestro)
// ====================================================
TIM3->PSC = 84 - 1; // Reloj Timer a 1 MHz
TIM3->ARR = 1000 - 1; // Disparo cada 1000 µs (1 ms = 1 kHz)
// Generar TRGO (Trigger Output) cuando ocurra un Evento de Actualización (Update/Overflow)
TIM3->CR2 &= ~TIM_CR2_MMS;
TIM3->CR2 |= (2 << TIM_CR2_MMS_Pos); // MMS = 010 (Update)
// ====================================================
// 3. CONFIGURACIÓN DEL DMA2 (El Transportista)
// ====================================================
DMA2_Stream0->CR &= ~DMA_SxCR_EN;
while(DMA2_Stream0->CR & DMA_SxCR_EN);
DMA2_Stream0->CR &= ~DMA_SxCR_CHSEL; // Canal 0
DMA2_Stream0->PAR = (uint32_t)&ADC1->DR;
DMA2_Stream0->M0AR = (uint32_t)&adc_buffer[0];
DMA2_Stream0->NDTR = 2; // Dos sensores
// MINC (Incrementar RAM), CIRC (Modo Circular), Tamaño 16 bits
DMA2_Stream0->CR |= DMA_SxCR_MINC | DMA_SxCR_CIRC |
(1 << DMA_SxCR_MSIZE_Pos) | (1 << DMA_SxCR_PSIZE_Pos);
DMA2_Stream0->CR |= DMA_SxCR_EN;
// ====================================================
// 4. CONFIGURACIÓN DEL ADC1 (El Esclavo del Timer)
// ====================================================
ADC1->CR2 &= ~ADC_CR2_ADON;
ADC1->CR1 |= ADC_CR1_SCAN;
ADC1->SQR1 |= (1 << ADC_SQR1_L_Pos); // 2 canales
ADC1->SQR3 |= (1 << 0) | (2 << 5); // CH1 y CH2
// DMA Enable y DMA Disable Selection (Continuo Request)
ADC1->CR2 |= ADC_CR2_DMA | ADC_CR2_DDS;
// HARDWARE TRIGGER: Reaccionar al TIM3
// EXTEN = 01 (Disparo en flanco de subida)
ADC1->CR2 |= (1 << ADC_CR2_EXTEN_Pos);
// EXTSEL = 1000 (Valor 8 corresponde al TIM3_TRGO en el STM32F4)
ADC1->CR2 |= (8 << ADC_CR2_EXTSEL_Pos);
ADC1->CR2 |= ADC_CR2_ADON;
for(volatile int i=0; i<10000; i++); // Estabilización
// ====================================================
// 5. ARRANCAR EL CORAZÓN DEL SISTEMA
// ====================================================
// Solo encendemos el Timer. El ADC y el DMA reaccionarán solos para siempre.
TIM3->CR1 |= TIM_CR1_CEN;
}
int main(void) {
inicializar_adquisicion_autonoma();
while(1) {
// En cualquier microsegundo aleatorio del día, tu CPU puede leer estas variables.
// Siempre tendrán la lectura física de hace menos de 1 milisegundo.
// ¡Magia asíncrona pura!
uint16_t crudo_presion = adc_buffer[0];
uint16_t crudo_temp = adc_buffer[1];
// (Aquí llamarías a tu filtro pasa-bajos de promedio móvil del Día 113
// y a la matemática de compensación termovolumétrica del Día 115)
}
return 0;
}
Objetivo del día: Determinismo Temporal en Muestreo Metrológico.
En el procesamiento de señales, el teorema de Nyquist-Shannon es implacable: si el tiempo entre tus muestras (lecturas) varía por un microsegundo de latencia (Jitter), tus filtros digitales matemáticos y cálculos de promedio generarán ruido irreal en lugar de limpiarlo.
Al encadenar el TIM3 ➡️ ADC1 ➡️ DMA2, acabas de inyectar rigor matemático absoluto al muestreo de tu Despachador de Gas. La matriz de hardware de tu placa garantiza que la presión y la temperatura se capturen y almacenen en la SRAM cada $1000\text{ \mu s}$ exactos, incluso si tu núcleo Cortex-M se traba momentáneamente o si una interrupción prioritaria ocurre al mismo tiempo. Es ingeniería civil aplicada al diseño de silicio: carreteras separadas, flujo ininterrumpido.
MMS = 010, el Timer avisa cada vez que completa un ciclo.01 para flanco de subida).