DÍA 114 DE 365

Fase 2: Fundamentos de Hardware y Electrónica

📖 Teoría a Estudiar

El Multiplexor Analógico y el Modo Scan

Un STM32F401 no tiene 16 ADCs separados por dentro. Tiene 1 solo núcleo ADC súper rápido conectado a un interruptor rotativo electrónico llamado Multiplexor (MUX). Este MUX es capaz de conectar secuencialmente varios pines físicos (Canales) al mismo núcleo de conversión.

Al activar el Modo Scan (SCAN), programamos al hardware para que haga el trabajo sucio. Definimos cuántas lecturas queremos (Registro SQR1_L) y en qué orden (Registros SQR3). Cuando damos la orden de inicio (SWSTART), el ADC lee el Canal A, luego el MUX gira automáticamente y lee el Canal B, sin intervención del procesador.

El Problema del Overrun y el bit EOCS

Aquí hay una trampa clásica de la arquitectura ARM. El ADC tiene un solo Registro de Datos (DR). Si en modo Scan lee el primer canal y tú no extraes el dato de inmediato, cuando termine de leer el segundo canal sobrescribirá el dato anterior. A esto se le llama Overrun (Desbordamiento).

Para solucionar esto usando interrupciones, activamos el bit EOCS (End Of Conversion Selection). Este bit obliga al ADC a disparar la interrupción ADC_IRQHandler al final de cada canal individual, en lugar de esperar al final de toda la secuencia. Así, el CPU puede entrar rápidamente, rescatar el dato del Canal 1 y guardarlo en un arreglo de software antes de que el Canal 2 lo aplaste.

MODO SCAN: SECUENCIA DE MÚLTIPLES CANALES CON INTERRUPCIÓN (EOCS=1) Presión Temp PA1 PA2 MUX Auto Núcleo ADC1 SQR1_L = 2 conv ADC->DR EOC_IRQ (Por cada canal) ADC_IRQHandler Lee DR a RAM Memoria RAM array[0] = Presión array[1] = Temp ¡PELIGRO DE OVERRUN! El software DEBE leer DR rápido

⚙️ Ejercicios Prácticos

Ejercicio 1: Configura el ADC para escanear en secuencia los pines PA1 (Canal 1 - Presión) y PA2 (Canal 2 - Temperatura). Configura el bit EOCS para que la interrupción se dispare dos veces por ciclo. Crea un arreglo global en C y una máquina de estados dentro del ISR para guardar cada dato en su posición correcta.


/**
 * @file adc_scan_isr.c
 * @brief Adquisición multi-canal por Interrupción (Modo Scan + EOCS)
 */

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

// --- ESTRUCTURA DE DATOS EN RAM ---
// Array para almacenar crudos: [0] = Presión, [1] = Temperatura
volatile uint16_t adc_buffer[2] = {0, 0}; 

// Variables de control de la Máquina de Estados del ISR
volatile uint8_t  adc_indice = 0;
volatile bool     flag_scan_completo = false;

void inicializar_adc_multi_canal(void) {
    
    // 1. RELOJES Y PINES FÍSICOS (PA1 y PA2)
    RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN;
    RCC->APB2ENR |= RCC_APB2ENR_ADC1EN;
    
    // Configurar PA1 y PA2 en Modo Analógico (11) y sin resistencias internas
    GPIOA->MODER |= GPIO_MODER_MODE1 | GPIO_MODER_MODE2;
    GPIOA->PUPDR &= ~(GPIO_PUPDR_PUPD1 | GPIO_PUPDR_PUPD2);
    
    // 2. CONFIGURACIÓN DEL ADC CORE
    ADC1->CR2 &= ~ADC_CR2_ADON; // Aseguramos apagado
    
    // Activar Modo Scan (Permite secuencias multi-canal)
    ADC1->CR1 |= ADC_CR1_SCAN;
    
    // IMPORTANTÍSIMO: End Of Conversion Selection (EOCS = 1)
    // Fuerza al hardware a disparar el EOC al terminar CADA CANAL, no toda la secuencia.
    ADC1->CR2 |= ADC_CR2_EOCS;
    
    // Habilitar la interrupción de Fin de Conversión
    ADC1->CR1 |= ADC_CR1_EOCIE;
    
    // 3. SECUENCIADOR (SQR)
    // SQR1: Longitud de la secuencia. "0001" significa L = 2 conversiones.
    ADC1->SQR1 &= ~ADC_SQR1_L;
    ADC1->SQR1 |= (1 << ADC_SQR1_L_Pos); 
    
    // SQR3: Orden de la secuencia. 
    // Primer conversión (SQ1) = Canal 1 (PA1)
    // Segunda conversión (SQ2) = Canal 2 (PA2)
    ADC1->SQR3 &= ~(ADC_SQR3_SQ1 | ADC_SQR3_SQ2);
    ADC1->SQR3 |= (1 << 0);  // SQ1 = 1
    ADC1->SQR3 |= (2 << 5);  // SQ2 = 2
    
    // (Opcional) Aumentar tiempo de muestreo para ambos canales
    ADC1->SMPR2 |= (4 << 3) | (4 << 6); // 84 ciclos para CH1 y CH2
    
    // 4. ÁRBITRO DE INTERRUPCIONES (NVIC)
    NVIC_SetPriority(ADC_IRQn, 4); // Prioridad media (debajo de EXTI0)
    NVIC_EnableIRQ(ADC_IRQn);
    
    // 5. ENCENDER ADC
    ADC1->CR2 |= ADC_CR2_ADON;
    for(volatile int i = 0; i < 10000; i++); // Estabilización analógica
}

