Botão no Arduino: por que um clique vira dez e como ler um só
Como usar o pull-up interno, detectar a mudança de estado e filtrar o quique do contato com millis, para que cada aperto do botão conte uma vez só.
Você escreveu um código para o LED alternar entre aceso e apagado a cada aperto do botão. Aperta uma vez e o LED pisca, volta, às vezes fica no estado errado. O contador no monitor serial pula de 1 para 7. O botão não está com defeito. A resposta curta: o programa precisa reagir só à mudança de solto para pressionado, não ao estado de pressionado, e precisa ignorar as oscilações dos primeiros milissegundos, o chamado quique ou bounce do contato. Com INPUT_PULLUP, detecção de borda e uma espera de 20 a 50 ms medida com millis(), cada aperto vira exatamente um clique.
Por que um aperto vira vários
São três problemas diferentes, e é comum eles aparecerem juntos.
O pino flutuando
Se o botão liga o pino ao 5 V quando pressionado e não liga a nada quando solto, o pino solto fica "flutuando". Ele capta ruído elétrico do ambiente e lê HIGH e LOW ao acaso. Encostar o dedo perto do fio já muda a leitura. A solução é um resistor que puxa o pino para um nível definido quando o botão está solto. O Arduino tem um resistor de pull-up interno que se ativa com pinMode(pino, INPUT_PULLUP). Com ele, ligue o botão entre o pino e o GND. Solto, o pino lê HIGH. Pressionado, lê LOW. A lógica fica invertida, e isso é normal.
Ler o estado em vez da mudança
A função loop() roda milhares de vezes por segundo. Um código que faz "se o botão está pressionado, alterne o LED" alterna o LED a cada volta do loop enquanto o dedo estiver em cima. Um aperto de um quarto de segundo vira centenas de alternâncias, e o estado final é sorte. O que interessa é o instante em que o pino passou de solto para pressionado. Para saber isso, guarde a leitura anterior e compare.
O quique do contato
Mesmo detectando a mudança, ainda aparecem cliques extras. Dentro do botão, duas lâminas metálicas se encostam. No instante do contato, elas batem e se afastam várias vezes antes de se assentarem, em questão de milissegundos. Para você, foi um aperto. Para um microcontrolador rápido, foram várias transições de HIGH para LOW e de volta. O mesmo acontece ao soltar.
Como ler um clique só
- Configure o pino com
INPUT_PULLUPe ligue o botão entre o pino e o GND. - Guarde a última leitura bruta e o momento em que ela mudou pela última vez.
- Considere uma leitura como estável só depois de ela ficar igual por um tempo mínimo, como 30 ms.
- Compare o estado estável novo com o anterior. Só reaja na transição de HIGH para LOW, que é o aperto.
- Não use
delay()para esperar o quique. Usemillis(), para o resto do programa continuar rodando.
const int PINO_BOTAO = 2;
const int PINO_LED = 13;
const unsigned long ESPERA_MS = 30;
int ultimaLeitura = HIGH;
int estadoEstavel = HIGH;
unsigned long ultimaMudanca = 0;
bool ledAceso = false;
unsigned int cliques = 0;
void setup() {
pinMode(PINO_BOTAO, INPUT_PULLUP);
pinMode(PINO_LED, OUTPUT);
Serial.begin(9600);
}
void loop() {
int leitura = digitalRead(PINO_BOTAO);
if (leitura != ultimaLeitura) {
ultimaMudanca = millis();
ultimaLeitura = leitura;
}
if (millis() - ultimaMudanca >= ESPERA_MS && leitura != estadoEstavel) {
estadoEstavel = leitura;
if (estadoEstavel == LOW) {
cliques++;
ledAceso = !ledAceso;
digitalWrite(PINO_LED, ledAceso ? HIGH : LOW);
Serial.print("Clique ");
Serial.println(cliques);
}
}
}
Toda vez que a leitura bruta muda, o relógio é zerado. Enquanto o contato está quicando, ele é zerado de novo a cada batida, então o estado estável não muda. Quando a leitura fica parada por 30 ms, o código aceita o novo estado. Se ele for LOW, foi um aperto, e o contador sobe uma vez.
A conta millis() - ultimaMudanca com variáveis unsigned long continua correta mesmo quando o millis() volta a zero, o que acontece depois de cerca de 49 dias ligado. Comparar com millis() > ultimaMudanca + ESPERA_MS não tem essa garantia. Prefira sempre a subtração.
Quanto tempo esperar
O quique de botões táteis comuns costuma acabar em poucos milissegundos, mas varia com o modelo e com o desgaste. Entre 20 e 50 ms é uma faixa segura: longa o bastante para absorver o quique e curta demais para alguém perceber atraso. Se ainda aparecerem cliques duplos, aumente aos poucos. Se o botão parecer "lento", diminua.
Debounce em hardware
Também dá para filtrar no circuito, com um resistor e um capacitor formando um filtro, de preferência seguido de uma porta com entrada Schmitt trigger, que limpa a transição. Faz sentido quando o pino vai para uma interrupção ou quando o microcontrolador está muito ocupado. Para a maioria dos projetos, o software resolve e é mais fácil de ajustar. Entenda o problema no código primeiro, antes de soldar componentes.
Bibliotecas prontas
Bibliotecas como a Bounce2 encapsulam essa lógica, o que ajuda quando há vários botões. Vale escrever a versão manual ao menos uma vez para saber o que a biblioteca faz e para ajustar o tempo com consciência.
E com interrupção?
Ligar o botão a uma interrupção com attachInterrupt() parece mais eficiente, mas a interrupção dispara em cada transição do quique, então o problema aumenta. Se usar interrupção, filtre dentro dela pelo tempo desde a última borda aceita, mantenha a rotina curta e declare como volatile as variáveis compartilhadas com o loop(). Para um botão apertado por uma pessoa, ler no loop costuma ser suficiente.
Se o botão vai comandar um relé, a leitura limpa é ainda mais importante: cada clique fantasma vira um acionamento mecânico. Veja como usar relé no Arduino com segurança.
Erros comuns
- Pino sem resistor de pull-up ou pull-down. A entrada flutua e registra cliques sem ninguém encostar no botão.
- Reagir ao estado "pressionado" em vez da transição. Um aperto vira centenas de comandos enquanto o dedo estiver em cima.
- Usar
delay(200)depois de cada clique. O programa inteiro para durante a espera, perde outras leituras e ainda pode contar o quique da soltura. - Esquecer que o
INPUT_PULLUPinverte a lógica. O código reage ao soltar em vez de apertar, e parece que há atraso. - Usar
intpara guardar omillis(). O valor estoura em pouco tempo e o filtro passa a se comportar de forma errática. - Ligar o botão direto numa interrupção sem filtro. Cada batida do contato dispara a rotina, e o problema fica pior do que no loop.
Checklist
- O pino usa
INPUT_PULLUPou tem resistor externo. - O botão está ligado entre o pino e o GND, e o código trata LOW como pressionado.
- O código guarda a leitura anterior e reage só na transição.
- Existe um tempo mínimo de estabilidade, entre 20 e 50 ms, medido com
millis(). - As variáveis de tempo são
unsigned longe a comparação usa subtração. - Não há
delay()no caminho da leitura do botão. - Vinte apertos seguidos resultam em vinte cliques no monitor serial.
Abra o monitor serial, aperte o botão vinte vezes, devagar e depois rápido, e conte. O número na tela bate com o número de apertos? Se não bate, que outro sensor no seu projeto pode estar lendo ruído como se fosse comando?
Referência: http://arduinoifsul.blogspot.com.br/2012/09/botao-de-deteccao-de-mudanca-de-estado.html