1. 项目概述
两轮自平衡车作为典型的嵌入式控制系统,融合了传感器技术、自动控制理论和嵌入式开发等多个领域的知识。这个项目使用STM32F103C8T6作为主控芯片,通过MPU-6050六轴传感器获取姿态数据,采用PID算法实现平衡控制,并整合蓝牙遥控功能,是一个非常适合嵌入式开发者进阶学习的综合性项目。
我在实际开发过程中发现,很多初学者在尝试制作自平衡车时,往往会在姿态解算、PID参数整定和系统稳定性这几个关键环节遇到困难。本文将详细解析从硬件选型到软件实现的完整开发流程,特别注重分享那些在常规教程中很少提及的实战经验和调试技巧。
2. 硬件系统设计
2.1 核心控制器选型
STM32F103C8T6这款Cortex-M3内核的MCU之所以成为我们的首选,主要基于以下几个关键考量:
-
处理性能:72MHz的主频配合32位总线宽度,能够满足实时控制的需求。在实际测试中,完成一次完整的姿态采集→解算→PID计算→电机控制的闭环周期可以控制在5ms以内。
-
外设资源:
- 3个USART接口(分别用于蓝牙通信、调试输出和备用)
- 2个硬件I2C接口(连接MPU6050和OLED)
- 多达15个定时器通道(用于PWM生成和编码器接口)
-
成本优势:相比同性能的ARM芯片,ST的供货稳定性和价格都更有优势。
注意:在PCB布局时,务必在VDD和VSS之间放置0.1μF的去耦电容,且尽量靠近芯片引脚。我在初期测试中就曾因为忽略这个细节,导致ADC采样出现明显波动。
2.2 电源系统设计
电源稳定性直接关系到整个系统的可靠性,我们的设计采用两级稳压方案:
-
第一级(LM2596):将锂电池电压(7.4V)降至5V
- 输入电容:100μF/16V电解电容
- 输出电容:220μF/10V电解电容
- 续流二极管:1N5822(3A肖特基)
-
第二级(AMS1117-3.3):为MCU和传感器提供3.3V
- 输入/输出电容:10μF陶瓷电容
- 特别注意:AMS1117的压差需≥1V,因此5V输入是必要的
实测表明,这种设计在电机启动瞬间(电流可达2A)仍能保持3.3V输出的波动不超过±0.05V。
2.3 传感器模块
2.3.1 MPU-6050配置要点
-
硬件连接:
- SCL/SDA:配置为开漏输出,需接4.7kΩ上拉电阻
- INT引脚:用于DMP中断,配置为下降沿触发
-
初始化流程:
c复制// 典型初始化代码
MPU_InitStructure.I2C_Mode = I2C_Mode_I2C;
MPU_InitStructure.I2C_ClockSpeed = 400000; // 400kHz
MPU_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2;
MPU_InitStructure.I2C_OwnAddress1 = 0x68<<1;
MPU_InitStructure.I2C_Ack = I2C_Ack_Enable;
MPU_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;
I2C_Init(MPU_I2C, &MPU_InitStructure);
- 校准技巧:
- 上电后保持设备静止2秒,采集100组数据取平均作为零偏
- 定期(如每小时)进行动态零偏补偿
2.3.2 编码器接口设计
我们选用增量式光电编码器(500线),通过TIM的编码器接口模式实现四倍频:
c复制TIM_EncoderInterfaceConfig(TIMx, TIM_EncoderMode_TI12,
TIM_ICPolarity_Rising, TIM_ICPolarity_Rising);
实测分辨率达到2000脉冲/转,速度测量误差<1%。
2.4 电机驱动电路
TB6612相比传统的L298N具有明显优势:
| 参数 | TB6612 | L298N |
|---|---|---|
| 效率 | 90% | 60% |
| 静态功耗 | 0.5W | 2W |
| PWM频率上限 | 100kHz | 20kHz |
驱动电路设计要点:
- VM电机电源需与逻辑电源隔离
- STBY引脚需上拉到VCC
- AO1/AO2、BO1/BO2需加0.1μF电容滤波
3. 软件系统实现
3.1 系统架构设计
采用前后台系统架构:
-
前台:中断服务程序
- 1kHz定时中断(姿态更新)
- 200Hz定时中断(PID计算)
- 蓝牙数据接收中断
-
后台:主循环
- 状态显示
- 按键扫描
- 低优先级任务
mermaid复制graph TD
A[系统初始化] --> B[外设初始化]
B --> C[传感器校准]
C --> D[主循环]
D --> E[状态显示]
D --> F[按键处理]
D --> G[蓝牙指令解析]
3.2 姿态解算实现
3.2.1 DMP配置流程
- 加载官方dmpKey.h和dmpmap.h
- 初始化DMP:
c复制dmp_load_motion_driver_firmware();
dmp_set_orientation(inv_orientation_matrix_to_scalar(gyro_orientation));
dmp_enable_feature(DMP_FEATURE_6X_LP_QUAT | DMP_FEATURE_SEND_RAW_ACCEL);
dmp_set_fifo_rate(200); // 200Hz
- 中断处理:
c复制if(mpu_get_int_status() & 0x02){
dmp_read_fifo(gyro, accel, quat, &sensor_timestamp, &sensors, &more);
}
3.2.2 四元数转欧拉角优化
标准转换公式存在奇异点问题,我们采用以下优化算法:
c复制float q0 = quat[0] / 1073741824.0f; // q30格式转换
float q1 = quat[1] / 1073741824.0f;
float q2 = quat[2] / 1073741824.0f;
float q3 = quat[3] / 1073741824.0f;
// 优化后的俯仰角计算(避免除零)
pitch = asin(2*(q0*q2 - q1*q3));
if(fabs(pitch) > 0.999f){
roll = 0;
} else {
roll = atan2(2*(q0*q1 + q2*q3), 1 - 2*(q1*q1 + q2*q2));
}
3.3 PID控制算法
3.3.1 串级PID结构
我们采用角度环(外环)+速度环(内环)的串级控制:
code复制角度偏差 → 角度PID → 目标速度 → 速度PID → 电机输出
参数整定步骤:
- 先调角度环P(其他参数为0)
- 增加角度环D抑制震荡
- 最后调速度环I消除静差
3.3.2 代码实现
c复制typedef struct {
float kp, ki, kd;
float integral;
float last_error;
} PID;
float PID_Calculate(PID* pid, float error) {
float p_out = pid->kp * error;
pid->integral += error;
if(pid->integral > 1000) pid->integral = 1000;
if(pid->integral < -1000) pid->integral = -1000;
float i_out = pid->ki * pid->integral;
float d_out = pid->kd * (error - pid->last_error);
pid->last_error = error;
return p_out + i_out + d_out;
}
调试技巧:通过OLED实时显示PID各分量输出,可以直观判断哪个环节需要调整。例如发现角度环输出频繁正负跳变,说明D参数需要增大。
3.4 蓝牙通信协议
自定义轻量级协议帧格式:
| 字节 | 含义 | 说明 |
|---|---|---|
| 0 | 帧头 | 固定0xAA |
| 1 | 指令类型 | 0x01:前进/后退 |
| 2 | 数据 | 速度值(0-100) |
| 3 | 校验和 | 前3字节异或 |
Android端示例代码:
java复制private void sendCommand(byte cmd, byte value) {
byte[] buffer = new byte[4];
buffer[0] = (byte)0xAA;
buffer[1] = cmd;
buffer[2] = value;
buffer[3] = (byte)(buffer[0]^buffer[1]^buffer[2]);
mBluetoothService.write(buffer);
}
4. 系统调试与优化
4.1 静态测试
-
电源测试:
- 空载时测量各点电压
- 带载(启动电机)测试纹波
- 长时间工作温升测试
-
传感器校准:
- 使用专业转台验证角度精度
- 记录零偏随温度的变化曲线
4.2 动态调试
4.2.1 PID参数整定
推荐初始参数范围:
| 参数 | 角度环 | 速度环 |
|---|---|---|
| P | 20-50 | 5-15 |
| I | 0 | 0.1-0.5 |
| D | 1-5 | 0 |
调试步骤:
- 先调角度环P使车体能勉强站立
- 增加角度环D抑制震荡
- 最后加入速度环I消除静差
4.2.2 抗干扰测试
- 施加瞬时推力(约1N·s)
- 记录恢复时间(应<1s)
- 观察超调量(应<5°)
4.3 常见问题排查
-
车体持续单方向加速
- 检查编码器接线是否正确
- 验证速度环极性(输出应与偏差同号)
-
高频振荡
- 降低角度环P
- 增加角度环D
- 检查传感器数据是否有噪声
-
蓝牙连接不稳定
- 确保模块与天线距离>5cm
- 在VCC引脚加10μF钽电容
- 降低UART波特率(建议9600bps)
5. 关键优化技巧
-
传感器数据滤波:
- 加速度计:低通滤波(α=0.2)
- 陀螺仪:滑动平均(窗口大小=5)
-
PID输出限幅:
c复制#define LIMIT(x, min, max) ((x)<(min)?(min):((x)>(max)?(max):(x)))
float out = PID_Calculate(&pid, error);
out = LIMIT(out, -900, 900); // 对应PWM占空比±90%
-
低功耗设计:
- 空闲时降低MPU采样率
- 动态调整OLED刷新率
- 使用WFI指令进入低功耗模式
-
代码优化:
- 将频繁调用的函数添加
__inline修饰 - 使用查表法替代复杂三角函数
- 关键代码用汇编优化
- 将频繁调用的函数添加
经过这些优化后,系统功耗从最初的850mA降至500mA,续航时间提升40%。
这个项目最让我有成就感的部分是PID参数的整定过程。记得第一次成功让小车稳定站立时,那种兴奋感至今难忘。建议初学者一定要亲手调试PID参数,这是理解控制理论最好的实践方式。当看到小车从摇摆不定到稳稳站立,你会对"负反馈"这个概念有全新的认识。