/**
 * @brief Rutina de Servicio de Interrupción del ADC
 *        Se disparará 2 veces (Una por canal) cuando ordenemos leer.
 */
void ADC_IRQHandler(void) {
    
    // Verificar que fue el EOC quien disparó (End of Conversion)
    if (ADC1->SR & ADC_SR_EOC) {
        
        // Al leer el registro de datos (DR), la bandera EOC se limpia mágicamente por hardware
        adc_buffer[adc_indice] = (uint16_t)ADC1->DR;
        
        // Avanzamos en el arreglo
        adc_indice++;
        
        // Si ya leímos todos los canales definidos
        if (adc_indice >= 2) {
            adc_indice = 0;                 // Reiniciamos para la próxima vez
            flag_scan_completo = true;      // Avisamos al main que puede usar los datos
        }
    }
}

// Macro para iniciar la ametralladora de mediciones
#define INICIAR_ESCANEO_ADC() (ADC1->CR2 |= ADC_CR2_SWSTART)

int main(void) {
    
    inicializar_adc_multi_canal();
    // SysTick_Init();
    
    while(1) {
        
        // 1. Iniciamos el escaneo (No bloquea el CPU)
        INICIAR_ESCANEO_ADC();
        
        // 2. Esperamos a que el hardware nos avise 
        // (En un sistema real usaríamos una máquina de estados con el SysTick para
        // iniciar escaneos periódicamente y leer la bandera sin usar while bloqueantes)
        while(!flag_scan_completo);
        
        // 3. Procesamos los datos extraídos
        flag_scan_completo = false;
        
        uint16_t crudo_presion = adc_buffer[0];
        uint16_t crudo_temp    = adc_buffer[1];
        
        // uint16_t presion_limpia = filtrar_adc_ruido(crudo_presion);
        // compensar_volumen_gas_por_temperatura(presion_limpia, crudo_temp);
    }
    
    return 0;
}
        

🚀 Avance del Proyecto Tesis: Despachador de Gas LP

Objetivo del día: Compensación Volumétrica por Termodinámica.

El Gas LP es traicionero. Un litro de gas a 15°C no tiene la misma masa energética que un litro de gas a 35°C bajo el sol abrasador; el líquido se expande. Si el cliente compra 20 litros al mediodía, el volumen es mayor pero la masa es menor. La Secretaría de Energía y los lineamientos API exigen que la facturación de hidrocarburos se compense a una temperatura estándar (usualmente 15°C o 20°C).

Para lograr esto, tu procesador debe saber la presión (PA1) y la temperatura del tubo (sensor NTC o PT100 en PA2) exactamente en el mismo instante matemático en que el caudalímetro EXTI atrapa un pulso de flujo. Utilizando el Modo Scan del ADC con EOCS=1 e Interrupciones, ordenas la captura de ambos sensores en menos de 5 microsegundos. Los valores se cargan silenciosamente en la RAM (adc_buffer) listos para que tu algoritmo de Punto Fijo aplique las Tablas de Corrección API, facturando litros exactos en masa y superando las estrictas auditorías de la PROFECO.

📝 Resumen del Día