PTENES
MÓDULO 1.4 · PRÁTICA

📡 Sensores: o robô percebe

O "sentir" do ciclo do robô. Você aprende os quatro jeitos de um sensor entregar um dado, mede distância com o HC-SR04 sem arriscar o ESP32, acha linha e obstáculo com infravermelho, limpa o ruído com filtro de média, liga sensores no barramento I2C e lê tudo ao mesmo tempo, sem travar o programa.

6
Tópicos
Iniciante
Nível
Prática
Tipo
HC-SR04 + IR
Hardware (ou Wokwi)
0%
0 de 0 tópicos lidos · Seção 1 de 6

Conteúdo detalhado

1

🗣️ Como um sensor "fala" com a placa

Antes de ligar qualquer sensor, descubra em que "idioma" ele entrega o dado. São quatro: digital (sim ou não, como "tem obstáculo"), analógico (uma tensão que varia, como a refletância do chão), largura de pulso (o dado está na duração de um pulso, como no HC-SR04) e barramento (o sensor manda números prontos por um protocolo, como o I2C). O idioma decide o pino, a função de leitura e o cuidado elétrico.

🌱 Novo aqui?

Um módulo sensor é o sensor já montado numa plaquinha com resistores e, às vezes, um comparador que transforma o analógico em digital. A folha de dados (datasheet) é o documento do fabricante com tensão de trabalho, pinos e tempos. Um barramento é um par de fios compartilhado por vários dispositivos, cada um com seu endereço.

digitaldigitalRead() analógicoanalogRead() largura de pulso pulseIn() duração = valorbarramento I2C Wireendereço + dados, bit a bit

Como ler: cada quadro é um idioma, com a função do Arduino que o entende no canto. Em cima (verde), o valor está no nível da tensão. Embaixo (ciano), o valor está no TEMPO: na duração do pulso ou na sequência de bits. Os de baixo exigem que o programa espere ou converse, por isso aparecem de novo no tópico 6.

Sensor do kitIdiomaAlimentaçãoCuidado
HC-SR04largura de pulso5 VECHO volta em 5 V: divisor
IR de obstáculodigital3,3 Vajustar o trimpot
TCRT5000 (linha)analógico (AO) ou digital (DO)3,3 Vusar pino do ADC1
VL53L0X, MPU-6050I2C3,3 V (módulo)endereços diferentes

Por que aprender

Sabendo o idioma, qualquer sensor novo vira uma pergunta rápida: qual pino, qual função, qual tensão. Você para de depender de um tutorial para cada peça e passa a ler a folha de dados.

Conceitos-chave

Sinal digital
sim ou não
ADC
tensão vira número
Pulso
a duração é o dado
I2C
dois fios, endereços
2

🦇 HC-SR04: distância por eco

O HC-SR04 funciona como um morcego. Você manda um pulso curto no pino TRIG; ele emite um chiado de ultrassom (40 kHz, que você não ouve) e deixa o pino ECHO em nível alto até o eco voltar. A duração desse pulso é o tempo de ida e volta. Como o som anda cerca de 343 m/s (0,0343 cm por microssegundo), a distância é duração × 0,0343 ÷ 2, dividindo por 2 porque o som foi e voltou.

HC-SR04 obstáculoeco voltaTRIG 10 µsECHO duração = ida + volta ESP32GPIO 5 (TRIG)GPIO 18 (ECHO) 1 kΩ 2 kΩGND

Como ler: em cima, o caminho do som até o obstáculo e de volta. No meio, os dois sinais no tempo: o pulso curto do TRIG e o pulso longo do ECHO, cuja largura é o que você mede. À direita, a proteção: o ECHO sai em 5 V e passa pelo divisor 1 kΩ/2 kΩ, que entrega cerca de 3,3 V ao GPIO 18. O TRIG pode ir direto: 3,3 V do ESP32 é suficiente para disparar.

Montagem em três passos

1

Alimentação

VCC no 5 V (pino VIN/5V do DevKit alimentado por USB) e GND no GND comum. A versão HC-SR04P aceita 3,3 V e dispensa o divisor; confira a sua.

2

TRIG direto

TRIG no GPIO 5, com jumper simples.

3

ECHO pelo divisor

