DÍA 127 DE 365

Fase 3: Comunicaciones y Redes Industriales

📖 Teoría a Estudiar

La Ilusión de la Multiplexación Matricial

Un teclado matricial de 4x4 tiene 16 botones mecánicos, pero solo 8 cables de salida. Internamente, los botones no están conectados a GND o VCC; están conectados en intersecciones. Hay 4 cables de Filas (Rows) y 4 cables de Columnas (Columns). Un botón simplemente actúa como un puente temporal entre una fila y una columna.

El algoritmo de escaneo consiste en:

  1. Configurar las Columnas como Entradas con resistencias Pull-Up internas (siempre leen `1`).
  2. Configurar las Filas como Salidas y ponerlas todas en Alto (`1`).
  3. Poner la Fila 1 en Bajo (`0`). Revisar las 4 columnas. Si la Columna 2 lee `0`, significa que el botón en la intersección (Fila 1, Col. 2) fue presionado, cerrando el circuito a tierra.
  4. Devolver la Fila 1 a Alto (`1`), y poner la Fila 2 en Bajo (`0`). Y repetir a velocidad extrema.

El Cortocircuito Oculto y la Magia del Open-Drain

Si configuras las salidas de las Filas en modo Push-Pull (Inyectando 3.3V o 0V de manera forzada), corres un peligro enorme. ¿Qué pasa si el cliente presiona el botón '1' (Fila 1) y el botón '4' (Fila 2) exactamente al mismo tiempo y están en la misma columna? Crearás un cortocircuito directo entre el pin de la Fila 1 (que está inyectando 3.3V) y el pin de la Fila 2 (que está drenando a 0V), destruyendo los pines del STM32.

La solución Bare-Metal es configurar las Filas en modo Open-Drain (Drenador Abierto). En este modo, el pin solo sabe "jalar" hacia 0V. Cuando le escribes un `1`, el pin no inyecta voltaje, simplemente se desconecta (Alta impedancia). Así, si presionan dos botones, un pin a 0V se conecta con un pin desconectado... ¡Cero cortocircuitos!

ARQUITECTURA DE ESCANEO DE TECLADO MATRICIAL (OPEN-DRAIN) STM32 R1 R2 R3 R4 Salidas Open-Drain C1 C2 C3 C4 Entradas Pull-Up (Leen 1 por defecto) 1 2 3 A 4 5 6 B 7 8 9 C * 0 # D Momento del Escaneo R1 = Hi-Z (1) R2 = GND (0) <-- R3 = Hi-Z (1) R4 = Hi-Z (1) C2 lee '0V' (Conectada a R2)

⚙️ Ejercicios Prácticos

Ejercicio 1: Integraremos el teclado al Puerto B de tu microcontrolador. Las Filas serán salidas (PB4 a PB7) configuradas expresamente como Open-Drain. Las Columnas serán entradas (PB0 a PB3) configuradas con Pull-Up interno. Construiremos la Máquina de Estados para escanear y hacer el "Debounce" (Antirrebote mecánico) de 20 milisegundos sin utilizar la función delay().


/**
 * @file keypad_async.c
 * @brief Escaneo de Teclado Matricial 4x4 (No Bloqueante / Open-Drain)
 */

#include "stm32f401xe.h"

// Variable global de tiempo (Del SysTick, Día 98)
extern volatile uint32_t milisegundos_globales;

// Mapa de caracteres de la matriz
const char keypad_map[4][4] = {
    {'1', '2', '3', 'A'},
    {'4', '5', '6', 'B'},
    {'7', '8', '9', 'C'},
    {'*', '0', '#', 'D'}
};

void inicializar_teclado_matricial(void) {
    
    RCC->AHB1ENR |= RCC_AHB1ENR_GPIOBEN;
    
    // --- COLUMNAS (Entradas PB0 a PB3) ---
    // Limpiamos los bits para forzar el modo entrada (00)
    GPIOB->MODER &= ~(0xFF << 0); 
    // Habilitamos Pull-Up interno (01) en PB0, PB1, PB2, PB3
    GPIOB->PUPDR &= ~(0xFF << 0);
    GPIOB->PUPDR |=  (0x55 << 0);
    
    // --- FILAS (Salidas PB4 a PB7) ---
    // Modo Salida General (01)
    GPIOB->MODER &= ~(0xFF << (4*2));
    GPIOB->MODER |=  (0x55 << (4*2));
    
    // IMPORTANTE: Modo OPEN-DRAIN para las filas (Evita cortocircuitos)
    GPIOB->OTYPER |= (GPIO_OTYPER_OT4 | GPIO_OTYPER_OT5 | GPIO_OTYPER_OT6 | GPIO_OTYPER_OT7);
    
    // Por defecto, apagar todas las filas (Alta impedancia en Open-Drain escribiendo 1)
    GPIOB->BSRR = (0xF << 4);
}

/**
 * @brief Cambia el estado de una fila específica a GND (0). Las demás a Hi-Z (1).
 */
