Configuración y manejo de interrupciones GPIO en ESP32 con Arduino IDE
En el mundo de la programación con microcontroladores, es común que desees que tu dispositivo, como el ESP32, ejecute su programa principal mientras vigila continuamente ciertos eventos. Para lograr esta funcionalidad, una de las soluciones más efectivas es el uso de interrupciones.
El ESP32 ofrece hasta 32 ranuras de interrupción para cada núcleo, lo que permite gestionar múltiples eventos de manera eficiente. Cada interrupción puede tener un nivel de prioridad específico y se clasifica en dos tipos principales.
- Entendiendo las interrupciones GPIO en ESP32
- Cómo adjuntar una interrupción a un pin GPIO
- Desconectando una interrupción de un pin GPIO
- Rutina de servicio de interrupción (ISR)
- Conexión del hardware
- Código de ejemplo: Interrupción simple
- Manejo del rebote de interruptores
- Código de ejemplo: Debouncing de una interrupción
Entendiendo las interrupciones GPIO en ESP32
Las interrupciones GPIO son esenciales en proyectos que requieren una reacción inmediata a cambios en el estado de un pin. En el ESP32, se puede definir una función de rutina de servicio de interrupción (ISR) que se activará cuando un pin GPIO cambie su nivel lógico.
Todos los pines GPIO de una placa ESP32 pueden ser configurados para funcionar como entradas de solicitud de interrupción, lo que proporciona flexibilidad en el diseño del hardware y la lógica del programa.
Cómo adjuntar una interrupción a un pin GPIO
Para establecer una interrupción en un pin específico utilizando el IDE de Arduino, se emplea la función attachInterrupt()
. La sintaxis básica es la siguiente:
attachInterrupt(GPIOPin, ISR, Mode);
Los argumentos que acepta son:
- GPIOPin: Especifica el pin GPIO que se utilizará para la interrupción, indicando al ESP32 qué pin debe monitorear.
- ISR: Es el nombre de la función que se llamará cada vez que ocurra la interrupción.
- Mode: Define las condiciones bajo las cuales se activará la interrupción. Las opciones incluyen:
RISING
: Se activa cuando la señal cambia de bajo a alto.FALLING
: Se activa cuando la señal cambia de alto a bajo.CHANGE
: Se activa en cualquier cambio de estado del pin.LOW
: Se activa cuando la señal está por debajo de un nivel lógico bajo.HIGH
: Se activa cuando la señal está por encima de un nivel lógico alto.
Desconectando una interrupción de un pin GPIO
Si en algún momento deseas que el ESP32 deje de monitorear un pin específico, puedes utilizar la función detachInterrupt()
. Su sintaxis es sencilla:
detachInterrupt(GPIOPin);
Este comando es útil para evitar que se generen interrupciones innecesarias que pueden interferir con otras operaciones del programa.
Rutina de servicio de interrupción (ISR)
La rutina de servicio de interrupción (ISR) es una función que se invoca cada vez que se activa una interrupción en el pin GPIO. La sintaxis básica de una ISR en ESP32 es:
void IRAM_ATTR ISR() {
// Instrucciones
}
Las ISRs en ESP32 tienen reglas específicas que las diferencian de las funciones convencionales:
- Una ISR no puede tener parámetros y no debe devolver ningún valor.
- Las ISRs deben ser lo más cortas y rápidas posible, ya que bloquean la ejecución normal del programa mientras se ejecutan.
- Es recomendable incluir el atributo
IRAM_ATTR
, según la documentación de ESP32.
¿Qué es IRAM_ATTR?
El atributo IRAM_ATTR
indica que el código de la ISR se almacenará en la RAM interna (IRAM) del ESP32. Esto es crucial, ya que la RAM interna es mucho más rápida que la memoria flash, y en el contexto de una ISR, el tiempo de respuesta es fundamental. Un retraso en la carga desde la memoria flash podría ocasionar problemas serios en la ejecución del programa.
Conexión del hardware
Pasando a la parte práctica, veamos un ejemplo de cómo conectar un botón pulsador al pin GPIO#18 (D18) del ESP32. No se requiere una resistencia de pull-up externa, ya que el pin puede configurarse para usar la resistencia interna.
Código de ejemplo: Interrupción simple
El siguiente código ilustra cómo utilizar interrupciones y cómo escribir correctamente una rutina de servicio de interrupción.
Este programa observa el pin GPIO#18 (D18) en búsqueda de un borde de FALLING. Es decir, detecta un cambio de voltaje de estado alto a estado bajo, que ocurre cuando se presiona el botón. Cuando esto sucede, se llama a la función isr
, que cuenta cuántas veces se ha presionado el botón.
struct Button {
const uint8_t PIN;
uint32_t numberKeyPresses;
bool pressed;
};
Button button1 = {18, 0, false};
void IRAM_ATTR isr() {
button1.numberKeyPresses++;
button1.pressed = true;
}
void setup() {
Serial.begin(115200);
pinMode(button1.PIN, INPUT_PULLUP);
attachInterrupt(button1.PIN, isr, FALLING);
}
void loop() {
if (button1.pressed) {
Serial.printf("El botón ha sido presionado %u vecesn", button1.numberKeyPresses);
button1.pressed = false;
}
}
Después de cargar el sketch, presiona el botón EN en el ESP32 y abre el monitor serie a una velocidad de 115200 baudios. Al presionar el botón, deberías ver la salida correspondiente en el monitor.
Explicación del código
Al inicio del sketch, se crea una estructura llamada Button
, que incluye tres miembros: el número de pin, la cantidad de pulsaciones y el estado del botón (presionado o no).
struct Button {
const uint8_t PIN;
uint32_t numberKeyPresses;
bool pressed;
};
Se inicializa una instancia de la estructura Button
, estableciendo el número del pin en 18
, el contador de pulsaciones en 0
y el estado de presionado en false
.
Button button1 = {18, 0, false};
La ISR simplemente incrementa el contador de pulsaciones y marca el estado del botón como presionado.
void IRAM_ATTR isr() {
button1.numberKeyPresses++;
button1.pressed = true;
}
En la función setup
, se inicializa la comunicación serie y se configura el pin D18 para usar la resistencia pull-up interna. Luego, se indica al ESP32 que debe monitorear el pin D18 y llamar a la ISR isr
al detectar un borde de FALLING.
Serial.begin(115200);
pinMode(button1.PIN, INPUT_PULLUP);
attachInterrupt(button1.PIN, isr, FALLING);
En el bucle principal, se verifica si el botón ha sido presionado y se imprime el número de veces que se ha pulsado, restableciendo el estado de presionado a false
para permitir nuevas interrupciones.
if (button1.pressed) {
Serial.printf("El botón ha sido presionado %u vecesn", button1.numberKeyPresses);
button1.pressed = false;
}
Manejo del rebote de interruptores
Un problema común al trabajar con interrupciones es que a menudo se activan múltiples veces para el mismo evento. Si observas la salida serie del ejemplo anterior, notarás que al presionar el botón una sola vez, el contador incrementa varias veces.
La razón detrás de este comportamiento se debe a un fenómeno mecánico conocido como rebote de interruptor. Al presionar un botón, los contactos internos pueden hacer contacto varias veces antes de estabilizarse, lo que resulta en múltiples activaciones de la interrupción.
Este fenómeno se asemeja a dejar caer una pelota: rebota varias veces antes de finalmente detenerse. Aunque el tiempo que toma estabilizarse es muy corto para el ser humano, el ESP32 puede ejecutar múltiples instrucciones en ese breve periodo.
El proceso de eliminar el rebote se llama debouncing y se puede lograr de dos maneras:
- A través de hardware: agregando un filtro RC apropiado para suavizar la transición.
- A través de software: ignorando temporalmente nuevas interrupciones durante un corto periodo después de que se activa la primera interrupción.
Código de ejemplo: Debouncing de una interrupción
Este código reescribe el ejemplo anterior para demostrar cómo debouncing de una interrupción mediante programación. En este caso, permitimos que la ISR se ejecute solo una vez por cada pulsación del botón, evitando así múltiples activaciones.
Las modificaciones en el sketch están resaltadas en verde.
struct Button {
const uint8_t PIN;
uint32_t numberKeyPresses;
bool pressed;
};
Button button1 = {18, 0, false};
// Variables para controlar el tiempo de las interrupciones recientes
unsigned long button_time = 0;
unsigned long last_button_time = 0;
void IRAM_ATTR isr() {
button_time = millis();
if (button_time - last_button_time > 250) {
button1.numberKeyPresses++;
button1.pressed = true;
last_button_time = button_time;
}
}
void setup() {
Serial.begin(115200);
pinMode(button1.PIN, INPUT_PULLUP);
attachInterrupt(button1.PIN, isr, FALLING);
}
void loop() {
if (button1.pressed) {
Serial.printf("El botón ha sido presionado %u vecesn", button1.numberKeyPresses);
button1.pressed = false;
}
}
Al observar la salida serie nuevamente mientras presionas el botón, notarás que la ISR se activa una sola vez por cada pulsación del botón.
Explicación del código de debouncing
Este enfoque funciona porque cada vez que se ejecuta la ISR, se compara el tiempo actual, obtenido mediante la función millis()
, con el tiempo en que se llamó por última vez la ISR.
Si la diferencia es inferior a 250 ms, el ESP32 ignora la interrupción y regresa inmediatamente a la ejecución normal. Si la diferencia es mayor, se ejecuta el código dentro de la declaración if
, incrementando el contador y actualizando la variable last_button_time
, lo que proporciona un nuevo valor para comparar en futuras interrupciones.
Deja una respuesta
Estos temas te pueden interesar