Los botones físicos del teclado matricial son resortes de metal. Cuando un operario presiona el botón '0', el metal no hace un contacto perfecto instantáneo. Choca, rebota y vuelve a chocar a nivel microscópico durante unos 10 a 20 milisegundos. Tu STM32 corre a 84 MHz; en esos 10 milisegundos, el procesador lee el pin 5,000 veces. Para la CPU, el operario acaba de teclear "00000" en lugar de "0". Esto se llama Switch Bouncing.
Un teclado de membrana industrial suele ser rígido. Si el usuario trae equipo de protección personal (EPP), pierde sensibilidad táctil. El hardware debe compensar esto asaltando otro sentido: el oído. Un Buzzer piezoeléctrico debe emitir un bip corto (50ms) exactamente en el momento en que el firmware valida (Debounce) la pulsación, no antes ni después. Además, para evitar que el operario borre una venta accidentalmente al rozar la tecla 'CLEAR', la interfaz debe ignorar la acción a menos que detecte una Intención Sostenida (Long Press de 2 segundos).
Paso 1: Debouncing por Hardware (Filtro RC).
En el circuito, agregamos una resistencia $R_2$ y un capacitor $C_1$. Cuando el botón rebota y corta la corriente, el capacitor actúa como un "amortiguador" de electrones. Tarda un tiempo en descargarse basado en la constante de tiempo $\tau = R_2 \times C_1$. Si usas $10\text{k}\Omega$ y $100\text{nF}$, el tiempo de descarga es $\sim 1\text{ms}$, suficiente para suavizar los picos metálicos antes de que lleguen al STM32.
Paso 2: Disparador Schmitt (Schmitt Trigger).
El voltaje bajará lentamente formando una curva. Los pines digitales odian las curvas (causan oscilaciones internas). Afortunadamente, los pines del STM32 tienen un Schmitt Trigger en su entrada. Este circuito interno se niega a cambiar de estado a LOW hasta que el voltaje cruza limpiamente por debajo del umbral $V_{IL}$ (aprox. 1.0V), dándonos un cero digital inmaculado.
Incluso con el filtro de hardware, necesitamos filtrar la torpeza humana. En lugar de leer el pin con un `if()` crudo, programaremos una Máquina de Estados que verifica el pin repetidamente sin bloquear la CPU (No `HAL_Delay`), genera el *Beep* de confirmación y calcula si fue un toque rápido o si el operador está sosteniendo el botón intencionalmente.
/**
* @file hmi_button_logic.c
* @brief Manejador de teclado con Software Debounce y Detección Long-Press
*/
#include "stm32f4xx_hal.h"
#include <stdbool.h>
// Configuraciones Ergonomicas
#define DEBOUNCE_TIME_MS 50 // Ignorar rebotes mecánicos y humanos rápidos
#define LONG_PRESS_TIME_MS 2000 // 2 Segundos para confirmar acción destructiva
extern void Buzzer_Beep(uint16_t duration_ms);
extern void FSM_Abort_Transaction(void); // Cancela la venta de gas
typedef enum {
BTN_IDLE,
BTN_DEBOUNCING,
BTN_PRESSED_VALID,
BTN_WAIT_RELEASE,
BTN_LONG_PRESS_TRIGGERED
} ButtonState_t;
ButtonState_t ClearBtn_State = BTN_IDLE;
uint32_t ClearBtn_Timer = 0;
// =========================================================================
// MÁQUINA DE ESTADOS NO BLOQUEANTE PARA EL BOTÓN "CLEAR / ABORTAR"
// =========================================================================
/**
* @brief Debe llamarse constantemente desde el bucle principal.
* Analiza el pin GPIO_PIN_5 (Atado con Pull-Up. LOW = Presionado).
*/
void HMI_Process_Clear_Button(void) {
bool is_physically_pressed = (HAL_GPIO_ReadPin(GPIOC, GPIO_PIN_5) == GPIO_PIN_RESET);
uint32_t current_time = HAL_GetTick();
switch (ClearBtn_State) {
case BTN_IDLE:
if (is_physically_pressed) {
// El botón acaba de bajar. Iniciamos temporizador de debounce.
ClearBtn_Timer = current_time;
ClearBtn_State = BTN_DEBOUNCING;
}
break;
case BTN_DEBOUNCING:
// ¿Ya pasaron 50ms y sigue presionado?
if ((current_time - ClearBtn_Timer) >= DEBOUNCE_TIME_MS) {
if (is_physically_pressed) {
// ¡Confirmado! No fue un rebote.
Buzzer_Beep(50); // Feedback auditivo inmediato al operario
ClearBtn_State = BTN_PRESSED_VALID;
} else {
// Fue ruido o un roce accidental.
ClearBtn_State = BTN_IDLE;
}
}
break;
case BTN_PRESSED_VALID:
// Si lo suelta rápido, procesamos como pulsación normal (Short Press)
if (!is_physically_pressed) {
// (Ejemplo: borrar un solo dígito del teclado)
HMI_Delete_Last_Digit();
ClearBtn_State = BTN_IDLE;
}
// Si lo sostiene...
else if ((current_time - ClearBtn_Timer) >= LONG_PRESS_TIME_MS) {
// ¡Confirmación de Intención Sostenida!
Buzzer_Beep(500); // Tono largo de advertencia
FSM_Abort_Transaction(); // Abortar la venta actual
ClearBtn_State = BTN_LONG_PRESS_TRIGGERED;
}
break;
case BTN_LONG_PRESS_TRIGGERED:
case BTN_WAIT_RELEASE:
// Obligamos al usuario a soltar el botón antes de registrar otra acción
if (!is_physically_pressed) {
// Damos un pequeño debounce al soltar para evitar falsos re-disparos
HAL_Delay(10);
ClearBtn_State = BTN_IDLE;
}
break;
}
}
Objetivo del día: Ergonomía Industrial (HMI) y Resiliencia Operacional.
El primer contacto de tu máquina con humanos reales reveló la arrogancia clásica de los ingenieros: creer que los operadores son máquinas deterministas. La telemetría nos decía que la electrónica de la placa sobrevivió a las 24 horas (Día 322), pero el usuario humano reportó frustración. Presionaban botones múltiples veces porque no sabían si la máquina los había detectado. Borraban ventas enteras por rozar una tecla al manipular la pesada manguera de gas.
Al iterar basándote en esta retroalimentación, elevaste el grado de tu Tesis de un simple circuito a un **Producto Industrial**. Combinaste la física (Filtro RC Pasabajos de Hardware) para matar el rebote metálico, con la lógica de software (Máquina de Estados) para filtrar la torpeza humana. Implementaste retroalimentación auditiva sincronizada al milisegundo para que el operador de la gasera escuche y sienta confianza, e introdujiste el paradigma de **Long Press** para prevenir desastres por toques accidentales. Has adaptado la fría rigidez del silicio a la caótica naturaleza humana.