用 ESP32-S3 造一辆遥控小车:BLE/WiFi 双模、自动寻迹、路线记忆
基于 ESP32-S3 + L298N 电机驱动 + 4 个直流减速电机,打造一辆支持 BLE 蓝牙遥控、WiFi 仪表盘驾驶舱、五路红外自动寻迹、路线学习与回放的全功能遥控车。
最近用 ESP32-S3 造了一辆遥控小车,前前后后花了几个周末。从最初简单地在手机上打指令,一路迭代到支持 WiFi 仪表盘、自动循迹跑赛道、甚至能记住路线自己跑一圈。这篇文章分享一下做了什么、怎么做的、以及为什么这么做。
测试先行
嵌入式项目测试起来很麻烦——每次改代码都要编译、烧录、上板跑。但核心逻辑完全可以脱离硬件测试。
我把所有控制逻辑提取成纯 C++ 头文件:没有硬件依赖,没有 Arduino 库调用,只有输入→输出的纯函数。包括:
- 手动驾驶指令解析(
F/B/L/R→ 左右轮速) - 循迹 PD 控制器计算
- 路线记录与回放算法
- 仪表盘状态分类
然后用标准 C++ 编译器直接跑单元测试。像 routeBuildMapPoints 这种函数,输入几个 segment,输出一串路径点坐标,对不对一目了然:
RouteEvent events[] = {
{RouteSegmentKind::Straight, 3},
{RouteSegmentKind::Left, 1},
{RouteSegmentKind::Straight, 2},
{RouteSegmentKind::Right, 1},
{RouteSegmentKind::Straight, 1},
};
RouteMapPoint points[16];
int count = routeBuildMapPoints(events, 5, points, 16);
// points = [(0,0,Straight,N), (0,-1,Straight,N), (0,-2,Straight,N),
// (0,-2,Left,W), (1,-2,Straight,W), ...]
这种纯函数式的设计让测试变得简单,也迫使自己写出更模块化的代码。
文档
写了详细的接线说明和 App 使用指南,每个连接点、每条指令都有说明,算是为”下次再拿起这个项目”做点准备。
docs/WIRING.md 包含 28 个连接点的完整接线表、电源规划、上电顺序、以及常见踩坑点汇总。
docs/APP_GUIDE.md 涵盖了四种控制方式的操作步骤:WiFi 驾驶舱、nRF Connect App、Web Bluetooth 控制器、以及自定义 App 的协议说明。
代码在 github 上,欢迎拿去玩。
先上成品图——OLED 仪表盘亮起来的时候还是挺有成就感的:
为什么是 ESP32-S3
选芯片的时候纠结了一下。ESP32 是标准答案,但手头正好有一块 ESP32-S3 的开发板。踩了个坑才知道:ESP32-S3 没有经典蓝牙(SPP),只有 BLE(低功耗蓝牙)。
这意味着那些用蓝牙串口 App 连小车的教程全都不管用。没法直接用蓝牙当串口用,得走 BLE GATT 协议。
不过换个角度想,BLE 也有好处:不需要配对,不需要绑定,App 打开就能连。而且 ESP32-S3 的 WiFi 功能完全不受影响,可以同时跑 BLE 和 WiFi。
项目结构
├── firmware/
│ └── car_ble_remote/ # 主固件(Arduino C++)
│ ├── car_ble_remote.ino
│ ├── manual_drive_logic.h
│ ├── line_search_logic.h
│ ├── route_memory_logic.h
│ ├── dashboard_state.h
│ ├── oled_boot_animation.h
│ ├── wifi_control_config.h
│ └── wifi_control_page.h
├── controller/
│ ├── index.html # Web Bluetooth 遥控器
│ └── wifi.html # WiFi 驾驶舱页面(参考)
├── docs/
│ ├── WIRING.md # 接线文档(28 根线逐条说明)
│ └── APP_GUIDE.md # 控制方式指南
└── tests/ # 纯 C++ 单元测试(无硬件依赖)
├── manual_drive_logic_test.cpp
├── line_search_logic_test.cpp
├── route_memory_logic_test.cpp
└── ...
硬件选型和接线
小车用的是最经典的四驱底盘方案:
| 部件 | 型号 | 用途 |
|---|---|---|
| 主控 | ESP32-S3 | BLE + WiFi 通信,传感器处理,电机控制 |
| 电机驱动 | L298N | 双路 H 桥,驱动 4 个直流减速电机 |
| 电机 | 直流减速电机 × 4 | 前后轮独立驱动 |
| 循迹 | TCRT5000 五路红外模块 | 检测地面黑线,支持 PID 循迹 |
| 显示 | SH1106 1.3寸 OLED | 128×64,I2C 接口,显示驾驶仪表盘 |
| 指示灯 | WS2812 RGB LED | 命令反馈,连接状态指示 |
接线方面有几个容易被忽略的关键点:
- L298N 的 ENA/ENB 跳线帽必须拔掉,否则 PWM 调速无效
- L298N 的 GND 必须和 ESP32-S3 的 GND 共地,否则控制信号乱飞
- 电机电源和 ESP32 电源最好分离,否则电机启动瞬间的压降会让 ESP32 重启
- 4 个电机分为左右两组并联:左前+左后接 OUT1/OUT2,右前+右后接 OUT3/OUT4
详细的 28 根接线表在 docs/WIRING.md 里,每个连接点都有说明。
通信方式:不止 BLE
一开始只做了 BLE 控制,用 nRF Connect App 或者 Web Bluetooth 页面发送指令。但用了一段时间觉得还是不够方便——手机每次都要打开 App、连接、发送指令。
于是加上了 WiFi 热点模式。ESP32-S3 启动后自动创建一个 WiFi 热点(ESP32-S3-Car),连上后打开浏览器就能看到一个完整的驾驶舱页面:
- 虚拟摇杆,支持触摸拖拽和键盘 WASD
- 五路红外传感器的实时雷达图
- 路线时间轴,每个 segment 用颜色标注
- 速度、模式、连接状态等诊断信息
- 醒目的红色紧急停止按钮
WiFi 和 BLE 可以同时工作,互不干扰。平时用手机连 WiFi 热点开驾驶舱,BLE 作为低延迟备选通道。
Web Bluetooth 那个 HTML 页面可以直接在 Chrome/Edge 里打开,不需要装任何 App,点一下连接按钮就能遥控。完整的 JavaScript 实现不到 300 行。
自动循迹:五路红外 + PD 控制器
TCRT5000 是红外反射式传感器,检测到黑线时输出低电平。五个传感器一字排开,覆盖约 8cm 宽的检测范围。
循迹的核心逻辑在 line_search_logic.h 里,思路不复杂:
- 读取五路传感器的二进制掩码
- 计算加权位置误差(左边传感器权重负值,右边正值)
- 用 PD 控制器(P=42,D=24)计算左右轮速差
- 根据误差大小和活跃传感器数量动态限制速度
lineTrackingPositionError: 加权位置误差
lineTrackingMotorCommand: PD 控制器输出左右轮速
比较有意思的是丢线处理。当小车冲出赛道(所有传感器都检测不到黑线)时,会启动一个多阶段搜索算法:
- 回退:先倒退一段,回到丢线前的位置
- 记忆方向:根据丢线前最后一次有效误差,判断线偏向哪一侧
- 旋转搜索:向记忆方向旋转,同时逐步增加转速
- 交替搜索:如果转了几步还没找到,自动交替方向防止卡死
搜索过程中还有一个超时机制:超过 3.2 秒还没找到线就停车,避免原地打转到电池耗尽。
宽线处理也单独做了逻辑。当四个或更多传感器同时检测到黑线(说明到了终点粗线或者 T 型路口),会切换到宽线搜索模式,速度和搜索步长都降下来。
路线记忆:让小车记住赛道
这是我觉得最有意思的功能。按 E 键开始学习模式,手动驾驶小车跑一圈,固件会记录途中每个路段的类型和持续时间。再按 P 键,小车就能自己跑一遍。
路线存储在 ESP32 的 NVS(非易失性存储)里,掉电不丢。
路段分类
每 120ms 采样一次传感器状态,分类为:
- Straight:直线路段
- Left/Right:左弯/右弯
- Wide:宽线区域(十字路口、粗线终点)
- Lost:丢线(理论上是路段,实际很少出现)
运行长度编码
连续相同类型的路段会合并成一条记录,带上持续时间(tick 数)。最多存储 96 条 segment,对于普通赛道绰绰有余。
回放与纠错
回放时不是死板地重放电机指令,而是结合实时传感器数据进行修正:
- 根据时间进度,计算当前应该在第几个 segment
- 对比期望路段类型和实际传感器观测到的类型
- 如果有偏差,降低速度进入安全模式
- 用 PD 循迹算法实时修正路线,而非盲跑
这种”期望 + 实时修正”的方式让路线记忆变得很鲁棒——地面摩擦变化、电池电压变化都不会让小车跑偏。
地图重建
固件里还内置了一个简单的路径重建算法:根据 segment 序列(Straight 前进,Left/Right 转向),在地图上模拟行走路径。虽然在 128×64 的 OLED 上看地图不太现实,但这个结构为后续在 WiFi 驾驶舱里显示路线图做好了准备。
OLED 仪表盘
SH1106 是 128×64 单色 OLED,I2C 接口,只用两根线(SDA、SCL)。要做的事情不少,但屏幕小,得精打细算。
仪表盘布局分几个区域:
- 顶部:BLE 连接状态 + 当前速度值
- 中部:驾驶模式(REMOTE/AUTO/LEARN/MAP)+ 当前指令 + 方向箭头
- 底部:五路传感器迷你条 + 运行时间或路线进度
方向箭头是用像素画出来的:前进是向上的箭头,后退向下,左转/右转分别对应左右箭头。停止状态显示一个微笑表情,算是给用户的一点小趣味。
开机时有一段 8 帧的启动动画,逐帧显示传感器初始化进度条和 “READY” 状态。
安全设计
遥控车跑起来还是挺快的(PWM 255 全速时目测约 1.5m/s),所以安全设计从一开始就考虑在内:
- 断连自动停车:BLE 客户端断开后,小车立即停车
- 500ms 超时保护:超过 500ms 未收到新指令,自动停车。防止客户端卡死导致失控
- 速度约束:所有 PWM 值限制在 0-255 范围,初始化速度不会超过 200
- 紧急停止按钮:WiFi 驾驶舱页面上有一个醒目的红色按钮,任何时候点击立即停车
- RGB LED 指示:未连接客户端时 LED 闪烁,连接后根据指令显示不同颜色
总结
这个项目从”让小车动起来”开始,到”让它记住路线自己跑”结束,中间陆陆续续加了 WiFi 驾驶舱、OLED 仪表盘、自动循迹、路线记忆这些功能。
回头来看,最值得分享的几个设计决策:
- 用 BLE 而不是经典蓝牙:虽然踩了 S3 没有 SPP 的坑,但 BLE 带来的是更好的跨平台兼容性和不需要配对的体验
- 纯函数式核心逻辑:把控制算法提取成无硬件依赖的纯函数,大大降低了测试和调试的复杂度
- 期望+修正的回放机制:路线记忆不死板记录电机指令,而是记录路段语义 + 实时传感器修正,适应性强很多
原文链接: 用 ESP32-S3 造一辆遥控小车:BLE/WiFi 双模、自动寻迹、路线记忆 ,转载请注明来源!
– EOF –