ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

基于STM32的平衡小车实战:从硬件选型到串级PID调参全解析

基于STM32的平衡小车实战:从硬件选型到串级PID调参全解析 1. 项目缘起与整体方案敲定1.1 为什么选STM32做平衡小车做自平衡小车这件事我前前后后折腾过三轮。第一轮用Arduino Nano加MPU6050跑是能跑但串级PID一上主频和浮点性能立刻吃紧姿态环稍微调猛一点就抖成筛子。第二轮换了某国产M0内核的板子结果定时器资源不够编码器测速和PWM输出抢通道最后只能降级用单环凑合。第三轮我老老实实回到STM32F103C8T6才算把整个控制链路跑顺。STM32F103这颗芯片做平衡小车有几个实打实的好处。72MHz主频配合硬件浮点单元F4系列才有F1是软件浮点但定点运算也够用跑双环PID绰绰有余。它自带的高级定时器TIM1能输出互补PWM还带死区通用定时器TIM2到TIM5全部支持编码器接口模式这意味着两路编码器可以直接硬件解码不用外部中断里手动翻电平CPU占用率能压到很低。再加上12位ADC、多个USART、I2C硬件外设后面想加蓝牙调参、OLED显示、陀螺仪扩展都很从容。平衡小车的本质是一个倒立摆系统核心任务就三件事读姿态、算PID、驱动电机。听起来简单但每一环都有坑。姿态解算要处理陀螺仪零漂和加速度计噪声PID要处理积分饱和和微分噪声电机驱动要处理死区电压和正反转切换。我见过太多人卡在“车能站起来但一推就倒”或者“原地高频抖动”这两个状态上问题往往不在PID参数本身而在底层驱动和采样时序没做干净。1.2 JGB-520编码电机与TB6612的搭配逻辑JGB-520这个型号是直流减速电机里比较经典的一款520指的是电机外径52mm减速比常见有1:30、1:48、1:90几档。我手上这对是1:30的空载转速大概在330RPM左右额定电压12V堵转扭矩能到2kg·cm以上。它自带霍尔编码器一般是13线或11线配合1:30减速比输出轴转一圈能产生390到330个脉冲。这个分辨率对平衡小车来说刚好够用既不会因为脉冲太少导致低速测速跳变也不会因为脉冲太多让定时器溢出。TB6612FNG是东芝的双路H桥驱动芯片相比L298N它的导通内阻小得多典型值0.5Ω左右意味着大电流下发热明显更低。最大连续电流1.2A峰值3.2A驱动JGB-520这种空载电流200mA、堵转1A左右的电机完全在安全范围内。它支持PWM频率到100kHz实际用10kHz到20kHz就行太低会有啸叫太高开关损耗大。逻辑电平兼容3.3V和5VSTM32的3.3V GPIO可以直接驱动不需要电平转换。选TB6612还有一个隐性好处它每个通道有独立的PWM输入和方向输入不像L298N那样需要两个PWM做正反转。这意味着每个电机只占用一个定时器通道两个电机占两个通道剩下的定时器通道还能拿来做别的。而且TB6612的待机引脚STBY可以硬件急停调试时万一PID发疯拉低STBY就能立刻切断电机输出比软件刹车可靠得多。1.3 系统整体架构与信号流整个系统的信号流是这样的MPU6050通过I2C把原始加速度和角速度送给STM32STM32在定时器中断里做姿态融合得到俯仰角同时另一个定时器中断读取编码器计数算出车速主循环里跑串级PID——外环角度环、内环速度环输出值再经过PWM限幅和死区补偿最后写到TB6612的PWM寄存器。这里有个关键设计决策姿态解算和PID计算放在哪个频率跑。我的做法是姿态解算放在1kHz的定时器中断里保证角度更新足够快速度环放在100Hz因为车速变化本来就慢跑太快反而引入编码器量化噪声角度环放在200Hz兼顾响应和噪声。三个频率用不同的定时器分频得到互不干扰。电源部分我用的是3S锂电11.1V经过一个LM2596降压到5V给STM32和MPU6050供电电机直接吃11.1V。这里要注意电机启动瞬间电流冲击很大如果5V和电机共地处理不好MPU6050的I2C通信会随机丢包。我的做法是电机电源和逻辑电源在PCB上单点共地中间串一个磁珠每个电机的电源引脚并一个100nF加一个470uF电容。2. 硬件层面的关键细节与避坑2.1 电机与编码器的接线确认JGB-520出线一般是6根电机正、电机负、编码器VCC、编码器GND、编码器A相、编码器B相。颜色各家可能不同拿到手第一件事是用万用表蜂鸣档测哪两根是电机线——电机线之间电阻很小大概几欧到几十欧编码器线之间电阻很大或者有二极管压降。确认之后电机线接TB6612的AO1/AO2编码器线接STM32的定时器编码器接口。编码器VCC一定要接3.3V或者5V看编码器规格。JGB-520的霍尔编码器一般是5V供电但输出信号是开漏的需要上拉电阻。我直接在STM32端开内部上拉实测波形上升沿有点缓后来在编码器A/B相各加了一个4.7kΩ外部上拉到3.3V波形就干净了。如果你发现测速值偶尔跳变先检查上拉电阻这是最常见的坑。TB6612的接线要注意VM接电机电源11.1VVCC接逻辑电源3.3V或5VGND共地。STBY接STM32的一个GPIO初始化时拉高使能。AIN1/AIN2和BIN1/BIN2接方向控制GPIOPWMA和PWMB接定时器PWM输出。这里有个细节TB6612的PWM输入是高电平有效但如果你用的是STM32的高级定时器互补输出要注意极性配置否则会出现“给正转指令电机反而反转”的情况。2.2 STM32定时器资源分配STM32F103C8T6有4个通用定时器加2个高级定时器。我的分配方案是TIM1_CH1和TIM1_CH4输出两路PWM给TB6612频率设10kHzTIM2和TIM3配置为编码器接口模式分别接左右电机编码器TIM4做1kHz中断用于姿态解算TIM5做200Hz中断用于角度环SysTick做100Hz用于速度环和主循环任务调度。编码器接口模式有个容易忽略的点STM32的编码器模式有TI1、TI2、TI12三种计数方式。TI12是四倍频即A相和B相的上升沿下降沿都计数分辨率最高。但四倍频对信号质量要求也最高如果编码器线较长或者有干扰四倍频容易多计数。我一开始用TI12发现静止时计数值偶尔会自己加一后来改成TI1模式只在A相上升沿计数B相判断方向虽然分辨率降了一半但稳定性好了很多。对于平衡小车390脉冲每圈已经够用没必要追求四倍频。PWM频率选10kHz是权衡的结果。低于5kHz电机会有可闻啸叫高于20kHz虽然安静但TB6612开关损耗增加而且STM32的PWM分辨率会下降。10kHz时72MHz主频下ARR值设为7200-1分辨率足够精细。2.3 电源与抗干扰处理平衡小车对电源噪声极其敏感。电机PWM开关会在电源线上产生大量高频纹波如果这些纹波串到MPU6050的供电上陀螺仪读数会直接跳变。我的处理方案是三级滤波电机电源入口并一个470uF电解电容吸收低频波动再并一个100nF陶瓷电容滤高频5V逻辑电源用LM2596降压后再经过一个LC滤波10uH电感加100uF电容MPU6050的VCC引脚单独并一个100nF加一个10uF。地线处理也很关键。电机电流大地线上会有压降如果MPU6050和电机共用地线且走线较长地弹会导致I2C通信误码。我的做法是逻辑地和功率地在电源入口处单点连接MPU6050的地线直接连到STM32的GND引脚不经过电机地线路径。PCB布局时电机驱动部分和MCU部分分区放置中间用地线隔离带隔开。还有一个实测经验TB6612的VM引脚一定要并一个100nF陶瓷电容紧贴芯片引脚否则电机换向瞬间的电压尖峰可能超过芯片耐压值。我第一版没加这个电容跑了半小时后TB6612发热严重加了之后温度明显下降。3. 软件框架与核心算法实现3.1 工程结构与初始化顺序我的工程结构分四层硬件驱动层motor.c、encoder.c、mpu6050.c、oled.c、算法层pid.c、kalman.c或互补滤波、应用层balance.c、control.c、主函数。初始化顺序很重要必须遵循“时钟→GPIO→外设→中断”的顺序否则会出现外设配置不生效的问题。具体初始化流程先配置RCC使能所有用到的时钟包括GPIOA/B、TIM1/2/3/4/5、I2C1、USART1。然后配置GPIO注意编码器输入引脚要设为浮空输入或上拉输入PWM输出引脚设为复用推挽。接着配置定时器编码器接口模式要先配置TI1和TI2的输入捕获滤波再设置编码器模式。PWM输出要配置ARR和CCR注意先设置ARR再设置CCR否则第一个周期可能输出异常。最后配置NVIC中断优先级姿态解算中断优先级最高速度环最低。这里有个Keil5的坑如果你同时装了C51和STM32的芯片包有时候编译STM32工程会报“cannot open source input file”错误。原因是C51的路径被优先搜索了。解决办法是在Keil的Options for Target → C/C → Include Paths里把STM32的库路径移到最前面或者干脆在系统环境变量里把C51的路径删掉。3.2 MPU6050姿态解算与互补滤波MPU6050读出来的原始数据是加速度计的三轴分量和陀螺仪的三轴角速度。平衡小车只需要俯仰角所以用X轴加速度和Y轴角速度就够了。加速度计算角度用atan2(ax, az)但加速度计对振动极其敏感电机一转数据就跳。陀螺仪积分得到的角度短期准但长期漂所以要用互补滤波融合。互补滤波的公式很简单角度 α × (角度 陀螺仪角速度 × dt) (1-α) × 加速度计角度。α一般取0.95到0.98。dt是采样周期1kHz中断里就是0.001秒。这个公式的物理意义是高频段信任陀螺仪低频段信任加速度计。α越大陀螺仪权重越高角度越平滑但漂移修正越慢α越小加速度计权重越高角度跟随快但噪声大。我实测下来α取0.96比较合适。但有个细节MPU6050上电后陀螺仪有零漂必须在静止状态下校准。我的做法是上电后前2秒不输出PWM采集1000个陀螺仪样本求平均作为零偏后续读数都减去这个零偏。如果不做这一步车会一直往一个方向缓慢加速PID怎么调都站不稳。还有一个进阶做法是用卡尔曼滤波替代互补滤波。卡尔曼滤波理论上更优但计算量大F1系列跑起来有点吃力。我试过在F103上跑简化卡尔曼1kHz中断里耗时约80us还能接受。但调试卡尔曼的Q和R参数比互补滤波的α麻烦得多对于平衡小车这种应用互补滤波的性价比更高。3.3 编码器测速与速度计算编码器测速有两种方法M法固定时间测脉冲数和T法固定脉冲数测时间。平衡小车车速低M法在低速时分辨率不够T法在高速时精度下降。我用的是M/T混合法在100Hz速度环中断里读编码器计数同时记录两次中断之间的精确时间用脉冲数除以时间得到速度。具体实现TIM2和TIM3配置为编码器模式后CNT寄存器会随编码器脉冲自动增减。在速度环中断里读取CNT值减去上次的值得到增量然后清零CNT。注意STM32的编码器模式CNT是16位的如果脉冲数超过65535会溢出但平衡小车速度环100Hz最大速度下每周期脉冲数也就几百不会溢出。速度计算要注意单位统一。我最终用的是“脉冲数/秒”然后乘以一个系数转换成“米/秒”用于显示。系数怎么算轮子直径65mm周长约204mm编码器390脉冲每圈减速比1:30所以轮子转一圈对应390×3011700个脉冲如果是TI1模式则减半。每脉冲对应距离204mm/11700≈0.0174mm。这个系数用于OLED显示车速PID计算里直接用脉冲数就行不需要转换。3.4 TB6612驱动与死区补偿TB6612的控制逻辑很简单AIN1和AIN2决定方向PWMA决定占空比。AIN11、AIN20时正转AIN10、AIN21时反转AIN1AIN20时滑行AIN1AIN21时刹车。我一般用正转和反转两种模式滑行模式在平衡小车里会导致响应变慢。PWM输出用TIM1的CH1和CH4配置为PWM模式1极性高。CCR值从0到ARR对应占空比0到100%。但实际使用时要加死区补偿由于电机有启动死区电压PWM占空比低于某个阈值时电机根本不转。我实测JGB-520在11.1V供电下占空比低于8%时电机不转。所以PID输出要做一个映射如果输出绝对值小于死区阈值直接给0如果大于阈值则把输出从[阈值, 最大值]线性映射到[阈值补偿, 最大值]。死区补偿的代码逻辑是这样的假设PID输出范围是-7200到7200死区阈值是600。如果输出是500映射后还是0如果输出是1000映射后是600(1000-600)×(7200-600)/(7200-600)1000实际上就是线性缩放。更简单的做法是输出大于0时加600小于0时减600然后限幅到±7200。这样在死区附近会有轻微不连续但实际跑起来感觉不到。4. 串级PID调参实战指南4.1 串级PID的结构与作用平衡小车用串级PID外环是角度环输入是目标角度通常设为0度即直立反馈是MPU6050解算的当前角度输出是目标速度内环是速度环输入是角度环输出的目标速度反馈是编码器测得的实际速度输出是PWM占空比。等等这里有个容易搞混的地方。标准的平衡小车串级结构其实是角度环是内环速度环是外环。因为角度控制是快变量速度控制是慢变量。正确的信号流是速度环输入是目标速度通常为0反馈是实际速度输出是目标角度角度环输入是速度环输出的目标角度反馈是当前角度输出是PWM。我一开始也搞反了把角度环放外面结果车要么不动要么疯跑。后来想明白了速度环的输出应该是“我需要倾斜多少度才能达到目标速度”这个角度再送给角度环去执行。所以速度环是外环角度环是内环。4.2 角度环参数整定角度环是平衡小车的核心调好了车才能站住。角度环用PD控制就够了不需要积分。P决定回复力D决定阻尼。调参顺序是先调P再调D。调P的方法把D设为0P从0开始慢慢加。每加一次用手扶住车感受车抵抗倾斜的力度。P太小车软绵绵的推一下要很久才回正P太大车会高频抖动像得了帕金森。我实测F103加JGB-520这套配置P在80到120之间比较合适。具体值取决于电机扭矩和车重我的车重约500gP100时手感刚好。调D的方法P固定D从0开始加。D的作用是预测趋势在车还没倒之前就施加反向力。D太小车回正时会过冲来回摆几次才停D太大车会对噪声敏感电机发出“滋滋”声但不转。我实测D在0.5到1.5之间。D1.0时用手推车车会平滑地回正没有明显过冲。这里有个经验角度环的P和D要一起微调。加了D之后P可以适当加大一点因为D提供了额外阻尼。我的最终参数是P110D1.2角度环输出限幅±300对应目标速度范围。4.3 速度环参数整定速度环用PI控制不需要D因为速度反馈本身噪声就大再加D会放大噪声。速度环的P决定跟随速度的力度I消除稳态误差。调速度环之前角度环必须已经调好车能稳定站立。然后给速度环一个很小的目标速度比如10个脉冲/秒观察车的行为。速度环P从0开始加加到车能缓慢加速到目标速度且不抖动。我实测P在0.5到2之间。P1.0时车会以很慢的速度前进推一下会自己调整回来。速度环的I要非常小心。I太大车会前后振荡像在跳舞I太小车会一直往一个方向缓慢漂移。我实测I在0.01到0.05之间。I0.02时车能保持位置基本不变用手推一下会慢慢回到原位。速度环输出限幅很重要。速度环输出的是目标角度这个角度不能太大否则车会猛冲。我限幅在±5度。也就是说速度环最多让车倾斜5度来加速超过这个角度就饱和了。4.4 调参顺序与常见问题完整的调参顺序是先调角度环P再调角度环D然后调速度环P最后调速度环I。每调一个参数都要用手推车测试响应不要只看车能不能站住。车能站住但推一下就倒说明D不够车能站住但一直往一个方向跑说明速度环I不够车能站住但高频抖动说明角度环P太大或D太小。常见问题速查表现象可能原因解决方法车原地高频抖动角度环P太大或D太小减小P增大D车缓慢往一个方向跑速度环I太小或陀螺仪零漂未校准增大I重新校准陀螺仪车前后振荡速度环I太大减小I推一下车倒角度环D太小增大D车加速后停不下来速度环输出限幅太大减小限幅电机发出滋滋声但不转角度环D太大减小D车能站但响应迟钝角度环P太小增大P还有一个隐藏问题如果车能站住但总是往一个方向偏除了速度环I还要检查机械中值。所谓机械中值就是车在没有任何控制时自然平衡的角度。由于电池位置、电机安装误差这个角度可能不是0度。我的做法是把车放在地上用手扶到它能自己站住的角度读MPU6050的角度值把这个值作为目标角度。我的车机械中值是-1.5度设了之后车就不偏了。5. 调试工具与实战技巧5.1 串口调参与上位机调PID时如果每次都要改代码、编译、下载效率太低了。我的做法是用串口做参数在线调整。STM32的USART1接一个蓝牙模块或者USB转串口波特率115200。定义一个简单的协议上位机发送“P100”表示设置角度环P为100“D1.2”表示设置D为1.2“S”表示保存到Flash。上位机我用的是匿名科创的调试助手支持波形显示和参数发送。把角度、速度、PWM输出都通过串口发上去实时看波形。调角度环时看角度波形和PWM波形如果角度波形有超调说明D不够如果PWM波形有高频毛刺说明D太大。这里有个技巧串口发送数据不要放在中断里中断里只置标志位主循环里发送。否则串口发送耗时会影响中断实时性。我一开始在1kHz中断里直接发串口结果姿态解算周期变成了1.5ms车抖得厉害。后来改成主循环发送中断里只更新数据就正常了。5.2 OLED显示与按键交互车上装一个0.96寸OLED用I2C接口可以实时显示角度、速度、PID参数。再配三个按键一个进入菜单一个加参数一个减参数。这样不用连电脑就能调参户外调试很方便。OLED刷新不要放在中断里I2C通信耗时较长。我的做法是主循环里每100ms刷新一次OLED只刷新变化的数值不整屏刷新。整屏刷新会有明显闪烁局部刷新就流畅很多。按键处理要加消抖我用的是定时器扫描加状态机20ms扫描一次连续两次相同状态才确认。按键功能设计短按加/减参数长按切换参数项双击保存。这样三个按键就能完成所有调参操作。5.3 电池电压监测与低压保护3S锂电满电12.6V放到9.9V就该充电了。如果放到9V以下电池会过放损坏。我在STM32的ADC上接了一个分压电路监测电池电压分压比1:11ADC读到的电压乘以11就是实际电压。低压保护逻辑电压低于10.5V时OLED显示警告低于10V时逐渐降低最大PWM输出让车慢慢停下来低于9.6V时直接切断电机输出进入待机。这样既保护电池又不会让车突然断电摔倒。分压电阻要用高精度1%的否则电压读数误差可能到0.5V。我一开始用5%的电阻显示11.1V实际只有10.6V差点把电池过放了。换了1%电阻后误差在0.1V以内。5.4 常见硬件故障排查调试过程中遇到最多的问题不是软件而是硬件。整理几个典型故障电机不转但TB6612发热检查STBY引脚是否拉高检查PWM输入是否有信号。如果STBY没拉高TB6612处于待机模式输出高阻电机不转但芯片可能因为输入信号而发热。编码器计数方向反了如果车往前推编码器计数反而减小说明A相和B相接反了。解决办法是交换A/B相接线或者在代码里把速度取反。我建议在代码里取反因为拆线麻烦。MPU6050读不到数据先检查I2C地址MPU6050默认地址是0x68如果AD0接高电平则变成0x69。然后检查上拉电阻I2C总线必须有4.7kΩ上拉。最后检查供电MPU6050的VCC是3.3V不要接5V。车能站但一推就倒除了PID参数还要检查车重分布。电池尽量放在底部降低重心。重心越低车越稳PID也越好调。我的车一开始电池绑在顶部怎么调都站不稳后来把电池移到最下面立刻就好调了。6. 进阶优化与扩展方向6.1 前馈控制提升响应速度基础PID调好后车能站住但快速推一下还是会晃。这时候可以加前馈控制。前馈的思路是根据目标加速度直接给出一个PWM补偿不等误差产生再反应。对于平衡小车前馈可以用在速度环当目标速度变化时直接给角度环一个额外的目标角度加快响应。前馈系数的整定方法给车一个阶跃速度指令观察实际速度的响应。如果响应太慢增大前馈系数如果超调减小前馈系数。我实测前馈系数在0.3到0.5之间效果比较好。6.2 蓝牙遥控与手机APP加一个HC-05蓝牙模块接USART2就可以用手机遥控了。手机APP我用的是一款通用的蓝牙串口助手自定义协议发送“F”前进“B”后退“L”左转“R”右转“S”停止。STM32收到指令后修改目标速度或目标转向角。转向控制用差速实现左轮速度基础速度转向量右轮速度基础速度-转向量。转向量由遥控指令决定。这样车就能边走边转了。6.3 数据记录与离线分析调参时如果能把数据记录下来事后分析会方便很多。我在STM32上挂了一个SD卡模块用SPI接口每10ms记录一次角度、速度、PWM输出到CSV文件。跑完一圈把SD卡拔下来插电脑用Excel画图分析。SD卡写入要注意不要频繁开关文件上电时打开文件一直追加写入断电前关闭。如果每次写入都开关文件SD卡寿命会很快耗尽。我用的是一张8GB的卡跑了两个月还没坏。6.4 从平衡小车到更复杂的控制平衡小车调通之后这套串级PID框架可以迁移到很多地方。比如独轮车、平衡机器人、甚至四轴飞行器的姿态控制。核心思想是一样的快变量内环、慢变量外环、前馈加快响应、限幅防饱和。如果想进一步深入可以研究LQR控制。LQR比PID理论上更优能同时考虑多个状态变量。但LQR需要精确的系统模型平衡小车的模型参数质量、转动惯量、摩擦系数不容易测准实际效果不一定比调好的PID好。我的建议是先把PID吃透再考虑LQR。最后分享一个我踩过的坑调参时不要一次改多个参数。我一开始心急P、D、I一起改结果车的行为完全无法预测。后来老老实实一次只改一个改完测试记录效果再改下一个。虽然慢但每一步都清楚。调PID是个耐心活急不得。
返回列表