ECHO → 1 kΩ → ponto do meio → GPIO 18; do ponto do meio, 2 kΩ até o GND. Conta do módulo 1.2: 5 × 2 ÷ 3 ≈ 3,3 V. No Wokwi o divisor não é necessário, mas monte para treinar.

Objetivo: medir a distância cinco vezes por segundo e mostrar em centímetros, avisando quando não houver eco. No Wokwi, adicione "HC-SR04" e clique nele durante a simulação para mudar a distância.

hc-sr04.ino
// HC-SR04: distância por eco (ESP32, pacote esp32 3.x)
const int PINO_TRIG = 5;
const int PINO_ECHO = 18;            // pelo divisor 1k/2k no hardware real
const unsigned long TIMEOUT_US = 30000;  // ~5 m: sem eco depois disso

float medirCm() {
  digitalWrite(PINO_TRIG, LOW);  delayMicroseconds(2);
  digitalWrite(PINO_TRIG, HIGH); delayMicroseconds(10);  // pulso de disparo
  digitalWrite(PINO_TRIG, LOW);
  unsigned long dur = pulseIn(PINO_ECHO, HIGH, TIMEOUT_US);
  if (dur == 0) return -1;              // nenhum eco
  return dur * 0.0343 / 2.0;             // cm (ida e volta)
}

void setup() {
  Serial.begin(115200);
  pinMode(PINO_TRIG, OUTPUT);
  pinMode(PINO_ECHO, INPUT);
  Serial.println("HC-SR04 pronto");
}

void loop() {
  float cm = medirCm();
  if (cm < 0) Serial.println("sem eco (longe demais ou superficie macia)");
  else { Serial.print("distancia: "); Serial.print(cm, 1); Serial.println(" cm"); }
  delay(200);
}

Como verificar

  • ✓Mão a uns 20 cm do sensor: o Monitor mostra perto de "distancia: 20.0 cm"; confira com uma régua (erro de 1 a 2 cm é normal).
  • ✓No Wokwi, o valor acompanha o controle deslizante do sensor.
  • ✗Sempre "sem eco"? Confira o 5 V no VCC e se TRIG e ECHO não estão trocados.

✓ O HC-SR04 vê bem

  • ✓Parede, caixa, perna de cadeira de frente.
  • ✓Entre uns 2 cm e 2 a 3 m, em ambiente fechado.
  • ✓Superfícies duras e lisas.

✗ O HC-SR04 se confunde

  • ✗Cortina, sofá, almofada: absorvem o som.
  • ✗Parede muito inclinada: o eco vai para o lado.
  • ✗Dois HC-SR04 disparando juntos: um ouve o eco do outro.

Por que aprender

É o "olho" do rover que desvia de obstáculos no módulo 1.6. Saber os limites dele (superfícies macias, ângulos) explica por que o robô às vezes bate numa cortina e não numa parede.

Conceitos-chave

TRIG / ECHO
dispara / mede
Velocidade do som
0,0343 cm/µs
pulseIn
mede o pulso, com timeout
Divisor no ECHO
5 V → 3,3 V
3

🔦 Infravermelho: linha e obstáculo

Os sensores infravermelhos (IR) de reflexão têm um LED que emite luz invisível e um receptor ao lado. Superfície clara reflete muito; superfície preta absorve. No sensor de linha (TCRT5000), apontado para o chão a poucos milímetros, isso separa a fita preta do piso. No sensor de obstáculo, apontado para a frente, um objeto perto devolve luz e o módulo avisa. Muitos módulos trazem um comparador com trimpot (parafusinho azul) que transforma o analógico em digital.

🌱 Novo aqui?

O limiar (threshold) é o valor que separa "linha" de "chão": acima dele é uma coisa, abaixo é outra. Calibrar é medir o valor no claro e no escuro na superfície real e pôr o limiar no meio. Em muitos módulos a saída digital DO fica em LOW quando detecta; confira o LED indicador da plaquinha.

LED IR · receptor piso claro reflete muitoleitura ≈ 300 LED IR · receptor fita pretaabsorve leitura ≈ 3500 preto limiarno meioclaro

