Leitura de Botões e o Bounce

Leitura de Botões e o Bounce

Botões são utilizados na criação de interfaces entre maquinas e humanos. Nesse tutorial veremos como fazer com que o Arduino reconheça o estado de um botão e entenderemos o que é o Bounce.

Esse tutorial requer conhecimento em assuntos explicados em posts anteriores. Caso tenha dificuldades em acompanhar os assuntos abordados aqui, confira alguns de nossos tutoriais anteriores:

[toc]

REALIZANDO A LEITURA DE UMA ENTRADA DIGITAL ACIONADA POR UM BOTÃO

Para que o Arduino consiga saber o estado na qual o botão se encontra devemos fazer com que cada um de seus estados defina um nível lógico diferente na entrada na qual ele esta conectado. Para isso podemos utilizar o pull up interno do Arduino como mostra o esquema da figura a seguir.

No esquema da Figura acima, quando o botão estiver pressionado o pino escolhido estará com 0V e quando não estiver pressionado estará com 5V.

A Figura abaixo mostra o esquema de ligação para os exemplos apresentados a seguir. O resistor de 1k é utilizado para proteger o pino 8 de um possível curto para a terra.

Exemplo 1: Utilizando o botão para controlar um led.

void setup() {
pinMode(13, OUTPUT); // Configura o pino 13 (led interno) como saída;
pinMode(8, INPUT_PULLUP); // Configura pino 8 como entrada e habilita pull up interno;
}
void loop() {
 if (digitalRead(8) == LOW) { // Botão Pressionado;
  digitalWrite(13, HIGH); // Liga led.
 }
 else { // Botão Não Pressionado
  digitalWrite(13, LOW); // Desliga led.
 }
}

Apesar do esquema acima funcionar perfeitamente um fenômeno indesejado está acontecendo. Vamos analisá-lo:

O QUE É O BOUNCE?

Ao pressionar um botão nós fazemos com que os seus contatos se choquem o que fará com que o botão trepide um pouco antes de se estabilizar. Esse fenômeno é similar ao que acontece com uma bolinha de ping pong quando jogada contra ao chão, que quica algumas vezes antes de se estabilizar. A figura abaixo mostra a forma de onda do sinal produzido pelo botão ao ser pressionado.

Podemos observar que o botão varia entre nível alto e baixo várias vezes antes de se estabilizar em 0V, ou seja, o led do exemplo 1 pisca várias vezes cada vez que o botão é pressionado, porém isso acontece tão rápido que nosso olho não consegue perceber.

Exemplo 2: Detectando o Bounce.

O código abaixo implementa um contador, que conta o número de vezes que o botão mudou de estado. Essa quantidade é impressa na porta serial a cada segundo. Ao apertar e soltar o botão esperamos que esse contador incremente de duas unidades.

bool last; // Guarda o último estado do botão;
uint32_t print_timer; // Timer para a impressão na porta serial;
uint8_t counter = 0; // Conta o número de mudança de estados no botão;

void setup() {
 Serial.begin(9600);
 pinMode(8, INPUT_PULLUP); // Configura pino 8 como entrada e habilita pull up interno;
 last = digitalRead(8);
}

void loop() {
 bool now = digitalRead(8); // Lê o estado atual do botão;
 if (now != last) { // Checa se houve uma mudança de estado;
  ++counter;
  last = now; // Atualiza o ultimo estado;
 }

 if (millis() - print_timer > 1000) { // Imprime a quantidade de mudanças a cada segundo;
  Serial.println(counter);
  print_timer = millis();
 }
}

Ao pressionarmos o botão com força uma única vez e soltarmos veremos que o contador será incrementado mais que duas vezes como mostra a Figura 4. Isso ocorre devido a trepidação do botão.

Exemplo 3: Tratamento simples para o Bounce.

