Debounce, o que é e como resolver via software 5

Se você já montou algum circuito que dependia de botões para alguma tarefa, talvez tenha reparado que nem sempre tudo funcionava como esperado. Todas as chaves mecânicas possuem um comportamento indesejável chamado “bouncing“, que é quando o microcontrolador interpreta múltiplos toques quando na verdade o botão só foi pressionado uma única vez. Mas você sabe o que isso? Nesse artigo vamos detalhar um pouco essa questão e apresentar formas de “debounce” para resolver esse problema.

O que é o efeito “bouncing”

Se abrirmos um botão qualquer para estudar seu funcionamento, vamos reparar que dentro deles temos normalmente duas pequenas e finas placas de metal alinhadas, e que na posição de “desligada” estão separadas. Quando acionamos o botão, estamos simplesmente fechando esse circuito, ou seja, fazendo com essas placas encostem uma na outra para permitir a passagem de corrente.

Como as dimensões das placas são bem pequenas, nós dificilmente conseguiremos ver que na verdade elas não são tão planas assim e não estão exatamente alinhadas. Essa “falta” de precisão na montagem e fabricação faz parte da construção e é praticamente inevitável.

Essas questões levam a um pequeno problema quando fechamos o circuito: em um intervalo de tempo muito pequeno, quando os contatos já estão bem próximos, pode ser que partes da placa se toquem em um ponto e se separem rapidamente, podem ocorrer pequenas vibrações ocasionando toques múltiplos entre as placas (efeito bem comum nos botões do tipo “push-up” por causa da mola que existe em seu interior), isso tudo antes das placas finalmente se encostarem completamente e fechar o circuito.

Esse efeito de transição pode passar despercebido para nós humanos, pois ocorre muito rapidamente mas para um microprocessador que verifica o status do botão milhões de vezes por segundo, isso é visto como se o botão estivesse sendo ligado e desligado várias vezes.

Se quisermos ter uma noção exata do que acontece, podemos utilizar um osciloscópio. Fazendo isso, nós teremos uma medição bastante similar a da figura:

Efeito bouncing - Debounce, o que é e como resolver via software

Podemos ver na figura que o circuito oscila antes de estabilizar completamente, formando seguidos vales (ou picos, dependendo da polaridade).

Material Necessário

Para observarmos o efeito bouncing, vamos montar um circuito bem simples e esse mesmo circuito será utilizado para verificar o debounce posteriormente. Para isso vamos precisar dos seguintes componentes:

Circuito

Para montar o circuito é só fazer como na figura:

Código sem o Debounce

Vamos criar um contador que acenderá o LED após pressionarmos o botão 10X. Segue o código:

//inicializando e declarando as variáveis
const int ledPin = 13;   // porta usada para o led
const int buttonPin = 2; // porta usada para o push-button

int buttonState;         // variável para vericar o estado do botão
int countClicks = 0;     // variável para contar o número de clicks no botão
							  
void setup() {
  // configura os pinos que vamos usar no arduíno
  pinMode(ledPin, OUTPUT);
  pinMode(buttonPin, INPUT);
}

void loop() {
  // verifica se o botão foi pressionado
  buttonState = digitalRead(buttonPin);

	if (buttonState == HIGH & countClicks < 10) {
		countClicks++;
	}

	// liga o led após 10 acionamentos do botão
	if (countClicks >= 10) {
		digitalWrite(ledPin, HIGH);
	}	
}

Você vai reparar que o LED dificilmente vai ligar exatamente no décimo toque, provavelmente será bem antes (testei com um botão antigo que tinha e bastava no máximo 3 toques para ligar)… Não dá para conseguir resultados confiáveis.

Antes de discutir a solução, ou seja, o Debounce, cabe uma reflexão: esse problema realmente importa para minha montagem? Está claro que ele só afeta o circuito por alguns milisegundos antes do botão fechar complemente. Somente durante essa transição é que o problema aparece.

Se a sua montagem só precisa saber se o botão foi pressionado ou não, isso pode não ser tão importante assim e talvez não seja necessário fazer qualquer modificação no seu código ou ajustes no circuito.

Para ligar um LED, por exemplo, certamente ele irá piscar quando o botão for acionado para ligar ou desligar mas isso ocorre tão rapidamente que é imperceptível para o olho humano. O circuito “gastou” alguns ciclos do processador oscilando entre o estado de HIGH e LOW mas não é nada prejudicial.

Esse efeito precisa ser remediado quando é importante que se tenha certeza da quantidade de vezes que o botão foi pressionado. Por exemplo, se estivéssemos fazendo um controle remoto para uma televisão, pode ser que sem o ajuste desse efeito seja quase impossível trocar de canal… Você irá apertar uma vez o botão e a TV iria andar uns 5 canais para frente…. Você vai tentar voltar e ela dá um salto de 8 canais para trás…

Debounce

Existem duas maneiras de solucionar esse problema: via software, utilizando um código que de alguma forma compense essa saltos na tensão ou via hardware, acrescentando componentes que irão “absorver” as oscilações na fração de tempo antes do botão fechar o circuito completamente.

