简介:面向ESP32开发者与智能小车入门者,这份压缩包提供从零搭建智能小车的完整源码与配套文档,解决硬件选型、电路设计、固件编程到无线控制的全流程问题。内容以构建指南为主线,覆盖ESP32双核、Wi-Fi/蓝牙特性,包含模块匹配、PCB布局、焊接组装及常见故障应对;编程部分讲解Arduino IDE与ESP-IDF环境,并提供Wi-Fi、蓝牙、TCP/IP通信及传感器数据处理的实现思路。压缩包仅11KB,共4个文件,以Markdown说明文档和网页版资源为主,配合编辑器配置与Git忽略规则,便于初学者直接阅读和后续代码管理。已有101人学习。通过该资源可掌握从电路搭建到高级应用的关键步骤,并获得图像处理、电源管理、固件更新等进阶优化方向,是快速上手ESP32智能小车项目的高性价比参考资料。 玩 ESP32 智能小车,最痛苦的不是买零件,也不是焊线,而是资料东拼西凑,代码到处抄,最后编译报错一脸懵。我前前后后做了三版小车,从最开始的 51 单片机到后来的 STM32,最后稳定在 ESP32 方案,原因很简单:Wi-Fi 和蓝牙都在一颗芯片上,省掉一堆外设,而且用 Arduino 写起来特别顺手。这篇就把整个构建过程、踩过的坑、以及我最终在用的源码架构一次性讲清楚,你照着做,大概率能少走一两个月的弯路。
这个项目最终完成的小车具备三种玩法:手机蓝牙遥控、超声波自动避障、红外循迹行驶。整机成本控制在 150 元以内,代码全部开源,工程结构清晰,拆解下来单个模块的代码量都不大,非常适合嵌入式入门玩家、电子设计竞赛备赛学生,以及想拿 ROS 2 做硬件底层的开发者当作起步平台。
1. 项目整体设计与思路拆解
1.1 为什么选 ESP32 而不是 STM32 或 51 单片机
很多人一上来就问:做智能小车不是应该用 STM32 吗?竞赛里清一色都是它。这话没错,但要看场景。STM32F103ZET6 这类芯片的优势在于外设资源丰富、生态成熟,可如果你只是想快速把小车跑起来,而不是深入研究寄存器操作,那 ESP32 的性价比就体现出来了。
先看芯片本身的差异。ESP32 集成了 2.4GHz Wi-Fi 和蓝牙双模,这意味着遥控不再需要外接蓝牙模块或无线收发模块,板载解决。而我早期用 STM32 做小车时,光是一个 HC-05 蓝牙模块就要二十多块钱,还得单独接稳压电路。ESP32 自带 320KB SRAM 和 4MB Flash,跑一个 RTOS 系统加几个传感器完全够用,主频 240MHz 双核,处理四路编码器数据、超声波测距、PID 运算这些任务,简直绰绰有余。
再说开发门槛。Arduino 框架下的 ESP32 支持度已经非常完善,官方维护的 esp32 库函数据我用了三年基本没遇到严重 Bug。相比之下,STM32 如果不用 CubeMX 自动生成代码,光配置时钟树和 GPIO 就能劝退一批新手。51 单片机就更不用提了,做做课程设计还行,性能上限摆在那里,想扩展个摄像头或者跑个 Micro-ROS,基本没戏。
1.2 功能模块划分与整体架构
我的习惯是先把整个系统按功能拆成独立模块,再逐个实现,最后组装联调。这个思维模式在做任何嵌入式项目时都适用。
整辆小车的逻辑架构分四层:
- 感知层:超声波传感器负责测距避障,红外传感器负责循迹检测,两者互不干扰,通过 GPIO 读取数据
- 决策层:ESP32 主控芯片,运行状态机逻辑,根据传感器数据判断当前应该直行、转弯还是后退
- 执行层:TB6612FNG 电机驱动模块,接收 ESP32 的 PWM 信号,控制四个 TT 电机的转速和方向
- 交互层:手机 APP 通过蓝牙发送指令,也可以在调试时通过串口监视器直接查看传感器数据
这种分层设计的最大好处是:哪一层出了问题,直接隔离排查,不用推翻重来。比如电机不转了,先量执行层的 PWM 信号,再查决策层代码逻辑,两个小时的活压缩到二十分钟。
2. 硬件选型与搭建细节
2.1 核心硬件清单与选型理由
硬件清单我在下面盘了一下,都是实际用过验证过的型号,不是纸上谈兵。
| 硬件 | 型号/规格 | 数量 | 参考价 | 备注 |
|---|---|---|---|---|
| 主控 | ESP32-DevKitC(30引脚) | 1 | 25 | 别买 38 引脚的,体积大且多数引脚用不上 |
| 电机驱动 | TB6612FNG | 1 | 12 | 比 L298N 体积小一半,效率高,发热低 |
| 电机 | TT 减速电机(带编码器) | 4 | 20(4个) | 编码器版本方便后续做 PID 测速 |
| 小车底盘 | 四驱亚克力底盘 | 1 | 30 | 买带铜柱和螺丝包的套装,省事 |
| 超声波 | HC-SR04 | 1 | 4 | 便宜好用,测距精度在 2cm-400cm |
| 循迹模块 | TCRT5000 红外传感器 | 3 | 9(3个) | 三路布局,比两路更好覆盖黑线 |
| 电池 | 18650 电池两节 + 电池盒 | 1 | 18 | 电压 7.4V,正好驱动电机和给 ESP32 供电 |
| 稳压模块 | AMS1117-3.3 | 1 | 2 | 给 ESP32 提供稳定 3.3V |
| 蓝牙 | ESP32 板载 | - | - | 不需要额外买 |
这里特别要提一下电机驱动的选择。L298N 是很多教程的首选,但我强烈建议你用 TB6612FNG。L298N 的压降高达 2-3V,意味着 7.4V 的电池组到了电机端只剩 4-5V,四驱小车根本跑不快。TB6612FNG 的 MOSFET 导通电阻只有 0.4Ω 左右,几乎无压降,同样电压下电机转速更高,续航也更好。
2.2 GPIO 引脚分配与电气接线
ESP32 的引脚资源丰富,但有些引脚有默认功能,不能乱接。比如 GPIO 12 是 MTDI 引脚,上电时如果拉高会导致 Flash 电压异常,直接进不了下载模式。我踩过这个坑,后来固定用下面的分配方案。
| ESP32 引脚 | 功能 | 连接对象 |
|---|---|---|
| GPIO 14 | PWM 通道 A | TB6612FNG PWMA |
| GPIO 27 | 方向 AIN1 | TB6612FNG AIN1 |
| GPIO 26 | 方向 AIN2 | TB6612FNG AIN2 |
| GPIO 25 | PWM 通道 B | TB6612FNG PWMB |
| GPIO 33 | 方向 BIN1 | TB6612FNG BIN1 |
| GPIO 32 | 方向 BIN2 | TB6612FNG BIN2 |
| GPIO 5 | 超声波 Trig | HC-SR04 Trig |
| GPIO 18 | 超声波 Echo | HC-SR04 Echo |
| GPIO 19/21/22 | 循迹传感器 | TCRT5000 数字输出 |
| GPIO 16/17 | 编码器 A/B | 左侧电机编码器(后续扩展) |
接线时需要注意:TCRT5000 模块是 3.3V-5V 供电都支持,但数字输出引脚电平与供电电压一致。如果你给传感器供 5V,那输出高电平就是 5V,直接接到 ESP32 的 GPIO 上会伤芯片。我的做法是传感器统一供 3.3V,虽然红外 LED 的亮度略降,但实测 2cm-3cm 的检测距离完全够用。
2.3 供电方案:双电源 vs 单电源
供电是整个小车最容易出问题的地方。我第一版小车就翻车了,四个电机一启动,ESP32 直接重启,后来查了半天是电源跌落导致的。
问题根源在于电机启动瞬间的电流冲击。TT 电机堵转电流可以达到 1A 以上,四个电机同时启动就是 4A 的瞬时电流,别说 AMS1117 扛不住,就是 18650 电池组也会有明显的电压跌落。
解决方案是采用电源隔离设计:
- 电机电源:两节 18650 电池(7.4V)直接给 TB6612FNG 的 VM 供电
- 逻辑电源:从电池组引线,接 AMS1117-3.3 稳压后给 ESP32 供电
- 共地:把电池负极同时接到电机驱动和 ESP32 的 GND,保证信号参考电平一致
这个方案实测非常稳定,即使四个电机急停急转,ESP32 的供电电压也稳定在 3.3V 附近。要注意的是,AMS1117 的输入输出压差需要至少 1V,7.4V 输入降到 3.3V 完全没问题,但会有一部分能量以热量形式消耗,所以我在 AMS1117 上加了一个小铝散热片,效果不错。
3. 源码架构与核心代码实现
3.1 开发环境搭建:Arduino IDE 还是 ESP-IDF
做 ESP32 开发主要有两条路:Arduino IDE 和乐鑫官方的 ESP-IDF 框架。我的建议很明确:如果是初学者或者追求开发效率,用 Arduino IDE;如果后续要做 Micro-ROS 2 或者需要精细控制硬件资源,再切 ESP-IDF 不迟。
Arduino 框架下的 ESP32 核心库封装得非常完善,模拟输入输出、PWM、I2C、SPI、蓝牙、Wi-Fi 都有现成接口。一个简单的 GPIO 控制代码在 ESP-IDF 里要写几十行结构体配置,在 Arduino 里就一行pinMode()。但 Arduino 也有缺点,就是隐藏了太多底层细节,出了问题不容易排查,而且 float 运算效率不如 ESP-IDF 的原生驱动。
我目前的习惯是:功能验证阶段用 Arduino 快速搭原型,产品化阶段再把关键模块迁移到 ESP-IDF。这个策略帮我节省了大量调试时间。
安装 ESP32 开发支持时,有几个细节容易踩坑。Arduino IDE 的“开发板管理器 URL”需要手动添加乐鑫的 JSON 链接,国内网络环境下下载可能很慢,建议使用乐鑫国内镜像地址。安装完成后,在开发板列表里选择 “ESP32 Dev Module” 即可,Flash Mode 一定要选择 “QIO”,否则可能出现烧录成功但运行异常的情况。
3.2 源码模块划分
整个项目的源码我按功能模块拆成了五个文件,结构非常清晰:
esp32_car/ ├── esp32_car.ino // 主逻辑,状态机 ├── motor.h/cpp // 电机控制封装 ├── ultrasonic.h/cpp // 超声波测距封装 ├── tracking.h/cpp // 循迹传感器封装 └── bluetooth.h/cpp // 蓝牙指令解析这种分层的好处是:每个文件只干一件事,出了问题直接打开对应文件排查。比如小车循迹不灵敏,你只需要看 tracking 相关的代码,不用在动辄几百行的主逻辑里翻来找去。
主控制逻辑采用一个简单的状态机方案。状态机是嵌入式开发里非常基础且高效的思想——把复杂的决策过程拆解为有限个状态,每个状态下只处理对应的输入信号。
我用枚举类型定义了小车的几个运行状态:
enum CarState { STATE_STOP, STATE_FORWARD, STATE_BACKWARD, STATE_LEFT, STATE_RIGHT, STATE_AVOID };然后设置mode变量来切换运行模式:蓝牙遥控、避障、循迹。每种模式下,状态机的跳转逻辑都不一样。避障模式下,超声波检测到前方障碍物小于 20cm 就进入转弯状态;循迹模式下,根据三个红外传感器的组合值决定走直线还是修正方向。
3.3 电机控制:PWM 调速与方向控制
电机控制核心就两个点:速度用 PWM 调,方向用电平组合切。TB6612FNG 的控制逻辑其实很简单,每个通道有三个控制引脚:PWMA 负责调速,AIN1 和 AIN2 负责方向。
电机方向控制真值表如下:
| AIN1 | AIN2 | PWMA | 电机状态 |
|---|---|---|---|
| HIGH | LOW | PWM | 正转,速度由 PWM 决定 |
| LOW | HIGH | PWM | 反转 |
| LOW | LOW | 任意 | 停止(刹车) |
| HIGH | HIGH | 任意 | 停止(惰性) |
有了这个表,写代码就非常容易了。下面是电机控制的封装函数:
void motor_left_set(int speed, bool forward) { digitalWrite(PIN_AIN1, forward ? HIGH : LOW); digitalWrite(PIN_AIN2, forward ? LOW : HIGH); ledcWrite(PWM_CHANNEL_LEFT, map(speed, 0, 100, 0, 255)); }这里用ledcWrite而不是 Arduino 原生的analogWrite,是因为 ESP32 的 Arduino 核心并没有完整实现analogWrite,用了也是基于ledc的封装,而且指定的引脚通道不同。我在初始化里配置了 PWM 通道参数:
ledcSetup(PWM_CHANNEL_LEFT, 1000, 8); // 频率 1kHz,分辨率 8 位 ledcAttachPin(PIN_PWMA, PWM_CHANNEL_LEFT);PWM 频率我选了 1kHz,这个值对 TT 电机的刷式换向器来说是很合适的。频率太低(比如 100Hz)电机会有明显噪音和抖动,频率太高(比如 50kHz)则会因为电机线圈电感的滤波作用导致实际电流很小,扭矩不足。10kHz-20kHz 之间是听不见噪音的常见选择,但 1kHz 在实际调试中已经够用且好观察。
3.4 超声波避障与循迹实现
超声波测距的原理很简单:Trig 引脚拉高 10us,模块自动发射 8 个 40kHz 的脉冲,Echo 引脚输出高电平,高电平的持续时间就是声波往返的时间。距离等于时间乘以声速(340m/s)再除以 2。
float ultrasonic_read() { digitalWrite(PIN_TRIG, LOW); delayMicroseconds(2); digitalWrite(PIN_TRIG, HIGH); delayMicroseconds(10); digitalWrite(PIN_TRIG, LOW); long duration = pulseIn(PIN_ECHO, HIGH, 30000); // 超时 30ms return duration * 0.034 / 2; }加超时参数很重要,因为如果超声波前方没有遮挡物,Echo 引脚会一直保持高电平,pulseIn会阻塞程序执行。我设定 30ms 超时,对应最大测距距离约为 5m,超出就返回 0,让上层逻辑当作无障碍处理,这样就不会卡死。
循迹部分就更直接了。TCRT5000 是一个红外反射传感器,当它对准黑色跑道线时,红外光被黑线吸收,输出高电平;当它偏离到白色地面时,红外光被反射回来,输出低电平。三个传感器并排安装在小车底盘前方,形成一个简单的检测阵列,就能判断小车相对黑线的位置状态。
int tracking_read() { int s1 = digitalRead(PIN_TRACK_LEFT); int s2 = digitalRead(PIN_TRACK_MIDDLE); int s3 = digitalRead(PIN_TRACK_RIGHT); return (s1 << 2) | (s2 << 1) | s3; }三个传感器返回的二进制组合对应不同的循迹动作:传感器值组合中,如果只有中间传感器检测到黑线,说明小车正直前行;如果左边或右边的传感器偏了,就分别向左或向右修正。判断逻辑在状态机里统一处理,这样代码就不零散。
3.5 蓝牙遥控与手机 APP 对接
蓝牙遥控这块,ESP32 双模蓝牙支持经典蓝牙(Bluetooth Classic)和低功耗蓝牙(BLE)。APP 遥控我用的是经典蓝牙,因为它支持 SPP 串口透传协议,手机连接后虚拟出一个串口,收发数据就像串口一样简单。
在 Arduino 框架下使用经典蓝牙,核心就几行代码:
#include "BluetoothSerial.h" BluetoothSerial SerialBT; void setup() { SerialBT.begin("ESP32_Car"); // 蓝牙设备名称,手机可搜索到 } void loop() { if (SerialBT.available()) { char cmd = SerialBT.read(); handle_command(cmd); } }关键是设计一套简洁的指令协议。我自定义的协议是单字符指令,简单高效:
| 指令 | 含义 |
|---|---|
| 'F' | 前进 |
| 'B' | 后退 |
| 'L' | 左转 |
| 'R' | 右转 |
| 'S' | 停止 |
| 'A' | 切换避障模式 |
| 'T' | 切换循迹模式 |
| 'H' | 切换手动遥控模式 |
为什么要用单字符?因为嵌入式设备的内存和带宽都有限,协议越简单越可靠。你当然可以用 JSON 格式传指令,但解析 JSON 在 ESP32 上要额外引入库,占用资源,还容易出错。单字符协议在串口监视器里调试也直观,一看就知道发的是什么。
手机 APP 端我用一个开源的蓝牙串口调试工具,也可以用 Arduino IDE 自带的串口监视器配合调试。APP 里设置一个方向按键面板,四个方向键加一个停止键,按下时发送对应字符,松开时发送停止指令。这里有一个从实际调试中总结的经验:按键按下立即发指令,松开后不要马上发停止指令,而是延迟 100ms 再发。否则电机会频繁启停,电流冲击很大,容易触发保护。
4. 联调流程与功能验证
4.1 先分模块测试,再整机联调
我犯过最蠢的错误,就是所有模块焊接完之后直接上代码,结果小车纹丝不动,查了一下午才发现是电机驱动的两个接口线序接反了。从那以后,我坚持一个基本原则:模块先单独测试,确认无误再整机联调。
具体的测试顺序如下:
- 电源板测试:焊好电源模块后,先用万用表量输出是否稳定在 3.3V 和 5V。这一步非常关键,如果这一步电压不对,后续所有测试都是白搭,甚至会烧毁传感器。
- 电机测试:单独写一个测试代码,让四个电机依次正转、反转、停止,确认每个电机的方向控制逻辑都正常。同时用记号笔在每个电机上做个方向标记,方便后续组装的轮向判断。
- 传感器测试:超声波模块可以通过串口打印当前测距值,用手在传感器前方移动,观察数值是否线性变化。循迹模块则可以在白纸上放一段黑色胶带,看串口输出是否在黑线上方产生跳变。
- 蓝牙通信测试:手机连接 ESP32 的蓝牙,发送各指令字符,观察串口输出是否收到正确的指令数据。
- 整机联调:所有模块单独确认无误后,再合并到主代码里做整机测试。先地板擦干净放好黑线,然后一个小功能一个小功能地验证。
这个过程看起来繁琐,但实际上是帮我节省了大量时间。每个模块单独出问题时,排错范围很小,基本看一眼串口输出就能定位。整机联调如果出问题,哪哪都像嫌疑犯,排查效率低到令人崩溃。
4.2 四种模式的切换测试
联调完成后,我在不同场景下对四种模式做了验证测试,这里把测试记录整理出来,供你参考对照。
手动遥控模式是最基本的,测试时主要观察几个指标:手机的连接是否稳定、指令响应是否及时、电机转向是否和按键方向一致。实测下来,ESP32 蓝牙的连接稳定性不错,在无障碍物的情况下,直线距离 10 米内都能稳定控制。这里有个小坑:如果你同时开了 Wi-Fi 和蓝牙,两者共用 2.4GHz 频段,会导致蓝牙响应变慢,我通常测试时直接关掉 Wi-Fi。
避障模式的测试场景是客厅,我用纸箱摆了一个简单的障碍物布局。小车的逻辑是:直行遇到障碍物,停车,测距判断是向左还是向右转避开,然后继续直行。这里有一个需要调优的细节:转弯角度如果固定,小车在被障碍物围住的“死胡同”里可能会来回打转。我的做法是引入一个“连续转向”计数,如果连续三次转向都在同一个方向,就大幅增加转向角度,让小车有更大的机动空间。
循迹模式的测试在地上贴了两条黑线胶带,一条直线和一条 S 弯。直线循迹基本没有悬念,S 弯是对 PID 参数的考验。我一开始用固定速度直行,结果在 S 弯的弯心处小车冲出去线了。后来把弯道的线速度降到了直道的 60%,效果立竿见影。
4.3 关键参数调优记录
下面是几个我在调试过程中记录的关键参数,供大家参考:
| 参数名 | 初始值 | 调优后 | 说明 |
|---|---|---|---|
| 直行 PWM 占空比 | 50% | 40% | 四驱车空载时速度太快,循迹易冲出线 |
| 转弯 PWM 占空比 | 50% | 35% | 转弯速度过高,容易甩尾 |
| 避障触发距离 | 30cm | 20cm | 30cm 触发太早,频繁转向,行动效率低 |
| 循迹传感器阈值 | 无 | 中值 800 | 用模拟量读取判断阈值更可靠 |
| 蓝牙响应延时 | 无 | 停止延迟 100ms | 防止电机瞬间刹车导致过流 |
循迹传感器的阈值调优让我折腾了一晚上。TCRT5000 的数字输出在阈值附近会产生抖动,导致传感器状态频繁跳变。我的解决方案是:把传感器切到模拟量读取模式,在串口监视器里观察黑线和白地的模拟值差异,取两者的中间值作为判断阈值。这样比直接用数字输出稳定得多,特别是环境光线变化的情况下。
5. 常见问题与排查技巧实录
5.1 烧录失败与驱动问题
ESP32 烧录失败是新手遇到最多的问题,表现是编译成功,但上传时串口一直报 “Connecting...”,然后超时失败。
我遇到这个问题的排查路径是:
- 检查串口驱动:ESP32 开发板用的串口芯片通常是 CP2102 或 CH340,需要安装对应驱动。Windows 系统一般会自动安装,但需要确认设备管理器里是否识别到 COM 口。
- 按住 BOOT 键上传:如果驱动没问题,按住开发板上的 BOOT 键不放,再点上传,等出现 “Connecting” 时松手。这是因为 ESP32 的上电时序偶尔会异常,进入不了下载模式,手动按 BOOT 可以强制进入。
- 检查数据线:这个问题非常隐蔽。很多 USB 线只有充电功能,没有数据线芯,特别是那种细的白色安卓线。换一根短的、品牌靠谱的数据线,能解决 80% 的烧录问题。
烧录成功后,还有一个经常被忽略的坑:上传代码后没有任何反应。这时候在串口监视器里看输出,如果卡在乱码或者没有任何输出,尝试按下开发板的 EN 复位键,大概率就能恢复。
5.2 电机抖动与小车跑偏
小车通电后电机抖动、转速不均匀,甚至伴随异常电流声,这个问题我排查过很多次,最终归因到三个可能:
最普遍的原因是电池电压不足。刚买来的 18650 电池可能没有充满电,四个电机同时启动时,电压被拉低到电机驱动芯片的最小工作电压以下,导致 PWM 信号不稳定。解决方法是先充满电再测试,并且不要吝啬电池的放电能力参数,建议买容量 2500mAh 以上的动力型 18650。
其次是共地问题。驱动板和主控板之间如果没有连接 GND,控制信号就会因为参考电位不一致而失效,表现为电机动作异常、传感器数据漂移。我的接线规范是:所有模块的 GND 全部拧到一起,形成一个星型接地结构。
最后是 PWM 频率不匹配。我之前用模拟输出口直接驱动电机,TT 电机内部的减速齿轮结构在低转速段天然存在力矩波动,如果 PWM 频率又正好落在机械共振区间,抖动就会特别严重。可以试着把 PWM 频率调高或者调低一个量级(比如从 1kHz 调到 500Hz),看抖动是否改善。
跑偏问题就更常见了,尤其是四驱小车。四个电机的转速不可能完全一致,即使给定相同的 PWM 占空比,由于电机内部绕组、碳刷和机械齿轮的差异,实际转速也会有 5%-10% 的偏差。早期方案里我直接用开环控制,跑着跑着就歪了。解决办法有两个层级的:
- 简单方案:在代码里给慢速的电机加上补偿值,比如左侧电机 PWM 设为 40%,右侧设为 36%,反复调直到小车能走直线
- 进阶方案:利用编码器测速,跑一个闭环 PID 调速算法,把四个电机的实际转速都锁定到目标值附近
如果要做竞赛项目,或者对线的准确性有很高要求,强烈建议直接上 PID 方案,开环补偿只能保证当前电池电压下的直线度,电池电量下降后参数又要重调。
5.3 传感器误触发与数据漂移
超声波传感器在电机同时工作时经常出现数据跳变,原因有两个。
第一是电源干扰。电机产生的电磁噪声会通过共地回路耦合到超声波模块的供电线上,导致测距值偶尔跳变。我在模块供电输入端加了一个 10uF 电解电容和一个 0.1uF 陶瓷电容做滤波,问题就改善了一大半。
第二是壁面反射和斜射误差。超声波束是一个锥形区域,如果探测目标是一个斜面或者墙角,回波会被反射到其他地方,导致测不出来或者数据偏大。处理办法是对测量数据做中值滤波——连续读五次,取中间值作为有效数据,可以有效消除单次大概率错数据。
循迹传感器对环境光非常敏感。太阳光中的红外成分有一定概率直接触发传感器,导致它在明亮环境下误判。我一般给传感器加上一个遮光罩(黑色热缩管或者电工胶带),把环境光线挡掉,只让地面反射光进入传感器。
5.4 蓝牙连接不稳定与串口调试技巧
蓝牙连接不稳定,容易出现连接后又断开、或者指令延迟严重的情况。我的排查结论是:ESP32 的蓝牙和 Wi-Fi 同时启用时,两者在 2.4GHz 频段互相干扰,尤其是在 Wi-Fi 传输数据量大的时候,蓝牙的时延会明显升高。
如果你只做遥控,不打算联网,那就在代码里把 Wi-Fi 彻底停掉:
#include "nvs_flash.h" nvs_flash_erase(); nvs_flash_init(); esp_wifi_deinit();实测关闭 Wi-Fi 后,蓝牙连接稳定性和响应速度都有明显提升。
还有一个调试技巧:在代码里大量使用Serial.println()输出调试信息,然后通过 USB 串口监视器观察。但要注意,一旦连接了手机蓝牙,蓝牙和 USB 串口会共用同一个 UART,两个都开的时候,数据会在两个方向来回串,导致串口监视器输出混乱。我的做法是:调试蓝牙指令时,断开 USB 连接,直接通过手机 APP 发指令,然后把 ESP32 的日志输出到另一个 GPIO 映射的串口上。
6. 源码获取与后续扩展方向
源码组织方式刚才说过了,这里补充几个核心逻辑的细节。主循环里我用了一个简单的switch-case来处理不同模式下的状态机,结构清晰,方便扩展新的传感器和执行器。
void loop() { switch (mode) { case MODE_REMOTE: handle_remote_command(); break; case MODE_AVOID: handle_avoid_mode(); break; case MODE_TRACK: handle_track_mode(); break; default: car_stop(); break; } }每个handle_xxx函数内部再根据传感器输入执行对应的状态跳转。这套代码的总量不大,全部加起来也就三四百行,很适合作为学习素材。在实现时,我尽量保持了代码的可读性,变量命名都是完整单词,关键逻辑段有中文注释,你拿到手之后直接烧录就能跑。
6.1 从 Arduino 到 Micro-ROS:走上机器人系统开发之路
如果你在完成基础功能后,想把小车接入 ROS 2 系统,就可以进一步了解 micro-ROS 在 ESP32 上的移植和应用。这正好是近几年机器人开发的一个热点方向。
Micro-ROS 是一个在 MCU 上运行的 ROS 2 客户端,它通过串口或 Wi-Fi 与上位机(比如树莓派或装有 ROS 2 Humble 的 PC)通信。在 ESP32 上跑 Micro-ROS,等于把你的小车变成了一个真正的分布式机器人节点,传感器数据可以发到 ROS 2 的 Topic 里,控制指令也可以从 ROS 2 的节点发下来。
移植 micro-ROS 到 ESP32 需要用到乐鑫的micro_ros_espidf_component组件库。大致步骤是:先安装 ESP-IDF 环境,然后克隆 micro-ROS 的 ESP32 组件库到工程的 components 目录,在 menuconfig 里根据你的板子性能选择合适的传输方式和内存配置,编译烧录即可。Wi-Fi 通信模式下,ESP32 作为 UDP 客户端连接上位机的 micro-ROS Agent;串口模式下,则通过物理串口连接,稳定性和实时性更高,是机器人竞赛中更常用的方式。
6.2 项目扩展的四个方向
项目跑通之后,你会发现自己对 ESP32 和外设的控制方式都有了比较全面的理解。这时候可以往下面几个方向扩展:
- 视觉识别方向:给小车加一个摄像头模块(比如 ESP32-CAM 作为独立视觉节点),通过 Wi-Fi 传输图像到上位机,在 OpenCV 里做目标检测和巡线。这样一来,就不只局限在红外循迹了。
- 高精度定位方向:给底盘加装霍尔编码器,并融合 MPU6050 六轴陀螺仪,配合 PID 闭环就能实现走直线不跑偏,再往下做可以完成简单的轨迹规划,这也是很多工业物流小车方案的简化版。
- 语音交互方向:在手机 APP 端集成语音识别,把识别出的文本转成蓝牙指令,比如“前进”“左转”这些指令,车就能听懂人话。这也算是个比较讨巧的炫酷功能。
- 多机协作方向:用两块 ESP32 分别控制两台小车,通过两个开发板上的 Wi-Fi 建立 TCP 通信,实现一主一从的编队控制。这个方向的扩展比较有挑战性,适合作为毕设或竞赛课题。
顺便提一句,如果你手边只有 STM32F103ZET6 或树莓派,这套思路也可以直接平移。STM32 需要外接蓝牙和 Wi-Fi 模块,代码上用 HAL 库重写一遍;树莓派则可以直接用 Python 的gpiozero库或者 ROS 2 的驱动包,逻辑一模一样,区别主要在于底层 API 不同。
6.3 最后分享三个实操心得
从我做了三版小车、前后折腾了两个多月的经验来看,有三件事对你可能最有帮助。
第一,第一次做小车,一定要选择成熟的方案而不是追求配置堆料。我见过太多人一开始就想要机械臂、摄像头、激光雷达全加上,结果项目进行到一半连轮子都转不起来。先把基础版跑通,建立信心和手感,再去逐项加功能,这个节奏是最舒服的。
第二,把调试接口留足。我的每一版小车都保留了一个串口调试接口和一个状态 LED 灯。遇到问题时,串口打日志、LED 看状态,信息越丰富,定位越快速。有人觉得这些冗余设计浪费时间,等真正出问题的夜晚,你才知道它们是你最忠实的朋友。
第三,选择一个由浅入深的项目路线。如果你的目标是参加竞赛,直接参考近期工创赛智能物流小车的题目,这类比赛大多数以视觉识别、色块抓取、物品搬运为主,底层底盘和电机控制就是今天这套 ESP32 平台的变体。提前把底盘稳了,比赛时就有了底牌。如果是课程设计,加一路循迹加避障就足够拿高分,剩余的精力用来把代码注释和报告写好,反而更容易出彩。
我这套小车源码已经在文末给出了完整工程,包含了硬件的接线图、Arduino 版的全部源码以及规避的一堆坑。照着做,一个周末的时间,你就能看到自己的小车在地板上撒欢跑了。
本文还有配套的精品资源,点击获取