简介:这是一份基于ESP32的双轮自平衡小车完整开源资料,面向单片机爱好者、创客及机器人初学者,解决从零搭建闭环自平衡小车的硬件选型、结构组装与代码调试问题。资源共10个文件,压缩包约1.5MB,包含IGES三维结构源文件、PDF工程图与BOM清单、PlatformIO工程源码(.cpp/.ini)及接线表(xlsx),并附有装配图片和说明文档,无需打板,除3D打印件外其余部件均可采购。程序基于SimpleFOC库实现闭环控制,支持自平衡、前进后退、转弯及手机蓝牙调PID,代码带注释简洁易读。已有208人学习下载,适合想快速上手两轮自平衡项目、希望同时获取机械电子代码全套方案的开发者参考改进。 从来没想过,一个“ESP32自平衡小车开源”的项目标题,能把我从硬件选型一直折腾到ROS 2通信,最后还顺手把手机蓝牙App和LVGL界面给做了。如果你也想做一个能拿得出手、可以长期玩下去的嵌入式作品,这个项目非常适合入手。我会把自己从立项到收尾的全过程、踩过的坑、调参经验,一次性整理出来,尽量让后来的人少走弯路。
先说结论:自平衡小车不是简单“倒不了”就完事,它背后涉及姿态解算、闭环PID、电机驱动、机械重心设计,甚至还要处理电源波动和无线遥控干扰。用ESP32来做主控,是因为它性能足够、外设丰富、社区资料多,而且能轻松扩展出蓝牙、Wi-Fi、LVGL、micro-ROS等功能。这篇就跟着我当时的实现路径,把每个关键环节都拆开讲清楚。
1. 先聊清楚:这个开源项目到底解决什么问题
1.1 平衡车原理:一个简化版倒立摆
很多人第一次看到平衡车时,都会觉得它“自己站起来了”很玄乎。本质上它就是一个移动的倒立摆模型:车身重心在轮轴上方,静止状态下这个状态是不稳定的,任何微小扰动都会让车身倒下。想要让它站起来,只能让车轮不断向车身倾倒的方向移动,把重心重新“接住”。
数学上,这个系统可以简化成力矩平衡问题。车身倾角记为θ,重力会产生一个让车身继续倾倒的力矩mg·l·sinθ;如果我们给车轮一个水平加速度a,惯性力就会产生一个反向力矩ma·l·cosθ。当两者平衡时,车身就能保持动态稳定。控制算法的目标,就是实时计算应该给车轮多大的加速度(也就是PWM占空比)来“抵消”倾倒趋势。
所以平衡小车本质上和无人机是一类问题——传感器、姿态解算、运动控制三者必须紧密配合,任何一个环节delay都会让系统发散失控。这也是为什么我特别建议初学者先用手上现有的模块搭原型,而不是一上来就设计PCB,因为机械、传感器、软件之间的配合误差才是最花时间的地方。
1.2 为什么选ESP32而不是Arduino Uno或STM32
先看图:Arduino Uno的主频只有16MHz,内存2KB,跑一个卡尔曼滤波都费劲,加上无线模块基本就顶不住了。STM32F103倒是够用,但开发环境配置、调试器、引脚复用这些东西对新手来说门槛偏高,而且后续想加蓝牙、Wi-Fi、LVGL触摸屏,成本和时间都会明显增加。
ESP32的优势主要体现在这么几点:
- 双核240MHz,一个核跑控制loop,另一个核跑蓝牙/Wi-Fi/屏幕刷新,互不干扰。
- 内置Wi-Fi和蓝牙,手机App遥控、OTA升级都变成“顺手的事”。
- 有成熟的Arduino框架支持,也有ESP-IDF可以做生产级开发,无论是学习还是进阶都通畅。
- 外设丰富,LEDC硬件PWM、PCNT脉冲计数、I2C、UART这些平衡车需要的资源全部内置。
我用的是常见的ESP32 DevKitC(WROOM-32芯片,30 pin版),某宝几十块钱就能拿下,开源硬件首选。不过提醒一句,ESP32的ADC是非线性的,如果你后面想用电池电压做补偿,不建议直接用内置ADC读电池,实在要读就得做软件校准或者外接高精度ADC。
2. 硬件搭建与选型:清单、接线、避坑
2.1 核心器件清单与选型理由
我这条开源车用的是以下方案,成本不高但稳定性相当不错:
| 模块 | 型号/规格 | 作用 | 选型理由 |
|---|---|---|---|
| 主控板 | ESP32 DevKitC(WROOM-32) | 传感器读取、控制算法、无线通信 | 性能强、外设丰富、社区资料多 |
| 姿态传感器 | MPU6050六轴模块 | 测量加速度与角速度 | 便宜、通用性强、资料多 |
| 电机驱动 | TB6612FNG模块 | 驱动两个直流减速电机 | 体积小、压降小、比L298N效率高 |
| 电机+编码器 | 带AB相霍尔编码器的直流减速电机(1:30减速比) | 输出动力并反馈转速 | 编码器用于速度闭环和轮速测量 |
| 电源 | 2节18650串联(7.4V)+ 5V稳压模块 | 给电机和主控供电 | 容量大、供电稳定、方便车载 |
| 轮胎与车架 | 直径约65mm橡胶轮 + 亚克力或3D打印底盘 | 支撑与运动 | 轮径大一点,对小颠簸更友好 |
有一点必须强调:电机驱动不要用L298N。L298N的饱和压降大,在7.4V电池下实际到电机端的电压可能只有6V左右,而且发热严重,稍微带点负载就烫手。TB6612FNG的MOSFET压降低,体积小,体感上用起来完全是两个世界。如果你手头只有L298N,也不是不行,但电池电压最好提高到9V以上,否则直立环的动态响应会明显不够。
2.2 接线布局与机械结构建议
我的接线参考如下(ESP32 30 pin版):
- MPU6050:VCC接3.3V,GND接GND,SCL接GPIO22,SDA接GPIO21。
- TB6612FNG:VM接电池正极,VCC接5V,STBY接3.3V或5V,AIN1/AIN2接GPIO26/25,BIN1/BIN2接GPIO33/32,PWMA/PWMB接GPIO27/14。
- 左右电机编码器:我用的电机编码器A相/B相接在ESP32支持的PCNT引脚上,比如左轮编码器接GPIO15/16,右轮编码器接GPIO34/35区域,具体看你使用的IDF和Arduino配置。
这里有个非常重要的机械细节:电池尽量放在底盘正中央靠下的位置,重心越低越好。我之前第一版把电池竖着绑在支架顶部,结果传感器稍微动一下,车身就左右摇摆,调参怎么调都调不稳。后来把电池横过来贴在底盘底部,整车平衡明显改善,调参也容易了很多。重心高会让系统等效摆长变长、转动惯量变大,响应迟钝,不是好事。
另外建议给底盘加一个万向滚珠或者小尾轮辅助防摔,但这会影响“纯两轮”的观感,我开源版本里提供了两种底盘的装配方案,你可以自己选择。
3. 软件实现:从传感器姿态解算到闭环控制
3.1 姿态解算:MPU6050读取与互补滤波
平衡车需要知道车身倾角,这个角度来自于MPU6050的加速度计和陀螺仪。加速度计在静止时能给出很准确的重力方向,但动态时毛刺很大;陀螺仪短时间很准,但积分久了会漂移。两者正好互补,最常用的做法就是互补滤波,比卡尔曼滤波实现简单得多,而且在这个场景下精度足够。
我实测下来,互补滤波的α系数取0.98、控制周期5ms左右,效果已经很稳。公式大概是:
float dt = 0.005; // 200Hz控制周期 float rate = gyroZ; // 绕前后轴的角速度,具体符号看传感器安装 float accAngle = atan2(accY, accZ) * 180 / PI; angle = 0.98 * (angle + rate * dt) + 0.02 * accAngle;这里的angle就是车身倾角,rate是角速度。很多教程喜欢用MPU6050的DMP固件输出四元数再转欧拉角,但DMP对初学者来说就是个黑盒,出了问题很难排查,所以我开源版本里是直接读原始数据,用互补滤波。这样每一行代码你都看得懂,后面如果要做扩展也方便。
需要注意的是,传感器读取和滤波都要放在高优先级循环里,不能和显示、蓝牙这些耗时操作混在一起。ESP32双核正好派上用场:核心0跑Wi-Fi和蓝牙协议栈,核心1跑控制循环,互不干扰。
3.2 PID控制结构:直立、速度、转向三个环
真正的平衡车控制不是简单PD就完事,而是三环嵌套:
- 直立环(角度环):输入车身倾角和角速度,输出是消除倾角所需的加速度量,核心是PD控制。
- 速度环:输入当前轮速,目标是让车平均速度稳定在0或遥控目标值,核心是PI控制,用于抑制车体朝一个方向漂移。
- 转向环:输入绕Z轴的角速度(偏航速率),输出左右轮差速,用于转向和抗外部扭转,核心是PD控制。
三个环从内到外优先级是:直立环 > 速度环 > 转向环。输出叠加到左右电机的PWM上。我的简化主循环框架大致如下:
void loop() { imuUpdate(); // 读取MPU6050,互补滤波得到angle、rate balancePWM = pid_balance(angle, rate); // 直立环PD speedPWM = pid_speed(wheelSpeed, targetSpeed); // 速度环PI turnPWM = pid_turn(gyroZ); // 转向环PD leftMotor = balancePWM - speedPWM + turnPWM; rightMotor = balancePWM - speedPWM - turnPWM; motorApply(limit(leftMotor), limit(rightMotor)); // 限幅输出 }直立环是整个系统的主环,PD控制的含义是:倾角越大,输出的补偿加速度越大;角速度越大,越要“拉住”它防振荡。速度环的输出通常是负反馈到直立环上,这一点很多人容易搞混。如果速度环符号反了,起步时会发现车不仅不往前走,反而猛地往后倒,一种简单排查方法就是:把车控制在直立状态附近,用手往前推一下,如果车不仅不跟上反而越退越快,说明速度环极性反了。
3.3 编码器测速与PWM生成
电机编码器是速度环的数据来源,我用的是AB相霍尔编码器,通过读取两路脉冲的相位差判断旋转方向。ESP32的PCNT外设是硬件计数器,比直接在loop里用digitalRead中断靠谱得多。在Arduino框架下初始化PCNT后,只需要定时读取count值再换算成转速就行。
PWM输出方面,ESP32用的是LEDC(LED控制外设),本质上就是硬件PWM通道。我用的是8位分辨率,频率1kHz左右,滤波电容配合电机本身感抗,基本能避免啸叫。很多新人在这个环节踩坑:LEDC初始化后忘了写ledcWrite初始值,输出悬空,结果电机乱转。建议上电时先给PWM通道清零并启动电机驱动STBY脚,然后再进主循环。
4. 调参实操:从抖动到稳定的完整路径
4.1 调参顺序与观察要点
自平衡车的调参看起来玄学,实际上有一定的流程。我推荐下面这个顺序,几乎适用于所有车体:
- 先调直立环Kp。Kp从小往大加,加到车身能“顶住”倾倒、并在中点附近来回摆动为止。这个过程中不要管振荡,就观察有没有“恢复力”。
- 再调直立环Kd。Kd从0开始逐渐加,用来抑制刚才的振荡。当车体能比较平稳地维持直立,且用手拨一下会快速回正、不会持续抖动时,直立环基本完成。
- 接着调速度环。先让车在手里悬空,转动轮子观察左右轮反馈方向是否正确;然后把车放地上,看它是否朝一个方向持续漂移。速度环的Kp负责“回正”,Ki负责消除静态误差。
- 最后调转向环。可以用手绕底盘中心扭转,观察会不会受到一个反向抵抗。转向环增益不用太大,否则原地打转或行驶中“画龙”。
我给的参考起始参数(8位PWM、控制周期5ms、电池7.4V、电机减速比1:30):
| 环节 | 参数 | 参考起始值 |
|---|---|---|
| 直立环 | Kp | 130 |
| 直立环 | Kd | 0.6 |
| 速度环 | Kp | 5 |
| 速度环 | Ki | 0.08 |
| 转向环 | Kp | 3 |
| 转向环 | Kd | 0.01 |
注意:这些参数强烈依赖你的机械结构。重心高一点、轮径大一点,参数就会明显变化。如果发现车体高频抖动,多半是Kd太大或传感器噪声太大,别盲目继续加Kd,先回查滤波是否到位。
4.2 调参工具与实时可视化
裸调参数非常痛苦,我开源版本里直接内置了一个“参数无线调试”功能:ESP32通过蓝牙广播一个简单协议,手机端用一段串口蓝牙助手或者写个简单App,就能实时改PID,还能看车体倾角、目标PWM、实际速度等数据。这样你不需要每次都重新接线刷固件,肉眼看着车体反应来调参,效率提升至少三倍。
如果条件允许,建议用ESP32的Wi-Fi + WebSocket把实时波形画到浏览器里。我试过用串口绘图仪和自制的网页波形两种方式,网页波形的调试体感是最好的,能看到直立环输出和倾角的相位关系,判断是否出现延迟过大、符号反相、饱和限幅等问题。
我个人用下来,一个关键心得是:调参前先确认你的传感器安装方向和控制方向一致。如果MPU6050装反了,你会觉得Kp怎么加都没有效果,甚至越加越坏。可以用一个最简单的方法判断:拿起小车,保持静止倾角10度左右,看程序里打印的angle是否为+10(或-10,只要符号在逻辑上一致)。
5. 常见问题与排查技巧速查
5.1 高频故障的调试思路
我把自己和群友在调车过程中遇到的典型故障整理成了一张速查表:
| 现象 | 可能原因 | 定位与处理 |
|---|---|---|
| 上电没反应,电机不转 | 驱动板没使能(STBY脚未拉高)、PWM初始值为0、电池没接对 | 检查STBY电平,程序开头先写PWM清零,再用万用表测电池电压 |
| 车身一上电就朝一个方向加速倒下 | 角度符号反 / 直立环输出极性反 | 手动倾斜车身,观察PWM跟随方向,取反角度或取反Kp |
| 车身持续高频抖动、嗡嗡响 | Kd过大、传感器噪声大、机械间隙大 | 降低Kd、提升滤波平滑度、检查轮轴和联轴器框量 |
| 车身直立但缓慢向一边漂移 | 速度环没生效、重心偏、编码器数据异常 | 打印左右轮速度,确认速度环积分方向;调整重心配平 |
| 电池用一会就失控 | 电池电压跌落导致PWM增益变化、限幅饱和 | 增加电压补偿或换更高C数电池,检查限幅逻辑 |
| 蓝牙连接断断续续 | 蓝牙协议栈占资源与控制loop冲突 | 把蓝牙操作放到另一个核心或降低蓝牙数据频率 |
排查思路永远是先机械后软件、先开环后闭环。如果怀疑机械有问题,直接把车放平,用手转动轮子感受阻力是否均匀,再逐环节排查传感器、控制量输出、电机驱动。
5.2 几个容易被忽略的细节
这里补充几个在开源分享里不常见、但非常影响结果的小点:
- MPU6050模块上的VCC不能直接用5V供电。新买的一些模块带稳压电路,但老版本没有,直接5V进去很可能会烧芯片,读出来的数据全是乱码。ESP32的3.3V引脚供电就够了。
- 编码器线材要用屏蔽或者双绞线,尤其是PWM电机线离得近时,干扰非常明显。我第一次接线太随意,编码器数据在电机加速时乱跳,速度环直接崩溃。
- 电池电压波动对直立环影响很大。电池从8.4V掉到7V附近时,相同占空比下电机实际电压差距很大,你会发现车体越跑越软。我后来在程序里用ADC采样电池电压,对PWM输出做了线性补偿,整车在电量变化下也能保持稳定。
- 电机如果带金属齿轮,运行时间长了有轻微磨损,会引入齿隙(backlash)。齿隙会让编码器读数在换向瞬间出现“假反应”,这种情况只能靠控制周期和滤波来缓解,做不到完全消除。
6. 开源项目结构与后续扩展
6.1 开源仓库里放了什么
我这个项目的开源仓库不只是代码,还包括完整的设计资料,这样别人拿到就能复现。目录结构大概如下:
firmware/ esp32_balance_car.ino // Arduino工程,包含PID、互补滤波、蓝牙协议 config.h // 引脚配置、PID参数宏定义 readme.md // 编译烧录说明 hardware/ chassis.stl // 3D打印车架结构 wiring_diagram.pdf // 接线图 docs/ tuning_guide.md // 参数调试指南 troubleshooting.md // 常见问题排查 app/ ble_remote_demo // 手机端蓝牙遥控参考代码代码部分我刻意保持了极简风格,没有用复杂的操作系统,全部注释用中文,方便任何人直接拿去看、去改。整个控制器状态都放在一个结构体里,串口和蓝牙都能打印出来,这样就算你没有逻辑分析仪,也能用串口看到内部变量到底发生了什么。
6.2 从入门到进阶的功能扩展路径
这个项目做完之后,可玩性非常高,扩展方向也很多:
- 加一块OLED屏或者TFT屏,把倾角、PID输出、电池电压、速度显示出来。用LVGL写界面的话,ESP32的性能完全带得动。
- 接入micro-ROS,让小车变成ROS 2里的一个节点。网上有esp32 micro_ros_espidf_component这类组件,配合ROS 2 humble环境,可以实现上位机下发目标速度和角度,小车回传状态,这基本就是轮式机器人教学平台了。
- 加摄像头模块,后续做视觉巡线、巡人或者颜色识别,配合Wi-Fi传输图像。
- 做OTA升级:使用ESP32内置Wi-Fi实现固件远程更新,这样以后每次改代码都不用重新插USB线。我在调试后期就频繁用OTA,非常方便。
当你走完这一步,你会发现,表面上是“一辆能站起来的车”,实际上你已经亲手搭建了一套包含传感器融合、实时控制、无线通信、电源管理的完整嵌入式系统。后面再做无人机、机械臂、移动机器人,很多思路和模块都能直接复用。等你的车能达到“随手放手就能稳定直立5秒以上”的状态,你会发现之前所有的折腾都值了。
本文还有配套的精品资源,点击获取