教程 · 中级 · 1.5 小时
用 ESP32 网页服务器在浏览器里遥控机器人
把 ESP32 变成一个 Wi-Fi 热点,由它自己提供控制页面,这样任何一部手机都能在浏览器里遥控你的机器人,无需安装任何 App。
引言
HC-05需要一个串口终端应用,还要跳一遍配对的舞。ESP32能做更好的事:**自己就是网络,自己就提供控制页面。**你把手机连到机器人的 Wi-Fi 上,打开浏览器,就可以开车。什么都不用装,而且任何带浏览器的设备都能用。
ESP32 片上就带 Wi-Fi 和蓝牙,双核 240 MHz,RAM 足够把一个网页放在闪存里再提供出来。整个技巧就是这个。
热点还是现有网络
两种模式,而选择比看上去重要得多。
热点(AP)——ESP32 自己建一个 Wi-Fi 网络,手机加入它。
- 在任何地方都能用,不需要路由器。机器人是自给自足的。
- 开车时手机会失去上网能力,而且有些手机会悄悄切回移动数据、把你踢下线。
- 地址永远相同,通常是
192.168.4.1。
站点(STA)——ESP32 加入你现有的 Wi-Fi。
- 手机保持上网。
- 机器人的 IP 由路由器分配,每次启动都可能变。
- 出了那个网络的范围就没用,这一条就足以否掉演示。
对于一台要随身带走的机器人,AP 模式几乎总是对的:
#include <WiFi.h>
#include <WebServer.h>
WebServer server(80);
void setup() {
WiFi.softAP("RobotCar", "drive1234"); // password must be 8+ characters
Serial.println(WiFi.softAPIP()); // 192.168.4.1
server.on("/", handleRoot);
server.on("/cmd", handleCommand);
server.begin();
}
void loop() {
server.handleClient();
applyDeadman(); // see below — this is not optional
}
把控制页面提供出来
页面以原始字符串的形式住在闪存里。保持精简——每一个字节都要由一颗单片机来提供——并且把控件做得对触屏友好,因为用手机开车就是全部的意义所在。
const char PAGE[] PROGMEM = R"(
<!doctype html><meta name=viewport content="width=device-width,initial-scale=1">
<style>
body{font-family:system-ui;margin:0;display:grid;place-items:center;height:100vh}
button{width:90px;height:90px;font-size:24px;margin:4px;touch-action:manipulation}
</style>
<div>
<div style="text-align:center"><button data-c="F">▲</button></div>
<div><button data-c="L">◀</button><button data-c="S">■</button><button data-c="R">▶</button></div>
<div style="text-align:center"><button data-c="B">▼</button></div>
</div>
<script>
let held = null;
const send = c => fetch('/cmd?c=' + c);
for (const b of document.querySelectorAll('button')) {
// Repeat while held, so releasing and losing signal look the same to the robot.
b.onpointerdown = e => { held = b.dataset.c; send(held);
b.setPointerCapture(e.pointerId); };
b.onpointerup = b.onpointercancel = () => { held = null; send('S'); };
}
setInterval(() => held && send(held), 100);
</script>
)";
void handleRoot() { server.send_P(200, "text/html", PAGE); }
按钮上的 touch-action: manipulation 很关键:没有它,手机浏览器会等上约 300 ms 来判断你是不是想双击,于是机器人感觉像是坏了。
松手超时,再说一次
蓝牙教程里关于链路丢失说的一切在这里都适用,而且更严重——Wi-Fi 的覆盖范围是突然断掉,而不是优雅地断掉。
unsigned long lastCommandAt = 0;
void handleCommand() {
char c = server.arg("c")[0];
drive(c);
lastCommandAt = millis();
server.send(200, "text/plain", "ok");
}
void applyDeadman() {
if (millis() - lastCommandAt > 400) stop();
}
浏览器每 100 ms 发送一次当前按住的指令,所以 400 ms 的超时会容忍三个丢掉的请求才停车。这个比例才是要调的东西:太紧,弱链路下机器人会一顿一顿;太松,你已经失去控制权它还在开。
什么时候该换到 WebSocket
上面那套 fetch 做法每条指令都新开一条 HTTP 连接。它简单,在浏览器的网络面板里也容易调试,每秒十条指令完全够用。
但当你需要回传遥测时,它就不够用了。用更多 HTTP 请求去轮询传感器数值会把流量翻倍,还给两个方向都加上延迟。WebSocket 保持一条连接常开,任何一方随时都能说话:
#include <WebSocketsServer.h>
WebSocketsServer ws(81);
void onWsEvent(uint8_t num, WStype_t type, uint8_t* payload, size_t len) {
if (type == WStype_TEXT) { drive(payload[0]); lastCommandAt = millis(); }
if (type == WStype_DISCONNECTED) stop(); // the link closing IS a stop command
}
最后一行才是真正的那次升级。用 HTTP 时机器人只能从“没有动静”去推断连接丢了;WebSocket 会在连接关闭的那一刻告诉你,于是机器人立刻停下,而不用等超时。
先用 HTTP。等你希望机器人能回话时,再换到 WebSocket。
供电
ESP32 是一颗 3.3 V 器件,无线电发射时电流是尖锐的脉冲——峰值远超 200 mA。由此带来两个后果:
**不要从 Arduino 的 3.3 V 引脚给它供电。**那个稳压器供不上发射峰值,症状是 Wi-Fi 一忙板子就重启,看起来像代码缺陷而其实不是。
**它的 GPIO 引脚不耐 5 V。**任何从 5 V 逻辑把信号送进 ESP32 的东西都需要分压器或电平转换器,和 HC-05 的 RX 线一样。
从电池包经它自己的稳压器供电,并且把电机电源分开走。
开跑前先验证
- **把页面提供出来。**手机连上
RobotCar,打开http://192.168.4.1,确认按键出现。 - **观察指令。**通过 USB 把每一条收到的指令打印出来。按住一个按键,确认重复指令以大约每秒 10 次到达。
- **掐断链路。**按住前进,然后把手机的 Wi-Fi 关掉。机器人必须在超时之内停下。
然后把它装进一台机器人里:手机遥控车项目把整件事都做了,无线电任选,底盘也可以自己开。
探索关联图谱
本教程所属的搭建
包含本教程的项目和学习路线。
延伸阅读