void activar_fila(uint8_t fila) {
    // Apagamos todas primero (Escribir 1 en BSRR Set)
    GPIOB->BSRR = (0xF << 4);
    
    // Activamos solo la solicitada (Escribir 1 en BSRR Reset, lo que pone un 0 físico)
    GPIOB->BSRR = (1 << ((fila + 4) + 16)); 
}

/**
 * @brief Máquina de Estados del Teclado (No Bloqueante)
 *        Debe llamarse en cada ciclo del Súper Bucle.
 * @return char Retorna el caracter si hubo una pulsación válida, de lo contrario 0.
 */
char escanear_teclado_async(void) {
    
    static uint8_t fila_actual = 0;
    static uint32_t tiempo_debounce = 0;
    static char tecla_candidata = 0;
    static uint8_t estado = 0; // 0=Escaneando, 1=Debounce Presionado, 2=Esperando liberación
    
    switch(estado) {
        
        case 0: // ESCANEO RÁPIDO
            activar_fila(fila_actual);
            
            // Le damos unos ciclos de reloj para que el voltaje eléctrico baje físicamente a cero
            __NOP(); __NOP(); __NOP();
            
            // Leemos el puerto B y aislamos los primeros 4 bits (Columnas)
            uint16_t lecturas = GPIOB->IDR & 0x0F;
            
            // Si la lectura NO es 0x0F (1111), significa que un 0 se coló (Botón presionado)
            if (lecturas != 0x0F) {
                // Buscamos cuál columna es la responsable
                for (int col = 0; col < 4; col++) {
                    if ((lecturas & (1 << col)) == 0) {
                        tecla_candidata = keypad_map[fila_actual][col];
                        tiempo_debounce = milisegundos_globales;
                        estado = 1; // Pasamos a validar el rebote mecánico
                        break;
                    }
                }
            } else {
                // Si no hay nada presionado, saltamos a la siguiente fila para la próxima iteración
                fila_actual = (fila_actual + 1) & 3; // Bitwise equivalente a % 4
            }
            break;
            
        case 1: // DEBOUNCE (Esperar 20ms sin bloquear el CPU)
            if ((milisegundos_globales - tiempo_debounce) >= 20) {
                // Volvemos a leer. Si sigue en 0, es una pulsación real y no estática.
                if ((GPIOB->IDR & 0x0F) != 0x0F) {
                    estado = 2; // Esperar a que suelte el botón
                    return tecla_candidata; // ¡Disparo exitoso!
                } else {
                    estado = 0; // Falsa alarma (Ruido)
                }
            }
            break;
            
        case 2: // ESPERAR LIBERACIÓN (Anti-Repetición)
            if ((GPIOB->IDR & 0x0F) == 0x0F) {
                // Todos los botones se han soltado
                tiempo_debounce = milisegundos_globales;
                estado = 3; // Debounce de liberación
            }
            break;
            
        case 3: // DEBOUNCE LIBERACIÓN (20ms)
            if ((milisegundos_globales - tiempo_debounce) >= 20) {
                estado = 0; // Máquina reseteada, lista para un nuevo escaneo
            }
            break;
    }
    
    return 0; // Ninguna pulsación completada en este ciclo
}

int main(void) {
    // SysTick_Init();
    inicializar_teclado_matricial();
    
    while(1) {
        
        // El teclado escanea una fracción de la matriz por cada ciclo de reloj.
        // No cuesta recursos y no utiliza la prohibida función delay().
        char tecla = escanear_teclado_async();
        
        if (tecla != 0) {
            // procesar_tecla_presionada(tecla);
            // Ej: Si presiona 'A', se activa Despacho por Importe ($200.00 MXN)
        }
        
        // ... Tareas de caudalímetro y telemetría siguen corriendo al 100%
    }
    
    return 0;
}
        

🚀 Avance del Proyecto Tesis: Despachador de Gas LP

Objetivo del día: Interacción Industrial Segura y Tolerancia a Fallos de Usuario.

Un usuario impaciente podría presionar el botón de "$500" y la tecla "Aceptar" al mismo tiempo. En un equipo mal diseñado, eso generaría un cortocircuito quemando los pines del microcontrolador. Al diseñar la etapa de filas del STM32 con arquitectura Open-Drain, tu hardware es ahora inmune al pánico del usuario y a los teclados de membrana defectuosos que cierran circuitos incorrectos por desgaste de la mica interna.

Aún más importante, el algoritmo del Despachador de Gas LP requiere leer pulsos de la turbina magnética con el EXTI cada pocos microsegundos. Si hubieras usado una librería de Arduino estándar para el teclado (que usa `delay()` para el debounce mecánico), el microcontrolador se quedaría ciego durante 20 milisegundos completos cada vez que el despachador tocara un botón. Con tu nueva **Máquina de Estados de 4 fases**, la matriz se escanea de forma 100% asíncrona. El usuario siente una interfaz responsiva, y el contador metrológico no pierde ni un solo mililitro de gas.

📝 Resumen del Día