Podemos tratar a trepidação de um botão utilizando um pequeno delay assim que uma mudança por detectada. Se ao final desse delay a mudança ainda persiste então realmente houve uma alteração no estado do botão. O tempo na qual um botão trepida dependerá de vários fatores e pode ser diferente até mesmo em botões similares. Experimente diferentes valores de delay.

bool last; // Guarda o último estado do botão
uint32_t print_timer;
uint8_t counter = 0;

void setup() {
 Serial.begin(9600);
 pinMode(8, INPUT_PULLUP); // Configura pino 8 como entrada e habilita pull up interno;
 last = digitalRead(8);
}

void loop() {
 bool now = digitalRead(8); // Lê o estado atual do botão;
 if (now != last) { // Checa se houve uma mudança de estado;
  delay(10); // Espera até que a trepidação pare;
  if (now == digitalRead(8)) { // Checa se a mudança ainda persiste;
   ++counter;
   last = now; // Atualiza o ultimo estado;
  }
 }

 if (millis() - print_timer > 1000) {
  Serial.println(counter); // Imprime um ponto para indicar a mudança;
  print_timer = millis();
 }
}

Por utilizar um delay essa abordagem faz com que o Arduino fique bloqueado/parado enquanto o botão trepida o que não é interessante.

Exemplo 4: Tratando Bounce sem bloquear o Arduino.

Criaremos uma variável para guardar o tempo em millisegundos. Sempre que uma mudança for detectada no botão essa variável receberá o tempo do millis atual. Caso a diferença entre o millis atual e essa variável seja maior que o tempo de bounce o botão parou de trepidar e então podemos checar se a mudança realmente aconteceu.

bool stable; // Guarda o último estado estável do botão;
bool unstable; // Guarda o último estado instável do botão;
uint32_t bounce_timer;
uint8_t counter = 0;

bool changed() {
 bool now = digitalRead(8); // Lê o estado atual do botão;
 if (unstable != now) { // Checa se houve mudança;
  bounce_timer = millis(); // Atualiza timer;
  unstable = now; // Atualiza estado instável;
 }
 else if (millis() - bounce_timer > 10) { // Checa o tempo de trepidação acabou;
  if (stable != now) { // Checa se a mudança ainda persiste;
   stable = now; // Atualiza estado estável;
   return 1;
  }
 }
 return 0;
}

void setup() {
 Serial.begin(9600); // Configura comunicação serial a uma taxa de 9600 bauds.
 pinMode(8, INPUT_PULLUP); // Configura pino 8 como entrada e habilita pull up interno;
 stable = digitalRead(8);
}

void loop() {
 if (changed()) {
  ++counter;
  Serial.println(counter);
 }
 // Outras tarefas;
}

Biblioteca auxiliar

A biblioteca Bounce2 possui uma implementação eficiente e de fácil uso para o tratamento de bounce. Confira!

#include <Bounce2.h>

Bounce debouncer = Bounce();

void setup() {
 pinMode(8, INPUT_PULLUP); // Configura pino 8 como entrada e habilita pull up interno;
 debouncer.attach(8); // Informa que o tratamento de debouce será feito no pino 8;
 debouncer.interval(10); // Seta o intervalo de trepidação;
}

void loop() {
 debouncer.update(); // Executa o algorítimo de tratamento;

 int value = debouncer.read(); // Lê o valor tratado do botão;

 if (value == HIGH) {
  Serial.println("Botao Nao Pressionado!");
 } else {
  Serial.println("Botao Pressionado!");
 }
}

FINALIZANDO

Este conteúdo foi totalmente voltado para esclarecer alguns pontos à respeito da leitura de botões. Esperamos que você tenha gostado deste conteúdo, sinta-se à vontade para nos dar sugestões, críticas ou elogios. Lembre-se de deixar suas dúvidas nos comentários abaixo.

Apostila Arduino Básico

 


Teclado Matricial e Multiplexação

Teclado Matricial e Multiplexação