Como ler: as setas vermelhas são a luz infravermelha. À esquerda ela volta inteira; no meio, quase nada volta. Nos módulos TCRT5000 comuns, menos luz no receptor = número MAIOR no ADC (os valores 300 e 3500 são exemplo; os seus vão ser outros). À direita, a régua: o limiar fica no meio entre a leitura do claro e a do preto, medidas no seu chão.

Objetivo: calibrar o sensor de linha (AO no GPIO 35) nos primeiros 5 segundos, passando-o sobre o claro e o preto, e depois dizer "LINHA" ou "chao"; ler junto um sensor de obstáculo (DO no GPIO 27). No Wokwi, use um potenciômetro no lugar do AO e um botão no lugar do DO.

ir-linha-obstaculo.ino
// IR de linha (analógico) com calibração + IR de obstáculo (digital)
const int PINO_LINHA = 35;       // AO do TCRT5000, ADC1
const int PINO_OBST  = 27;       // DO do módulo de obstáculo
int minimo = 4095, maximo = 0, limiar = 2048;

void setup() {
  Serial.begin(115200);
  pinMode(PINO_OBST, INPUT_PULLUP); // segura o nível se o módulo estiver solto
  Serial.println("Calibrando por 5 s: passe o sensor no claro e no preto");
  unsigned long inicio = millis();
  while (millis() - inicio < 5000) {
    int v = analogRead(PINO_LINHA);
    if (v < minimo) minimo = v;
    if (v > maximo) maximo = v;
    delay(5);
  }
  limiar = (minimo + maximo) / 2;
  Serial.print("min="); Serial.print(minimo);
  Serial.print(" max="); Serial.print(maximo);
  Serial.print(" limiar="); Serial.println(limiar);
}

void loop() {
  int v = analogRead(PINO_LINHA);
  bool naLinha = (v > limiar);                    // preto = valor alto
  bool obstaculo = (digitalRead(PINO_OBST) == LOW);  // muitos módulos: LOW = detectou
  Serial.print("linha="); Serial.print(v);
  Serial.print(naLinha ? " LINHA" : " chao");
  Serial.println(obstaculo ? " | OBSTACULO" : " | livre");
  delay(100);
}

Como verificar

  • ✓Depois da calibração, "max" deve ser bem maior que "min" (diferença de centenas ou mais).
  • ✓Sobre a fita aparece "LINHA"; fora dela, "chao". Mão a poucos cm do sensor de obstáculo: "OBSTACULO".
  • ✗min e max quase iguais? O sensor está alto demais (o ideal fica entre uns 3 e 10 mm do chão) ou você não passou sobre o preto.

Por que aprender

São os sensores mais baratos do kit e a base do seguidor de linha da Trilha 2, onde cinco deles lado a lado viram uma barra. A calibração automática que você acabou de escrever é a mesma que o projeto 2.6 usa.

Conceitos-chave

Reflexão
claro volta, preto absorve
Limiar
fronteira entre dois estados
Calibração
medir claro e escuro reais
Trimpot
ajusta a saída digital
4

📉 Ruído e filtro de média

Nenhum sensor dá o mesmo número duas vezes. A leitura "pula" por ruído elétrico, vibração, eco perdido. Às vezes vem um valor absurdo (0 cm, 900 cm) que, se o robô levar a sério, faz ele frear do nada. Duas ferramentas simples resolvem quase tudo: descartar o que está fora da faixa possível e fazer a média móvel das últimas N leituras válidas.

🌱 Novo aqui?

A média móvel guarda as últimas N leituras numa lista circular e devolve a média delas; a cada leitura nova, a mais antiga sai. A mediana ordena os valores e pega o do meio, ótima contra picos isolados. O preço do filtro é o atraso: com N grande, a curva fica lisa mas demora a reagir.

pico descartado eco perdidocru (ruidoso)filtrado tempo → (o objeto se aproximou no meio do gráfico)

Como ler: a linha ciano é o que o sensor entrega; a verde é o que o robô deve usar. Os círculos vermelhos são leituras impossíveis, jogadas fora antes da média. Repare que, quando o objeto se aproxima, a verde desce um pouco depois da ciano: esse é o atraso do filtro, pequeno com 5 amostras.

Objetivo: aplicar descarte + média móvel de 5 leituras ao HC-SR04 do tópico 2 e ver as duas curvas juntas no Serial Plotter.

