Control eficiente de 10 botones con una sola interrupción en Arduino UNO
Componentes y suministros
| Arduino UNO (o cualquier otro microcontrolador compatible) | × | 1 |
| Protoboard (genérica) | × | 1 |
| Cables de conexión (genéricos) | × | 21 |
| Pulsadores táctiles (cualquier tipo, grande o pequeño) | × | 10 |
Aplicaciones y servicios en línea
- Arduino IDE
Sobre este proyecto
Introducción
Las interrupciones son muy útiles. Además de simplificar el código en ocasiones, permiten temporización precisa o despertar al Arduino desde el modo de suspensión (sleep).
Imagina una interfaz de usuario, por ejemplo un mando a distancia alimentado por baterías. Quizás quieras poner el Arduino (o un ATmega independiente) en modo power-down para ahorrar energía. Cuando el Arduino entra en este modo, solo puede despertarse mediante una interrupción externa. El chip ATmega328P usado en el Arduino Uno tiene solo dos interrupciones externas de pin (INT0 y INT1 en los pines 2 y 3). Como una interfaz de usuario suele tener más de dos botones, eso es un problema.
La solución estándar sería conectar todos los botones normalmente, pero también unirlos a un pin de interrupción mediante diodos. Esto, sin embargo, complica significativamente el circuito.

Además de las interrupciones externas estándar, el ATmega328P también tiene interrupciones por cambio de pin (pin change interrupts). Existen librerías para gestionarlas y son una buena solución a este problema.
Sin embargo, durante una competición de programación, descubrí cómo hacer esto usando interrupciones externas estándar sin componentes eléctricos adicionales.
El circuito



Usamos varios botones (en este caso 10, que encajan bien en mi protoboard). Puedes tener un botón por pin, lo que permite hasta 20 en un Uno y hasta 70 en un Mega. (Si realmente necesitas 70 botones, recomiendo usar multiplexado; no necesitas un Mega entero para eso).
Cada botón tiene un lado conectado a un pin arbitrario (pines 4-13 en mi caso). Los otros lados de todos los botones se conectan juntos a un único pin capaz de interrupción (pin 2 en mi caso).

El código
El código se adjunta a continuación. Para que este ejemplo funcione, cárgalo en tu placa. Abre el monitor serie. Al pulsar un botón, aparecerá su número. Como verás, la función loop() no se usa en absoluto.

¿Cómo funciona?
Obviamente hay una interrupción. En mi caso, está conectada al pin 2 y configurada como FALLING (flanco descendente).
Para evitar el uso de diodos, el Arduino reconfigura el circuito sobre la marcha. Hay dos configuraciones posibles: Modo común y Modo distinto.
Modo común
La mayor parte del tiempo, el circuito estará en modo común. El pin de interrupción se configura como INPUT_PULLUP y el resto como OUTPUT y LOW.

void configurarComun() {
pinMode(pinComun, INPUT_PULLUP);
for (int i = 0; i < sizeof(pinesBotones) / sizeof(int); i++) {
pinMode(pinesBotones[i], OUTPUT);
digitalWrite(pinesBotones[i], LOW);
}
}
En modo común, pulsar cualquier botón bajará el pin de interrupción y disparará nuestra interrupción. Una vez que esto ocurre, la rutina de servicio de interrupción (ISR) reconfigurará los pines para el modo distinto.
Modo distinto
Una vez que se dispara nuestra interrupción, cambiamos rápidamente al modo distinto.
El modo distinto es lo opuesto al modo común. El pin de interrupción será OUTPUT y LOW, y el resto serán INPUT_PULLUP.

void configurarDistinto() {
pinMode(pinComun, OUTPUT);
digitalWrite(pinComun, LOW);
for (int i = 0; i < sizeof(pinesBotones) / sizeof(int); i++) {
pinMode(pinesBotones[i], INPUT_PULLUP);
}
}
En modo distinto, solo los pines correspondientes a los botones realmente pulsados se bajarán. Podemos recorrer fácilmente todos los pines para averiguar cuál disparó la interrupción.
Después, el Arduino puede volver al modo común y esperar otra interrupción. O, dependiendo de tu aplicación, puede permanecer en modo distinto y procesar la entrada del usuario normalmente, volviendo al modo común antes de que el Arduino entre en suspensión.
Un ejemplo más complejo
Probemos algo más complejo. Conectaremos un servo y asignaremos cada botón a un ángulo diferente (1=0°, 2=20°... 10=120°). También alimentaremos el Arduino con unas pilas.



