ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

ESP32自平衡小车实战:从硬件到PID整定与蓝牙控制

ESP32自平衡小车实战:从硬件到PID整定与蓝牙控制 简介基于ESP32的双轮自平衡小车开源资源定位为单片机/机器人领域的完整DIY方案适合电子爱好者、嵌入式初学者及想要快速上手平衡车项目的开发者。项目以SimpleFOC库实现双轮闭环控制支持自平衡、前进后退、转弯以及手机蓝牙在线调整PID参数结构上无需打板除4个3D打印件外其余零部件均可从淘宝等渠道采购组装工具仅需电烙铁、老虎钳、剥线钳等常规工具。压缩包共10个文件、约1.5MB主要包含VSCODEPlatformIO工程下的C源码280多行带详细注释、IGES三维结构源文件、工程图PDF内含BOM清单、引脚接线表Excel、预览图片及说明文档类型覆盖代码、机械、文档三大类对照接线表即可点对点完成连接。已有208人学习下载。这套资源将机械电子代码完整开放不仅提供可直接装配的零件清单和打印件设计还清晰展示了SimpleFOC闭环控制与蓝牙PID调参的实现思路适合用来学习平衡车原理、改造硬件或进一步优化控制算法。 做自平衡小车这件事我前前后后折腾了大概三个月。从最初用Arduino Uno搭面包板到后来彻底转到ESP32平台中间踩过的坑比代码行数还多。之所以最终选择ESP32是因为它在一颗芯片上同时解决了控制、通信和扩展三个问题——不用再像以前那样MCU负责算PID还得外挂一个蓝牙模块跟手机通信接线一多故障点就成倍上涨。这篇内容我会把基于ESP32的自平衡小车从硬件选型、姿态解算、PID整定到蓝牙控制的全过程以及那些文档里不会写的坑一次性说清楚。适合手里已经有一块ESP32开发板、想动手做个能跑的平衡小车的朋友也适合那些已经在玩Arduino平衡车、想升级到ESP32平台的玩家。1. 为什么是ESP32而不是Arduino或树莓派先说结论做自平衡小车ESP32的性价比高得有点不讲道理。Arduino Uno能跑但资源太紧张。MPU6050的I2C读取、互补滤波运算、PID计算、PWM输出这四件事同时跑起来ATmega328P的16MHz主频已经捉襟见肘。更头疼的是Uno板载的MPU6050模块通常是直接插在排针上的振动一大接触不良就开始飘零漂移小车直接躺平。树莓派则是另一个极端——算力过剩但实时性反而成了问题。Linux系统调度不确定性导致PWM输出抖动加上启动时间长、需要SD卡、功耗高做自平衡小车是杀鸡用牛刀还用得不太顺手。ESP32的优势在于它恰到好处地卡在中间240MHz双核一个核专门跑控制环路另一个核处理蓝牙和WiFi通信互不干扰内置蓝牙经典版和BLE和手机APP通信不需要额外模块16通道LEDC PWM频率可调驱动电机模块很干净丰富的外设接口I2C、SPI、UART随便用后续加OLED显示屏、编码器都不需要改板子我用的是ESP32 DevKitC V4开发板某宝上二十几块钱板载CP2102串口芯片插上USB线就能烧录。这个板子对新手最友好因为它的引脚都引出来了杜邦线直接插就行不用自己焊接。选型上的另一个考量是生态。ESP32的Arduino支持非常成熟esp32 by Espressif Systems这个开发板包安装后可以直接用Arduino框架开发语法和标准Arduino几乎一样。对于从Arduino迁移过来的开发者学习成本几乎为零。2. 硬件搭建花最少的钱搭最稳的车架2.1 物料清单与选型逻辑自平衡小车的物料说简单也简单说讲究也讲究。从零开始的话下面这份清单可以直接照抄部件型号/规格参考价选型理由主控ESP32 DevKitC V4约25元双核、内置蓝牙、性价比高姿态传感器MPU6050模块带稳压约8元六轴数据、例程多、资料全电机驱动TB6612FNG模块约15元效率高、发热低、支持3.3V逻辑电平减速电机N20微型减速电机带霍尔编码器约12元/个体积小、扭矩够、编码器可测速电池18650锂电池×2串联7.4V约20元容量大、放电倍率够、便宜车架亚克力板三层结构约25元坚固、好打孔、层与层之间方便固定轮胎直径65mm橡胶轮约10元/个抓地力好降低打滑导致的震荡电源模块MP1584降压模块约5元DE一步到3.3V给ESP32和传感器供电整体预算控制在150元以内而且每个部件的选型都有讲究不是随便凑的。电机这里多说一句。N20电机是很常见的微型减速电机自带霍尔编码器一圈可以输出好几百个脉冲。编码器的作用有两个测速做速度环闭环以及在没有编码器数据时的姿态修正参考。有条件的建议选带编码器的版本没条件的话用那种普通N20也能玩只是速度环做不了控制效果会糙一些。电机驱动的选择上TB6612比大家更常见的L298N好用太多。L298N的压降大发热严重而且逻辑电平需要5V和ESP32的3.3V GPIO直接通信会有电平不匹配的问题你还得额外接线。TB6612的逻辑电平支持2.7V到5.5V正好覆盖3.3V可以直接连。2.2 接线方案与电源分配接线是整个项目里最容易出错也最容易被忽略的部分。我的接线方案是这样的电源部分先把两节18650串联成7.4V这个电压直接给TB6612供电作为电机电源。然后用MP1584降压模块把7.4V降到5V再经过板载稳压给ESP32供电。注意MPU6050最好从ESP32的3.3V引脚取电不要直接挂在5V上否则容易烧模块。控制信号部分TB6612的两个PWM输入PWMA、PWMB接ESP32的GPIO16和GPIO17两个方向控制AIN1、AIN2、BIN1、BIN2接GPIO18、GPIO19、GPIO21、GPIO22。MPU6050的SDA接GPIO21SCL接GPIO22注意ESP32的这两个引脚默认有上拉电阻I2C通信不需要外接上拉也能跑。这里有个极易踩的坑ESP32的GPIO引脚不是所有都适合输出PWM。比如GPIO34到GPIO39是纯输入引脚不能接PWM输出。还有GPIO12是RTC域引脚上电瞬间会读取它的电平来决定Flash电压如果外部电路把这个引脚拉高了可能导致Flash工作异常板子直接起不来。我第一次做的时候就把PWM接到了GPIO36上折腾了一个晚上才发现问题后来查引脚定义才明白是怎么回事。电机电源和逻辑电源最好共地——就是TB6612的GND、ESP32的GND、MP1584的GND全部接到一起。不共地的话PWM信号无法形成回路电机会毫无反应或者不听使唤地抖动。2.3 车架的安装顺序车架的组装顺序也会影响调车效率。建议按照底层装电机→中层装电池和驱动→顶层装ESP32和传感器的顺序来。底层电机安装时两个电机的轴线必须在同一水平线上而且与车架长边保持绝对对称。如果左右电机前后错位小车静止时就会出现持续的修正力矩PID输出会有一个恒定的偏置跑起来一边转一边冲。MPU6050的安装位置也很关键要尽量安装在车体的中心轴线上也就是两个轮子的中垂线上。这样姿态计算时加速度计测到的重力分量不会因为安装偏心而产生额外的切向分力滤波效果会干净很多。很多教程不会提这个细节但实际调车时差别很大。3. 姿态解算互补滤波比卡尔曼更实用3.1 为什么非要滤波MPU6050能输出三轴加速度和三轴角速度但直接用这些原始数据是不行的。加速度计在静止时很准但小车在运动时电机振动和加减速产生的惯性力会叠加到重力加速度上导致角度计算出现尖刺噪声。而陀螺仪虽然动态响应快、短时间精度高但存在零漂问题——积分时间一长角度缓慢漂移这个漂移不修正的话小车会在几十秒内逐渐偏向一边直到倒下去。两种传感器正好互补加速度计是低频准、高频噪陀螺仪是高频准、低频漂。把它们结合起来就是姿态解算的核心思路。3.2 互补滤波的完整算式互补滤波的公式看起来简单实际调起来有讲究angle 0.98 * (angle gyro_y * dt) 0.02 * accel_angle这里gyro_y是绕Y轴车身左右方向的角速度dt是采样周期accel_angle是从加速度计算出的倾角。代码实现其实很短float complementaryFilter(float accel_angle, float gyro_rate, float dt) { static float angle 0; float alpha 0.98; angle alpha * (angle gyro_rate * dt) (1.0 - alpha) * accel_angle; return angle; }alpha取0.98表示98%信任陀螺仪的积分结果2%信任加速度计的修正。这个比例决定了滤波的响应速度和对噪声的抑制程度。alpha太大会导致角度修正慢小车起步时反应迟钝alpha太小则角度噪声大电机嗡嗡响一直小幅抖动。我实测下来采样频率控制在1kHz左右时alpha取0.96到0.99都比较合适。但注意这个值是跟采样频率强相关的换了采样频率必须重新调否则滤波特性会完全变掉。3.3 陀螺仪零漂的软件修正互补滤波能抑制漂移但无法完全消除。因为加速度计的修正权重只有2%如果陀螺仪的零漂太大积分出来的角度和实际角度的差值会恒定在某个偏置上。解决方法是开机后让小车保持静止一到两秒采集100到200组陀螺仪Y轴数据取平均值作为零偏以后每次读取后先减去这个零偏再积分void calibrateGyro() { const int samples 200; float sum 0; for (int i 0; i samples; i) { sum readGyroY(); delay(5); } gyroOffset sum / samples; }这里还有个细节主板和传感器上电到稳定工作需要一个短暂过程开机立即校准的话第一个读到的数据可能偏大。稳妥的做法是上电后等500ms再开始采样。有人在这个校准过程中用手扶着车有人把车放倒其实差别不大因为陀螺仪对重力不敏感水平和垂直的零偏是一样的。4. PID控制直立环、速度环和转向环的分工与整定4.1 三个控制环各管什么事自平衡小车的控制本质上是一个多环嵌套系统。拆开看就是三个环直立环最内层根据车身倾角控制电机让小车保持直立。这是整个系统的基础环。速度环中间层根据小车速度微调直立环的目标角度让车能前进、后退或者保持静止不溜车。转向环最外层根据转向目标控制左右轮差速实现转弯。我在项目里用的是双环结构直立环加速度环转向环单独用比例控制做差速。直立环的PID输出计算公式是pid_p Kp * angle // 比例项对应恢复力矩 pid_d Kd * gyro_rate // 微分项对应阻尼注意直立环只用PD不用I。原因在于比例项已经能让系统回正积分项反而会带来超调。系统里也不需要积分来消除稳态误差因为期望角度是0。速度环用PI控制speed_pid Kp_speed * speed Ki_speed * speed_integral速度环的输出不是直接给电机而是叠加到直立环的目标角度上——想让小车前进就把期望倾斜角度设为一个正值车往前倾直立环就会让电机往前追车就跑起来了。4.2 PID参数整定的具体流程整定PID参数是自平衡小车项目里最磨人的环节。网上有很多公式和方法但实际操作中我的经验是分成三步走。第一步整定直立环的Kp。先把Kd和速度环系数全部设为0Kp从一个很小的值开始加。每加一点用手扶着车感觉车体的刚度。当车体开始能够对抗重力、有一个明显的恢复力时就差不多到了可用区间。之后慢慢加大Kp直到车体在无外力下能维持直立震荡3到5秒。第二步整定直立环的Kd。Kd的作用是增加阻尼抑制震荡。从小到大加Kd直到手的感受是车变软了——就是震动幅度明显变小车体不再抖。这里有个度Kd太大会导致高频噪声放大电机会发烫声音尖锐。第三步加入速度环。速度环的Kp_speed比较小的时候就能起作用主要作用是让车能在平衡状态下慢慢修正位置。先加Kp_speed观察车是否在平衡点附近缓慢漂移再加Ki_speed消除稳态偏移。速度环的响应速度要比直立环慢得多否则两个环会互相干扰甚至共振。我的实际初始参考值直立环Kp在60到100之间取决于PWM满量程Kd在2到5之间速度环Kp_speed在0.1到0.3Ki_speed在0.01到0.05。这些值是针对我的车架、电机和控制周期采样频率1kHz而言的换一套硬件值会变但整定顺序和方法是通用的。4.3 控制频率和任务调度的坑ESP32的Arduino框架里loop()里跑控制逻辑默认情况下会被WiFi和蓝牙任务抢占导致控制频率不稳定。我在调试时就遇到过这个问题小车刚上电时正常一旦手机连上蓝牙车体就开始抖动测量发现控制周期从1ms变成了5ms波动。解决方法是使用xTaskCreatePinnedToCore把控制任务单独绑定到一个核心上比如核心1设置高优先级关掉看门狗或者给它足够周期的喂狗操作void controlTask(void *parameter) { while (1) { // 读取MPU6050、姿态解算、PID计算、PWM输出 vTaskDelay(pdMS_TO_TICKS(1)); // 1kHz控制频率 } } void setup() { // ... xTaskCreatePinnedToCore(controlTask, ControlTask, 4096, NULL, 2, controlTaskHandle, 1); }蓝牙、WiFi、OLED刷新这些非实时任务放核心0跑互不干扰。这一步是很多开源代码里没有做好但在实际应用中必须做的事情特别是你要在同一个ESP32上跑micro-ROS或者同时用蓝牙的时候。5. 蓝牙APP控制与状态反馈5.1 通信协议设计从手机控制自平衡小车收发数据需要一个简单可靠的协议。我用的方案是经典蓝牙SPP也就是串口透传模式。手机上装一个Serial Bluetooth Terminal之类的APP就能直接通过蓝牙模块收发数据。协议的格式没有什么神秘的地方关键是定义清晰。我定义的是一个两字节的指令帧字节0: 0xAA帧头 字节1: 控制指令或角度数据控制指令定义如下0x01: 前进0x02: 后退0x03: 左转0x04: 右转0x05: 停止0x10 ~ 0x1F: 目标角度设定映射到-45度到45度代码里用状态机解析这些指令每次只读取一帧避免粘包问题。ESP32收到帧头后进入等待状态等下一字节到了就组成完整指令执行对应动作。5.2 状态数据回传除了接收指令ESP32也在不断把运行状态发给手机。数据格式是Key-Value形式的字符串例如angle:0.32,speed:15,pid_p:12.3,pid_d:0.8,battery:7.2手机上可以看到实时角度和PID输出。这个回传功能在调试阶段是无价之宝——你可以把手机放在旁边观察小车在各种扰动下的角度响应和PID输出是否在合理范围内。我开始做的时候没有这个功能全靠串口监视器看数据车一跑起来就没法看串口了非常被动。提示调试时优先用有线串口监视器USB连接电脑看高频数据蓝牙主要用于遥控和查看低频状态。串口的波特率拉到115200蓝牙传输的波特率最好也保持一致否则两端的数据会乱码。5.3 micro-ROS方向的扩展考虑这几年ROS2的生态发展很快很多做机器人的朋友开始研究在MCU上跑micro-ROS。ESP32的算力足以支持micro-ROS节点斯坦福和社区里都有基于ESP32的micro-ROS开源项目。如果你的目标是做更复杂的机器人项目比如搭建一个多传感器融合的小型机器人平台那么可以考虑把自平衡小车作为micro-ROS的一个执行器节点主控电脑通过WiFi和ESP32通信接收里程计数据下达速度指令。这个方向的坑也不少主要是IDF版本的兼容性和VSCode PlatformIO环境的配置。但作为进阶方向它能让这个小小的平衡车直接接进ROS生态变成一个非常灵活的教学平台。6. 常见故障的排查链路6.1 小车在静止时持续抖动这个问题我排查了很久。现象是上电后小车能站起来但车身一直轻微颤抖电机有嗡嗡声。第一步检查PID输出是否饱和。我把PID输出和PWM限幅值打印出来对比发现Kd项在高频振动下输出很大几乎占满了整个PWM量程。问题出在DX数据噪声上。第二步检查MPU6050的读取是否有异常值。在控制循环里打印原始的gyro_y值和滤波后的angle值发现gyro_y偶尔会出现一个远大于正常范围的跳变那个跳变被PID的D项放大后就变成了高频率的电机抖动。第三步定位跳变来源。最终发现是杜邦线过长电机启停瞬间的电流变化在I2C线路上感应出了噪声。解决方法是把I2C线换成尽量短的线并且让它们远离电机电源线。如果降低控制频率到500Hz还抖那大概率是Kd过大了先把Kd降回来再查硬件。6.2 小车一上电就往一个方向猛冲这个现象的常见原因是MPU6050的初始角度没校准好或者安装方向反了。第一次做的时候我把MPU6050反着装angle的符号逻辑全反了PID输出变成了正反馈——车身越往一边倒电机越往同方向加力车自然就冲出去了。如果是往一个方向匀速加速而不是猛冲先看加速度计在水平状态下的输出是否约等于重力加速度g。不等于的话要么是安装倾斜了要么是MPU6050的量程设置错了。我习惯设置加速度计量程为±2g陀螺仪量程为±250°/s对应16位ADC输出的灵敏度分别是16384 LSB/g和131 LSB/(°/s)算角度的时候系数错了也会出现类似现象。6.3 车能平衡但缓缓朝一个方向漂移车能站起来但慢慢朝一个方向走这个基本就是陀螺仪零漂没校准干净。重新运行校准校准过程中确保车体完全静止——哪怕是手轻微的颤抖都会把抖动带进零偏值。另外检查一下速度环的积分项。如果Ki_speed设置得太大速度积分会快速累积积分饱和后输出一直顶着上限小车就会持续朝一个方向加速。解决方法是加积分限幅把speed_integral的值限制在某个范围内比如PWM满量程的10%到20%。7. 开源发布与资料整理硬件方案、固件代码、手机APP和结构件图纸我整理后都放到了GitHub上。存储库里的内容包括ESP32 Arduino完整工程、3D打印车架结构件STL文件、手机蓝牙遥控APP源码、完整的接线图和PID整定记录表格。开源的意义不只是分享代码而是让后来的人能少走弯路。所以代码里的注释我写得比较详细尤其是PID参数整定那一节把每一组测试的硬件配置、现象、调整方向都记录了方便别人复现和调整。如果你也想做这个项目我建议的下手路径是先照着完整的接线图搭好硬件烧录我提供的默认固件让车先站起来。站起来之后再动PID参数慢慢调这个过程比从零开始调要快很多。等你有感觉了再考虑修改速度环参数、加入转向控制、甚至迁移到micro-ROS方向。做自平衡小车最折磨人的阶段是车一直倒、PID参数调得你怀疑人生的时候。但当你第一次看到它在没有任何外力的情况下稳稳地立在那里那种感觉值得之前的每一个加班的夜晚。如果你在做的过程中卡住了——无论是硬件接线、姿态解算还是PID整定——欢迎在评论区把现象发出来我会根据调试经验帮你分析方向。本文还有配套的精品资源点击获取
返回列表