Controle Eficiente de 10 Botões com uma Única Interrupção no Arduino UNO
Componentes e Materiais
- 1x Arduino UNO (ou outro microcontrolador compatível)
- 1x Protoboard (breadboard)
- 21x Jumpers (fios de conexão)
- 10x Chaves táteis (botões push-button)
Software Necessário
- Arduino IDE
Sobre o Projeto
Introdução
As interrupções são extremamente úteis: além de simplificar o código, permitem temporização precisa e acordar o Arduino do modo de suspensão (sleep).
Imagine uma interface de usuário, como um controle remoto alimentado por baterias. Para economizar energia, você colocaria o Arduino (ou um ATmega standalone) no modo power-down. Nesse modo, o chip só pode ser acordado por uma interrupção externa. O ATmega328P (usado no Arduino Uno) possui apenas duas interrupções externas (INT0 e INT1, nos pinos 2 e 3). Como uma interface típica tem mais de dois botões, isso é um problema.
A solução padrão seria conectar todos os botões normalmente e também ligá-los a um pino de interrupção através de diodos. Isso, porém, complica significativamente o circuito.

Além das interrupções externas padrão, o ATmega328P também possui interrupções de mudança de pino (pin change interrupts). Existem bibliotecas para gerenciá-las e elas são uma boa solução.
No entanto, durante uma competição de programação, descobri como fazer isso usando apenas interrupções externas padrão, sem componentes elétricos extras.
O Circuito



Utilizei dez botões, que couberam perfeitamente na minha protoboard. Você pode ter um botão por pino, o que permite até 20 botões no Uno e até 70 no Mega! (Se realmente precisar de 70 botões, recomendo usar multiplexação; não é necessário um Mega inteiro para isso.)
Cada botão tem um lado conectado a um pino arbitrário (no meu caso, pinos 4 a 13). Os outros lados de todos os botões são conectados juntos a um único pino capaz de interrupção (pino 2 no meu caso).

O Código
O código completo está disponível abaixo. Para testar, faça o upload para sua placa e abra o Monitor Serial. Ao pressionar um botão, seu número aparecerá. Note que a função loop() não é usada.

Como Funciona?
Existe, obviamente, uma interrupção. No meu caso, ela está anexada ao pino 2, configurada como FALLING (borda de descida).
Para evitar o uso de diodos, o Arduino reconfigura o circuito dinamicamente. Existem dois modos de operação: Modo Comum e Modo Distinto.
Modo Comum
Na maior parte do tempo, o circuito fica no modo comum. O pino de interrupção é configurado como INPUT_PULLUP e os demais pinos dos botões como OUTPUT e LOW.

void configureCommon() {
pinMode(commonPin, INPUT_PULLUP);
for (int i = 0; i < sizeof(buttonPins) / sizeof(int); i++) {
pinMode(buttonPins[i], OUTPUT);
digitalWrite(buttonPins[i], LOW);
}
}
Neste modo, pressionar qualquer botão puxará o pino de interrupção para LOW, disparando a interrupção. Quando isso ocorre, a rotina de serviço de interrupção (ISR) reconfigura os pinos para o modo distinto.
Modo Distinto
Assim que a interrupção é disparada, mudamos rapidamente para o modo distinto.
O modo distinto é o oposto do comum: o pino de interrupção torna-se OUTPUT e LOW, enquanto os pinos dos botões viram INPUT_PULLUP.

void configureDistinct() {
pinMode(commonPin, OUTPUT);
digitalWrite(commonPin, LOW);
for (int i = 0; i < sizeof(buttonPins) / sizeof(int); i++) {
pinMode(buttonPins[i], INPUT_PULLUP);
}
}
No modo distinto, apenas os pinos correspondentes aos botões efetivamente pressionados serão puxados para LOW. Podemos então varrer todos os pinos para descobrir qual botão causou a interrupção.
Após a leitura, o Arduino pode voltar ao modo comum e aguardar a próxima interrupção. Dependendo da aplicação, pode permanecer no modo distinto processando a entrada do usuário normalmente, voltando ao modo comum apenas antes de entrar em sleep.
Exemplo Mais Complexo: Controle de Servo com Modo Sleep
Vamos a algo mais elaborado: anexamos um servo e mapeamos cada botão para um ângulo diferente (1=0°, 2=20°... 10=120°). O Arduino será alimentado por baterias.



