Introdução
Em muitos projetos, o uso de botões é fundamental para interações com o usuário. No entanto, esses botões podem apresentar um fenômeno indesejável conhecido como efeito bounce, que pode comprometer o funcionamento correto do projeto.
Para resolver esse problema, é necessário implementar uma técnica chamada de debounce. Neste contexto, exploramos como implementar o debounce utilizando Arduino com a Franzininho C0
O Que é Debounce?
O debounce é uma técnica utilizada para evitar que múltiplas detecções de um único evento sejam identificadas por um microcontrolador. Por exemplo, quando um botão de pressão (push button) é acionado, ele pode gerar múltiplos sinais elétricos de curta duração devido à oscilação mecânica dos contatos, esse fenômeno é conhecido como “bouncing”.
Como a leitura do sinal do botão é feita de maneira extremamente rápida, o efeito bounce pode passar despercebido para nós humanos. Já para o microprocessador que verifica o status do botão milhões de vezes por segundo, isso é interpretado como se o botão estivesse sendo pressionado e solto várias vezes.
Como pode ser visto na Figura 1, o efeito bounce pode aparecer quando apertamos e quando soltamos o botão.
Fonte: https://arduinobymyself.blogspot.com/2012/02/debouncing.html
De que forma isso pode impactar no meu projeto? Quando você tem um sistema que depende do número de vezes que o botão é pressionado para realizar uma ação, o efeito bounce pode causar problemas. Uma vez que o microcontrolador pode erroneamente contar várias pressões em vez de uma única, resultando na execução incorreta das ações.
A solução para esse problema pode ser implementada tanto via hardware quanto via software. Hoje, veremos como fazer isso via software.
Pinout da Franzininho C0
Para definir os pinos utilizados, precisamos consultar o pinout da placa:
Acesse a documentação completa em:
https://docs.franzininho.com.br/docs/franzininho-c0/franzininho-c0-board
Código
Para resolver o efeito Bounce na leitura do botão da Franzininho C0 utilizamos o código abaixo. Este código foi desenvolvido com base no trabalho de Daniel Quadros e está disponível no Github da Franzininho:
#define BOTAO 8
#define DEBOUNCE_STABLE_PERIOD 10 // 100ms
HardwareTimer *MyTim;
bool apertouBotao = false;
bool soltouBotao = false;
bool botaoApertadoDb = false;
void setup() {
// Conecta a UART aos pinos ligados à USB
Serial.setRx(PA_10_R);
Serial.setTx(PA_9_R);
// Inicia a serial
Serial.begin(115200);
// tempo para a IDE do Arduino ativar o Monitor Serial
delay(3000);
// Inicia o botao
pinMode(BOTAO, INPUT_PULLUP);
// Inicia a interrupção de timer
MyTim = new HardwareTimer(TIM1);
MyTim->setOverflow(10000, MICROSEC_FORMAT); // 10 ms
MyTim->attachInterrupt(Tim_callback);
MyTim->resume();
// Envia mensagem de identificação
Serial.println("Debounce de Botao\n");
}
// Rotina chamada periodicamente, atualiza situação do botão
void Tim_callback(void) {
static bool botaoApertado = false;
static uint8_t debounceCounter = 0;
bool apertado = digitalRead(BOTAO) == LOW;
if (botaoApertado != apertado) {
// botão mudou, aguarda estabilizar
botaoApertado = apertado;
debounceCounter = 0;
return;
}
if (debounceCounter < DEBOUNCE_STABLE_PERIOD) {
// Validando mudança
if (++debounceCounter == DEBOUNCE_STABLE_PERIOD) {
// estabilizou
if (botaoApertadoDb != botaoApertado) {
botaoApertadoDb = botaoApertado;
if (botaoApertado) {
apertouBotao = true;
} else {
soltouBotao = true;
}
}
}
}
}
void loop() {
if (apertouBotao) {
apertouBotao = false;
Serial.println("Apertou o botao");
}
if (soltouBotao) {
soltouBotao = false;
Serial.println("Soltou o botao");
}
}
Obs2.: Antes da IDE iniciar o carregamento do programa, a Franzininho C0 deve executar o bootloader, caso contrário ocorrerá um erro.
Explicação do Código
Para esse código funcionar é importante verificar no menu “Tools” se a opção “U(S)ART Support” está configurada para “Enabled (generic Serial)”.
Antes do setup() são criadas algumas variáveis globais. Com #define, BOTAO é definido como 8, que corresponde ao pino que o botão está conectado e DEBOUNCE_STABLE_PERIOD é definido com 10, que corresponde ao número de períodos de 10 ms necessários para considerar uma leitura estável, totalizando 100 ms.
Já *MyTim é um ponteiro para um objeto HardwareTimer e será usado para configurar e controlar o timer. apertouBotao, soltouBotao, e botaoApertadoDb são variáveis booleanas usadas para definir o estado do botão.
#define BOTAO 8
#define DEBOUNCE_STABLE_PERIOD 10 // 100ms
HardwareTimer *MyTim;
bool apertouBotao = false;
bool soltouBotao = false;
bool botaoApertadoDb = false;No setup(), configuramos os pinos UART para comunicação serial. Isso é necessário para enviar mensagens ao monitor serial, como explicado detalhadamente em Franzininho C0: Comunicação serial no Arduino.
O botão é definido como entrada com resistor pull-up interno. Em seguida, configuramos o timer 1 para gerar uma interrupção a cada 10 ms. Quando a interrupção é gerada, a função de callback ‘Tim_callback’ é chamada. Depois disso, o timer é iniciado. Para entender melhor como funciona uma interrupção no Arduino, veja Franzininho C0: Blink LED com Delay e com Interrupção no Arduino
Por último, uma mensagem indicando que o código está pronto para uso é exibida no monitor serial.
void setup() {
// Conecta a UART aos pinos ligados à USB
Serial.setRx(PA_10_R);
Serial.setTx(PA_9_R);
// Inicia a serial
Serial.begin(115200);
// tempo para a IDE do Arduino ativar o Monitor Serial
delay(3000);
// Inicia o botao
pinMode(BOTAO, INPUT_PULLUP);
// Inicia a interrupção de timer
MyTim = new HardwareTimer(TIM1);
MyTim->setOverflow(10000, MICROSEC_FORMAT); // 10 ms
MyTim->attachInterrupt(Tim_callback);
MyTim->resume();
// Envia mensagem de identificação
Serial.println("Debounce de Botao\n");
}Na função Tim_callback(), temos ‘botaoApertado’ que é uma variável estática que armazena o estado atual do botão e debounceCounter que é um contador estático que rastreia o número de leituras consecutivas do mesmo estado.
void Tim_callback(void) {
static bool botaoApertado = false;
static uint8_t debounceCounter = 0;
... }Com ‘digitalRead(BOTAO)’ lemos o estado atual do botão e depois verificamos se esse estado é LOW. A variável booleana ‘Apertado’ receberá o resultado dessa comparação, então se:
digitalRead(BOTAO) = LOW, ‘Apertado’ = truedigitalRead(BOTAO)= HIGH, ‘Apertado’ = false
Lembre-se que o botão está definido como pull-up, isso significa que em LOW ele está pressionado.
Se o estado do botão mudou em relação à última leitura, ou seja, se a variável ‘Apertado’ tem um valor diferente da variável ‘botaoApertado’, ‘botaoApertado’ é atualizado para receber o novo estado do botão e o contador de debounce é reiniciado.
bool apertado = digitalRead(BOTAO) == LOW;
if (botaoApertado != apertado) {
// botão mudou, aguarda estabilizar
botaoApertado = apertado;
debounceCounter = 0;
return;
}Em outra estrutura condicional verificamos se o contador de debounce (debounceCounter) ainda não atingiu o período necessário para considerar o estado do botão como estável (DEBOUNCE_STABLE_PERIOD = 10).
Se ainda não atingiu, o contador de debounce é incrementado. Caso contrário, isso significa que o estado do botão permaneceu estável por tempo suficiente e podemos considerá-lo válido para ser processado.
if (debounceCounter < DEBOUNCE_STABLE_PERIOD) {
// Validando mudança
if (++debounceCounter == DEBOUNCE_STABLE_PERIOD) {
// estabilizou
...}Então, se o contador de debounce atingiu o período estável de debounce precisamos verificar se o estado atual de estabilidade do botão (‘botaoApertado’) é diferente do estado estável anterior (‘botaoApertadoDb’). Se for diferente, o ‘botaoApertadoDb’ é atualizado com o novo estado estável do botão.
Por último, se o botão estiver pressionado (‘botaoApertado’ = true), definimos a variável ‘apertouBotao’ como verdadeira; caso contrário, definimos a variável ‘soltouBotao’ como verdadeira.
if (botaoApertadoDb != botaoApertado) {
botaoApertadoDb = botaoApertado;
if (botaoApertado) {
apertouBotao = true;
} else {
soltouBotao = true;
}
}No loop(), estaremos constantemente verificando as variáveis ‘apertouBotao’ e ‘soltouBotao’. Assim, se ‘apertouBotao’ = true, seu valor é alterado para false e uma mensagem é enviada para serial. O mesmo ocorre com ‘soltouBotao’ = true, seu valor é alterado para false e uma mensagem é enviada para serial.
void loop() {
if (apertouBotao) {
apertouBotao = false;
Serial.println("Apertou o botao");
}
if (soltouBotao) {
soltouBotao = false;
Serial.println("Soltou o botao");
}
}Executando o código conforme demonstrado no vídeo, podemos controlar precisamente cada vez que o botão é pressionado e solto.
Conclusão
Neste artigo vimos uma implementação eficaz da técnica de debounce em software para lidar com o efeito de bounce de um botão. Utilizando o timer foi possível gerar interrupções em intervalos regulares e verificar o estado do botão dentro da função de callback associada a essas interrupções, o que nos permitiu garantir que apenas mudanças de estado estáveis seriam registradas.
O debounce nos permite obter um comportamento confiável e preciso do botão, evitando leituras falsas e possíveis erros no projeto.
Desafio
Neste desafio, você usará a técnica de debounce para controlar dois LEDs da Franzininho C0 com base no número de vezes que um botão é pressionado. A meta é implementar o seguinte comportamento:
- Pressionar uma vez: Acender o LED 1.
- Pressionar duas vezes: Acender o LED 2.
- Pressionar três vezes: Apagar ambos os LEDs.





