Conteúdo detalhado
📏 O que medir e com que frequência
Telemetria é o robô relatando o próprio estado enquanto trabalha. A regra prática: registre a entrada (o que o sensor leu), a decisão (erro, saída do PID, estado da máquina) e a saída (PWM de cada motor), sempre com o tempo em milissegundos. Com essas três colunas você descobre se o problema está no sentir, no pensar ou no agir.
🌱 Novo aqui?
Frequência de amostragem é quantas medidas por segundo você guarda, em hertz (Hz): 50 Hz = uma linha a cada 20 ms. Baud é a velocidade da porta serial em bits por segundo; a 115200 baud cabem cerca de 11 500 caracteres por segundo, porque cada caractere gasta 10 bits.
| O que | Exemplo | Ritmo sugerido |
|---|---|---|
| Laço de controle | erro, correção do PID, PWM | 50 a 200 Hz (o ritmo do PID) |
| Movimento | velocidade das rodas, ângulo da IMU | 20 a 100 Hz |
| Saúde | tensão da bateria, sinal Wi-Fi (RSSI) | 1 a 2 Hz |
| Eventos | troca de estado, linha perdida | quando acontecem |
| Painel para pessoas | resumo dos itens acima | 5 a 10 Hz (o olho não vê mais) |
Como ler: a caixa tracejada de cima é a capacidade da porta serial. Cada barra é uma linha de CSV com cerca de 40 caracteres, mandada em ritmos diferentes. Quando a barra passa do limite (vermelho), o envio começa a atrasar o próprio laço de controle. Grave rápido no cartão; mande devagar pela rede.
✓ Faça
- ✓Uma coluna de tempo (t_ms) em toda linha.
- ✓Nomes de coluna curtos e iguais em todos os logs.
- ✓Uma linha de cabeçalho com os parâmetros do teste (Kp, Kd, velocidade).
✗ Evite
- ✗Frases longas a cada ciclo ("O valor do sensor agora é...").
- ✗Imprimir dentro do laço de controle mais rápido do que a porta aguenta.
- ✗Log sem tempo: você não sabe o que veio antes.
Por que aprender
No seguidor de linha do 2.6 você vai ajustar Kp e Kd olhando gráficos de antes e depois. Se o log não tiver as colunas certas no ritmo certo, o gráfico não mostra a oscilação que você quer corrigir.
Conceitos-chave
📈 Gráfico ao vivo pelo Serial Plotter
A Arduino IDE 2.x tem um Serial Plotter (menu Ferramentas, ou o ícone de gráfico no canto superior direito). Ele lê cada linha da Serial e desenha uma curva por valor. O formato é uma linha por instante, com pares nome:valor separados por vírgula. Só uma janela pode usar a porta: feche o Serial Monitor antes de abrir o Plotter.
Como ler: o eixo horizontal é o tempo (as linhas chegando), o vertical é o valor. A curva azul é o que o sensor entrega; a ciano é a mesma leitura depois do filtro, mais lisa e um pouco atrasada; o tracejado é o alvo fixo. Comparar as curvas lado a lado é o jeito mais rápido de ver ruído, atraso e erro.
Objetivo: desenhar três curvas ao vivo: a leitura crua de um potenciômetro (ou sensor IR) no GPIO 34, a versão filtrada e um alvo fixo. Funciona no Wokwi, que também tem Serial Plotter.
// Três curvas no Serial Plotter da Arduino IDE 2.x (ESP32, pacote esp32 3.x)
const int PINO_SENSOR = 34; // potenciômetro ou sensor IR analógico (ADC1)
const int ALVO = 2000;
float filtrado = 0;
unsigned long tPlot = 0;
void setup() { Serial.begin(115200); }
void loop() {
int cru = analogRead(PINO_SENSOR); // 0 a 4095
filtrado = 0.9 * filtrado + 0.1 * cru; // filtro exponencial: 10% do novo, 90% do antigo
if (millis() - tPlot >= 20) { // 50 linhas por segundo: o Plotter acompanha
tPlot = millis();
Serial.printf("pot:%d,filtrado:%.0f,alvo:%d\n", cru, filtrado, ALVO);
}
}
Como verificar
- ✓O Plotter mostra três curvas com a legenda pot, filtrado e alvo.
- ✓Girando o potenciômetro rápido, a curva filtrada chega depois, sem os picos.
- ✗Uma curva só, chamada "value 1"? Confira o separador: dois-pontos entre nome e valor, vírgula entre pares. Se a sua versão da IDE mudar o formato, a ajuda do Plotter mostra o atual.
💡 O mesmo formato serve para o PID
Troque as variáveis por erro:%d,corr:%d,esq:%d,dir:%d e você vê o PID do 2.3 trabalhando: a correção sobe quando o erro aparece e as rodas se separam. Com o robô sobre a mesa, preso ao cabo, isso já revela sinal trocado e ganho exagerado.
Por que aprender
É a telemetria de custo zero: nenhum fio extra, nenhum programa extra. Para testes de bancada (sensor, filtro, sinal do PID) resolve a maior parte das dúvidas em minutos.
Conceitos-chave
💾 Gravar em cartão microSD (CSV)
Robô andando não pode arrastar cabo USB. A solução é levar um caderno de bordo: um módulo de cartão microSD gravando um arquivo CSV a 50 ou 100 linhas por segundo. Depois do teste, você tira o cartão e abre no computador, na planilha ou no Python. O cartão conversa com o ESP32 pelo barramento SPI.
🌱 Novo aqui?
CSV (valores separados por vírgula) é um texto em que cada linha é um registro e cada vírgula separa uma coluna; a primeira linha dá os nomes. SPI é um barramento de quatro fios (relógio SCK, dados MOSI e MISO, seleção CS) mais rápido que o I2C, usado por cartões, telas e alguns sensores.
Como ler: cada fio liga o pino do ESP32 (esquerda) ao pino de mesmo papel no módulo (direita). Os quatro de cima são o SPI padrão do ESP32 (VSPI). O vermelho é a alimentação: há módulos de 3,3 V e módulos com regulador que pedem 5 V no VCC; leia a serigrafia antes de ligar.
Objetivo: criar um arquivo novo a cada ligação (/log001.csv, /log002.csv...), gravar 50 linhas por segundo e fechar com segurança ao apertar BOOT. Cartão formatado em FAT32.
// Log CSV no cartão microSD (ESP32, pacote esp32 3.x, módulo microSD por SPI)
#include <SPI.h>
#include <SD.h>
const int PINO_CS = 5, PINO_SENSOR = 34, BOTAO = 0; // SCK 18, MISO 19, MOSI 23 (VSPI padrão)
File arq;
char nome[16];
unsigned long tLog = 0, linhas = 0;
float filtrado = 0;
bool gravando = true;
void setup() {
Serial.begin(115200);
pinMode(BOTAO, INPUT_PULLUP);
if (!SD.begin(PINO_CS)) { Serial.println("SD nao encontrado: confira fios e FAT32"); while (true) delay(1000); }
for (int i = 1; i < 1000; i++) { // primeiro nome livre
snprintf(nome, sizeof nome, "/log%03d.csv", i); // a barra inicial é obrigatória
if (!SD.exists(nome)) break;
}
arq = SD.open(nome, FILE_WRITE);
arq.println("t_ms,sensor,filtrado"); // cabeçalho, uma vez
Serial.printf("Gravando em %s (BOOT encerra)\n", nome);
}
void loop() {
if (!gravando) return;
if (millis() - tLog >= 20) { // 50 linhas por segundo
tLog = millis();
int cru = analogRead(PINO_SENSOR);
filtrado = 0.9 * filtrado + 0.1 * cru;
arq.printf("%lu,%d,%.1f\n", tLog, cru, filtrado);
if (++linhas % 25 == 0) arq.flush(); // grava de fato a cada 0,5 s
}
if (digitalRead(BOTAO) == LOW) { // encerrar antes de tirar o cartão
arq.close(); gravando = false;
Serial.printf("Arquivo fechado: %lu linhas. Pode tirar o cartao.\n", linhas);
}
}
Como verificar
- ✓O Serial Monitor mostra "Gravando em /log001.csv"; depois de apertar BOOT, o total de linhas (cerca de 50 por segundo gravado).
- ✓No computador, o arquivo abre na planilha com as colunas t_ms, sensor e filtrado.
- ✗"SD nao encontrado": cartão maior que 32 GB em exFAT, fio de CS trocado ou módulo sem alimentação certa.
💡 Por que o flush() importa
O ESP32 junta as linhas na memória e só escreve no cartão de tempos em tempos. Se a bateria cair antes, o fim do teste se perde e o arquivo pode corromper. flush() a cada meio segundo limita a perda a 0,5 s; close() antes de tirar o cartão evita arquivo quebrado.
Por que aprender
O cartão grava rápido, sem rede e sem cabo, e guarda testes inteiros para comparar depois. É o registro que você vai usar no ajuste do seguidor de linha.
Conceitos-chave
📡 MQTT: publicar telemetria na rede
Para ver o robô ao vivo, sem cabo, use MQTT: um protocolo leve de mensagens, feito para aparelhos pequenos. O robô publica num tópico (robo/telemetria) e não precisa saber quem está ouvindo. Um servidor chamado broker entrega a mensagem a todo mundo que assinou aquele tópico: o painel no navegador, o Node-RED, um script Python. Comandos fazem o caminho inverso, por outro tópico.
🌱 Novo aqui?
O broker é o correio central (o mais usado é o Mosquitto, gratuito). Um tópico é o endereço da mensagem, com níveis separados por barra; robo/# assina tudo abaixo de robo/. Publicar é enviar; assinar é pedir para receber.
Como ler: as setas cheias são a telemetria saindo do robô e sendo copiada pelo broker para cada assinante. As tracejadas são o comando: o painel publica em robo/comando e o broker entrega ao robô. Ninguém fala direto com ninguém; todos falam com o broker.
Passo a passo: broker Mosquitto no seu PC ou Raspberry Pi
Instale
Linux e Raspberry Pi OS pelo apt (abaixo). No Windows e no macOS, use o instalador de mosquitto.org/download.
Abra para a rede local
O Mosquitto 2.x só aceita conexões da própria máquina até você declarar um listener. O arquivo robo.conf abre a porta 1883 (ESP32) e a 9001 (navegador, WebSocket).
Descubra o IP e escute
Anote o IP do PC na rede (hostname -I no Linux, ipconfig no Windows) e deixe um terminal com mosquitto_sub aberto.
# Broker para a rede de casa ou da escola (Mosquitto 2.x)
listener 1883
listener 9001
protocol websockets
# sem senha: só em rede local de confiança; exposto na internet, configure usuário e senha
allow_anonymous true
sudo apt install mosquitto mosquitto-clients
sudo nano /etc/mosquitto/conf.d/robo.conf # cole o bloco acima e salve
sudo systemctl restart mosquitto
mosquitto_sub -h localhost -t 'robo/#' -v # mostra tudo que o robô publica
mosquitto_pub -h localhost -t robo/comando -m parar # em outro terminal: manda um comando
Objetivo: o ESP32 publica um JSON curto 5 vezes por segundo e obedece a "parar" e "andar" (o LED do GPIO 2 indica parado). Instale a biblioteca PubSubClient (Nick O'Leary) pelo Gerenciador de Bibliotecas. Troque os campos entre < >.
// Telemetria por MQTT (ESP32, pacote esp32 3.x + biblioteca PubSubClient)
#include <WiFi.h>
#include <PubSubClient.h>
const char *WIFI_NOME = "<seu_wifi>"; // no Wokwi: "Wokwi-GUEST" com senha ""
const char *WIFI_SENHA = "<sua_senha>";
const char *BROKER = "<ip_do_seu_pc>"; // teste rápido: "test.mosquitto.org" (público: qualquer um lê)
const int PINO_SENSOR = 34, PINO_LED = 2;
WiFiClient wifi;
PubSubClient mqtt(wifi);
unsigned long tPub = 0, tTentativa = 0;
bool parado = false;
void aoReceber(char *topico, byte *dados, unsigned int n) { // comandos chegam aqui
String msg; for (unsigned int i = 0; i < n; i++) msg += (char)dados[i];
if (msg == "parar") parado = true;
if (msg == "andar") parado = false;
digitalWrite(PINO_LED, parado);
Serial.printf("comando: %s\n", msg.c_str());
}
void setup() {
Serial.begin(115200);
pinMode(PINO_LED, OUTPUT);
WiFi.begin(WIFI_NOME, WIFI_SENHA);
while (WiFi.status() != WL_CONNECTED) { delay(300); Serial.print("."); } // espera só no setup
Serial.printf("\nWi-Fi ok: %s\n", WiFi.localIP().toString().c_str());
mqtt.setServer(BROKER, 1883);
mqtt.setCallback(aoReceber);
}
void loop() {
if (!mqtt.connected() && millis() - tTentativa > 2000) { // reconecta sem travar o robô
tTentativa = millis();
if (mqtt.connect("robo-inema-01")) { mqtt.subscribe("robo/comando"); Serial.println("MQTT conectado"); }
}
mqtt.loop(); // envia e recebe; chame em toda volta
if (mqtt.connected() && millis() - tPub >= 200) { // 5 mensagens por segundo
tPub = millis();
char json[96]; // curto: o buffer padrão é de 256 bytes
snprintf(json, sizeof json, "{\"t\":%lu,\"sensor\":%d,\"rssi\":%d,\"parado\":%d}", tPub, analogRead(PINO_SENSOR), WiFi.RSSI(), parado);
mqtt.publish("robo/telemetria", json);
}
}
Como verificar
- ✓O terminal do mosquitto_sub mostra robo/telemetria {"t":12345,"sensor":2048,...} 5 vezes por segundo.
- ✓O mosquitto_pub ... -m parar acende o LED; "andar" apaga.
- ✗Nunca conecta? Firewall do PC bloqueando a porta 1883, IP errado ou PC e ESP32 em redes diferentes (rede de convidados isola aparelhos). Em broker público, use um tópico só seu, como inema/seunome/telemetria.
Por que aprender
MQTT é o idioma comum da internet das coisas: o mesmo robô fala com painel, planilha, Home Assistant ou outro robô sem mudar uma linha. E é a mesma ideia de publicar e assinar tópicos que você vai reencontrar no ROS 2, na Trilha 4.
Conceitos-chave
🖥️ Painel no navegador ou no Node-RED/Grafana
Com a telemetria no broker, qualquer programa pode desenhar o painel. Há três caminhos, do mais simples ao mais completo. Comece pelo primeiro e só suba quando precisar.
Um arquivo só, abre no navegador e fala com o broker pela porta WebSocket (9001). Bom para a bancada e para mostrar em aula.
Monta fluxos arrastando blocos: mqtt in → json → gráfico. Com o pacote de dashboard da FlowFuse, vira painel com medidores e botões.
Gráficos de longo prazo e históricos. Em geral guarda os dados num banco de séries temporais (InfluxDB, por exemplo) alimentado pelo Node-RED ou pelo Telegraf.
Objetivo: uma página que mostra o sensor e o sinal Wi-Fi ao vivo, desenha o gráfico dos últimos 120 valores e tem botões Parar e Andar. Salve como painel.html e abra no navegador do PC que roda o broker (ou troque o endereço).
<!doctype html>
<html lang="pt-BR"><head><meta charset="utf-8"><title>Painel do robô</title>
<script src="https://unpkg.com/mqtt@5.10.1/dist/mqtt.min.js"></script></head>
<body style="font-family:sans-serif;background:#111;color:#eee;padding:16px">
<h1>Robô: <span id="estado">desconectado</span></h1>
<p>sensor: <b id="sensor">-</b> · Wi-Fi: <b id="rssi">-</b> dBm · parado: <b id="parado">-</b></p>
<canvas id="g" width="600" height="200" style="background:#000;max-width:100%"></canvas>
<p><button onclick="cmd('parar')">Parar</button> <button onclick="cmd('andar')">Andar</button></p>
<script>
const cli = mqtt.connect('ws://localhost:9001'); // em outro aparelho: ws://<ip_do_seu_pc>:9001
const pts = [], g = document.getElementById('g').getContext('2d');
const $ = id => document.getElementById(id);
cli.on('connect', () => { $('estado').textContent = 'conectado'; cli.subscribe('robo/telemetria'); });
cli.on('message', (topico, m) => {
const d = JSON.parse(m.toString());
$('sensor').textContent = d.sensor; $('rssi').textContent = d.rssi; $('parado').textContent = d.parado ? 'sim' : 'não';
pts.push(d.sensor); if (pts.length > 120) pts.shift();
g.clearRect(0, 0, 600, 200); g.strokeStyle = '#60a5fa'; g.lineWidth = 2; g.beginPath();
pts.forEach((v, i) => g.lineTo(i * 5, 200 - v / 4095 * 200)); g.stroke();
});
function cmd(c) { cli.publish('robo/comando', c); }
</script></body></html>
Como verificar
- ✓O título muda para "Robô: conectado" e a linha azul anda conforme você gira o sensor.
- ✓"Parar" acende o LED do ESP32 e o campo "parado" vira "sim".
- ✗Continua "desconectado"? Falta a porta 9001 com protocol websockets no robo.conf. Abra o console do navegador (F12) para ver o erro.
Passo a passo: o mesmo painel no Node-RED
Instale e abra
Com o Node.js instalado: npm install -g --unsafe-perm node-red, depois node-red. O editor abre em http://localhost:1880.
Adicione o dashboard
Menu → Manage palette → Install → procure @flowfuse/node-red-dashboard.
Ligue os blocos
mqtt in (servidor localhost:1883, tópico robo/telemetria) → json → change (msg.payload = msg.payload.sensor) → ui-chart. Um ui-button com payload "parar" ligado a um mqtt out em robo/comando. Clique em Deploy e abra /dashboard.
Por que aprender
Um painel transforma o robô numa caixa transparente para quem está em volta: o grupo inteiro vê o erro e a bateria enquanto ele anda. Em sala de aula, é o que permite discutir o comportamento do robô com dados, não com impressão.
Conceitos-chave
🐍 Analisar o log em Python (pandas + gráfico)
A planilha abre o CSV, mas trava com 50 000 linhas e não compara dois testes com facilidade. O pandas lê o arquivo numa tabela em uma linha de código, calcula médias e intervalos, e o matplotlib desenha os gráficos. O script abaixo serve para qualquer log deste curso: você diz o arquivo e as colunas.
Como ler: siga as caixas da esquerda para a direita. O passo do meio é o que mais ensina: a diferença entre um t_ms e o seguinte deveria ser sempre 20 ms; um pico ali revela onde o laço atrasou (cartão lento, Wi-Fi, um delay esquecido).
Objetivo: ler o CSV do tópico 3, imprimir um resumo e salvar um PNG com as curvas e o intervalo entre linhas. Prepare o ambiente uma vez: python3 -m venv .venv, source .venv/bin/activate (no Windows, .venv\Scripts\activate) e pip install pandas matplotlib.
# analisa_log.py: resumo e gráfico de um log CSV do robô
# Uso: python analisa_log.py log001.csv [coluna1 coluna2 ...]
import sys
import pandas as pd
import matplotlib.pyplot as plt
arquivo = sys.argv[1] if len(sys.argv) > 1 else "log001.csv"
df = pd.read_csv(arquivo, comment="#") # linhas com # são anotações
colunas = sys.argv[2:] or [c for c in df.columns if c != "t_ms"]
df["t_s"] = (df["t_ms"] - df["t_ms"].iloc[0]) / 1000 # segundos desde o início
dt = df["t_ms"].diff() # intervalo entre linhas
print(f"{len(df)} linhas, {df['t_s'].iloc[-1]:.1f} s gravados")
print(f"intervalo: médio {dt.mean():.1f} ms, maior {dt.max():.0f} ms")
print(df[colunas].describe().round(1))
fig, (a1, a2) = plt.subplots(2, 1, sharex=True, figsize=(10, 6))
df.plot(x="t_s", y=colunas, ax=a1, title=arquivo)
a1.set_ylabel("valor"); a1.grid(alpha=0.3)
a2.plot(df["t_s"], dt, color="tab:red")
a2.set_ylabel("intervalo (ms)"); a2.set_xlabel("tempo (s)"); a2.grid(alpha=0.3)
plt.tight_layout()
plt.savefig(arquivo.replace(".csv", ".png"), dpi=120)
plt.show()
Como verificar
- ✓python analisa_log.py log001.csv imprime algo como "1500 linhas, 30.0 s gravados" e "intervalo: médio 20.0 ms".
- ✓Aparece uma janela com dois gráficos e o arquivo log001.png fica salvo ao lado do CSV.
- ✗"externally-managed-environment" no pip? O ambiente virtual não está ativo: rode o activate de novo.
✓ Boas perguntas para o log
- ✓O intervalo ficou constante ou teve picos?
- ✓O erro oscila? De quanto em quanto tempo?
- ✓O PWM bateu em 255 (saturou) em algum trecho?
✗ Conclusões apressadas
- ✗Comparar dois testes com bateria em cargas diferentes.
- ✗Mudar três parâmetros de uma vez e não saber qual ajudou.
- ✗Olhar só a média: ela esconde o pico que derrubou o robô.
Por que aprender
Este script é a ferramenta de ajuste do projeto 2.6: grave um teste, rode o script, mude um parâmetro, grave de novo e compare os PNG. É também a porta para a Trilha 3, onde Python é a língua principal.
Conceitos-chave
✅ Resumo do módulo
Para ir além
- Serial Plotter na Arduino IDE 2 — formato e opções
- Biblioteca SD do Arduino-ESP32 — exemplos oficiais
- PubSubClient — API do cliente MQTT
- Mosquitto — broker, listeners e segurança
- Node-RED e FlowFuse Dashboard
- Grafana — painéis de séries temporais
- pandas: primeiros passos
Próximo módulo
2.6 · Projeto: seguidor de linha com PID, ajustado com a telemetria que você acabou de montar.