1. 项目概述:STM32平衡车的核心架构
这个基于STM32的自平衡小车项目,本质上是一个典型的嵌入式控制系统案例。核心功能是通过MPU6050陀螺仪获取姿态数据,经过PID算法处理后驱动电机实现平衡控制。相比市面上常见的玩具级平衡车,这个项目的亮点在于开放了完整的硬件设计资料和软件源码,并且预留了丰富的扩展接口。
硬件架构上采用模块化设计思路:
- 主控:STM32F103C6T6(72MHz Cortex-M3内核)
- 传感器:MPU6050(三轴加速度计+陀螺仪)
- 驱动:DRV8833双H桥驱动芯片
- 通信:HLK-B40蓝牙4.0模块
- 电源:双节14500锂电池(7.4V系统)
这种设计有几个精妙之处:首先,STM32F103虽然属于入门级MCU,但其性能足以应对平衡控制这种实时性要求高的任务;其次,DRV8833驱动芯片内置了过流保护,比常用的L298N方案更安全可靠;最后,所有IO口都通过排针引出,为后续功能扩展提供了极大便利。
2. 核心模块详解与实现原理
2.1 MPU6050姿态检测系统
MPU6050的初始化流程需要特别注意寄存器配置顺序。实际项目中常见的初始化代码如下:
c复制#define MPU6050_ADDR 0x68
void MPU6050_Init(void)
{
I2C_WriteByte(MPU6050_ADDR, PWR_MGMT_1, 0x80); // 复位设备
delay_ms(100);
I2C_WriteByte(MPU6050_ADDR, PWR_MGMT_1, 0x00); // 解除休眠
I2C_WriteByte(MPU6050_ADDR, SMPLRT_DIV, 0x07); // 采样率1kHz
I2C_WriteByte(MPU6050_ADDR, CONFIG, 0x06); // 低通滤波器设置
I2C_WriteByte(MPU6050_ADDR, GYRO_CONFIG, 0x18); // 陀螺仪±2000dps量程
I2C_WriteByte(MPU6050_ADDR, ACCEL_CONFIG, 0x10); // 加速度计±8g量程
}
关键参数说明:
- 低通滤波器设置为0x06时,截止频率约为5Hz,能有效滤除高频噪声
- 陀螺仪量程选择±2000dps是为了应对平衡车可能出现的快速倾斜
- 加速度计量程±8g足够应对常规运动场景
重要提示:MPU6050的I2C通信需要严格遵循时序要求,建议在初始化后读取WHO_AM_I寄存器(0x75)验证通信是否正常,返回值应为0x68。
2.2 平衡控制算法实现
平衡控制采用串级PID结构,包含直立环和速度环两个控制回路。核心算法实现如下:
c复制typedef struct {
float Kp, Ki, Kd;
float integral;
float last_err;
} PID_TypeDef;
float PID_Calculate(PID_TypeDef *pid, float target, float feedback, float dt)
{
float err = target - feedback;
pid->integral += err * dt;
float derivative = (err - pid->last_err) / dt;
pid->last_err = err;
// 积分限幅防止windup
if(pid->integral > 1000) pid->integral = 1000;
else if(pid->integral < -1000) pid->integral = -1000;
return pid->Kp * err + pid->Ki * pid->integral + pid->Kd * derivative;
}
参数整定经验:
- 先调直立环Kp,让车能勉强站住但会缓慢移动
- 然后调速度环Kp,消除车的缓慢移动趋势
- 最后加入微分项KD抑制振荡
- 典型参数范围:
- 直立环:Kp=30~50, Ki=0, Kd=0.5~2
- 速度环:Kp=10~20, Ki=0.1~0.5, Kd=0
3. 蓝牙控制与电机驱动
3.1 HLK-B40蓝牙模块配置
蓝牙通信协议设计建议采用以下格式:
code复制!CMD,PARAM1,PARAM2,...#
例如前进指令:
code复制!MOVE,100,0#
表示以100的速度直行,0表示不转向。
接收端处理代码示例:
c复制void USART3_IRQHandler(void)
{
static uint8_t buffer[20], index = 0;
if(USART_GetITStatus(USART3, USART_IT_RXNE)){
char ch = USART_ReceiveData(USART3);
if(ch == '!') { // 开始接收新指令
index = 0;
memset(buffer, 0, sizeof(buffer));
}
else if(ch == '#') { // 指令结束
process_command(buffer);
}
else if(index < sizeof(buffer)-1) {
buffer[index++] = ch;
}
}
}
3.2 DRV8833电机驱动实现
DRV8833的正确驱动方式需要注意以下几点:
- PWM频率建议设置在18-20kHz之间
- 必须配置死区时间(典型值1-2μs)
- 电流检测电阻通常选用0.1Ω/1%精度
典型驱动代码:
c复制#define PWM_FREQ 20000 // 20kHz
void Motor_Init(void)
{
TIM_TimeBaseInitTypeDef TIM_Struct;
TIM_Struct.TIM_Prescaler = SystemCoreClock / PWM_FREQ / 1000 - 1;
TIM_Struct.TIM_Period = 1000 - 1; // 分辨率1000
TIM_TimeBaseInit(TIM3, &TIM_Struct);
TIM_OCInitTypeDef OC_Struct;
OC_Struct.TIM_OCMode = TIM_OCMode_PWM1;
OC_Struct.TIM_OutputState = TIM_OutputState_Enable;
OC_Struct.TIM_Pulse = 0;
TIM_OC1Init(TIM3, &OC_Struct);
TIM_OC2Init(TIM3, &OC_Struct);
TIM_Cmd(TIM3, ENABLE);
}
void Set_Motor(int16_t pwm_left, int16_t pwm_right)
{
// 限制PWM范围
pwm_left = constrain(pwm_left, -1000, 1000);
pwm_right = constrain(pwm_right, -1000, 1000);
// 左电机控制
if(pwm_left > 0) {
GPIO_ResetBits(GPIOA, GPIO_Pin_0); // IN1=0
GPIO_SetBits(GPIOA, GPIO_Pin_1); // IN2=1
TIM_SetCompare1(TIM3, pwm_left);
} else {
GPIO_SetBits(GPIOA, GPIO_Pin_0); // IN1=1
GPIO_ResetBits(GPIOA, GPIO_Pin_1); // IN2=0
TIM_SetCompare1(TIM3, -pwm_left);
}
// 右电机控制(类似左电机)
// ...
}
4. 系统集成与调试技巧
4.1 电源管理设计
电源电路设计要点:
- 锂电池保护电路必不可少,建议选用DW01+8205方案
- 主控板需要3.3V LDO,推荐AMS1117-3.3
- 电机驱动直接使用电池电压(7.4V)
- 各模块电源建议添加滤波电容:
- 主控电源:100μF电解+0.1μF陶瓷
- 传感器电源:10μF钽电容+0.1μF陶瓷
4.2 调试工具与方法
推荐调试工具组合:
- 逻辑分析仪(观察PWM波形和通信时序)
- 串口调试助手(查看传感器原始数据)
- 匿名四轴上位机(可视化姿态数据)
调试步骤建议:
- 先验证各模块单独工作正常
- 用I2C扫描工具确认MPU6050通信正常
- 单独测试电机正反转
- 测试蓝牙AT指令响应
- 然后逐步集成系统
- 先实现直立平衡
- 再加入速度环
- 最后整合蓝牙控制
4.3 常见问题排查
-
小车无法保持平衡:
- 检查MPU6050数据是否正常
- 确认电机极性是否正确(正反馈会导致剧烈振荡)
- 尝试减小PID参数
-
蓝牙连接不稳定:
- 检查天线是否完好
- 尝试降低蓝牙通信速率
- 在APP端添加心跳包机制
-
电机异常发热:
- 检查PWM频率是否合适
- 测量电机工作电流是否超标
- 确认死区时间设置正确
5. 扩展功能实现
5.1 超声波避障模块
HC-SR04模块的驱动代码示例:
c复制float Get_Distance(void)
{
GPIO_SetBits(TRIG_PORT, TRIG_PIN);
delay_us(10);
GPIO_ResetBits(TRIG_PORT, TRIG_PIN);
while(!GPIO_ReadInputDataBit(ECHO_PORT, ECHO_PIN));
uint32_t start = TIM_GetCounter(TIM2);
while(GPIO_ReadInputDataBit(ECHO_PORT, ECHO_PIN));
uint32_t end = TIM_GetCounter(TIM2);
return (end - start) * 0.034 / 2; // 单位cm
}
5.2 OLED显示接口
SSD1306 OLED驱动要点:
- 使用硬件I2C或模拟I2C
- 需要实现基本的绘图函数
- 建议使用现成的驱动库
显示内容建议包含:
- 实时姿态角度
- 电池电压
- 控制模式指示
- PID参数显示
5.3 红外循迹模块
TCRT5000传感器的使用技巧:
- 安装高度距离地面5-10mm最佳
- 需要根据地面颜色调整阈值
- 建议使用ADC读取模拟量而非数字量
循迹算法伪代码:
code复制while(1) {
left = Read_IR_Left();
right = Read_IR_Right();
if(left && right) {
Move_Forward();
}
else if(left) {
Turn_Right();
}
else if(right) {
Turn_Left();
}
else {
Stop();
}
}
6. 项目优化与进阶方向
6.1 硬件优化建议
-
PCB布局改进:
- 电机驱动电路尽量靠近电机接口
- 模拟信号走线远离数字信号
- 增加电源去耦电容
-
元件选型升级:
- 换用更高精度的IMU传感器(如BMI160)
- 使用带编码器的直流电机
- 考虑改用Type-C接口充电
6.2 软件优化方案
-
控制算法升级:
- 实现自适应PID
- 加入模糊控制
- 尝试LQR控制
-
功能增强:
- 添加姿态记录与回放
- 实现手机APP参数调节
- 增加自动校准功能
6.3 教学与学习价值
这个项目非常适合用于:
-
嵌入式系统教学
- 外设驱动开发
- 实时控制系统实现
- 传感器数据融合
-
电子设计竞赛训练
- 硬件电路设计
- 控制算法实现
- 系统调试技巧
-
个人技能提升
- 从零开始构建完整项目
- 掌握问题排查方法
- 积累实际工程经验
在实际开发过程中,最耗时的往往不是代码编写而是调试环节。建议准备一个详细的调试日志模板,记录每次测试的条件、现象和解决方案,这会极大提高开发效率。