Neste exemplo, o Arduino entra em power-down mode após 5 segundos de inatividade para economizar energia. O servo é alimentado através de um transistor, sendo desligado quando não está em uso.
O código completo deste exemplo também está disponível abaixo.
Códigos
Logger Serial (Exemplo Básico)
const int commonPin = 2;
const int buttonPins[] = {4,5,6,7,8,9,10,11,12,13};
unsigned long lastFire = 0;
void setup() {
configureCommon();
attachInterrupt(digitalPinToInterrupt(commonPin), pressInterrupt, FALLING);
Serial.begin(9600);
}
void loop() {
// Vazio!
}
void pressInterrupt() {
if (millis() - lastFire < 200) return; // Debounce
lastFire = millis();
configureDistinct();
for (int i = 0; i < sizeof(buttonPins) / sizeof(int); i++) {
if (!digitalRead(buttonPins[i])) {
press(i);
}
}
configureCommon();
}
void configureCommon() {
pinMode(commonPin, INPUT_PULLUP);
for (int i = 0; i < sizeof(buttonPins) / sizeof(int); i++) {
pinMode(buttonPins[i], OUTPUT);
digitalWrite(buttonPins[i], LOW);
}
}
void configureDistinct() {
pinMode(commonPin, OUTPUT);
digitalWrite(commonPin, LOW);
for (int i = 0; i < sizeof(buttonPins) / sizeof(int); i++) {
pinMode(buttonPins[i], INPUT_PULLUP);
}
}
void press(int button) {
Serial.println(button + 1);
}
Servo com Modo Sleep (Exemplo Avançado)
#include <Servo.h>
#include <avr/sleep.h>
#include <avr/power.h>
const int commonPin = 2;
const int buttonPins[] = {4,5,6,7,8,9,10,11,12,13};
const int servoEnablePin = A1;
const int servoPin = A0;
Servo servo;
unsigned long lastFire = 0;
int status = 0;
void setup() {
pinMode(commonPin, INPUT_PULLUP);
configureCommon();
attachInterrupt(digitalPinToInterrupt(commonPin), pressInterrupt, FALLING);
servo.attach(servoPin);
pinMode(servoEnablePin, OUTPUT);
}
void loop() {
if (millis() - lastFire > 5000) {
digitalWrite(servoEnablePin, LOW);
set_sleep_mode(SLEEP_MODE_PWR_DOWN);
sleep_enable();
sleep_mode();
}
delay(10);
}
void pressInterrupt() {
sleep_disable();
power_all_enable();
if (millis() - lastFire < 200) return;
lastFire = millis();
configureDistinct();
for (int i = 0; i < sizeof(buttonPins) / sizeof(int); i++) {
if (!digitalRead(buttonPins[i])) {
press(i);
}
}
configureCommon();
}
void configureCommon() {
pinMode(commonPin, INPUT_PULLUP);
for (int i = 0; i < sizeof(buttonPins) / sizeof(int); i++) {
pinMode(buttonPins[i], OUTPUT);
digitalWrite(buttonPins[i], LOW);
}
}
void configureDistinct() {
pinMode(commonPin, OUTPUT);
digitalWrite(commonPin, LOW);
for (int i = 0; i < sizeof(buttonPins) / sizeof(int); i++) {
pinMode(buttonPins[i], INPUT_PULLUP);
}
}
void press(int button) {
digitalWrite(servoEnablePin, HIGH);
servo.write(map(button, 0, 9, 0, 180));
}
Esquemáticos
Arquivos Fritzing disponíveis:
multiinterrupt_hoF76Oc4T5.fzzservo_with_sleep_u9ZqxF0jhY.fzz


Processo de manufatura
- Construa um Decodificador DTMF Usando Apenas um Arduino – Sem CIs Externos
- Crie um Sistema Ambilight Impressionante para Monitor com Arduino
- Construa um Dispositivo de Levitação Ultrassônica com Arduino – Guia Passo a Passo
- Construa um Voltímetro Inteligente com Arduino e Smartphone – Projeto DIY Fácil
- Monitor de Frequência Cardíaca IoT em Tempo Real com Arduino e MAX30100
- Servidor Web Arduino Uno WiFi: Controle LED via Navegador
- Contador Inteligente de Vagas: Rastreamento de Veículos em Tempo Real com Arduino, Processing e PHP
- Automatize o Jogo do Dinossauro do Chrome com Arduino e LDR
- Construa um Rádio FM Compacto com Arduino Nano e RDA5807M
- Shield de Programação Wireless Arduino com HC-05 Bluetooth – Sem Cabo USB