前言

本文详细记录了从零开始实现基于STM32的PID自平衡小车全流程。通过实际可运行的代码,深入剖析直立环、速度环、转向环的三环串级PID控制策略,并结合MPU6050传感器数据融合、编码器测速、电机驱动等关键技术。文中不仅提供完整控制逻辑源码,还分享多日调参实战经验,解决高频抖动、跑偏、转向失灵等常见问题。无论你是电子竞赛选手、机器人爱好者还是嵌入式学习者,都能从中获得可直接复用的项目经验。

一、项目概述:我们要做什么?

目标:制作一辆能够自主保持直立,并响应遥控指令(前进、后退、转向)的两轮自平衡小车STM32 F103C8T6入门PID控制算法指南(以平衡小车为例)
核心挑战:
如何感知自身姿态?​ -> MPU6050(陀螺仪+加速度计)传感器,通过DMP库或滤波算法获取精确的倾角与角速度 。
如何保持直立?​ -> 直立环PD控制。这是小车的“本能反射”。
如何控制速度?​ -> 速度环PI控制。防止小车因微小偏差而越跑越快,实现定速与刹车。
如何实现转向?​ -> 转向环PD控制。通过差速实现灵活转向。
最终效果:小车在无外力下稳定直立,轻推可回正,通过遥控器可控制其前后移动和左右转向。

二、核心硬件选型建议

模块型号/参数备注
主控STM32F103C8T6 (蓝桥杯/正点原子最小系统)Cortex-M3,72MHz,资源足够
姿态传感器MPU6050 (集成DMP)必须支持DMP,极大降低姿态解算难度
电机25GA370减速电机 (带AB相编码器)减速比1:30,编码器11线,供电6V
电机驱动TB6612FNG双路驱动,性能优于L298N
电池2S锂电池 (7.4V)容量建议1500mAh以上
车架某宝平衡小车底盘套件含电机、轮子、亚克力板等

三、核心控制算法详解:三环PID串级控制

这是本文的核心思想,也是平衡车能够稳定运行的理论基础零基础制作平衡小车【连载】13—平衡小车代码讲解(附源码)

控制回路框架图

链接: Image source.

各环代码实现与解析

1.直立环(内环,核心)
作用:快速响应姿态变化,保持车身直立。控制周期要短(本例为5ms)。

// 直立环PD控制器
// 输入:期望角度(Med)、真实角度(Angle)、Y轴角速度(gyro_Y)
int Vertical(float Med, float Angle, float gyro_Y) {
    int temp;
    temp = Vertical_Kp * (Angle - Med) + Vertical_Kd * gyro_Y;
    return temp; // 输出为PWM基础值
}

参数范围:Vertical_Kp(约180),Vertical_Kd(约2)。Kd是镇定关键

2. 速度环(外环,PI控制)
作用:将编码器测量的轮速与目标速度比较,通过PI运算输出一个角度调整量,叠加到直立环的目标角度上,从而实现“想加速就前倾,想减速就后仰”。

int Velocity(int Target, int encoder_L, int encoder_R) {
    static int Err_LowOut_last, Encoder_S;
    static float a = 0.7; // 低通滤波系数
    int Err, Err_LowOut, temp;
    Velocity_Ki = Velocity_Kp / 200; // Ki与Kp关联设置,简化调参

    // 1. 偏差 = (左+右编码器值) - 目标速度
    Err = (encoder_L + encoder_R) - Target;
    // 2. 一阶低通滤波,消除脉冲干扰
    Err_LowOut = (1 - a) * Err + a * Err_LowOut_last;
    Err_LowOut_last = Err_LowOut;
    // 3. 积分分离与限幅,防止积分饱和
    Encoder_S += Err_LowOut;
    Encoder_S = Encoder_S > 20000 ? 20000 : (Encoder_S < (-20000) ? (-20000) : Encoder_S);
    if(stop == 1) Encoder_S = 0, stop = 0; // 停车时清空积分
    // 4. PI计算
    temp = Velocity_Kp * Err_LowOut + Velocity_Ki * Encoder_S;
    return temp; // 输出为角度调整量
}
  • 低通滤波:滤除编码器计数中的毛刺,使速度信号更平滑。
  • 积分限幅:必须做!否则积分项会无限累积(“windup”),导致系统失控。
  • 参数范围:Velocity_Kp(约0.6),Velocity_Ki(约0.003)。数值很小,但影响巨大