filtro-media.ino
// Descarte + média móvel no HC-SR04 (ESP32, pacote esp32 3.x)
const int PINO_TRIG = 5, PINO_ECHO = 18;
const int N = 5;                    // tamanho da janela
float janela[N];
int pos = 0, cheias = 0;

float medirCm() {
  digitalWrite(PINO_TRIG, LOW);  delayMicroseconds(2);
  digitalWrite(PINO_TRIG, HIGH); delayMicroseconds(10);
  digitalWrite(PINO_TRIG, LOW);
  unsigned long dur = pulseIn(PINO_ECHO, HIGH, 30000);
  return (dur == 0) ? -1 : dur * 0.0343 / 2.0;
}

float filtrar(float novo) {
  janela[pos] = novo; pos = (pos + 1) % N;  // lista circular
  if (cheias < N) cheias++;
  float soma = 0;
  for (int i = 0; i < cheias; i++) soma += janela[i];
  return soma / cheias;
}

void setup() {
  Serial.begin(115200);
  pinMode(PINO_TRIG, OUTPUT); pinMode(PINO_ECHO, INPUT);
}

void loop() {
  float cru = medirCm();
  static float filtrado = 0;
  if (cru >= 2 && cru <= 400) filtrado = filtrar(cru);  // descarta o impossível
  Serial.print("cru:"); Serial.print(cru);
  Serial.print(",filtrado:"); Serial.println(filtrado);
  delay(60);                         // HC-SR04 pede uns 60 ms entre medidas
}

Como verificar: no Serial Plotter, "cru" tem dentes e picos (inclusive -1 quando falta eco) e "filtrado" é uma linha lisa que segue sua mão com um pequeno atraso. Troque N para 15 e veja o atraso crescer.

✓ Filtro na medida

  • ✓Descartar primeiro, depois fazer a média.
  • ✓Janela pequena (3 a 7) para decisão rápida.
  • ✓Mediana quando os picos são raros e enormes.

✗ Filtro que atrapalha

  • ✗Média de 50 leituras: o robô vê a parede tarde demais.
  • ✗Incluir o -1 do "sem eco" na média.
  • ✗Filtrar no código um ruído que vem de fio solto.

Por que aprender

Robô que reage a ruído freia, vira e hesita sem motivo. Com o filtro, a decisão do módulo 1.6 fica confiável. Na Trilha 2 a mesma ideia reaparece como filtro complementar da IMU e filtro na derivada do PID.

Conceitos-chave

Média móvel
últimas N leituras
Mediana
o valor do meio
Fora da faixa
descartar antes
Atraso
o preço da suavidade
5

🔗 Sensores I2C: dois fios, vários sensores

O I2C é um barramento de dois fios, SDA (dados) e SCL (relógio), compartilhado por vários dispositivos. Cada um tem um endereço de 7 bits, escrito em hexadecimal: o sensor de distância a laser VL53L0X costuma responder em 0x29, a IMU MPU-6050 em 0x68, a telinha OLED em 0x3C. No ESP32 clássico os pinos padrão são SDA = GPIO 21 e SCL = GPIO 22, e a biblioteca é a Wire, que já vem no pacote.

ESP32GPIO 21 SDA GPIO 22 SCL SDASCL pull-ups → 3,3 VVL53L0X0x29 MPU-60500x68 OLED0x3C

Como ler: as duas linhas horizontais são o barramento inteiro. Cada módulo ciano se pendura nelas com dois fios (pontos brancos) e responde só quando o ESP32 chama o seu endereço. Os resistores âmbar mantêm as linhas em 3,3 V quando ninguém fala; a maioria dos módulos já traz esses pull-ups na plaquinha. Além de SDA e SCL, cada módulo precisa de 3,3 V e GND.

Do zero ao primeiro número

1

Ligue quatro fios

VCC → 3,3 V, GND → GND, SDA → GPIO 21, SCL → GPIO 22.

2

Rode o scanner

Ele pergunta a todos os 127 endereços quem responde. Se o seu sensor aparecer, a fiação está certa.

3

Instale a biblioteca do sensor

