1. 项目背景与核心原理
第一次看到自平衡自行车时,我完全被它的稳定性震惊了——没有支撑轮的单车竟然能像被施了魔法一样稳稳立住。这背后的核心秘密,其实是角动量守恒原理。当自行车开始倾斜时,动量轮会立即朝相反方向加速旋转,产生反向扭矩来抵消倾斜力矩。这就好比杂技演员走钢丝时,会通过左右摆动平衡杆来保持稳定。
STM32在这个项目中扮演着大脑的角色,它需要实时完成三件关键任务:
- 通过MPU6050传感器以每秒1000次的频率读取车身倾斜角度
- 用PID算法计算需要施加的纠正力矩
- 通过PWM信号精确控制无刷电机的转速
有趣的是,这种控制方式与人类保持平衡的机制惊人地相似。当我们快要摔倒时,小脑会立即指挥四肢做出补偿动作——STM32的PID控制器就像项目的"电子小脑"。
2. 硬件选型实战指南
2.1 主控芯片的选择
在调试过五六款STM32芯片后,我强烈推荐STM32F103C8T6(俗称蓝板)作为入门首选。这款芯片的性价比简直逆天:72MHz主频、20KB RAM、64KB Flash,还有丰富的定时器资源。最重要的是它的开发生态成熟,网上资料多如牛毛。
有个坑要特别注意:PA13/14/15默认是JTAG功能引脚,如果要用作普通IO,需要在代码开头添加:
GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable, ENABLE);2.2 传感器模块选型对比
MPU6050是姿态检测的不二之选,但市面上有多个版本需要注意:
| 型号 | 供电电压 | 通信方式 | 典型价格 | 推荐指数 |
|---|---|---|---|---|
| GY-521 | 3.3V/5V | I2C | ¥15 | ★★★★ |
| JY-61 | 3.3V | 串口 | ¥25 | ★★★☆ |
| ICM-20602 | 3.3V | SPI | ¥40 | ★★★★☆ |
实测发现GY-521虽然便宜,但需要额外安装DMP库才能获得稳定欧拉角。我建议新手直接使用集成DMP的MPU6050模块,可以省去大量调试时间。
2.3 电机驱动方案
动量轮需要快速响应,普通直流电机根本达不到要求。经过多次踩坑,最终锁定万宝至无刷伺服电机,它有三个突出优势:
- 内置100线编码器,分辨率足够
- 支持PWM调速,响应时间<5ms
- 自带驱动板,省去外接MOS管的麻烦
接线时要特别注意编码器供电必须接3.3V,否则会烧毁STM32!建议按这个顺序接线:
- 先接GND和12V电源
- 再接编码器3.3V供电
- 最后连接PWM和控制信号
3. PCB设计避坑手册
3.1 布局布线技巧
第一次画PCB时,我把电机驱动和单片机放得太近,结果电机一启动单片机就复位。后来总结出几个黄金法则:
- 电机电源走线宽度至少1.5mm
- 模拟信号线(如MPU6050的I2C)要远离数字信号线
- 在电源入口处放置100μF电解电容+0.1μF陶瓷电容组合
有个实用技巧:在嘉立创EDA中搜索"平衡小车",能找到很多现成的模块电路,可以直接复用。
3.2 电源管理设计
电源系统要特别小心,我的血泪教训是:
/* 错误示范:线性稳压方案 */ 电池12V → LM7805 → 5V → AMS1117-3.3V这种方案效率低下,7805会发烫到可以煎鸡蛋。后来改用DC-DC降压模块,效率提升到90%以上:
- 12V→5V:使用MP2307模块
- 5V→3.3V:选用SY8089芯片
4. PID调参实战心得
4.1 直立环调试
刚开始调PID时,小车像喝醉一样左右摇摆。后来发现关键是要先调PD参数,完全忽略积分项。具体步骤:
- 将I和D设为0,逐渐增大P直到小车出现高频抖动
- 记录此时的P值(假设为P_max)
- 取P = 0.6 * P_max作为初始值
- 逐步增加D值抑制振荡
实测中发现,角度微分项对噪声极其敏感。后来在代码中加入了一阶低通滤波:
float alpha = 0.3; // 滤波系数 filtered_gyro = alpha*gyro_raw + (1-alpha)*filtered_gyro;4.2 速度环调试
速度环要用PI控制,这里有个反直觉的要点:速度环输出要作为直立环的输入。调试时发现:
- P值太大会导致小车来回疯跑
- I值太大会产生积分饱和
- 最佳参数通常满足:P∈[0.5,2.0], I∈[0.01,0.1]
建议通过蓝牙发送调试指令,实时修改参数。我在代码中实现了这样的协议:
// 示例:SP=1.5,SI=0.05 if(收到'S'命令){ sscanf(蓝牙数据,"P=%f,I=%f",&speed_p,&speed_i); }5. 进阶优化技巧
5.1 卡尔曼滤波应用
原始DMP输出会有约2°的抖动,后来改用卡尔曼滤波后稳定性大幅提升。核心代码片段:
void KalmanUpdate(float *angle, float *bias, float angle_m, float gyro_m){ float rate = gyro_m - *bias; *angle += dt * rate; P[0][0] += dt*(P[1][1] + P[0][1]) + Q_angle*dt; P[0][1] += -dt*P[1][1]; P[1][0] += -dt*P[1][1]; P[1][1] += Q_gyro*dt; float S = P[0][0] + R_angle; float K[2] = {P[0][0]/S, P[1][0]/S}; *angle += K[0]*(angle_m - *angle); *bias += K[1]*(angle_m - *angle); P[0][0] -= K[0]*P[0][0]; P[0][1] -= K[0]*P[0][1]; P[1][0] -= K[1]*P[0][0]; P[1][1] -= K[1]*P[0][1]; }5.2 蓝牙调试APP开发
用微信小程序"蓝牙调试助手"可以实时监控参数,关键是要处理好数据分包。我的解决方案是:
- 定义帧头0xAA和帧尾0x55
- 每帧包含2字节数据长度
- 添加CRC8校验
当小车开始莫名失控时,通过蓝牙日志发现是定时器中断被意外关闭,这个经验告诉我:永远要在关键位置添加状态监测。
6. 常见问题解决方案
6.1 电机异常停转
遇到最诡异的问题是电机偶尔会突然停转,最后发现是电源线虚焊。现在我的焊接 checklist 包括:
- 使用60W烙铁,温度设定在350℃
- 焊点呈现完美的圆锥形
- 焊完后用力拉扯测试
6.2 角度漂移问题
MPU6050在长时间运行后会出现Z轴漂移,解决方法是在初始化时执行校准:
void MPU_Calibrate(){ for(int i=0; i<1000; i++){ MPU_Get_Gyroscope(&gx,&gy,&gz); gz_offset += gz; } gz_offset /= 1000; }7. 项目优化方向
最近在尝试用STM32的硬件I2C驱动MPU6050,相比软件模拟方式,速度从400kHz提升到了1MHz。关键配置如下:
I2C_InitStructure.I2C_ClockSpeed = 1000000; I2C_InitStructure.I2C_Mode = I2C_Mode_I2C; I2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2; I2C_Init(I2C1, &I2C_InitStructure);另一个改进是加入跌倒保护机制:当检测到倾斜超过45°时,立即切断电机电源。这需要修改PWM输出函数:
if(fabs(angle) > 45.0){ TIM_Cmd(TIM3, DISABLE); // 关闭PWM输出 GPIO_ResetBits(GPIOB, GPIO_Pin_5); // 刹车信号 }