Teclados são geralmente utilizados em aplicações na qual o usuário precisar interagir com um sistema, como computadores, calculadoras, controles remotos entre outros. Imagine que desejamos utilizar um teclado com 64 botões em nossa aplicação. Caso cada botão seja ligado diretamente a um pino do microcontrolador, gastaríamos 64 pinos o que tornaria a implementação dessa interface, em uma placa Uno por exemplo, impossível. Para evitar este problema podemos conectar as teclas no formato de matriz 8x8 gastando agora apenas 16 pinos e utilizar uma técnica chamada multiplexação para realizar a leitura das teclas. Para demonstrar essa técnica de leitura utilizaremos um teclado matricial de membrana 4x4 junto com um Arduino.

Esse tutorial requer conhecimento em assuntos explicados em posts anteriores. Caso tenha dificuldades em acompanhar os assuntos abordados aqui, confira alguns de nossos tutoriais anteriores:

[toc]

O Teclado Matricial

Este teclado como o nome indica é formado de botões organizados em linhas e colunas de modo a formar uma matriz. Quando pressionado, um botão conecta a linha com a coluna na qual está ligado. A figura 1 ilustra a ligação matricial.

Figura 1 - Ligação interna de um teclado matricial 4x4.
Figura 1 - Ligação interna de um teclado matricial 4x4.

O teclado matricial possui a seguinte pinagem:

  • Pino 1 (Esquerda) - Primeira Linha (L1)
  • Pino 2  - Segunda Linha (L2)
  • ...
  • Pino 5 - Primeira Coluna (C1)
  • ...
  • Pino 8 - Quarta Coluna (C4)
Figura 2 - Pinagem do teclado matricial de membrana.
Figura 2 - Pinagem do teclado matricial de membrana.

A Multiplexação

Essa técnica consiste no compartilhamento do mesmo barramento por vários dispositivos, entretanto apenas um deles utilizará o barramento por vez. No caso do teclado matricial, os barramentos serão as linhas do teclado e os dispositivos as colunas. Logo devemos permitir que apenas uma coluna se ligue as linhas por vez. Para desconectarmos as colunas que não devem ser lidas devemos configurá-las como entradas (alta impedância).


Mão à obra - Algoritmo de varredura simples

Componentes utilizados

Para esse projeto usaremos os seguintes componentes:

Programação

No início os pinos conectados as linhas serão configurados como entradas com pull up e as colunas como entradas (alta impedância). A varredura consiste em ativar uma coluna por vez (saída em nível lógico baixo) e checar se houve uma alteração nas linhas. Caso uma alteração em uma linha seja identificada, o bounce da tecla deve ser devidamente tratado para que possamos finalmente afirmar que o botão foi pressionado.

 

const uint8_t row_size = 4;                       // Quantidade de linhas.
const uint8_t col_size = 4;                       // Quantidade de colunas.
const uint8_t row_pin[row_size] = {4, 5, 6, 7};   // Pinos que estão ligados as linhas.
const uint8_t col_pin[col_size] = {8, 9, 10, 11}; // Pinos que estão ligados as colunas.
const char keys[row_size][col_size] = {           // Mapa de teclas do teclado.
 { '1', '2', '3', 'A' },
 { '4', '5', '6', 'B' },
 { '7', '8', '9', 'C' },
 { '*', '0', '#', 'D' }
};
 
bool stable[row_size][col_size];   // Guarda o último estado estável dos botões.
bool unstable[row_size][col_size]; // Guarda o último estado instável dos botões.
uint32_t bounce_timer;
 
// Executa o algoritmo de varredura simples.
void scan() {
  for (uint8_t e = 0; e < col_size; ++e) {   // Varre cada coluna.
    pinMode(col_pin[e], OUTPUT);
    digitalWrite(col_pin[e], 0);             // Habilita a coluna "c".
    for (uint8_t r = 0; r < row_size; ++r) { // Varre cada linha a procura de mudanças.
      if (changed(r, e)) {                   // Se houver uma mudança de estado.
        Serial.print(keys[r][e]);
        Serial.print(" : ");                 // Imprime a tecla que sofreu a alteração
        Serial.println(stable[r][e]);        // e seu estado atual.
      }
    }
    pinMode(col_pin[e], INPUT);              // Desabilita a coluna "c".
  }
}
 