Ferramentas → Gerenciar Bibliotecas. Para o VL53L0X, procure "VL53L0X" e instale a da Pololu. Abra o exemplo dela em Arquivo → Exemplos para conferir a versão.

Objetivo: listar no Serial Monitor todos os dispositivos I2C ligados. No Wokwi, adicione um "MPU6050" ligado em 21/22 e o scanner deve achar 0x68.

i2c-scanner.ino
// Scanner I2C: quem está no barramento? (ESP32, pacote esp32 3.x)
#include <Wire.h>

void setup() {
  Serial.begin(115200);
  Wire.begin();                       // SDA 21, SCL 22 no ESP32 clássico
  delay(300);
  Serial.println("Procurando dispositivos I2C...");
  int achados = 0;
  for (byte endereco = 1; endereco < 127; endereco++) {
    Wire.beginTransmission(endereco);
    if (Wire.endTransmission() == 0) { // 0 = alguém respondeu
      Serial.print("  encontrado em 0x");
      if (endereco < 16) Serial.print("0");
      Serial.println(endereco, HEX);
      achados++;
    }
  }
  Serial.print(achados); Serial.println(" dispositivo(s).");
}

void loop() {}

Como verificar: com um VL53L0X, aparece "encontrado em 0x29" e "1 dispositivo(s)."; com o MPU-6050 do Wokwi, 0x68. Zero dispositivos = SDA e SCL trocados, falta GND ou o módulo não tem alimentação.

Objetivo: ler a distância em milímetros do VL53L0X (placa real; o Wokwi não tem essa peça), com a biblioteca VL53L0X da Pololu.

vl53l0x.ino
// Distância a laser VL53L0X por I2C (biblioteca VL53L0X da Pololu)
#include <Wire.h>
#include <VL53L0X.h>

VL53L0X sensor;

void setup() {
  Serial.begin(115200);
  Wire.begin();
  sensor.setTimeout(500);
  if (!sensor.init()) {
    Serial.println("VL53L0X nao encontrado: confira fios e endereco");
    while (true) delay(1000);
  }
  sensor.startContinuous();           // mede sem parar
}

void loop() {
  int mm = sensor.readRangeContinuousMillimeters();
  if (sensor.timeoutOccurred()) Serial.println("timeout");
  else { Serial.print("distancia: "); Serial.print(mm); Serial.println(" mm"); }
  delay(100);
}

Como verificar: com a mão a 10 cm, aparece perto de "distancia: 100 mm"; sem nada à frente (acima de uns 1,2 m), valores perto de 8190 indicam "fora do alcance".

Por que aprender

IMU, telas, sensores de cor, de pressão e de distância a laser usam I2C. Com dois pinos você liga vários deles, o que vai fazer falta na Trilha 2, quando o robô ganhar IMU e encoders e os pinos começarem a acabar.

Conceitos-chave

SDA / SCL
dados / relógio
Endereço
0x29, 0x68, 0x3C
Biblioteca Wire
I2C do Arduino
Scanner
confere a fiação
6

⏱️ Ler vários sensores sem travar

Um delay(500) parece inofensivo, mas durante esse meio segundo o ESP32 não faz mais nada: não lê o IR, não vê a parede, não para o motor. A solução é trocar a espera por uma pergunta: "já passou tempo suficiente desde a última vez?", usando millis(). Cada sensor ganha seu próprio relógio e o loop() gira milhares de vezes por segundo, atendendo quem estiver na hora.

🌱 Novo aqui?

Código bloqueante é o que para tudo enquanto espera (como delay). Não bloqueante confere o relógio e segue em frente. Uma tarefa periódica é um pedaço de código que roda a cada X milissegundos. A conta millis() - ultimo >= intervalo, com unsigned long, continua certa mesmo quando o contador dá a volta, depois de uns 49 dias ligado.

com delay(500) parado, cego obstáculo aparecesó visto aquicom millis() IRdistância serial visto na hora

Como ler: em cima, cada bloco vermelho é meio segundo em que o programa está surdo e cego; o obstáculo (linha âmbar) só é notado na leitura seguinte. Embaixo, três tarefas com ritmos próprios: IR a cada 10 ms, distância a cada 60 ms, mensagem a cada 250 ms. Nenhuma espera pela outra, e o obstáculo é notado na próxima leitura de distância.

