☰
电赛轮腿式平衡小车:姿态解算与串联PID控制完整方案
2026/10/17 3:15:46 网站建设 项目流程

简介:面向电子设计竞赛与嵌入式开发者的轮腿式平衡小车完整工程包,定位为可二次开发的软硬件一体化方案,直接解决自平衡车的姿态检测、运动控制与复杂地形适应问题。项目基于ARM Cortex-M4,压缩包内包含STM32CubeMX工程配置、Keil/IAR工程文件、CMSIS-DSP数学库以及外设驱动源码,导入开发环境后即可编译、烧录与调试。控制核心覆盖陀螺仪、加速度计与磁力计的传感器融合,提供PID与卡尔曼滤波等多种平衡实现,并结合电机驱动与腿部支撑逻辑,支持前进、后退、转弯、倾斜行驶乃至楼梯等不平地面的越障;同时附带LCD显示或蓝牙模块的交互代码,可扩展到手机远程控制。资源共1311个文件,以C源码(619个)和头文件(316个)为主,另有汇编文件、链接脚本、CMake构建配置及txt说明文档,整体49.48MB;目录从硬件原理图、元器件清单到算法框架、调试记录都有分层整理,查找定位非常直接。目前已有584人学习下载,适合参赛学生快速搭建原型,也可供机器人开发者复用底层驱动、滤波算法与完整自平衡控制框架。

1. 电赛平衡小车:轮腿式方案为何比平板车更吃香

做电赛控制类题目的人都知道,直立平衡小车年年都有队伍做,但真正能在平地自平衡、遇到门槛还能抬腿越过的轮腿式小车,往往能在评分现场直接拉开差距。轮腿式平衡小车不是简单给平板车加两条腿,它在姿态控制之外多了一个腿部关节自由度,这让控制复杂度上了一个台阶,也让它能完成普通平板车做不了的动作。这份“电赛平衡小车轮腿式平衡小车”工程包,就是从硬件原理图、PCB到手写姿态解算和串联PID直立控制、再到轮腿模式切换完整可跑的工程,解压之后能对着原理图接线、直接烧录、按文档调参,把“能站住”和“能跨过去”两个目标拆开推进。适合正在备赛电赛控制类题目的学生,也适合把平板平衡车玩熟、想往轮腿方向进阶的机器人爱好者。

2. 轮腿车的机电架构:硬件接口规划与选型逻辑

2.1 轮腿式和平板平衡车的本质区别:多出来的那个自由度

先想清楚一个问题:轮腿式比平板平衡车多出来的东西到底是什么?

平板平衡车只有两个驱动轮,控制目标只有一个——通过车轮的力矩让车身质心保持在轮轴正上方。它的控制链非常短:MPU6050读出倾斜角,姿态环算出一个PWM值,PWM驱动电机,电机产生力矩把车身拉回来。整个过程是“一个输入、一个输出”,所以很多平板车工程包里的主程序就四个函数:读传感器、算姿态、算PID、输出PWM。

轮腿式平衡小车在车轮和车身之间多了一个可转动的腿部关节。这个关节改变的不只是外观,它让质心位置不再固定在轮轴上方,而是可以被腿部角度主动移动。这样一来,控制目标就变成了两个:一是保持车身姿态稳定,二是控制腿部关节的角度与动作时序。更关键的是,这两个目标互相牵连——腿部一抬,质心前移,车身就会往前倾,直立环必须立刻输出反向力矩去补偿。如果腿部动作太快,姿态环还没反应过来,车就趴了。

所以轮腿式工程包的核心难点,不在于某一个环调得多好,而在于“腿部动作”和“姿态补偿”这两件事如何协同。网络上常见的电赛方案有两种:一种是用舵机做腿部关节,位置控制简单但响应慢;另一种是用带编码器的减速电机做腿关节,可以做力矩控制但代码量翻倍。这套工程包采用的是“驱动电机+舵机腿部”的混合架构,也就是车轮用直流减速电机,腿部关节用舵机,控制策略上先把上身姿态稳住,再让腿部缓慢动作,每一帧都根据当前姿态修正腿部目标角度。

2.2 主控、传感器、电机驱动的接口规划一览