Para não alongar muito o artigo, vamos nos concentrar agora na solução via software e, em um outro momento, conversamos como resolver isso acrescentando alguns componentes extras no circuito.

Para consertamos via software, temos duas soluções bem simples utilizando funções nativas do Arduino que são:

  • delay()
  • millis()

Cada uma das abordagens tem suas vantagens e desvantagens. A primeira forma de utilizar o debounce é com a função delay(): ela é mais simples, pois podemos detectar o primeiro toque e pausar o processamento por alguns milisegundos. Provavelmente algo entre 50 e 200 milisegundos já serão o suficientes para a maioria dos botões. Dessa forma o código fica parado durante as oscilações e não irá registrá-las. Uma desvantagem dessa abordagem é que o delay é uma função bloqueante e nada mais pode ser feito nesse momento de pausa.

Para implementar essa solução, só precisamos acrescentar uma linha no código:

... 
	if (buttonState == HIGH & countClicks < 10) {
		countClicks++;
        delay(70);     // Usando 70ms
	}
...

Com alguns poucos testes vamos chegar em um valor que funcione bem para o botão que estamos usando. No meu caso 70ms foi o suficiente.

Outra forma de fazer o debounce é usando a função milis(): vamos fazer a mesma coisa, só que ao invés de forçar uma parada completa do código, vamos deixar o código seguir e contar o tempo a cada ciclo do processador quando registrarmos o primeiro toque. Quando tiver passado tempo que achamos o suficiente para desconsiderar o ruído, incrementamos o nosso contador. É uma solução bem mais robusta, pois se tivermos outras tarefas para serem feitas após a verificação do botão, elas não ficaram bloqueadas esperando o delay como no código anterior. A desvantagem é que temos um pouco mais de código e lógica para implementar.

//inicializando e declarando as variáveis
const int ledPin = 13;   // porta usada para o led
const int buttonPin = 2; // porta usada para o push-button

int buttonState;         // variável para vericar o estado do botão
int countClicks = 0;     // variável para contar o número de clicks no botão
unsigned long lastDebounceTime = 0; // última vez que o botão foi pressionado
unsigned long debounceDelay = 70;   // O intervalo, igual ao delay do código anterior

void setup() {
  // configura os pinos que vamos usar no arduíno
  pinMode(ledPin, OUTPUT);
  pinMode(buttonPin, INPUT);
}

void loop() {
  // verifica se o botão foi pressionado
  int buttonState = digitalRead(buttonPin);

  if (buttonState == HIGH) {
    if ((millis() - lastDebounceTime) > debounceDelay) {
      countClicks++;
    }
    lastDebounceTime = millis();
  }

  // liga o led após 10 acionamentos do botão
  if (countClicks >= 10) {
    digitalWrite(ledPin, HIGH);
  } 
}

Alguns destaques do código:

  • As variáveis de contagem de tempo são do tipo “unsigned long”, que é o maior tipo inteiro possível. Isso é feito porque esse número tende a sempre ir crescendo pois a função milis() começa a contar o tempo quando ligamos o Arduino e segue sempre incrementando.
  • É na linha: “if ((millis() – lastDebounceTime) > debounceDelay)” que estamos efetivamente fazendo o teste para saber se contamos ou não o toque.

Existem outras formas de fazer o código e se você quiser olhar mais um exemplo, pode encontrar na página do “help” do Arduíno.

Que tal praticarmos um pouco o que aprendemos? Seguem alguns exercícios.

  1. O que precisaríamos fazer para acionar o LED depois de 10 toques, para em seguida apagar depois de 5 toques?
  2. O que precisaríamos fazer para que depois dos 10 toques, com o LED acionado, dar um “reset” quando botão fosse pressionado por 1s?
  3. Vamos expandir um pouco o circuito? Adicione mais um botão ao circuito e passe a contar o toque dos dois como válidos.
  4. E para fechar… Usando o circuito anterior, considere que você tocou o botão 1. Agora a contagem só continua se você pressionar o 2 ( o 1 fica desativado). Em seguida o 2 ficará desativado e assim até chegar no 10, quando você acenderá o LED.

Gostou de saber mais sobre debounce e como resolver esse problema? Ajude-nos a melhorar o blog comentando abaixo sobre este tutorial.

Espero que vocês se divirtam e sigam sempre criando! 

Faça seu comentário

Acesse sua conta e participe

5 Comentários

  1. Seria legal também que mostrasse pra gente como seria feito via hardware

    1. Olá,

      Existem várias formas de resolver em hardware e com diferentes chaves, mas aqui há uma fonte (em inglês) com algumas técnicas: https://www.digikey.com.br/en/articles/how-to-implement-hardware-debounce-for-switches-and-relays

      Abraços!
      Vinícius – Equipe MakerHero

  2. Parabéns me ajudou bastante no funcionamento correto do meu projeto.

  3. em mais de 20 anos trabalhando com sistemas embarcador está é a primeira vez que vejo um debouce bem escrito, parabéns.

  4. Excelente Marcio; tecnologia é para ser usada e facilita a vida; parabéns!