3. 转向环(独立环,PD控制)
作用:根据遥控转向指令和Z轴角速度,计算左右电机的差速值,实现灵活、稳定的转向。

int Turn(float gyro_Z, int Target_turn) {
    int temp;
    temp = Turn_Kp * Target_turn + Turn_Kd * gyro_Z;
    return temp; // 输出为转向差速值
}
  • 转向约束:代码中一个精妙的设计是,当没有转向指令时,Turn_Kd较大,利用Z轴陀螺仪抑制小车自身意外偏转(“转向阻尼”);当有主动转向指令时,Turn_Kd设为0,消除阻尼,让转向更灵敏。
  • 参数范围:Turn_Kp(约10),Turn_Kd(0~0.6)。

4. 最终PWM合成

// 三环输出合成
Velocity_out = Velocity(Target_Speed, Encoder_Left, Encoder_Right); // 速度环输出是角度调整量
Vertical_out = Vertical(Velocity_out + Med_Angle, roll, gyrox);    // 直立环核心
Turn_out = Turn(gyroz, Target_turn);                               // 转向环差速

PWM_out = Vertical_out; // 直立环输出作为PWM基础值
MOTO1 = PWM_out - Turn_out; // 左轮 = 基础值 - 差速
MOTO2 = PWM_out + Turn_out; // 右轮 = 基础值 + 差速
Limit(&MOTO1, &MOTO2);      // PWM限幅
Load(MOTO1, MOTO2);         // 加载到电机驱动
}
  • Med_Angle(机械中值):小车物理平衡时的实际角度。因安装、重心偏移,通常不是0。必须通过实际测量校准

四、PID参数整定实战:从“摇头晃脑”到“稳如老狗”

调试顺序:先内环(直立),后外环(速度),最后转向环史上最详细的PID教程——理解PID原理及优化算法

4.1 直立环调参(先P后D)

  1. 准备:用支架或手稳住小车,连接上位机(如VOFA+)观察角度和PWM波形。参数先清零。
  2. 只加P:设Kp=10, Kd=0。上电,小车会向一边加速倒下。逐渐增大Kp(20, 50, 100…),直到小车在平衡点附近持续稳定地来回摆动。此时Kp约为临界值。
  3. 加入D:在摆动的基础上,逐渐加入Kd(从0.5开始)。观察现象:摆动幅度会明显减小。继续增大Kd,直到小车能勉强站立2-3秒。
  4. 微调:此时轻推小车,它应有“弹性”地回正。如果回正后来回振荡(“点头”),增大Kd或减小Kp。如果反应迟钝,推了没反应,减小Kd或增大Kp

4.2 速度环调参(先P后I)

  1. 前提:直立环已基本稳定,小车能用手扶着在原地站立。
  2. 只加P:设Ki=0,给Kp一个极小值(如0.1)。目标速度设为0。松手,观察现象:
  • 小车可能缓慢向一个方向移动 -> 说明P在起作用,但太小。
  • 小车突然加速冲向一边 -> 说明P太大,或极性反了(检查编码器方向)。
  1. 调整P缓慢增大Kp,让小车在受到轻微速度干扰(如轻推)后,能缓慢回到原点,而不是越跑越快。P太大会导致小车前后“抽搐式”移动。
  2. 加入I非常小心地增加Ki(如Kp/200)。I的作用是消除静态误差(比如地面不平导致的小范围漂移)。I过大会引起低频大幅振荡,非常危险。

4.3 转向环调参

  1. 静态转向:抬起小车,发送转向指令。观察左右轮是否按预期差速转动。调整Turn_Kp改变转向灵敏度。
  2. 动态转向:放下小车,在直立和速度环工作的基础上测试转向。如果转向时车身晃动大,适当增大Turn_Kd(但注意代码中的“转向约束”逻辑,主动转向时Kd=0)。