拿到工程包之后,第一件事不是看代码,而是先对着原理图确认接口。工程包里主控用的是F103系列最小系统板,传感器是MPU6050六轴模块,驱动芯片是TB6612,电机是带霍尔编码器的N20减速电机。这是电赛平衡车最常见的组合,成本低、资料多、坏了好换。

模块接口资源配置要点
MPU6050I2C1,PB6(SCL)、PB7(SDA)上拉电阻必须接,地址接AD0到GND为0x68
左电机TIM1的CH1+CH2,编码器接TIM2PWM频率设为10kHz,编码器模式设为TI1+TI2
右电机TIM1的CH3+CH4,编码器接TIM3与左电机分开定时器,方便单独极性反转
腿部舵机TIM4的CH1,50Hz频率占空比1ms-2ms对应0到180度
电池电压检测ADC1的通道4分压电阻1K+3.3K,算回真实电压用

接线时有几个细节必须注意。MPU6050的SCL和SDA不是随便接两个IO就行的,必须挂在同一个I2C外设上,而且模块上的VCC要接3.3V,接5V在某些模块上会直接把I2C电平拉坏。N20电机的编码器是开漏输出,必须接上拉电阻,很多工程包的原则图里画了,但PCB上没贴电阻,这会导致编码器读数随机跳变,速度环根本稳不住。舵机电源不要从主控板的3.3V取,腿部动作瞬间电流能到1A以上,主控复位就是从这里来的,单独从电池经过一个BEC模块取电比较稳。

2.3 工程包里各文件夹的作用与阅读顺序

一个完整工程包拿到手,最忌讳的做法是直接解压然后全选烧录。工程包里一般会有原理图、PCB文件、固件源码、调参记录、结构件STL这几个部分,它们的阅读顺序应该按调试顺序来。

我一般会先看调参记录文档,里面记录了这个项目在调试过程中遇到的现象和最终参数,比如“P=1.2时车身高频震荡”“速度环积分上限设为40”这种描述,比看代码能更快了解项目脾气。然后看原理图和接线表,确认传感器和电机的引脚映射。最后才是打开源码,按工程包里的功能模块逐个烧录验证,而不是一步到位烧完整的轮腿切换程序。大多数工程包的源码都会在main.c里用宏开关区分功能,比如定义#define MODE_SENSOR_TEST就是只读传感器、定义#define MODE_WHEEL_BALANCE就是只测直立平衡,这种分阶段开关在排查问题时特别好用。

3. 姿态解算与串联PID:让小车先站住再行动

3.1 MPU6050的DMP与互补滤波:工程包里到底用了哪套方案

MPU6050是电赛平衡车的默认传感器,原因很简单:一个芯片同时输出三轴加速度和三轴角速度,够用且便宜。但数据不是拿来就能用的,加速度计在静止时能测出重力方向,但一有运动加速度就混入干扰;陀螺仪短暂时间内很准,但积分久了会漂移。平衡车需要的是一个融合后的Pitch角,也就是车身前后倾斜角,这个角度不能抖,也不能缓慢漂移。

网络上有两种主流做法:一是直接读MPU6050内置DMP的四元数,再转成欧拉角;二是自己写互补滤波,把加速度计和陀螺仪数据按权重融合。工程包里用的是互补滤波方案。原因是DMP虽然省主控资源,但初始化时序很挑剔,经常卡在mpu_dmp_init()这个函数里,而且DMP输出的角度在小角度时响应偏慢,直立环用起来总觉得不够跟手。互补滤波代码量少,逻辑透明,出问题的时候能用串口打出中间变量来查,对于比赛调试来说这种可观测性比什么都重要。

3.2 从原始数据到Pitch角:互补滤波的完整链路

工程包里的姿态解算核心代码大概是这样一段:

#define DT 0.005f // 控制周期 5ms,即 200Hz #define K_COMP 0.98f // 互补滤波系数 float pitch = 0.0f; float gyro_y_raw = 0.0f, acc_pitch = 0.0f; void IMU_Update(float gx, float gy, float gz, float ax, float ay, float az) { // 陀螺仪y轴积分得到角度增量 float gyro_y_integ = gy * DT; // 加速度计计算出的pitch角,注意方向要跟陀螺仪y轴对齐 acc_pitch = atan2f(-ax, sqrtf(ay * ay + az * az)) * 57.29578f; // 互补滤波:陀螺仪角度占主要权重,加速度计负责拉回漂移 pitch = K_COMP * (pitch + gyro_y_integ) + (1.0f - K_COMP) * acc_pitch; }

