Tutorial · Iniciante · 25 min

Sensores e microchaves de contato: debounce para robôs

O fechamento de uma microchave de contato não é um flanco, são nove. Entenda o bounce, o custo do debounce e como criar um para-choque durável.

Uma microchave de contato não é um flanco digital limpo

Pressione uma microchave de contato e o contato metálico chega, desvia, se separa e chega novamente — várias vezes, nos primeiros milissegundos, antes de se estabilizar. Isso é bounce de contato, e é mecânico: nem muito software antes nem um capacitor depois faz o metal parar de se mover.

A consequência só importa porque o pino é lido por algo muito mais rápido que o metal. Um microcontrolador que amostra a 16 MHz vê cada uma dessas transições como um flanco real.

Dois gráficos empilhados. O superior mostra o sinal de uma microchave de contato ao longo de cerca de seis milissegundos: o pino bruto cai para baixo, depois oscila entre alto e baixo nove vezes durante 4,7 milissegundos antes de estabilizar em baixo, enquanto a saída com debounce permanece alta até a janela expirar e então cai de forma limpa. O gráfico inferior mostra o número de flancos que uma interrupção contaria caindo em degraus de nove para um conforme a janela de debounce aumenta de zero a oito milissegundos, chegando a um em 4,7 milissegundos.
Um acionamento de uma microchave de contato modelada: nove transições em 4,7 ms. Uma janela de debounce menor que o bounce contabiliza as transições — e uma janela longa o bastante para estabilizá-lo custa exatamente a própria duração em latência, a troca que ninguém menciona. Baixar SVG

O painel inferior é a curva de projeto. A contagem de flancos cai em degraus conforme a janela cresce, chega a um quando o bounce termina e permanece nesse valor. Qualquer janela mais curta superconta; qualquer janela mais longa representa latência adicional sem benefício.

Durações típicas de bounce, que valem medir em vez de confiar nelas:

Microchave de contato Bounce
Microchave de alavanca de qualidade 0,5–2 ms
Botão tátil barato 2–10 ms
Microchave reed 0,1–1 ms
Contato de relé 1–20 ms

O conselho usual de que “5 ms bastam” é um palpite no meio dessa tabela. Meça o seu: é uma linha de código e transforma uma superstição em um número.

Ligação, antes de tudo isso importar

Uma microchave de contato quer contatos normalmente abertos à massa, com um resistor de pull-up no pino:

pinMode(BUMP_L, INPUT_PULLUP);   // idle HIGH, LOW when the bumper is pressed

Há três razões para essa disposição, e não a outra:

  • Ativo em baixo oferece falha segura. Um fio rompido produz nível ALTO, que é não pressionado, então um fio do para-choque rompido não faz o robô acreditar que está permanentemente em colisão. Uma disposição com resistor de pull-down falha de forma oposta.
  • O resistor de pull-up interno é gratuito. Nenhum resistor, nenhum componente adicional em um robô cuja frente sofre impactos repetidamente.
  • O ruído é desviado para a massa, não capturado. Um fio longo do para-choque é uma antena; o caminho de baixa impedância à massa quando a microchave está fechada é o que impede que ele capte o driver de motor ao lado.

Use o contato normalmente fechado da microchave e terá o comportamento de falha oposto — um fio rompido parece uma colisão permanente, que é a escolha correta para uma parada de emergência e a errada para um para-choque.

Debounce: escolha um dos três

Ignorar depois de aceitar. A opção preguiçosa e correta para um para-choque. Aceite o primeiro flanco instantaneamente e não considere novas transições durante a janela:

const unsigned long BOUNCE_MS = 6;
unsigned long lastBumpAt = 0;

bool bumped() {
  if (digitalRead(BUMP_L) == HIGH) return false;         // not pressed
  if (millis() - lastBumpAt < BOUNCE_MS) return false;   // still inside the window
  lastBumpAt = millis();
  return true;
}

Latência zero no flanco que importa, e o bounce posterior é descartado. É isso que você quer quando a reação é parar os motores — esperar 6 ms para ter certeza significa avançar por 6 ms até o móvel.

Esperar estabilizar. A abordagem do exemplo do Arduino: lembrar quando a leitura mudou pela última vez e aceitá-la somente depois que permanecer inalterada durante a janela. Custa a janela inteira em latência e é a forma certa para um botão de menu, onde um acionamento falso é pior do que um lento.

Solução com hardware. Um capacitor de 100 nF entre os contatos, junto com o resistor de pull-up, dá um RC de cerca de 10 ms com um pull-up interno de 100 kΩ — mas o flanco que ele produz é lento, e um flanco lento em uma entrada digital pode cruzar o limiar várias vezes. Se seguir esse caminho, use depois uma entrada com gatilho de Schmitt. Geralmente não vale a pena.