五、 关键代码模块与调试技巧

5.1 传感器数据获取

// 1. 编码器(定时器编码器模式)
Encoder_Left = __HAL_TIM_GET_COUNTER(&htim2);
__HAL_TIM_SET_COUNTER(&htim2, 0); // 读取后清零
// 注意:右轮编码器方向取反,因为电机对称安装
Encoder_Right = -__HAL_TIM_GET_COUNTER(&htim4);
__HAL_TIM_SET_COUNTER(&htim4, 0);

// 2. MPU6050 DMP库(强烈推荐!)
if(mpu_dmp_get_data(&pitch, &roll, &yaw) == 0){
    // 姿态角已由DMP解算好,单位:度
    MPU_Get_Gyroscope(&gyrox, &gyroy, &gyroz); // 读取原始角速度
}

DMP库是成功的关键!它内部完成了复杂的传感器融合和四元数解算,直接输出稳定的欧拉角,极大降低了开发难度。

5.2 控制周期与定时器
使用一个基本定时器(如TIM3)产生精确的5ms中断,在中断服务函数中调用Control()函数。定时必须准确,这是数字控制器的“心跳”。

// HAL库定时器中断回调函数
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) {
    if(htim->Instance == TIM3) {
        Control(); // 核心控制函数,每5ms执行一次
    }
}

5.3 蓝牙遥控与指令解析
通过串口中断接收蓝牙指令(如手机APP发送的‘F’, ‘B’, ‘L’, ‘R’字符),设置全局标志位(Fore, Back, Left, Right),在主控制循环中响应。

// 串口中断服务函数 (简化)
void USART1_IRQHandler(void) {
    if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) {
        char cmd = USART_ReceiveData(USART1);
        switch(cmd) {
            case 'F': Fore = 1; break;
            case 'B': Back = 1; break;
            // ... 其他指令
        }
    }
}

六 、核心硬件选型建议

现象可能原因解决方案
一上电就猛转1. 电机线接反,2. 角度极性反,3. PWM输出极性反1. 交换电机线测试,2. 在代码中给roll角乘以-1,3. 检查电机驱动逻辑
高频剧烈抖动1. Kd过大,2. 控制周期不稳定,3. 机械结构松动1. 减小Kd,2. 检查定时器配置,确保中断周期准确
缓慢向一边漂移1. 机械中值不准,2. 速度环I项过大或过小,3. 地面不平1. 重新校准Med_Angle,2. 微调速度环Ki,或加入死区
转向不灵敏或过冲1. 转向环Kp/Kd不合适,2. 编码器安装松动,3. 电池电量低1. 调整转向环参数,2. 紧固编码器码盘,3. 充电!电量不足时控制性能急剧下降
用手能稳住,松手就倒1. 直立环参数未调好,2. 重心过高,3. 速度环在捣乱1. 回归步骤,重新调直立环PD,2. 降低重心(电池下置),3. 先将速度环参数设为0,只调直立环

结语

总之,PID参数的调试是一门结合理论知识与实践经验的精妙艺术。恰当的参数能让STM32平衡小车在受到扰动时迅速稳定恢复,展现出令人满意的动态性能。通过本文对三环串级PID控制原理的剖析、硬件平台的搭建、核心代码的实现以及分步调参方法的详细介绍,希望读者能够成功完成基于STM32与MPU6050的自平衡小车项目,并在此过程中,真正领悟到PID算法的精髓及其在嵌入式系统中的应用魅力PID深度解剖

源码获取与交流

本文涉及的所有完整工程源码(包含STM32 HAL库驱动、MPU6050 DMP库、完整控制逻辑、工程文件等),已整理归档。
获取方式:关注我的CSDN博客,在本文评论区留言"PID平衡车源码",或私信我,我会将GitHub仓库链接或网盘链接发送给你。
调参过程中遇到任何问题,欢迎在评论区留言,我们一起讨论解决
版权声明:​ 本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