这段代码的逻辑分三步。第一步,陀螺仪y轴角速度乘以控制周期DT,得到5毫秒内转过的角度增量,累加到pitch上。第二步,用加速度计的三轴数据算出当前重力方向相对车身的倾斜角,作为绝对参考。第三步,互补滤波把陀螺仪积分结果和加速度计结果按比例混合,K_COMP是0.98,意味着98%信任陀螺仪积分值、2%信任加速度计算值,这个比例既能滤掉加速度计的高频抖动,又能让角度不随时间漂移。

这里有两个参数需要关心。DT必须和实际中断周期严格一致,工程包用的是定时器5ms中断,如果改成10ms却没有同步修改DT,pitch角度会整体偏慢,直立环会表现为轻微低频晃动。K_COMP不是越大越好,在静止测试时你可以把它改成0.995,会发现角度非常平滑但回零很慢,反而在动态倾覆测试中更容易积分漂移,比赛现场我一般保持0.98不动。

3.3 直立环与速度环的串联结构:轮腿车特有的输入端

姿态角算出来了,接下来就是控制。工程包用的不是单个PID,而是串联PID结构,速度环的输出作为直立环的目标角度。直立环单独没法让小车稳定在一个位置,它只会让车在一个点附近来回徘徊,必须加速度环让车知道该往哪边跑才能停下。

// 直立环:比例控制角度,微分控制角速度 balance_PWM = Kp_angle * pitch + Kd_angle * gyro_y; // 速度环:PI控制,输出作为直立环目标角 speed_integral += speed_error * DT; if (speed_integral > SPEED_I_MAX) speed_integral = SPEED_I_MAX; if (speed_integral < -SPEED_I_MAX) speed_integral = -SPEED_I_MAX; speed_out = Kp_speed * speed_error + Ki_speed * speed_integral; // 融合:目标角 = 速度环输出,最终PWM = 直立环计算 target_angle = speed_out; motor_pwm = Kp_angle * (pitch - target_angle) + Kd_angle * gyro_y;

串联PID的理解要点在于:直立的本质不是让pitch等于0,而是让pitch等于目标角。用户想让车前进,就通过速度环把目标角往后压一点,车为了追目标角就会前倾,然后向前加速,形成一个“速度-角度-加速度”的闭环。嗯,逻辑实际上是你想前进,速度环检测到车还没达到目标速度,于是输出一个让车身向前倾的目标角,车身前倾后直立环输出车轮往前转的力矩,车加速,直到速度和目标一致、车身回到水平。

这段代码中target_angle就是轮腿模式和普通模式的主要接口。腿一抬,质心变了,pitch会往一个方向偏,直立环自然会产生对抗力矩。但如果腿部动作幅度大,偏出的角度超出了直立环能拉回的范围,就需要在速度环输出的目标角上再做一次修正,这个修正值就是下一章说的模式切换控制量。

4. 轮腿切换状态机:从平地自平衡到抬腿越障

4.1 三种工作模式怎么定义

轮腿车在工作时不是只有“平衡”一种状态,工程包里把运行过程分成三个阶段:纯轮平衡模式、抬腿巡视模式、腿式站立模式。

纯轮平衡模式就是普通平衡车,车身和腿保持固定夹角,靠车轮前后移动维持平衡,适合平地快速移动。抬腿巡视模式用于接近障碍物时减速并通过抬腿动作改变重心,让前轮搭上台阶边缘,再利用车轮驱动力把车身拉上去。腿式站立模式是幅度最大的动作,腿部关节转动让机身前部抬离地面,像人蹲下再站起一样改变整车高度,用于跨越较高的门槛或在不平路面上通过。

工程包里设定的切换阈值很直观:超声波传感器检测正前方距离,大于30厘米时纯轮模式,15到30厘米之间进入抬腿模式,小于15厘米时就地停车然后执行腿式站立。注意这里要加一个迟滞区间,不能正好卡在30厘米来回跳,否则模式切换本身就会引入干扰。

4.2 状态机代码与切换时的姿态补偿量

工程包里的模式切换核心逻辑大致如下:

