1. 为什么我最终选了ESP32来做这台Wi-Fi遥控车
1.1 从一堆吃灰的开发板里翻出ESP32
前阵子收拾工作台,翻出来好几块开发板,STM32的、Arduino Uno的、还有两块ESP32。本来想随便搭个蓝牙小车打发周末,但转念一想,蓝牙控制距离太短,隔堵墙就断连,体验很差。既然手头有ESP32,它自带Wi-Fi,干脆直接做一台Wi-Fi遥控车,手机连上就能控制,距离和稳定性都比蓝牙强不少。
这个项目说白了就三件事:让ESP32连上Wi-Fi、让它能驱动电机、让它能接收手机发来的控制指令。听起来简单,但实际动手时会遇到不少细节问题,比如电机供电和主控供电怎么隔离、PWM频率设多少合适、Wi-Fi指令延迟怎么优化。这些坑我在下面会一个个拆开讲。
适合谁来参考这篇内容?如果你玩过Arduino,点过LED,对GPIO和PWM有基本概念,那这篇就是为你写的。如果你完全零基础,也没关系,我会把每个环节的原理和操作都讲清楚,你照着做也能跑起来。整台车的物料成本大概在80到150块之间,取决于你手头已经有哪些零件。
1.2 核心思路:为什么是Wi-Fi而不是蓝牙
很多人做遥控车第一反应是用蓝牙,毕竟HC-05、HC-06这些模块便宜又常见。但蓝牙有几个硬伤:一是传输距离短,Class 2的蓝牙模块有效距离也就10米左右,稍微远一点或者隔堵墙就断;二是蓝牙的配对过程比较繁琐,每次换设备都要重新配对;三是蓝牙的带宽有限,如果你想后续加个摄像头回传画面,蓝牙根本扛不住。
ESP32的Wi-Fi方案就不一样了。它可以直接工作在AP模式(自己建热点)或者STA模式(连你家路由器),AP模式下手机直连,延迟低、响应快;STA模式下只要手机和车在同一个局域网内,理论上你家的Wi-Fi覆盖范围有多大,控制距离就有多远。而且ESP32的Wi-Fi还支持UDP和TCP两种通信方式,UDP延迟更低适合实时控制,TCP更可靠适合传指令,你可以根据需求灵活选。
我最终选的是AP模式+UDP通信。原因很简单:AP模式不依赖家里的路由器,拿到户外也能用;UDP虽然不保证送达,但对于遥控车这种场景,丢一两个包无所谓,下一帧指令马上就补上了,反而比TCP的重传机制更跟手。
1.3 整车架构一览
在动手之前,先把整台车的架构理清楚,后面操作才不会乱。整台车分成四个部分:
- 主控部分:ESP32开发板,负责Wi-Fi通信、指令解析、PWM输出
- 驱动部分:L298N或TB6612电机驱动模块,负责把ESP32的弱信号放大成能驱动电机的强电流
- 动力部分:两个直流减速电机+车轮,负责让车跑起来
- 供电部分:电池组+降压模块,负责给主控和电机分别供电
信号流向是这样的:手机App发送UDP数据包→ESP32接收并解析→ESP32输出PWM信号→电机驱动模块放大信号→电机转动→车动起来。整个链路里,最容易出问题的环节是供电和电机驱动,这两块我会重点讲。
2. 物料清单与工具准备
2.1 核心物料选型与避坑
先列一下我实际用到的物料,以及选型时的考量:
| 物料 | 型号/规格 | 数量 | 参考价格 | 选型说明 |
|---|---|---|---|---|
| 主控板 | ESP32 DevKit V1 | 1 | 25-35元 | 经典款,引脚多,资料全 |
| 电机驱动 | TB6612FNG | 1 | 8-15元 | 比L298N效率高,发热小 |
| 直流电机 | TT减速电机 | 2 | 10-15元/个 | 带轮子,扭矩够用 |
| 车轮 | 65mm橡胶轮 | 2 | 5元/个 | 抓地力好 |
| 万向轮 | 塑料万向轮 | 1 | 3元 | 前轮支撑 |
| 电池 | 18650锂电池×2 | 2 | 15元/节 | 7.4V供电 |
| 电池盒 | 双节18650电池盒 | 1 | 5元 | 带引线 |
| 降压模块 | LM2596可调降压 | 1 | 3元 | 降到5V给ESP32 |
| 车底盘 | 亚克力底盘 | 1 | 10元 | 也可以用纸板代替 |
| 杜邦线 | 公对公/公对母 | 若干 | 5元 | 连接用 |
这里重点说几个选型上的坑。电机驱动模块我强烈建议用TB6612而不是L298N。L298N便宜是便宜,但它的导通压降大,大概有1.4V左右,意味着你7.4V的电池经过它之后只剩6V给电机,动力损失明显,而且发热严重。TB6612的导通压降只有0.5V左右,效率高很多,体积也小。
电池方面,我用的是两节18650串联,标称7.4V。为什么不直接用一节3.7V?因为TT电机的额定电压一般在3-6V,但实际测试下来,7.4V供电时转速和扭矩都更充沛,通过PWM限幅就能控制速度。如果你用3.7V,电机可能转起来没劲,尤其是爬个小坡就卡住了。
2.2 工具准备
工具方面不需要太多,但有几样必须要有:
- 电烙铁:焊接电机引线和电池盒引线,如果你买的是带线的电机,可以省掉这一步
- 螺丝刀套装:组装底盘和固定电机
- 万用表:调试时测电压、查通断,非常关键
- USB数据线:给ESP32烧录程序,注意要选支持数据传输的线,有些线只能充电
- 电脑:安装Arduino IDE,用来写代码和烧录
提示:买ESP32开发板的时候,注意看是不是带CH340或CP2102 USB转串口芯片的版本。有些廉价板子用的是冷门芯片,驱动装起来很折腾。CH340的最省心,Windows和Mac都有现成驱动。
2.3 供电方案的计算过程
供电是整个项目里最容易翻车的地方,我单独拿出来算一下。ESP32的工作电压是3.3V,但它开发板上的USB口和VIN引脚可以接受5V输入,板载的LDO会降到3.3V。所以我们需要把电池的7.4V降到5V给ESP32。
用LM2596降压模块,输入7.4V,输出调到5V。这里要注意,LM2596的输出电流最大是3A,但实际使用时建议不要超过2A,留点余量。ESP32在Wi-Fi工作时峰值电流大概在240mA左右,所以5V这一路完全够用。
电机那一路直接接电池的7.4V,不经过降压。因为电机启动瞬间电流很大,如果和ESP32共用一路降压后的电源,电机一启动就会把电压拉低,导致ESP32复位。这就是为什么必须分开供电的原因。
注意:ESP32和电机驱动模块必须共地。也就是说,电池的负极、降压模块的负极、ESP32的GND、TB6612的GND要全部连在一起。不共地的话,PWM信号没有参考电平,电机不会转。
3. 硬件接线与电路搭建
3.1 ESP32与TB6612的接线
先搞定主控和驱动模块之间的连线。TB6612的引脚定义如下:
- VM:电机电源输入,接电池正极(7.4V)
- VCC:逻辑电源输入,接ESP32的3.3V
- GND:接地,接电池负极和ESP32的GND
- STBY:待机引脚,接高电平(3.3V)才能工作
- AIN1/AIN2:控制A路电机的方向
- BIN1/BIN2:控制B路电机的方向
- PWMA/PWMB:控制A/B路电机的速度
- AO1/AO2:A路电机输出
- BO1/BO2:B路电机输出
我选的ESP32引脚分配是这样的:
| TB6612引脚 | ESP32引脚 | 说明 |
|---|---|---|
| AIN1 | GPIO 25 | 左电机方向1 |
| AIN2 | GPIO 26 | 左电机方向2 |
| BIN1 | GPIO 27 | 右电机方向1 |
| BIN2 | GPIO 14 | 右电机方向2 |
| PWMA | GPIO 32 | 左电机速度 |
| PWMB | GPIO 33 | 右电机速度 |
| STBY | 3.3V | 常开 |
| VCC | 3.3V | 逻辑供电 |
| GND | GND | 共地 |
选这些引脚的原因:GPIO 25、26、27、14都是普通的数字引脚,没有特殊功能冲突;GPIO 32和33支持PWM输出,而且属于ESP32的LEDC(LED Control)外设可以驱动的引脚。ESP32的PWM不是用analogWrite,而是用ledcSetup和ledcAttachPin这套API,后面代码部分会详细讲。
3.2 电机与电源接线
电机接线比较简单,每个TT电机有两根线,分别接到TB6612的AO1/AO2和BO1/BO2。如果发现电机转向反了,不用改代码,直接把电机的两根线对调一下就行。
电源部分稍微复杂一点,我画个文字版的接线图:
电池正极(7.4V) ──┬── TB6612的VM │ └── LM2596的IN+ 电池负极 ────────┬── TB6612的GND │ ├── LM2596的IN- │ └── ESP32的GND LM2596的OUT+ (5V) ── ESP32的VIN LM2596的OUT- ──────── ESP32的GND这里有个细节:LM2596的输出电压在接负载之前,最好先用万用表调准。模块上有个蓝色电位器,顺时针转电压升高,逆时针转电压降低。先不接ESP32,只接万用表,调到5.0V左右再接上去。我见过有人没调就直接接,结果输出12V把ESP32烧了的。
3.3 底盘组装与走线
底盘组装没什么技术含量,但走线有讲究。电机线、电源线、信号线尽量分开走,尤其是PWM信号线不要和电机线捆在一起,否则电机换向时产生的电磁干扰可能让PWM信号抖动,表现为电机转速不稳或者突然抽一下。
我的做法是用扎带把电机线固定在底盘下方,信号线从底盘上方走,电源线走侧面。虽然看起来不那么整洁,但实测稳定性好很多。
实操心得:TB6612的STBY引脚一定要接高电平,我第一次做的时候忘了接,调试了半天以为代码有问题,结果是芯片处于待机状态,电机根本不转。这个坑很隐蔽,因为TB6612不会发热也不会报错,就是安静地不工作。
4. 开发环境搭建与代码实现
4.1 Arduino IDE配置ESP32开发环境
如果你还没装Arduino IDE,先去官网下载。版本建议用1.8.19或2.x都行,我用的是1.8.19,比较稳定。装好之后,需要添加ESP32的开发板支持。
打开Arduino IDE,点“文件”→“首选项”,在“附加开发板管理器网址”里填入:
https://espressif.github.io/arduino-esp32/package_esp32_index.json然后点“工具”→“开发板”→“开发板管理器”,搜索“esp32”,找到“esp32 by Espressif Systems”,点安装。这里有个坑:在线安装经常因为网络问题失败或者特别慢。如果你遇到这种情况,可以下载离线包手动安装。离线包是一个zip文件,解压后放到Arduino的hardware目录下,具体路径是:
- Windows:
C:\Users\你的用户名\AppData\Local\Arduino15\packages\ - Mac:
~/Library/Arduino15/packages/
放好之后重启Arduino IDE,在开发板列表里就能看到ESP32的选项了。我一般选“ESP32 Dev Module”,如果你的板子是ESP32-S3或者ESP32-C3,选对应的型号。
4.2 Wi-Fi AP模式与UDP通信代码
先上核心代码,我把它分成几个部分来讲。第一部分是Wi-Fi和UDP的初始化:
#include <WiFi.h> #include <WiFiUdp.h> const char* ssid = "ESP32_CAR"; const char* password = "12345678"; WiFiUDP udp; unsigned int localPort = 8888; char packetBuffer[255]; void setupWiFi() { WiFi.softAP(ssid, password); IPAddress IP = WiFi.softAPIP(); Serial.print("AP IP address: "); Serial.println(IP); udp.begin(localPort); }这段代码把ESP32设成AP模式,热点名字叫“ESP32_CAR”,密码是“12345678”。手机连上这个热点后,ESP32的IP地址默认是192.168.4.1。UDP监听8888端口,收到的数据存在packetBuffer里。
为什么用UDP而不是TCP?前面说过,UDP延迟低。实测下来,UDP的往返延迟在5-15ms之间,TCP因为要建立连接和确认重传,延迟在20-50ms。对于遥控车来说,这个差别在高速行驶时能明显感觉到。
4.3 电机控制与PWM配置
ESP32的PWM配置和Arduino Uno不一样,不能用analogWrite。需要用ledcSetup和ledcAttachPin:
#define AIN1 25 #define AIN2 26 #define BIN1 27 #define BIN2 14 #define PWMA 32 #define PWMB 33 void setupMotors() { pinMode(AIN1, OUTPUT); pinMode(AIN2, OUTPUT); pinMode(BIN1, OUTPUT); pinMode(BIN2, OUTPUT); ledcSetup(0, 1000, 8); // 通道0,1000Hz,8位分辨率 ledcSetup(1, 1000, 8); // 通道1,1000Hz,8位分辨率 ledcAttachPin(PWMA, 0); ledcAttachPin(PWMB, 1); } void setMotor(int leftSpeed, int rightSpeed) { // leftSpeed和rightSpeed范围:-255到255 if (leftSpeed >= 0) { digitalWrite(AIN1, HIGH); digitalWrite(AIN2, LOW); ledcWrite(0, leftSpeed); } else { digitalWrite(AIN1, LOW); digitalWrite(AIN2, HIGH); ledcWrite(0, -leftSpeed); } if (rightSpeed >= 0) { digitalWrite(BIN1, HIGH); digitalWrite(BIN2, LOW); ledcWrite(1, rightSpeed); } else { digitalWrite(BIN1, LOW); digitalWrite(BIN2, HIGH); ledcWrite(1, -rightSpeed); } }PWM频率我设的是1000Hz,8位分辨率。为什么选这个参数?频率太低电机会有啸叫声,太高TB6612可能响应不过来。1000Hz是人耳能听到的范围边缘,实际听起来是轻微的嗡嗡声,不刺耳。8位分辨率意味着速度可以分256级调节,够用了。
4.4 主循环与指令解析
主循环里不断检查有没有收到UDP数据,收到就解析并执行:
void loop() { int packetSize = udp.parsePacket(); if (packetSize) { int len = udp.read(packetBuffer, 255); if (len > 0) packetBuffer[len] = '\0'; String cmd = String(packetBuffer); if (cmd == "F") { setMotor(200, 200); // 前进 } else if (cmd == "B") { setMotor(-200, -200); // 后退 } else if (cmd == "L") { setMotor(-150, 150); // 左转 } else if (cmd == "R") { setMotor(150, -150); // 右转 } else if (cmd == "S") { setMotor(0, 0); // 停止 } } }指令格式很简单,单个字符:“F”前进、“B”后退、“L”左转、“R”右转、“S”停止。速度值我设的是200,不是满速255,留一点余量,避免电机堵转时电流过大。
实操心得:UDP接收这里有个细节,packetBuffer一定要在读取后手动加结束符
\0,否则String转换时可能读到上一次的残留数据,导致指令解析错误。我一开始没加,偶尔会出现车自己乱动的情况,查了半天才发现是这个原因。
5. 手机端控制方案
5.1 用现成App快速测试
代码烧录进去之后,先别急着写手机App,用现成的UDP调试工具测一下。手机连上ESP32的热点,打开任意一个UDP发送工具(应用商店搜“UDP Sender”就有),目标IP填192.168.4.1,端口8888,发送“F”,看车是不是往前跑。
这一步很关键,能帮你快速定位问题。如果车不动,先检查硬件接线和供电;如果车动了但方向不对,调整电机线或者代码里的方向逻辑。把硬件问题排除掉之后,再去做手机App,否则你会分不清是App的问题还是硬件的问题。
5.2 自己做一个简单的控制界面
如果你想让控制体验更好,可以用MIT App Inventor做一个简单的手机App。App Inventor是图形化编程,拖拖拽拽就能做出一个带方向按钮的界面。核心逻辑就是按下按钮时通过UDP发送对应的字符。
具体步骤:新建项目,拖一个UDP组件进来,设置目标IP为192.168.4.1,端口8888。然后放五个按钮,分别对应前进、后退、左转、右转、停止。每个按钮的点击事件里调用UDP发送对应的字符。
App Inventor的UDP组件有个坑:它默认是异步发送的,如果你快速连续按按钮,可能会出现指令乱序。解决办法是在发送前先调用一次关闭再打开,或者用时钟组件做一下节流,限制发送频率在50ms以上。
5.3 进阶:用WebSocket做网页控制
如果你不想装App,还有一个更优雅的方案:让ESP32同时跑一个Web服务器,手机浏览器直接打开网页就能控制。这个方案需要用到ESP32的WebServer库和WebSocket库,代码量比UDP方案大一些,但用户体验更好,不用装任何东西。
核心思路是ESP32建一个网页,网页里用JavaScript监听按钮事件,通过WebSocket把指令发给ESP32。ESP32收到后执行对应的电机控制。这个方案的好处是跨平台,安卓、苹果、电脑都能用,只要有浏览器就行。
6. 调试过程中踩过的坑与排查技巧
6.1 电机不转的排查流程
电机不转是最常见的问题,我整理了一个排查顺序:
- 先测电压:用万用表测TB6612的VM引脚,看有没有7.4V。如果没有,检查电池和接线。
- 再测STBY:测STBY引脚是不是3.3V。如果是0V,检查接线。
- 测PWM输出:用万用表测PWMA引脚,发送前进指令时应该有电压变化。如果没有,说明代码没跑起来。
- 测电机输出:测AO1和AO2之间有没有电压。如果有电压但电机不转,可能是电机坏了或者线断了。
- 检查共地:确认ESP32的GND和TB6612的GND是连通的。
这个顺序是从电源到信号、从简单到复杂,能帮你快速缩小问题范围。
6.2 Wi-Fi连接不稳定怎么办
有时候手机连上了ESP32的热点,但发送指令没反应。这种情况一般是IP地址或者端口不对。先在串口监视器里看ESP32打印的IP地址,确认是192.168.4.1。然后检查手机端的目标IP和端口是不是和代码里一致。
还有一个可能:手机连上ESP32热点后,因为热点没有互联网,手机会自动切换到移动数据,导致UDP包发不出去。解决办法是在手机Wi-Fi设置里,把ESP32的热点设为“始终连接”或者关闭“自动切换网络”。
6.3 电机干扰导致ESP32复位
这个问题很隐蔽:车一跑起来ESP32就重启,停下来就正常。原因是电机启动瞬间电流很大,把电池电压拉低,导致ESP32的5V供电不稳,触发欠压复位。
解决办法有两个:一是在电池和降压模块之间并联一个大电容(1000uF以上),起到缓冲作用;二是给ESP32单独供电,比如用一块充电宝给ESP32供电,电池只给电机供电。我用的是第一种方案,加了个2200uF的电解电容,问题就解决了。
6.4 常见问题速查表
| 现象 | 可能原因 | 解决方法 |
|---|---|---|
| 电机完全不转 | STBY没接高电平 | 把STBY接到3.3V |
| 电机只往一个方向转 | 方向引脚接线错误 | 检查AIN1/AIN2接线 |
| 车跑起来就复位 | 电源干扰 | 加电容或分开供电 |
| Wi-Fi连不上 | 热点名称或密码错误 | 检查代码中的ssid和password |
| UDP指令无响应 | IP或端口不对 | 确认目标IP为192.168.4.1,端口8888 |
| 电机转速不稳 | PWM频率不合适 | 调整ledcSetup的频率参数 |
| 串口监视器乱码 | 波特率不匹配 | 设置串口监视器波特率为115200 |
7. 后续可以扩展的方向
这台车跑起来之后,其实还有很多可以玩的方向。比如加一个超声波模块做避障,ESP32检测到前方有障碍物就自动停车或者转向。或者加一个MPU6050陀螺仪,做姿态检测,防止翻车。再进阶一点,可以加一个ESP32-CAM模块,把摄像头画面通过Wi-Fi回传,做成FPV遥控车。
软件方面,可以把UDP控制改成WebSocket,做一个更漂亮的网页控制界面,加上速度滑块和实时状态显示。还可以接入MQTT协议,让车接入智能家居系统,用语音助手控制。
我个人最想尝试的是加一个边缘AI功能,用ESP32-S3跑一个简单的手势识别模型,通过摄像头识别手势来控制车。虽然ESP32的算力有限,但跑一些轻量级的神经网络还是可以的,这个方向值得折腾。
最后分享一个小技巧:如果你觉得每次烧录程序都要插拔USB线很麻烦,可以在ESP32的EN引脚和GND之间接一个按钮,按一下就能复位,省得反复拔插。这个改动很小,但用起来舒服很多。