3.11. Debouncing

Uma chave é desenhada como um contato perfeito de aberto ou fechado, mas os contatos de uma chave real não alternam de forma limpa entre os dois estados. Eles trepidam – fazem e desfazem o contato elétrico muitas vezes em poucos milissegundos antes de estabilizar. Uma entrada GPIO lendo o pino enxerga isso como uma rajada de bordas; um laço de polling descuidado conta vários “toques” para um único toque real, e um manipulador de interrupção é executado várias vezes por toque efetivo.

Um traço de osciloscópio idealizado mostrando o sinal de entrada de uma chave. O sinal começa em nível alto (chave aberta), cai para nível baixo, oscila para frente e para trás várias vezes em poucos milissegundos e então estabiliza em nível baixo (chave fechada).

Uma chave que trepida produz uma rajada de transições rápidas antes de estabilizar.

Debouncing é a prática de filtrar a trepidação para que cada toque físico seja registrado como um único evento. Duas abordagens resolvem isso – software (uma regra de temporização no firmware) ou hardware (um pequeno filtro no fio). Elas não são mutuamente exclusivas.

3.11.1. Debouncing por software

A ideia é lembrar quando a entrada mudou pela última vez e rejeitar mudanças adicionais dentro de uma janela curta a partir desse instante. A trepidação de contato geralmente dura menos de 10 ms; um toque real leva de 50 a 100 ms; uma janela de 30 a 50 ms captura todas as trepidações sem bloquear toques reais.

Em um laço de polling, leia o pino, compare com o último valor estável e só aceite uma mudança depois que a janela de debounce tiver decorrido:

import time
from machine import Pin

button = Pin("P0", Pin.IN, Pin.PULL_UP)
last_state  = 1
last_change = 0
DEBOUNCE_MS = 50

while True:
    now = time.ticks_ms()
    state = button.value()
    if state != last_state and time.ticks_diff(now, last_change) > DEBOUNCE_MS:
        last_change = now
        last_state = state
        if state == 0:
            do_action()
    time.sleep_ms(10)

Para leituras orientadas a interrupção, aplique a mesma regra de temporização dentro do manipulador e então repasse o toque real ao contexto principal via micropython.schedule() (veja Entrada GPIO):

import time
import micropython
from machine import Pin

button = Pin("P0", Pin.IN, Pin.PULL_UP)
last_irq = 0
DEBOUNCE_MS = 50

def handle_press(pin):
    do_action()

def on_press(pin):
    global last_irq
    now = time.ticks_ms()
    if time.ticks_diff(now, last_irq) < DEBOUNCE_MS:
        return
    last_irq = now
    micropython.schedule(handle_press, pin)

button.irq(handler=on_press, trigger=Pin.IRQ_FALLING)

A ISR filtra trepidações pelo timestamp e enfileira o callback; handle_press é executado de volta no contexto principal, onde alocação e I/O lenta são seguros.

3.11.2. Debouncing por hardware

O debouncing por hardware filtra a trepidação eletricamente, antes que ela sequer chegue ao pino. A ferramenta padrão é um capacitor.

Um capacitor é um componente de dois terminais que armazena carga elétrica. Fisicamente, são duas placas condutoras mantidas a uma curta distância uma da outra, separadas por um isolante (o dielétrico).

Um capacitor desenhado como duas placas horizontais paralelas com um dielétrico (isolante) entre elas. Um terminal conecta cada placa a um terminal externo -- A em cima, B embaixo. Cargas iguais e opostas +Q e -Q se acumulam nas duas placas quando uma tensão V é aplicada entre os terminais.

Um capacitor de placas paralelas: dois condutores separados por uma camada isolante.

Aplicar uma tensão entre seus terminais força cargas iguais e opostas para as duas placas; a relação é

Q = C × V

onde Q é a carga armazenada (coulombs), V é a tensão sobre o capacitor e C é sua capacitância (farads). A capacitância é fixada pela construção do dispositivo; mais capacitância significa mais carga armazenada na mesma tensão.