typedef enum { MODE_WHEEL, MODE_LIFT, MODE_STAND } RobotMode_t; RobotMode_t current_mode = MODE_WHEEL; float leg_angle = 0.0f; // 腿部目标角度 float pitch_comp = 0.0f; // 腿部动作带来的姿态补偿角 float distance = Ultrasonic_GetDistance(); // 状态切换,带迟滞 if (current_mode == MODE_WHEEL && distance < 25.0f) { current_mode = MODE_LIFT; } if (current_mode == MODE_LIFT && distance < 12.0f) { current_mode = MODE_STAND; } if (current_mode == MODE_LIFT && distance > 35.0f) { current_mode = MODE_WHEEL; } // 抬腿模式:距离越近,腿抬得越高,限制动作速度 if (current_mode == MODE_LIFT) { float leg_target = 60.0f + (25.0f - distance) * 2.0f; leg_target = Constrain(leg_target, 60.0f, 120.0f); leg_angle += (leg_target - leg_angle) * 0.05f; // 斜坡限幅 } else if (current_mode == MODE_STAND) { leg_angle += (150.0f - leg_angle) * 0.03f; // 更慢的站立动作 } else { leg_angle += (80.0f - leg_angle) * 0.08f; // 回到平地姿态 } // 姿态补偿:腿部抬升会让质心后移/前移,根据实测方向反向补偿 pitch_comp = (leg_angle - 80.0f) * 0.12f; target_angle += pitch_comp;

这段代码的关键有两点。第一,腿部角度不能一步到位,而是每帧向目标值逼近一个比例,0.05f意味着每次中断只移动5%的剩余角度,这个值决定了腿部动作的速率,调大动作快但容易把车掀翻,调小动作慢但稳定。第二,腿部角度变化被换算成一个pitch_comp补偿量加进目标角,这是轮腿车和普通平衡车代码最大的不同。具体的补偿比例不是理论算出来的,而是在调试中实测黑匣子记录下来的——先让车保持纯轮平衡,手动缓慢改变腿部角度,记录pitch的变化量,然后拟合出一个比例。

4.3 腿部动作的时序:先补偿再动腿还是先动腿再补偿

这是工程包踩坑最多的地方。很多方案把腿部动作和姿态补偿放在同一个控制周期里执行,结果腿一抬,姿态还没反馈回来,直立环先按旧姿态输出了一波错误力矩,车身直接往前窜。

正确的做法是分两步走:腿部动作要在本次中断中执行,补偿量则要在下一次中断才生效。工程包里的实现方式是给pitch_comp加一个一阶滞后滤波,让它比leg_angle变化慢两个周期。用代码表示就是在腿部角度更新之后再执行pitch_comp += (pitch_comp_target - pitch_comp) * 0.1f,这样姿态修正不会和腿部动作完全同步,相当于给控制器一个“看到变化再反应”的缓冲时间。调参文档里把这一步叫作“错相补偿”,比赛前临时改这个参数的人,多半会在越障测试时翻车。

5. 接线与调参避坑:五条直接决定成败的实测记录

5.1 MPU6050初始化卡死在while循环

while (mpu_dmp_init()) { LED_Toggle(); delay_ms(500); }

现象是上电后LED一直闪烁,程序就是跳不出这个while。网上问了一圈,回答集中在“I2C没上拉”“MPU6050坏了”“接线对没对”。原因其实有两条:一是I2C速度配置过高,工程包里把I2C时钟设成400kHz,但杜邦线超过10厘米后总线电容变大,400kHz直接通信失败,降到100kHz就好了;二是SDA和SCL接反或者共用了别的外设引脚。解决方法是先写一个I2C扫描函数,把总线上所有设备地址都打印出来,能看到0x68说明接线没问题,看不到就先查硬件再看代码。

5.2 直立环P值越调越震荡,还伴随高频啸叫

这就是典型的翻车场景。P加到1.0的时候车还能勉强站住,加到1.2就开始前后抖,幅度越来越大,电机发出刺耳的啸叫声。原因不是P太大了,而是直立环的微分项没起作用——Kd_angle * gyro_y这一项如果符号反了,就相当于给系统注入正反馈,车越抖越厉害。检查方向是:把车拿在手上,快速向前倾一下,观察电机PWM输出是不是“反向力矩”而不是“顺着倾斜方向加速”。如果符号反了,把Kd取负或者把陀螺仪y轴读数取反。工程包源码里专门留了一个#define GYRO_DIR宏,就是干这个用的。