Objetivo: juntar HC-SR04, IR de linha e um LED de alerta num só programa sem nenhum delay no loop. O LED do GPIO 2 acende quando há obstáculo a menos de 20 cm. É o esqueleto do "sentir" do rover do módulo 1.6.

sensores-sem-travar.ino
// Vários sensores sem travar: millis() no lugar de delay (ESP32, 3.x)
const int PINO_TRIG = 5, PINO_ECHO = 18, PINO_LINHA = 35, PINO_LED = 2;
const float PERTO_CM = 20.0;

unsigned long tIR = 0, tDist = 0, tPrint = 0;
float distanciaCm = -1;
int linha = 0;

float medirCm() {
  digitalWrite(PINO_TRIG, LOW);  delayMicroseconds(2);
  digitalWrite(PINO_TRIG, HIGH); delayMicroseconds(10);
  digitalWrite(PINO_TRIG, LOW);
  unsigned long dur = pulseIn(PINO_ECHO, HIGH, 25000); // espera no máximo 25 ms
  return (dur == 0) ? -1 : dur * 0.0343 / 2.0;
}

void setup() {
  Serial.begin(115200);
  pinMode(PINO_TRIG, OUTPUT); pinMode(PINO_ECHO, INPUT); pinMode(PINO_LED, OUTPUT);
  Serial.println("Sensores rodando sem delay");
}

void loop() {
  unsigned long agora = millis();

  if (agora - tIR >= 10) {          // IR: 100 vezes por segundo
    tIR = agora;
    linha = analogRead(PINO_LINHA);
  }
  if (agora - tDist >= 60) {        // distância: ~16 vezes por segundo
    tDist = agora;
    float cm = medirCm();
    if (cm > 0) distanciaCm = cm;
    digitalWrite(PINO_LED, (distanciaCm > 0 && distanciaCm < PERTO_CM) ? HIGH : LOW);
  }
  if (agora - tPrint >= 250) {      // relatório: 4 vezes por segundo
    tPrint = agora;
    Serial.print("dist="); Serial.print(distanciaCm, 1);
    Serial.print(" cm | linha="); Serial.print(linha);
    Serial.println(distanciaCm > 0 && distanciaCm < PERTO_CM ? " | PERTO" : "");
  }
}

Como verificar

  • ✓O Monitor mostra 4 linhas por segundo com "dist=" e "linha="; aproximando a mão a menos de 20 cm, aparece "PERTO" e o LED acende na hora.
  • ✓Passar o sensor de linha sobre o preto muda "linha=" imediatamente, mesmo com a distância sendo medida.
  • ✗Tudo lento? Sobrou algum delay() no loop ou numa função chamada por ele.

💡 O pulseIn também espera

O pulseIn bloqueia até o eco voltar, por isso o timeout de 25 ms: no pior caso o loop perde 25 ms, nunca meio segundo. Na Trilha 2 você vai separar isso de vez com tarefas do FreeRTOS e interrupções.

Por que aprender

Um delay longo deixa o robô cego e ele bate antes de perceber. Com tarefas periódicas, o rover do módulo 1.6 mede, decide e move ao mesmo tempo, e você pode acrescentar sensores sem reescrever o resto.

Conceitos-chave

millis()
relógio desde que ligou
Tarefa periódica
roda a cada X ms
Não bloqueante
confere e segue
Timeout
teto para a espera

✅ Resumo do módulo

✓
Quatro idiomas: digital, analógico, largura de pulso e barramento I2C.
✓
HC-SR04: duração × 0,0343 ÷ 2; ECHO de 5 V pelo divisor 1 kΩ/2 kΩ.
✓
IR: claro reflete, preto absorve; calibre no seu chão e use o limiar do meio.
✓
Filtro: descarte o impossível, depois média móvel curta.
✓
I2C: SDA 21, SCL 22, endereços diferentes; o scanner confere a fiação.
✓
Sem travar: millis() e tarefas periódicas no lugar de delay.

Para ir além

Próximo módulo

1.5 · Motores e energia. Motor DC, servo ou passo; ponte H TB6612FNG ou DRV8833; velocidade com PWM; servo com ESP32Servo; baterias e GND comum.