En este ejemplo, ponemos el Arduino en modo power-down tras cinco segundos de inactividad para ahorrar energía. Puedes encontrar tutoriales sobre el modo sleep en línea. Además, alimentamos el servo a través de un transistor para apagarlo cuando no se usa.
El código para este ejemplo también está adjunto abajo.
Código
Registrador serie (Serial logger)
const int pinComun = 2;
const int pinesBotones[] = {4,5,6,7,8,9,10,11,12,13};
unsigned long ultimaActivacion = 0;
void setup() {
configurarComun(); // Configura pines para interrupción
attachInterrupt(digitalPinToInterrupt(pinComun), interrupcionPulsacion, FALLING);
Serial.begin(9600);
}
void loop() {
// ¡Vacío!
}
void interrupcionPulsacion() {
// ISR
if (millis() - ultimaActivacion < 200) { // Antirrebote
return;
}
ultimaActivacion = millis();
configurarDistinto(); // Configura pines para probar botones individuales
for (int i = 0; i < sizeof(pinesBotones) / sizeof(int); i++) {
// Prueba cada botón
if (!digitalRead(pinesBotones[i])) {
pulsacion(i);
}
}
configurarComun(); // Vuelve al estado original
}
void configurarComun() {
pinMode(pinComun, INPUT_PULLUP);
for (int i = 0; i < sizeof(pinesBotones) / sizeof(int); i++) {
pinMode(pinesBotones[i], OUTPUT);
digitalWrite(pinesBotones[i], LOW);
}
}
void configurarDistinto() {
pinMode(pinComun, OUTPUT);
digitalWrite(pinComun, LOW);
for (int i = 0; i < sizeof(pinesBotones) / sizeof(int); i++) {
pinMode(pinesBotones[i], INPUT_PULLUP);
}
}
void pulsacion(int boton) {
// Nuestro manejador
Serial.println(boton + 1);
}
Servo con modo sleep
#include <Servo.h>
#include <avr/sleep.h>
#include <avr/power.h>
const int pinComun = 2;
const int pinesBotones[] = {4,5,6,7,8,9,10,11,12,13};
const int pinHabilitaServo = A1;
const int pinServo = A0;
Servo servo;
unsigned long ultimaActivacion = 0;
int estado = 0;
void setup() {
pinMode(pinComun, INPUT_PULLUP);
configurarComun();
attachInterrupt(digitalPinToInterrupt(pinComun), interrupcionPulsacion, FALLING);
servo.attach(pinServo);
pinMode(pinHabilitaServo, OUTPUT);
}
void loop() {
if (millis() - ultimaActivacion > 5000) {
digitalWrite(pinHabilitaServo, LOW);
set_sleep_mode(SLEEP_MODE_PWR_DOWN);
sleep_enable();
sleep_mode();
}
delay(10);
}
void interrupcionPulsacion() {
sleep_disable();
power_all_enable();
if (millis() - ultimaActivacion < 200) {
return;
}
ultimaActivacion = millis();
configurarDistinto();
for (int i = 0; i < sizeof(pinesBotones) / sizeof(int); i++) {
if (!digitalRead(pinesBotones[i])) {
pulsacion(i);
}
}
configurarComun();
}
void configurarComun() {
pinMode(pinComun, INPUT_PULLUP);
for (int i = 0; i < sizeof(pinesBotones) / sizeof(int); i++) {
pinMode(pinesBotones[i], OUTPUT);
digitalWrite(pinesBotones[i], LOW);
}
}
void configurarDistinto() {
pinMode(pinComun, OUTPUT);
digitalWrite(pinComun, LOW);
for (int i = 0; i < sizeof(pinesBotones) / sizeof(int); i++) {
pinMode(pinesBotones[i], INPUT_PULLUP);
}
}
void pulsacion(int boton) {
digitalWrite(pinHabilitaServo, HIGH);
servo.write(map(boton, 0, 9, 0, 180));
}
Esquemáticos


Archivos Fritzing: multiinterrupt_hoF76Oc4T5.fzz, servo_with_sleep_u9ZqxF0jhY.fzz
Proceso de manufactura
- Construye un Decodificador DTMF Solo con Arduino – Sin CI Externos
- Crea un Impresionante Sistema Ambilight para Monitor con Arduino
- Construye un Dispositivo de Levitación Ultrasónica con Arduino – Guía Paso a Paso
- Construye un Voltímetro Inteligente con Arduino y Smartphone – Proyecto DIY Fácil
- Monitor de Frecuencia Cardíaca IoT en Tiempo Real con Arduino y MAX30100
- Servidor Web Arduino Uno WiFi: Controla un LED desde el Navegador
- Contador Inteligente de Plazas de Aparcamiento: Seguimiento en Tiempo Real con Arduino, Processing y PHP
- Automatiza el Juego del Dinosaurio de Chrome con Arduino y Python
- Construye una Radio FM Compacta con Arduino Nano y RDA5807M
- Shield de Programación Inalámbrica para Arduino con HC-05 Bluetooth – Sin Cable USB
- Construye un Reloj Digital en Arduino usando una Máquina de Estados Finitos