5.3 速度环调成“正反馈”,车越跑越快,直到倒下

现象是车能直立,但会朝一个方向逐渐加速,直到冲出桌面。新手第一反应是调小速度环P,其实问题不在于P,而在于编码器方向。编码器读数正方向、电机PWM正方向、车身前进方向三者必须一致,如果编码器反正,速度环会认为车在向后走,于是输出一个让车更向前的力矩,形成正反馈。解决方法是把车拿起来悬空,手动转动轮子向前,看串口打印的编码器速度是否为正,如果为负就在初始化代码里对编码器计数取反。工程包的Motor_Init()函数里就有两个方向宏,交换它们即可。

5.4 电池电压从8.4V掉到7.2V,平衡明显变软

同一套参数,满电时车很硬朗,飞一会就明显变“软”,倾斜角度稍微大一点就拉不回来。原因是电池电压降低后,同样的PWM占空比产生的电机实际电压更小,力矩打了折扣。解决方式叫电压归一化。在计算最终PWM之前,先读一下ADC采集的电池电压,然后用pwm_out = pwm_raw * NOMINAL_VOLTAGE / battery_voltage做一次补偿。工程包里这个系数是8.4V除以当前电压,放在主循环里每个周期执行,实际效果是低电量时PWM占空比会自动放大,保住平衡力矩。

5.5 腿一抬,车直接原地趴下

这个现象在测试时几乎复现了二十次。腿抬的过程中,车身先是往前一倾,然后控制板疯狂输出,最后直接“咚”一声倒下。排查出来的原因不在直立环,而在腿部动作使质心产生了额外力矩,这个力矩的导数很大,抵消了直立环的修正力矩。工程包的调参文档里记的解决方案是三条同时做:第一,把腿部动作速率从0.08f降到0.03f;第二,把pitch_comp补偿系数从0.25倍降到0.12倍;第三,也是最重要的,在抬腿动作开始前先让车提前减速到接近零速度,让速度环先稳定住,再开始动腿。顺序反了,神仙参数也救不回来。

6. 调参验证与进阶:串口回放、波形设计和参数迁移

工程包里除了源码还有一份调参上位机协议说明,核心思路是让小车把pitch角度、目标角度、PWM输出、腿角度这四个关键变量实时发到串口,上位机画出波形,调参时不再靠眼看车抖不抖,而是看波形上的延迟和超调来判断问题出在哪个环节。

数据结构可以复用工程包里的方式:帧头0xA5、帧长、功能字、四个float、校验和,总共21个字节,波特率460800。实话说,200Hz的控制频率下这样发串口不会阻塞主循环,因为发送只占很少的时间。波形上能看到三条线:红色是pitch角,蓝色是目标角,绿色是电机PWM。调直立环P的时候,盯着红色和蓝色的关系——P太小,红线偏离蓝线很久才拉回来;P太大,红线和蓝线之间出现高频抖动且抖动幅度不衰减,这时候就该降P或者加Kd。

参数迁移是个容易被忽略的问题。同一套代码从A车上拆下来装到B车上,结构重心高度变了,直立环P必须跟着变。经验是从0.8开始,每次加0.2,站住5秒不抖就继续加,出现轻微高频震荡就往回调一档,Kd通常是Kp的十分之一左右作为起点。腿补偿系数则要重新实测一次,方法很笨但有效:让车纯轮平衡,用串口把腿角度从80度缓慢摇到120度,记录pitch角的偏移量,偏移量除以角度变化量就是补偿系数。换车架后不做这一步直接沿用旧参数,十有八九要抬腿翻车。

说句实在话,这套工程包最值钱的地方不是那一堆调好的参数,而是它的调参文档里把每次“翻车”的现象和原因写得很诚实。从那以后我每次调完腿模式切换,都强制自己走一遍完整流程:先纯轮平衡5分钟,再模拟越障20次,最后回放波形确认腿部动作期间pitch偏差不超过3度,任何一步不过关就不往下走。这个习惯让我在比赛现场少吃了很多亏,希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询