A consequência: um capacitor não pode mudar sua tensão instantaneamente. A carga que entra ou sai precisa passar por qualquer resistência que houver no caminho, e essa resistência determina a velocidade com que a tensão pode mudar.

3.11.2.1. Constante de tempo RC

Carregar um capacitor através de um resistor produz uma subida exponencial suave em direção à tensão de alimentação, não um degrau. O tempo característico dessa subida é a constante de tempo RC:

τ = R × C

Após um τ, o capacitor atinge cerca de 63 % da tensão de alimentação. Após 5 τ, está acima de 99 % – “totalmente carregado” para fins práticos.

Um gráfico mostrando a tensão de um capacitor subindo ao longo de uma curva exponencial de 0 V em direção ao trilho de alimentação. O tempo τ = RC é marcado no eixo x onde a curva atinge 63 % da tensão de alimentação.

Um capacitor carrega ao longo de uma curva exponencial. τ = RC é o tempo para atingir 63 % da tensão final.

A descarga através de um resistor segue a imagem espelhada: a tensão cai exponencialmente a partir de seu valor inicial em direção a zero, caindo para 37 % da tensão inicial após um τ e para menos de 1 % após 5 τ.

Um gráfico mostrando a tensão de um capacitor caindo ao longo de uma curva exponencial de Vmax em direção a 0 V. O tempo τ = RC é marcado no eixo x onde a curva cai para 37 % da tensão inicial.

Um capacitor descarrega ao longo de um decaimento exponencial. τ = RC é o tempo para cair a 37 % da tensão inicial.

3.11.2.2. O circuito de debounce

Um capacitor entre um pino de entrada e o terra, alimentado através de um resistor em série, forma um filtro passa-baixas. Picos rápidos não têm tempo de carregar ou descarregar o capacitor através desse resistor; o pino permanece próximo da tensão em que estava antes do pico. Mudanças lentas – um toque deliberado – carregam ou descarregam o capacitor e a leitura acompanha.

R1 puxa o lado alto da chave para Vcc, produzindo um sinal bruto da chave que trepida. R2 e C então filtram esse sinal por passa-baixas até o pino:

Uma entrada de chave com debouncing por hardware. Vcc se conecta através de um resistor pull-up de 10 kΩ até uma junção. Essa junção se conecta ao terra através da chave em um ramo, e através de um resistor em série de 10 kΩ até o Pino no outro ramo. Um capacitor de 100 nF entre o Pino e o terra completa o filtro passa-baixas.

Debouncing por hardware: R2 e C filtram por passa-baixas o sinal bruto da chave antes que ele chegue ao pino.

Valores típicos: R1 = 10 (pull-up), R2 = 10 (série), C = 100 nF.

Quando a chave está aberta, a corrente flui Vcc → R1R2 → capacitor (em série), carregando o capacitor até Vcc com τ_charge = (R1 + R2) × C = 2 ms.

Quando a chave fecha, o nó da chave é fixado ao terra e o capacitor se descarrega através de R2 somente para esse terra com τ_discharge = R2 × C = 1 ms.

Ambas as bordas são filtradas por RC. Como o capacitor fica em seu próprio nó, a jusante de R2 a partir da chave, ele oscila de forma limpa entre Vcc (aberta) e 0 V (fechada) – nenhuma corrente precisa fluir através de R1 em regime permanente em qualquer um dos casos.

3.11.3. Escolhendo entre eles

  • Software é o padrão. Não custa nada em componentes, o limiar é fácil de ajustar e funciona em qualquer pino que a CPU leia.

  • Hardware vale as peças quando a trepidação alcança algo além do código de polling da CPU – uma interrupção que não pode disparar em dobro, um contador de hardware, um periférico sem filtro próprio.

Debouncing por software e por hardware também coexistem pacificamente: um pequeno filtro RC suprime os piores picos, e uma janela de debounce por software cobre o que restar.