Tutorial · Iniciante · 18 min
Varredura ultrassônica com servo para encontrar espaço livre
Monte um HC-SR04 em um servo SG90 e vire-o para medir a distância em vários ângulos, para que o robô gire na direção mais livre em vez de adivinhar.
Um único sensor de distância apontado para a frente pode dizer a um robô que há algo à frente, mas não qual direção está livre. Por isso ele gira às cegas e muitas vezes avança em direção a outro obstáculo ou entra em um beco sem saída. Montar o HC-SR04 em um servo SG90 resolve o problema: o robô para, olha para a esquerda e para a direita e gira na direção mais livre — o mesmo truque que uma pessoa usa ao chegar a uma esquina sem visão.
Por que um sensor fixo prende o robô
Imagine um robô que enxerga apenas para a frente. Ele encontra uma parede, sabe que precisa girar, mas não sabe se a esquerda ou a direita é melhor. Se escolher errado, volta para o canto de onde acabou de vir. Um sensor montado em servo transforma um número (a distância à frente) em um pequeno mapa do espaço ao redor da parte frontal do robô, de modo que a escolha deixa de ser ao acaso.
Montando o sensor no servo
Fixe o HC-SR04 no braço do servo e monte o servo na parte frontal central do chassi, orientado para que o braço varrer o sensor da esquerda para a direita (e não de cima para baixo). Na posição central do servo, o sensor deve apontar diretamente para a frente. Isso proporciona uma varredura limpa de um lado, passando pela frente, até o outro lado.
Varra e colete amostras
Percorra o servo por uma sequência de ângulos e faça uma leitura filtrada em cada um. Reutilize a função de distância limpa de como ler o HC-SR04 — um ping bruto no meio da varredura é ruidoso demais para usar no comando de direção.
#include <Servo.h>
Servo scanner;
const int ANGLES[] = {30, 60, 90, 120, 150};
const int N = 5;
int bestAngle() {
long best = -1;
int bestA = 90;
for (int i = 0; i < N; i++) {
scanner.write(ANGLES[i]);
delay(180); // let the servo REACH the angle first
long d = medianCm(); // filtered read from the previous tutorial
if (d > best) { best = d; bestA = ANGLES[i]; }
}
scanner.write(90); // re-centre for driving
return bestA; // > 90 = open to the left, < 90 = right
}
Não leia durante o movimento
O defeito mais comum aqui é ler antes de o servo chegar ao ângulo solicitado. servo.write() retorna imediatamente, mas o SG90 leva tempo para se mover até o novo ângulo. Se o ping ocorrer cedo demais, a distância corresponderá à orientação anterior do sensor, não àquela solicitada. Um delay curto depois de cada write() — tempo suficiente para o servo estabilizar — torna cada leitura confiável.
Transformando a varredura em uma decisão
O ângulo com a maior distância representa a direção mais livre. Dois refinamentos tornam a decisão mais sensata:
- Em caso de empate, prefira seguir em frente. Se duas direções tiverem leituras semelhantes, favoreça o centro para que o robô não oscile sem necessidade.
- Todas as direções estão bloqueadas? Isso é um beco sem saída — recue e varra novamente, em vez de avançar mais para dentro.
Depois da varredura, deixe o servo terminar de voltar à posição central antes de retomar a condução.
Velocidade versus cobertura
Mais ângulos fornecem uma visão mais detalhada, mas tornam o laço mais lento, e um laço lento faz o robô reagir tarde. Três a cinco ângulos — esquerda, frente, direita e alguns intermediários — é o equilíbrio habitual. Entregue essa decisão de direção mais livre ao algoritmo de desvio de obstáculos e teste todo o comportamento no simulador de desvio de obstáculos antes de construir.
Quanto tempo o servo realmente leva?
“Um atraso curto” é a instrução mais vaga do código acima, e vale a pena substituí-la por um número medido. Um SG90 é especificado em aproximadamente 0,1 s por 60° a 4,8 V, sem carga — portanto o tempo de estabilização depende da distância percorrida:
| Passo da varredura | Tempo de percurso (especificação) | Margem de estabilização | Use |
|---|---|---|---|
| 30° | 50 ms | ~30 ms | 80 ms |
| 60° | 100 ms | ~40 ms | 140 ms |
| 90° | 150 ms | ~40 ms | 190 ms |
| 180° (recentralizar a partir de uma extremidade) | 300 ms | ~50 ms | 350 ms |
Dois fatores tornam o valor real pior que a especificação. Carga: um HC-SR04 e seu cabo no braço representam uma carga pequena, mas não nula, que desacelera o servo e aumenta a ultrapassagem. Tensão: com 4,0 V de um conjunto que sofre queda de tensão, o mesmo servo fica claramente mais lento do que a 6 V.
Meça o tempo de estabilização no seu servo, em vez de confiar na tabela. Comande um passo de 60° e cronometre quanto tempo leva até a leitura de distância parar de mudar:
void measureSettle() {
scanner.write(60); delay(500); // known start
scanner.write(120); // command the step
const unsigned long t0 = millis();
long prev = -1;
for (int i = 0; i < 40; i++) { // sample every 10 ms
const long d = pingCm();
Serial.print(millis() - t0); Serial.print(' '); Serial.println(d);
delay(10);
}
}
A leitura estabiliza em um momento definido. Use esse tempo mais uma pequena margem, e você terá removido a maior fonte de leituras sem sentido em todo o comportamento.
Varrer em uma direção é mais rápido do que saltar entre ângulos
O código acima avança de 30 → 60 → 90 → 120 → 150, o que já constitui uma única varredura, e isso é deliberado. Ler os ângulos em uma ordem dispersa faz cada passo ser grande, e o custo de estabilização é cobrado por passo:
| Padrão | Percurso total | Custo de estabilização a ~1,7 ms/grau |
|---|---|---|
| Varredura em ordem (30→150) | 120° | ~200 ms mais cinco estabilizações curtas |
| Do centro para fora (90, 60, 120, 30, 150) | 300° | ~510 ms |
| Retorno ao centro entre cada ângulo | 480° | ~820 ms |
Uma varredura completa é um período sem percepção para o robô — ele fica parado e não avança — portanto reduzir a duração pela metade é uma melhoria real no tempo de resposta percebido. Varra em ordem e, se fizer varreduras repetidamente, alterne a direção para nunca pagar pelo trajeto de retorno.
Varredura não bloqueante
O delay(180) em cada passo faz uma varredura de cinco ângulos bloquear o laço por quase um segundo. Em um robô que já está parado, isso é tolerável, mas também significa que os interruptores de contato não são lidos e qualquer outro comportamento fica congelado.
O mesmo tratamento de máquina de estados usado para o ping se aplica aqui:
enum ScanState { SCAN_IDLE, SCAN_MOVING, SCAN_READING };
ScanState scanState = SCAN_IDLE;
uint8_t scanIndex = 0;
unsigned long scanSince = 0;
long scanResult[N];
bool scanComplete = false;
void updateScan() {
switch (scanState) {
case SCAN_IDLE:
break;
case SCAN_MOVING:
if (millis() - scanSince < SETTLE_MS) return; // measured, above
scanState = SCAN_READING;
break;
case SCAN_READING:
scanResult[scanIndex] = medianCm();
if (++scanIndex >= N) { scanComplete = true; scanState = SCAN_IDLE; scanner.write(90); }
else { scanner.write(ANGLES[scanIndex]); scanSince = millis(); scanState = SCAN_MOVING; }
break;
}
}
O laço principal chama updateScan() e continua. Os interruptores de contato continuam sendo lidos, a máquina de estados continua funcionando e a varredura termina quando chega ao fim.
Interpolação entre ângulos
Cinco leituras dão cinco direções, mas a abertura real raramente está exatamente em uma delas. Um centroide ponderado sobre as leituras abertas fornece uma direção entre as amostras, da mesma forma que uma matriz de reflectância fornece uma posição da linha entre os sensores:
// Weight each angle by how open it is, ignoring anything below the stop threshold.
float openBearing() {
float weighted = 0, total = 0;
for (int i = 0; i < N; i++) {
if (scanResult[i] < STOP_CM) continue; // blocked, contributes nothing
const float w = scanResult[i] - STOP_CM; // how much clearance beyond the threshold
weighted += w * ANGLES[i];
total += w;
}
return (total > 0) ? (weighted / total) : 90.0f; // 90 = straight ahead
}
Essa é uma melhoria real em relação a simplesmente “escolher o máximo”. Escolher o máximo faz uma porta amostrada ligeiramente fora do centro enviar o robô para a borda da porta; o centroide o envia para o meio da porta. Ele também lida bem com aberturas parciais — se dois ângulos adjacentes estiverem abertos, o resultado fica entre eles, em vez de escolher arbitrariamente um dos dois.
O cone limita o que uma varredura pode informar
O feixe de um HC-SR04 tem aproximadamente um semiângulo de 15°, que a 1 m corresponde a um cone com cerca de 53 cm de diâmetro. Portanto, amostrar em intervalos de 30° produz uma sobreposição grande — leituras adjacentes estão observando quase o mesmo espaço.
| Espaçamento das amostras | A 1 m | Consequência |
|---|---|---|
| 15° | Os cones se sobrepõem quase totalmente | Tempo desperdiçado; nenhuma informação nova |
| 30° | Sobreposição substancial | O equilíbrio prático |
| 45° | Pequenas lacunas entre os cones | Aceitável; pode perder um objeto estreito |
| 60° | Lacunas reais | Uma perna de cadeira pode ficar entre duas amostras sem ser vista |
A consequência importante é que uma varredura não oferece resolução angular melhor do que a do feixe. Uma leitura de 68 cm a 120° significa “há algo a 68 cm de distância, em algum lugar dentro de cerca de 15° de 120°” — informação suficiente para escolher uma direção, mas inútil para medir a largura de uma abertura. Se você precisa da segunda informação, um VL53L0X no mesmo servo tem um feixe muito mais estreito e oferece uma varredura com resolução real.
Solução de problemas
| Sintoma | Causa provável | Correção |
|---|---|---|
| O robô gira com confiança em direção a uma parede | Leitura antes de o servo estabilizar | Meça o tempo de estabilização e use-o |
| As leituras se misturam entre os ângulos | A mesma causa, em menor grau | Aumente o atraso de estabilização |
| A varredura leva mais de um segundo | Atrasos bloqueantes ou ordem de ângulos dispersa | Use uma máquina de estados não bloqueante; varra em ordem |
| A placa reinicia durante uma varredura | Corrente do servo passando pelo Arduino | Use uma fonte de 5 V separada e conecte os aterramentos |
| O robô vai para a borda de uma porta | Escolha do ângulo máximo | Use o centroide ponderado |
| O controle de velocidade dos motores parou de funcionar | A biblioteca Servo usa o Timer 1 (pinos 9, 10) |
Mova o PWM dos motores para 3, 5, 6 ou 11 |
| O servo treme entre as leituras | Ruído na fonte ou pulsos gerados por temporização de software | Coloque um capacitor no servo; use um PCA9685 se o problema persistir |
| Uma perna de cadeira não é detectada | Espaçamento entre amostras maior que o feixe | Use espaçamento de 30° ou um sensor de feixe estreito |
| A varredura está boa, mas o robô continua preso | A navegação reativa não tem memória | Comportamento esperado — adicione um mecanismo de recuperação ao detectar travamento |
Explore o grafo
Faz parte destas montagens
Projetos e caminhos de aprendizagem que incluem este tutorial.
Leitura complementar