Observe o que os três têm em comum: são não bloqueantes. Um delay(20) depois do flanco elimina corretamente o bounce, mas impede o robô de verificar qualquer outra coisa por 20 ms, o que em uma máquina a 0,3 m/s representa 6 mm de deslocamento sem controle — veja temporização com millis().

Interrupções e bounce são uma dupla ruim

Anexar uma interrupção a um para-choque parece obviamente correto e normalmente não é.

Nove transições significam que a ISR dispara nove vezes. Se ela incrementar um contador, você tem nove colisões; se definir uma flag, há apenas uma, sem consequências. Use uma interrupção apenas se o tratador for idempotente — definir uma flag, capturar um registro de tempo — e nunca para incrementar um contador.

volatile bool bumpFlag = false;
volatile unsigned long bumpAt = 0;

void onBump() {                       // idempotent: nine calls do what one call does
  if (millis() - bumpAt < BOUNCE_MS) return;
  bumpAt = millis();
  bumpFlag = true;
}

millis() não avança dentro de uma ISR em um AVR, o que torna isso seguro durante a duração de um bounce, mas errado para qualquer coisa mais longa — use micros() se a janela precisar ser medida com precisão.

Sinceramente, para um para-choque você quase nunca precisa da interrupção. Um laço rodando a 200 Hz percebe uma colisão em até 5 ms, e o robô percorre 1,5 mm nesse tempo. Faça a leitura por polling e use os pinos de interrupção nos encoders que realmente não podem ser consultados assim.

Construindo um para-choque que funciona

A microchave é a parte fácil. O mecanismo à frente dela decide se o robô detecta a perna de uma cadeira que atinge em um ângulo.

  • Uma única estrutura móvel, duas microchaves. Um único painel frontal em dois pontos de flexão, com uma microchave atrás de cada lado. Apenas a microchave esquerda indica que o obstáculo está à esquerda, que é a informação de que o comportamento de desvio precisa para escolher para que lado virar.
  • Dobre as laterais. Um para-choque frontal plano não detecta um objeto atingido a mais de cerca de 30°, e as colisões por raspão são as mais comuns. Leve a estrutura até pelo menos 45° de cada lado.
  • As molas o retornam, as microchaves não. A mola própria da microchave tem uma força de 50–100 gf e não empurrará a estrutura de volta de forma confiável. Use elásticos ou molas de compressão leves e deixe a microchave apenas detectar.
  • Dê curso e um batente rígido. De 3–5 mm de movimento antes de a microchave ser acionada, e um batente que receba o impacto em vez do corpo da microchave. O que mais quebra são os corpos das microchaves.
  • Combine com outro sensor sem contato. Um para-choque é a última linha de defesa — ele relata uma colisão que já aconteceu. Um sensor ultrassônico ou de tempo de voo à frente dele é o que evita a maioria dessas colisões; o para-choque pega a perna preta da cadeira, o vidro e o objeto na altura errada.

Medindo seu próprio bounce

// Count transitions in the 20 ms after the first edge. Run it, press the switch,
// read the number. That number is your debounce window, in your robot, with your wiring.
void loop() {
  if (digitalRead(BUMP_L) == HIGH) return;
  unsigned long start = micros();
  int last = LOW, edges = 0;
  while (micros() - start < 20000) {
    int now = digitalRead(BUMP_L);
    if (now != last) { edges++; last = now; lastEdgeAt = micros(); }
  }
  Serial.print(edges); Serial.print(" edges, settled after ");
  Serial.print(lastEdgeAt - start); Serial.println(" us");
  delay(500);
}

Defina a janela como o tempo de estabilização medido, mais cerca de 50%. Meça novamente quando a microchave se desgastar — os contatos pioram, não melhoram.

Quando algo dá errado

Sintoma Causa provável
Um impacto contado como vários Sem debounce, ou uma janela menor que o bounce
Impactos totalmente perdidos Uma chamada bloqueante em algum lugar; o laço não estava verificando
O robô inverte aleatoriamente Fio longo do para-choque sem proteção perto do driver de motor
Indica uma colisão permanente Fio rompido em um contato normalmente fechado, ou ausência de pull-up
Detecta apenas impactos de frente Estrutura do para-choque muito plana — dobre as laterais
A microchave para de funcionar após uma semana O corpo da microchave absorveu o impacto em vez do batente rígido
Detecta a parede, mas não a perna da cadeira Nada errado com a microchave; a estrutura tem uma folga

Para saber onde um para-choque fica em um robô completo, o robô de cobertura de ambiente é construído com dois deles e nada mais — e o simulador de cobertura mostra exatamente até onde um robô chega com apenas um sinal binário de detecção.

Explore o grafo

Faz parte destas montagens

Projetos e caminhos de aprendizagem que incluem este tutorial.

Leitura complementar

Referências