
简介这是一份电赛主题的轮腿式平衡小车完整工程源码包面向电子设计竞赛学生、嵌入式开发者与机器人爱好者。项目以C语言固件为核心覆盖传感器采样、姿态解算、电机驱动至平衡控制环路的关键实现既可作为赛题参考也适合日常自平衡算法学习与二次开发。压缩包共1311个文件约49.48MB以c/h源文件为主辅以s汇编、o目标文件、icf链接配置及cmake构建脚本结构上便于直接导入主流IDE或交叉编译环境查看。包内另有多种传感器库与数学运算库例如ARM Cortex-M系列DSP库、线性插值数据等方便对照学习滤波与数据融合思路。目前已有584人浏览学习适合需要快速上手轮腿式平衡车项目或深入调试底层逻辑的读者。1. 轮腿式平衡小车电赛选题里比普通两轮平衡更能打的形态实验室里十个人有九个在做普通两轮平衡小车站起来不难一上坡道就开始原地发抖过个减速带干脆直接躺平。后来我拿到这份轮腿式平衡小车的工程把轮子装在可以摆动的腿上整车重心投影和轮距都能主动改变通过性完全不是一个级别。这份包适合正在备赛、想把两轮小车升级成轮腿形态的同学主控代码、姿态解算、串级PID、轮腿步态和遥控逻辑都齐了。拆包时最大的感受是轮腿式没有把平衡问题变复杂而是把“调直立环”的玄学问题变成了“控制重心投影”的工程问题只要机械腿的参数和你代码里常量对得上调参方向非常明确。2. 硬件架构与机械底盘从轮腿结构到供电分配的完整方案2.1 整车分层五层结构决定代码怎么组织整个工程建议按五层理解机械层负责腿、轮、车身连接驱动层负责电机和电机驱动芯片感知层负责MPU6050姿态和编码器测速控制层跑串级PID应用层管蓝牙指令和OLED显示。这五层基本对应代码包里的HARDWARE、Control、User目录每一层都有独立接口改动一层不影响另外四层。我把这份工程的软件目录大致捋了一遍结构是这样的Core/ # 系统时钟、中断 HARDWARE/ # OLED、MPU6050、Motor、Encoder 底层驱动 User/ # main.c、pid.c、control.c 控制主循环 Doc/ # 电路图、结构装配图、元器件清单这个组织方式很标准移植到其它STM32板子时只需要改HARDWARE里的引脚映射PID和状态机基本不用动。2.2 轮腿底盘为什么腿比裸轮更值得做轮腿结构最核心的一点是轮子不再直接锁死在车体上而是安装在一段可以摆动的腿部末端。这份工程用的是铰接摆腿式大腿杆从车身两侧向外伸出电机装在膝关节位置轮子通过联轴器和大腿末端连接。腿部摆动时整车重心相对于轮子接地点的投影随之移动平衡控制不再只靠车身倾角还可以靠腿的摆动来主动调整质心。这种结构带来的直接好处有两个一是过小障碍时腿能抬起来不需要整车冲撞二是粗细调平衡时你既可以用目标倾角控制车身又可以用腿部角度控制质心两条路互相补偿。代价是机械结构多了一组连杆和轴承装配时要注意左右腿等长否则两侧响应会不一样。代码里对应的常量是腿长参数改机械腿长度时必须同步更新。2.3 元器件选型与供电拓扑一张表把物料说清楚我按这份工程的设计思路整理了一张选型参考表适合STM32F103级别的方案模块型号 / 规格作用关键参数主控STM32F103C8T6姿态解算、控制、通信72MHzFlash 64KB姿态传感器MPU6050三轴加速度 三轴角速度I2C接口默认地址0xD0电机带编码器直流减速电机轮腿驱动 速度反馈减速比1:30编码器13线电机驱动TB6612或DRV8833双路H桥输出峰值电流1.2A以上无线遥控HC-05蓝牙模块手机下发速度/模式指令串口透传波特率9600显示0.96寸OLED实时显示姿态角、PID输出I2C接口电池2S锂电池 7.4V整车供电容量1000-1500mAh稳压5V稳压 3.3V LDO给传感器和主控独立供电传感器电源纹波尽量小于50mV供电拓扑是我特别想提的一点电机驱动直接吃电池电压主控、MPU6050、OLED走稳压后的5V和3.3V并且模拟部分单独走一条地线汇合。常见翻车是电机启动瞬间拉低3.3V导致MPU6050的I2C通信直接失败这点在电赛现场特别常见。3. 感知层实现MPU6050姿态解算与编码器测速的代码拆解3.1 从I2C原始数据到姿态角互补滤波怎么做MPU6050默认I2C地址由AD0引脚决定AD0接地时地址是0xD0。读取姿势是连续读14个字节加速度计三个轴各2字节温度2字节陀螺仪三个轴各2字节。寄存器地址从ACCEL_XOUT_H开始数据是大端字节序也就是说高字节在前。#define MPU6050_ADDR 0xD0 // AD0接地时为0xD0接高电平则为0xD2 void MPU6050_ReadRaw(int16_t *acc, int16_t *gyro) { uint8_t buf[14]; // 从加速度计高字节寄存器连续读取14字节 HAL_I2C_Mem_Read(hi2c1, MPU6050_ADDR, MPU6050_ACCEL_XOUT_H, I2C_MEMADD_SIZE_8BIT, buf, 14, 100); // 大端字节序高8位左移8位再或上低8位 acc[0] (buf[0] 8) | buf[1]; // AX acc[1] (buf[2] 8) | buf[3]; // AY acc[2] (buf[4] 8) | buf[5]; // AZ gyro[0] (buf[8] 8) | buf[9]; // GX gyro[1] (buf[10] 8) | buf[11]; // GY gyro[2] (buf[12] 8) | buf[13]; // GZ }这段代码里地址和寄存器顺序是固定的移植到别的板子时最容易踩坑的是I2C速率。常见做法是把I2C时钟设为400kHz太慢了会导致姿态数据延迟太快则在长线上出现数据错位。原始数据拿到以后加速度计和陀螺仪的量程比例需要换算这份工程里陀螺仪量程取±2000°/s对应灵敏度是65.5 LSB/°/s。姿态融合我用的是互补滤波比卡尔曼轻量在STM32F103上跑500Hz毫无压力。核心思路是角速度积分负责短期信任加速度计算出的角度负责长期修正。#define KP_COMP 0.2f // 加速度权重越大越信任加速度计 #define DT 0.002f // 控制周期2ms对应500Hz static float pitch 0.0f; // 俯仰角输出 void Attitude_Update(int16_t *acc, int16_t *gyro) { // 用加速度计Y轴/Z轴计算俯仰角 float acc_pitch atan2f((float)acc[1], (float)acc[2]) * 57.29578f; // 陀螺仪X轴角速度除以灵敏度得到°/s float gyro_rate (float)gyro[0] / 65.5f; // 互补滤波角速度积分 加速度修正 pitch KP_COMP * acc_pitch (1.0f - KP_COMP) * (pitch gyro_rate * DT); }参数说明KP_COMP取0.2时加速度计修正占20%陀螺仪积分占80%。如果小车在粗糙地面上震动大可以把KP_COMP降到0.1如果车体很稳但静止时有零点漂移则往0.3调。这里要注意传感器安装方向如果MPU6050旋转90度安装计算俯仰角时要换成X轴和Z轴换轴之后陀螺仪的角速度轴也要跟着换。3.2 编码器测速四倍频和减速比换算成实际速度编码器测速我用定时器的编码器模式CH1和CH2分别接编码器的A相和B相STM32会自动做四倍频计数不需要中断参与。这样CPU负载很低控制主循环可以把全部时间花在PID计算上。int16_t enc_count, enc_last; float motor_speed; // 实际车速单位mm/s void Speed_Calc(uint16_t sample_period_ms) { // 读取定时器当前计数值这个值随时间递增/递减 enc_count __HAL_TIM_GET_COUNTER(htim3); int16_t delta enc_count - enc_last; // 周期内的脉冲增量 enc_last enc_count; // 编码器13线 * 4倍频 * 减速比30 每圈1560个计数 // 轮子直径80mm周长约251.2mm // 速度 脉冲数 / 每圈脉冲数 * 轮周长 / 采样时间 motor_speed (float)delta * 251.2f / 1560.0f / ((float)sample_period_ms / 1000.0f); }这段代码的关键在于采样周期和计数器溢出的配合。编码器模式计数器是16位范围为-32768到32767如果采样周期拖到50ms以上在高速转动时可能正好越过溢出边界导致算出来的delta是负的电机方向判断直接反掉。常见做法是采样周期固定在2ms到5ms之间既能满足速度环更新频率又不会让计数器跑飞。速度环的目标单位也是mm/s遥控指令从蓝牙收到后会被转换成mm/s再进入PID这样调参时不需要再去换算脉冲数。4. 控制层实现串级PID直立、速度环与轮腿步态的配合4.1 三环结构直立环、速度环、转向环各管什么这份工程的控制核心是串级PID但它的串级方式和传统四轴不一样直立环在最内层速度环输出的是角度修正量而不是直接PWM转向环单独叠加在左右轮差速上。三个环各管一件事——直立环保证车不倒下速度环控制前进后退转向环控制航向。控制主循环固定跑500Hz也就是每2ms执行一次完整的姿态读取和三环计算。这个频率对直流减速电机来说足够平滑对MPU6050来说也正好能发挥互补滤波的效果。如果降频到100Hz直立环输出会明显发涩车体会出现高频抖动。4.2 直立环与速度环的代码实现一份能抄的main loop控制代码的核心在主循环里逻辑非常紧凑。直立环用PD控制比例项响应倾角偏差微分项用陀螺仪角速度做阻尼。速度环用PI控制把直立环的目标倾角进行动态修正让车既能站住又能按指令速度前进。// 直立环目标倾角来自平衡点陀螺仪角速度做阻尼 float balance_out -KP_BAL * (pitch - BALANCE_TARGET) - KD_BAL * gyro_y; // 速度环偏差积分产生角度修正 float speed_err target_speed - motor_speed; speed_integral KI_SPD * speed_err; speed_integral LIMIT(speed_integral, -20.0f, 20.0f); float speed_out KP_SPD * speed_err speed_integral; // 转向环用Z轴角速度做航向阻尼 float turn_out KP_TURN * (target_turn - gyro_z); // 三环叠加左右轮转向相反 int16_t pwm_l LIMIT(balance_out speed_out turn_out, -7200, 7200); int16_t pwm_r LIMIT(balance_out speed_out - turn_out, -7200, 7200); Motor_Output(pwm_l, pwm_r);参数说明KP_BAL决定回复力矩太小车体软绵绵太大会持续震荡。KD_BAL用来吸收角速度等效于给系统加阻尼调大后能压制高频抖动。速度环的KP_SPD、KI_SPD不需要调节太激进它的输出只是修正目标倾角比如想让车向前走就让目标倾角前倾几度车体为了回到平衡位置就会追着倾角跑。这里有一个很容易搞反的符号问题如果速度环修正方向反了车会越走越猛直到倒下。常见做法是把速度环先摘掉只留直立环让车静止站稳然后手动前推车体观察速度反馈方向是否和编码器实际方向一致确认符号正确再挂速度环。4.3 从两轮平衡切到轮腿步态状态机和步态相位轮腿式不是静止站立就完了它的进阶玩法是步态行走。步态的本质是让左右腿交替改变目标倾角使整车重心不断前移。实现步态的关键是状态机站立状态、行走状态、摔倒保护状态。typedef enum { MODE_STAND, // 站立 MODE_WALK, // 步态行走 MODE_FALL // 倾倒保护 } State; static State state MODE_STAND; static float walk_phase 0.0f; // 步态相位单位rad void State_Update(float pitch) { // 倾角超过35度认为已经失控电机直接停止 if (fabsf(pitch) 35.0f) { state MODE_FALL; Motor_Stop(); return; } if (remote_cmd CMD_WALK state MODE_STAND) { state MODE_WALK; walk_phase 0.0f; } if (state MODE_WALK) { // 步态频率1.5Hz每次控制周期累加相位 walk_phase 1.5f * 2.0f * 3.14159f * DT; // 左右腿目标倾角相差半个周期交替摆动 float offset 5.0f * sinf(walk_phase); target_pitch_l BALANCE_TARGET offset; target_pitch_r BALANCE_TARGET - offset; } }这段代码里的核心是目标倾角的左右分离。普通两轮平衡车左右轮只有一个共同的目标倾角而轮腿车的左右腿各有一组直立环参数步态模式下左腿前摆时右腿后摆车身保持在中位附近。步态频率1.5Hz是一个折中值太快电机响应不过来太慢重心转移不明显。摆动幅度5度也是可调参数腿越长摆动幅度可以越小因为长腿本身就能产生更大位移。轮腿步态和普通行走的最大区别在于它全程不脱离平衡控制每一步都是动态平衡过程。调试时需要先用遥控器切换到站立模式确认直立环和速度环都稳定了再打开步态模式慢慢加幅度。5. 避坑与排查电赛现场最容易被调参逼疯的五个细节5.1 供电与传感器问题的血泪清单第一条上电后电机猛转一下然后MPU6050读数全部为0。原因是电机启动瞬间电流可以达到1A以上如果传感器和电机共用一条电源线3.3V会被拉到3.0V以下MPU6050直接复位。解决方法是传感器电源和电机电源彻底分离电池正极先到电机驱动再单独走一路稳压到5V和3.3V并且稳压输出就近并一个100uF钽电容和0.1uF陶瓷电容。第二条车静止时角度缓慢漂移速度环让车慢慢往一个方向溜。原因是MPU6050零点偏置没有校准或者互补滤波KP_COMP取太小导致加速度计的修正力不够。解决方法是上电初始化时连续采样500到1000次陀螺仪数据求平均把平均值作为零偏在姿态更新时减去另外确认KP_COMP不要低于0.15否则低速漂移会很难压住。第三条OLED显示的角度和实际倾斜方向相反。原因是传感器安装方向与代码假设不一致特别是把MPU6050贴在车身背面时加速度计Y轴正方向会反转180度。解决的土办法是拿一块已知水平的桌面分别记录车头朝前和车头朝后两个姿态下PY轴的读数再对照代码里atan2的轴顺序去修正。5.2 调参与机械装配的翻车现场第四条直立环KP加大到一定程度后车身开始高频抖动电机明显发烫。原因是控制频率跟不上或者微分项太大。很多同学只知道加KP不知道跟着加KD。解决办法是先固定KP在一个能站稳的值然后单独增大KD压制抖动如果KD加到很大还抖把控制频率从200Hz升到500Hz抖动通常会明显下降。第五条左右轮换上新打印的腿后原来调好的PID全部失效。原因是腿长变了重心投影位置变了原有的BALANCE_TARGET角度不再对应物理平衡点。解决方法是重新标定把车平放在地面上手动松开电机让车自由倒向一侧观察车身静止时MPU6050读出的角度把这个角度写入BALANCE_TARGET。腿长改动超过5mm时还要按比例缩放KP_BAL腿越长需要的回复力矩越大。6. 二次开发把这份工程改成你自己的轮腿方案6.1 把机械参数映射成代码常量拿到这份工程后第一步不是改PID而是把所有机械常量和你的实际底盘对齐。腿长、轮径、减速比、编码器线数这四个数值直接影响速度量程和步态幅度。我一般习惯在control.c头部建一个机械参数表每一行对应一个可测量尺寸这样后续换腿、换轮时只需要改常量不用去函数里找。步态参数建议做成可配置结构体抖动幅度和步频单独拎出来方便现场快速试跑。实际验证时先用手扶着车在平地走两步观察左右腿摆动是否对称再放手下指令。6.2 给工程加一套串口调参指令电赛现场最痛苦的是改一个KP就要重新烧录一次程序。我建议在串口中断里加一个简单的调参协议格式定为帧头加参数ID加数值比如// 帧格式: 0xAA 0x01 [param_id] [value_high] [value_low] [checksum] void UART_Parse(uint8_t byte) { static uint8_t buf[6]; static uint8_t idx 0; buf[idx] byte; if (idx 6) { if (buf[0] 0xAA buf[1] 0x01) { uint16_t value (buf[3] 8) | buf[4]; switch (buf[2]) { case 0x01: KP_BAL value / 100.0f; break; case 0x02: KD_BAL value / 100.0f; break; case 0x03: KP_SPD value / 100.0f; break; } } idx 0; } }这样只需要一个USB转串口模块用任意串口助手就能在车跑起来的时候实时改参数。验证方法是先改小参数观察车体反应再逐步逼近最优值每次调整幅度不超过20%。蓝牙遥控的指令协议也可以按同样思路扩展把速度值和步态开关一起打包成结构化帧这样手机端调试和串口调试共用一套解析逻辑。从那以后我每次拿到新的控制工程都强制先看底层传感器原始数据确认零点和方向干净了再上闭环。这个习惯让我少烧了好几块驱动板也让我在电赛现场面对临时换腿的突发状况时能十分钟内重新标定完线性参数。希望帮到你。本文还有配套的精品资源点击获取