// Checa se o estado do botão foi alterado além de tratar os efeitos de sua trepidação.
bool changed(const uint8_t& row, const uint8_t& col) {
  bool now = digitalRead(row_pin[row]);    // Lê o estado do botão na linha especificada.
  if (unstable[row][col] != now) {         // Checa se houve mudança.
    bounce_timer = millis();               // Atualiza timer.
    unstable[row][col] = now;              // Atualiza estado instável.
  }
  else if (millis() - bounce_timer > 10) { // Checa o tempo de trepidação acabou.
    if (stable[row][col] != now) {         // Checa se a mudança ainda persiste.
      stable[row][col] = now;              // Atualiza estado estável.
      return 1;
    }
  }
  return 0;
}
 
void setup() {
  Serial.begin(9600);
 
  // Configura o estado inicial das linhas como entradas com pull up.
  for (uint8_t r = 0; r < row_size; ++r) {
    pinMode(row_pin[r], INPUT_PULLUP);
  } 
 
  // Configura o estado inicial das linhas como entradas.
  for (uint8_t c = 0; c < col_size; ++c) {
    pinMode(col_pin, INPUT);
  }
 
  // Define estado inicial dos botões;
  for (uint8_t c = 0; c < col_size; ++c) {
    for (uint8_t r = 0; r < row_size; ++r) {
      stable[r] = 1;
    }
  }
}
 
void loop() {
  scan();
}

 


Pressionando várias teclas

Quando pressionamos 3 ou mais teclas um efeito conhecido como tecla fantasma pode ocorrer, vamos observar a animação a seguir para entender o porquê:

Figura 3 - Teclas fantasmas teclado matricial
Figura 3 - Teclas fantasmas

Infelizmente os problemas não acabam por aqui. Caso a tecla fantasma seja pressionada e em seguida uma das teclas anteriores for solta, a tecla que foi solta ainda será considerada como pressionada. Para solucionarmos este problema devemos adicionar um diodo em cada botão para evitar que estes caminhos indesejados sejam formados, como mostra a Figura 4.

Figura 4 - Solução para teclas fantasmas.

 


Bibliotecas

A biblioteca Keypad é uma biblioteca especializada no interfaceamento de teclados matriciais. Seu algoritmo de varredura  consegue detectar se uma tecla esta apertada, solta ou sendo segurada, entre outras funções.

Clique para baixar a biblioteca Keypad

Exemplo de aplicação

#include <Keypad.h>
 
const uint8_t row_size = 4;                       // Quantidade de linhas.
const uint8_t col_size = 4;                       // Quantidade de colunas.
const uint8_t row_pin[row_size] = {4, 5, 6, 7};   // Pinos que estão ligados as linhas.
const uint8_t col_pin[col_size] = {8, 9, 10, 11}; // Pinos que estão ligados as colunas.
const char keys[row_size][col_size] = {           // Mapa de teclas do teclado.
  { '1', '2', '3', 'A' },
  { '4', '5', '6', 'B' },
  { '7', '8', '9', 'C' },
  { '*', '0', '#', 'D' }
};
 
Keypad keypad = Keypad(makeKeymap(keys), row_pin, col_pin, row_size, col_size);
 
void setup() {
  Serial.begin(9600);
}
 
void loop() {
  char key = keypad.getKey();   // Retorna a última tecla que foi apertada.
  if (key != NO_KEY) Serial.println(key); // Imprime tecla na porta serial.
}

Fechamento

Esperamos que tenham gostado, deixe seu comentário com duvidas, sugestões ou com a foto ou vídeo de seu projeto!! Compartilhe à vontade.

 

Apostila Arduino Básico