Conteúdo detalhado
📶 Wi-Fi: modo estação e ponto de acesso
O Wi-Fi do ESP32 funciona de dois jeitos. No modo estação (STA) ele entra na rede da sua casa ou escola, como um celular qualquer, e recebe um endereço do roteador. No modo ponto de acesso (AP) ele cria a própria rede: o celular se conecta direto no robô, sem roteador. Para o robô que vai para a rua, a feira de ciências ou a quadra, o AP é o caminho mais previsível.
🌱 Novo aqui?
O SSID é o nome da rede que aparece no celular. O endereço IP é o "número da casa" de cada aparelho na rede (ex.: 192.168.4.1). Um roteador distribui esses endereços. No modo AP, o próprio ESP32 faz esse papel e fica sempre em 192.168.4.1.
Como ler: à esquerda, tudo passa pelo roteador; se ele estiver longe ou a rede pedir login, o robô fica sem controle. À direita, o ESP32 é a própria rede e o celular fala com ele direto. O sketch abaixo tenta o primeiro e cai no segundo se não conseguir.
Objetivo: tentar entrar na sua rede por 10 segundos; se falhar, criar a rede própria do robô. O Serial Monitor mostra qual modo venceu e o endereço para abrir no navegador.
// Wi-Fi: tenta modo estação, cai para ponto de acesso (pacote esp32 3.x)
#include <WiFi.h>
const char* REDE_SSID = "<seu_wifi>"; // troque pelo nome da sua rede
const char* REDE_SENHA = "<sua_senha>";
const char* AP_SSID = "RoverINEMA";
const char* AP_SENHA = "troque1234"; // mínimo de 8 caracteres
void setup() {
Serial.begin(115200);
WiFi.mode(WIFI_STA);
WiFi.begin(REDE_SSID, REDE_SENHA);
Serial.print("Conectando");
unsigned long inicio = millis();
while (WiFi.status() != WL_CONNECTED && millis() - inicio < 10000) {
delay(250);
Serial.print(".");
}
Serial.println();
if (WiFi.status() == WL_CONNECTED) {
Serial.print("Modo ESTACAO. Abra http://");
Serial.println(WiFi.localIP());
} else {
WiFi.disconnect(true);
WiFi.mode(WIFI_AP);
WiFi.softAP(AP_SSID, AP_SENHA);
Serial.print("Modo PONTO DE ACESSO. Conecte em RoverINEMA e abra http://");
Serial.println(WiFi.softAPIP());
}
}
void loop() { // a cada 5 s: quantos aparelhos estão na rede do robô
if (WiFi.getMode() == WIFI_AP) { Serial.print("Clientes: "); Serial.println(WiFi.softAPgetStationNum()); }
delay(5000);
}
Como verificar
- ✓Com SSID e senha certos: "Modo ESTACAO. Abra http://192.168.x.y".
- ✓Com a senha errada de propósito: depois de 10 s, a rede "RoverINEMA" aparece no celular e o contador de clientes sobe para 1 quando você entra.
- ✗Nunca conecta? O ESP32 só enxerga redes de 2,4 GHz; redes só de 5 GHz não aparecem para ele.
Por que aprender
Todo controle remoto deste módulo, e a telemetria do 2.5, começa com o ESP32 na rede. Escolher o modo certo evita a cena clássica da feira: o robô funcionava em casa e não conecta no Wi-Fi da escola, que pede login no navegador.
Conceitos-chave
🌐 Servidor web no ESP32: uma página com botões
A biblioteca WebServer, que já vem no pacote esp32 3.x, transforma a placa num pequeno site. Você registra rotas ("quem pedir /cmd?d=F, roda esta função") e o navegador do celular vira o controle remoto. Não precisa instalar app nenhum.
🌱 Novo aqui?
HTTP é a conversa do navegador com um site: o navegador faz um pedido (GET /cmd?d=F) e o servidor manda uma resposta (texto ou página). Cada clique abre uma conversa nova, que leva algumas dezenas de milissegundos. Para botões, basta; para um joystick, veremos no tópico 3 que não.
Objetivo: servir uma página com cinco botões. Cada toque chama uma rota, o ESP32 escreve o comando no Serial Monitor e acende o LED do GPIO 2 enquanto o robô "anda". Teste primeiro sem motores; no tópico 6 você troca o LED pelos motores.
// Página com botões servida pelo ESP32 (WebServer do pacote esp32 3.x)
#include <WiFi.h>
#include <WebServer.h>
WebServer server(80);
const int PINO_LED = 2;
const char PAGINA[] = R"html(
<!DOCTYPE html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
<style>body{font-family:sans-serif;text-align:center;background:#111;color:#eee}
button{width:90px;height:60px;margin:6px;font-size:18px;border-radius:10px}</style></head><body><h2>Rover INEMA</h2>
<button onclick="c('F')">Frente</button><br><button onclick="c('L')">Esq.</button><button onclick="c('S')">Parar</button>
<button onclick="c('R')">Dir.</button><br><button onclick="c('B')">Ré</button><p id="st">pronto</p>
<script>function c(d){fetch('/cmd?d='+d).then(r=>r.text()).then(t=>document.getElementById('st').textContent=t)}</script>
</body></html>)html";
void paginaInicial() { server.send(200, "text/html", PAGINA); }
void comando() {
String d = server.arg("d"); // F, B, L, R ou S
digitalWrite(PINO_LED, d != "S"); // LED aceso = "em movimento"
Serial.print("Comando: "); Serial.println(d);
server.send(200, "text/plain", "ok: " + d);
}
void setup() {
Serial.begin(115200);
pinMode(PINO_LED, OUTPUT);
WiFi.softAP("RoverINEMA", "troque1234");
server.on("/", paginaInicial);
server.on("/cmd", comando);
server.begin();
Serial.print("Conecte em RoverINEMA e abra http://");
Serial.println(WiFi.softAPIP());
}
void loop() {
server.handleClient(); // atende os pedidos que chegaram
}
Como verificar
- ✓No celular, entre na rede RoverINEMA e abra http://192.168.4.1: aparecem os cinco botões.
- ✓Tocar em "Frente" escreve "Comando: F" no Serial Monitor, acende o LED e a página mostra "ok: F".
- ✗A página abre mas os botões não respondem? O celular pode ter saído da rede do robô por "falta de internet"; mande ele manter a conexão.
✓ Faça
- ✓Chamar server.handleClient() em todo giro do loop.
- ✓Guardar a página numa constante e responder rápido.
- ✓Usar um botão "Parar" grande e sempre visível.
✗ Evite
- ✗delay() longo no loop: a página trava até ele acabar.
- ✗Carregar imagens e bibliotecas da internet: no modo AP não há internet.
- ✗Comando "andar" sem comando "parar" correspondente.
Por que aprender
Uma página servida pelo próprio robô funciona em qualquer celular, sem loja de apps. O mesmo servidor depois mostra telemetria, ajusta ganhos do PID do 2.3 e liga e desliga modos, sem regravar o ESP32.
Conceitos-chave
⚡ WebSocket: controle sem atraso
Com HTTP, cada comando abre e fecha uma conversa. Um joystick manda a posição dez vezes por segundo; abrir dez conversas por segundo gera fila, atraso e comandos chegando fora de ordem. O WebSocket abre um canal só, que fica aberto, e as mensagens passam por ele nos dois sentidos com poucos milissegundos de atraso.
Como ler: na linha de cima, três comandos ocupam a linha inteira, cada um pagando o custo de abrir e fechar. Na de baixo, o mesmo tempo carrega dez comandos. Para dirigir, o que importa é o tempo entre mexer o dedo e a roda responder.
| Situação | Use | Por quê |
|---|---|---|
| Botões, ajustes, ligar modo | HTTP | poucos pedidos, simples de testar |
| Joystick, volante, telemetria contínua | WebSocket | fluxo constante, baixo atraso, mão dupla |
| Sem rede, só o celular perto | BLE | baixo consumo, sem roteador (tópico 4) |
💡 Biblioteca extra
O pacote esp32 traz WiFi e WebServer, mas não um servidor WebSocket. Instale pelo Gerenciador de Bibliotecas da Arduino IDE a biblioteca WebSockets, de Markus Sattler (links2004), e use a versão mais recente indicada no repositório. Ela roda na porta 81, ao lado do servidor web da porta 80.
O protocolo que você vai inventar é mínimo: o navegador manda o texto x,y, com cada número de -100 a 100 (ex.: 0,80 = frente a 80%). O ESP32 converte isso em velocidade de cada roda. O código completo, com página, WebSocket e motores, está no tópico 6.
Por que aprender
Robô dirigido com atraso de meio segundo bate na parede antes de obedecer. O WebSocket também é a base do painel ao vivo do 2.5: o mesmo canal que leva o comando traz de volta a velocidade e o ângulo do robô.
Conceitos-chave
🔵 BLE: controlar pelo app do celular
O BLE (Bluetooth Low Energy) liga o celular ao robô sem rede nenhuma, gastando pouca bateria. Em vez de páginas, ele organiza os dados em serviços e características: o robô anuncia "tenho um serviço de comandos", o app se conecta e escreve um valor numa característica. O pacote esp32 3.x já traz a biblioteca BLE; a alternativa NimBLE-Arduino, mais leve, é opcional.
🌱 Novo aqui?
Pense numa característica como uma caixinha com nome (um UUID, código longo e único) onde o app pode escrever ou ler. Um serviço é a gaveta que agrupa caixinhas. Anunciar (advertising) é o robô gritando o próprio nome para os celulares em volta o encontrarem.
Como ler: a caixa tracejada é o serviço; dentro dele, a característica com brilho é o único lugar onde o app escreve. A letra que chega ali vira comando de motor. Os UUIDs do código abaixo seguem o padrão "UART sobre BLE" que muitos apps genéricos já reconhecem.
Objetivo: receber letras pelo BLE (F, B, L, R, S), mostrar no Serial Monitor e acender o LED. Use um app genérico gratuito como nRF Connect ou LightBlue para escrever na característica.
// Comandos por BLE com a biblioteca BLE embutida no pacote esp32 3.x
#include <BLEDevice.h>
#include <BLEServer.h>
#define SERVICO_UUID "6E400001-B5A3-F393-E0A9-E50E24DCCA9E"
#define RX_UUID "6E400002-B5A3-F393-E0A9-E50E24DCCA9E"
const int PINO_LED = 2;
volatile char ultimoCmd = 'S';
class ConexaoCb : public BLEServerCallbacks {
void onConnect(BLEServer* s) { Serial.println("Celular conectado"); }
void onDisconnect(BLEServer* s) {
ultimoCmd = 'S'; // perdeu o celular: para
Serial.println("Desconectou: PARADO. Anunciando de novo.");
BLEDevice::startAdvertising();
}
};
class RxCb : public BLECharacteristicCallbacks {
void onWrite(BLECharacteristic* c) {
String v = c->getValue(); // na 3.x o valor chega como String
if (v.length() == 0) return;
char cmd = toupper(v[0]);
if (strchr("FBLRS", cmd)) { ultimoCmd = cmd; Serial.print("Comando BLE: "); Serial.println(cmd); }
}
};
void setup() {
Serial.begin(115200);
pinMode(PINO_LED, OUTPUT);
BLEDevice::init("RoverINEMA");
BLEServer* srv = BLEDevice::createServer();
srv->setCallbacks(new ConexaoCb());
BLEService* servico = srv->createService(SERVICO_UUID);
BLECharacteristic* rx = servico->createCharacteristic(RX_UUID,
BLECharacteristic::PROPERTY_WRITE | BLECharacteristic::PROPERTY_WRITE_NR);
rx->setCallbacks(new RxCb());
servico->start();
BLEDevice::getAdvertising()->addServiceUUID(SERVICO_UUID);
BLEDevice::startAdvertising();
Serial.println("BLE pronto: procure RoverINEMA no app");
}
void loop() {
digitalWrite(PINO_LED, ultimoCmd != 'S'); // aqui entram os motores (tópico 6)
delay(20);
}
Como verificar
- ✓No app, "RoverINEMA" aparece na busca. Conecte: o Serial Monitor mostra "Celular conectado".
- ✓Escreva o texto F na característica 6E400002: aparece "Comando BLE: F" e o LED acende. S apaga.
- ✓Desligue o Bluetooth do celular com o LED aceso: aparece "Desconectou: PARADO" e o LED apaga.
💡 Wi-Fi e BLE no mesmo sketch
Cada um sozinho cabe na memória padrão. Juntos, o programa costuma passar do espaço de aplicação: em Ferramentas → Partition Scheme, escolha "Huge APP". O ESP32 divide um rádio só entre os dois, então o alcance e o atraso pioram um pouco. Para o rover, escolha um dos dois.
Por que aprender
BLE funciona onde não há Wi-Fi, gasta menos e é o mesmo padrão de controles de jogo, relógios e sensores de academia. Entender serviço e característica também ajuda a ler sensores BLE prontos no robô, sem fio.
Conceitos-chave
🛡️ Segurança: senha e parada se perder a conexão
Sem fio, duas coisas dão errado: alguém que não devia entra no controle, e o sinal cai no meio de um comando "frente". No primeiro caso, uma senha resolve. No segundo, a resposta é o watchdog de comando: o robô só se move enquanto recebe comandos novos. Se passar um tempo curto (300 ms, por exemplo) sem nada chegar, ele para sozinho, mesmo que o último comando fosse "frente".
🌱 Novo aqui?
Watchdog ("cão de guarda") é um temporizador que precisa ser "alimentado" de tempos em tempos. Se ninguém alimentar, ele age. Aqui, cada comando que chega alimenta o cão; o silêncio faz o robô parar. Isso se chama projetar para falha segura: o estado sem informação é o estado parado.
Como ler: cada traço ciano é um comando chegando. Enquanto eles chegam, o motor (linha azul) segue ligado. Quando param, a régua âmbar conta 300 ms e o motor cai para zero. Sem o watchdog, a linha azul ficaria no alto até a bateria acabar ou o robô bater.
✓ Robô sem fio seguro
- ✓Senha no AP (8 caracteres ou mais) e troca da senha de exemplo.
- ✓Watchdog de comando: parar após 200 a 500 ms de silêncio.
- ✓Parar ao desconectar (WebSocket ou BLE).
- ✓Velocidade máxima limitada no ESP32, não só na página.
✗ Receita de acidente
- ✗Rede aberta, sem senha, numa feira cheia de celulares.
- ✗"Frente" que vale até chegar um "parar".
- ✗Confiar que a página do celular vai mandar o "parar".
- ✗Testar o primeiro controle com o robô no chão perto da escada.
Teste do watchdog em 4 passos
Rodas no ar
Apoie o rover num estojo ou caixa, com as rodas livres.
Segure "frente"
Com o joystick do tópico 6, mantenha o dedo para cima: as rodas giram.
Corte o sinal
Ative o modo avião do celular sem soltar o dedo.
Confira
As rodas param em menos de meio segundo e o Serial Monitor mostra "watchdog: sem comando, PARADO". Só então leve o robô ao chão.
Por que aprender
Robôs de verdade, de aspirador a empilhadeira autônoma, param quando perdem o controle. Você vai usar a mesma ideia no 3.5, quando o Raspberry Pi mandar comandos ao ESP32, e na Trilha 4, com o tópico /cmd_vel do ROS 2.
Conceitos-chave
🕹️ Joystick no navegador para o rover
Agora tudo junto: o ESP32 cria a rede, serve uma página com um joystick redondo, recebe a posição do dedo pelo WebSocket e converte em velocidade das duas rodas. A conversão é a mistura diferencial: roda esquerda = y + x, roda direita = y − x. Dedo para cima, as duas giram iguais; dedo para a direita, a esquerda acelera e a direita freia, e o robô vira.
Como ler: o ponto azul é o dedo, a 50 para a direita e 40 para cima. As duas contas à direita viram a velocidade de cada roda, em porcentagem. Valores negativos giram a roda para trás; acima de 100 o código corta em 100.
Objetivo: dirigir o rover da Trilha 1 pelo celular, com joystick, WebSocket, parada ao desconectar e watchdog de 300 ms. Ligação no TB6612FNG: PWMA 25, AIN1 26, AIN2 27, PWMB 14, BIN1 16, BIN2 17, STBY ligado no 3V3 (o mesmo mapa de toda a Trilha 2; troque se o seu rover usa outros pinos). Precisa da biblioteca WebSockets (tópico 3).
// Rover com joystick no navegador: WebServer + WebSocket + watchdog (pacote esp32 3.x)
#include <WiFi.h>
#include <WebServer.h>
#include <WebSocketsServer.h> // biblioteca "WebSockets" (Markus Sattler)
const int PWMA = 25, AIN1 = 26, AIN2 = 27; // motor esquerdo
const int PWMB = 14, BIN1 = 16, BIN2 = 17; // motor direito
const int VEL_MAX = 200; // teto de PWM (0 a 255): limite de segurança
const unsigned long TIMEOUT_MS = 300; // watchdog de comando
WebServer server(80);
WebSocketsServer ws(81);
unsigned long ultimoComando = 0;
bool andando = false;
const char PAGINA[] = R"html(
<!DOCTYPE html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
<style>body{font-family:sans-serif;text-align:center;background:#111;color:#eee}
#pad{width:260px;height:260px;margin:20px auto;border-radius:50%;background:#222;position:relative;touch-action:none}#dot{width:70px;height:70px;border-radius:50%;background:#3b82f6;position:absolute;left:95px;top:95px}</style>
</head><body><h2>Rover INEMA</h2><div id="pad"><div id="dot"></div></div><p id="st">conectando...</p>
<script>
var pad=document.getElementById('pad'),dot=document.getElementById('dot'),st=document.getElementById('st');
var ws=new WebSocket('ws://'+location.hostname+':81/'),x=0,y=0,ativo=false;
ws.onopen=function(){st.textContent='conectado'};ws.onclose=function(){st.textContent='desconectado'};function mover(e){var r=pad.getBoundingClientRect(),dx=e.clientX-r.left-130,dy=e.clientY-r.top-130,d=Math.hypot(dx,dy);
if(d>95){dx=dx*95/d;dy=dy*95/d}dot.style.left=(95+dx)+'px';dot.style.top=(95+dy)+'px';
x=Math.round(dx/95*100);y=Math.round(-dy/95*100)}
function soltar(){ativo=false;x=0;y=0;dot.style.left='95px';dot.style.top='95px'}
pad.onpointerdown=function(e){ativo=true;pad.setPointerCapture(e.pointerId);mover(e)};
pad.onpointermove=function(e){if(ativo)mover(e)};pad.onpointerup=soltar;pad.onpointercancel=soltar;
setInterval(function(){if(ws.readyState===1)ws.send(x+','+y)},100);
</script></body></html>)html";
void motor(int pwm, int in1, int in2, int v) { // v de -255 a 255
v = constrain(v, -VEL_MAX, VEL_MAX);
digitalWrite(in1, v > 0);
digitalWrite(in2, v < 0);
ledcWrite(pwm, abs(v)); // API 3.x: ledcWrite recebe o PINO
}
void parar() { motor(PWMA, AIN1, AIN2, 0); motor(PWMB, BIN1, BIN2, 0); andando = false; }
void aoReceber(uint8_t num, WStype_t tipo, uint8_t* dados, size_t tam) {
if (tipo == WStype_TEXT) {
char buf[24];
size_t n = tam < sizeof(buf) - 1 ? tam : sizeof(buf) - 1;
memcpy(buf, dados, n); buf[n] = 0;
int x = 0, y = 0;
if (sscanf(buf, "%d,%d", &x, &y) == 2) {
motor(PWMA, AIN1, AIN2, (y + x) * 255 / 100); // mistura diferencial
motor(PWMB, BIN1, BIN2, (y - x) * 255 / 100);
ultimoComando = millis(); // alimenta o watchdog
andando = (x != 0 || y != 0);
}
} else if (tipo == WStype_DISCONNECTED) {
parar();
Serial.printf("Cliente %u saiu: PARADO\n", num);
} else if (tipo == WStype_CONNECTED) {
Serial.printf("Cliente %u conectou\n", num);
}
}
void setup() {
Serial.begin(115200);
int saidas[] = {AIN1, AIN2, BIN1, BIN2};
for (int p : saidas) pinMode(p, OUTPUT);
ledcAttach(PWMA, 20000, 8); // 20 kHz, 8 bits (0 a 255)
ledcAttach(PWMB, 20000, 8);
parar();
WiFi.softAP("RoverINEMA", "troque1234");
server.on("/", []() { server.send(200, "text/html", PAGINA); });
server.begin();
ws.begin();
ws.onEvent(aoReceber);
Serial.print("Conecte em RoverINEMA e abra http://");
Serial.println(WiFi.softAPIP());
}
void loop() {
server.handleClient();
ws.loop();
if (andando && millis() - ultimoComando > TIMEOUT_MS) {
parar();
Serial.println("watchdog: sem comando, PARADO");
}
}
Como verificar
- ✓Com as rodas no ar, abra http://192.168.4.1: a página diz "conectado" e o Serial Monitor, "Cliente 0 conectou".
- ✓Dedo para cima: as duas rodas para a frente. Dedo para a direita: as rodas giram em sentidos opostos. Soltar: param.
- ✓Fechar a aba com o dedo para cima: "Cliente 0 saiu: PARADO". Modo avião: "watchdog: sem comando, PARADO".
- ✗Uma roda gira ao contrário? Inverta os dois fios daquele motor no driver, não o código.
🌱 Novo aqui?
O texto entre R"html( e )html" é uma string crua do C++: tudo dentro dela, aspas e quebras de linha incluídas, vira texto sem precisar de barra invertida. É o jeito mais limpo de guardar uma página HTML inteira dentro do sketch.
Por que aprender
Com o rover dirigível pelo celular, você testa cada módulo seguinte com o robô no chão e com as mãos livres: leva até a pista, posiciona, solta. É também a base do modo "manual" que você vai querer em qualquer robô autônomo, para tirá-lo de um canto quando a autonomia errar.
Conceitos-chave
✅ Resumo do módulo
Para ir além
- Wi-Fi API (arduino-esp32) — modos STA e AP, eventos de conexão
- BLE API (arduino-esp32) — a biblioteca BLE embutida
- Guia de migração 2.x → 3.0 — o que mudou em Wi-Fi e BLE
- Biblioteca WebSockets (Links2004) — exemplos e versão atual
- NimBLE-Arduino — alternativa BLE mais leve (opcional)
Próximo módulo
2.2 · IMU e encoders: o robô passa a saber quanto girou, quanto andou e onde está.