ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

STM32平衡小车软件设计:从PID控制到姿态解算的实战指南

2026/9/2 1:54:02 拓冰建站 浏览量
STM32平衡小车软件设计:从PID控制到姿态解算的实战指南 1. 先搞清楚平衡小车软件到底要做什么平衡小车的软件部分核心就一件事让一个只有两个轮子的车能自己站稳、自己走直线、自己转弯甚至能应对轻微的推搡。听起来像魔法但拆开看就是一套实时采集数据、快速计算、精准控制的循环。很多人一上来就找代码、调参数结果要么小车疯狂“跳舞”要么直接“躺平”。问题往往出在没理清软件的逻辑闭环。这个闭环可以拆成三个关键环节感知、决策、执行。感知小车怎么知道自己要倒了靠的是传感器最核心的就是MPU6050这类惯性测量单元IMU。它能实时输出小车的倾角俯仰角和角速度。这是所有控制的“眼睛”数据不准后面全错。决策知道了倾角和角速度然后呢这里就是PID控制器登场的时候。它就像一个经验丰富的骑手感觉到车要往前倒角度为正就命令轮子往前转来“追”这个角度同时感觉到倒的速度太快角速度为正就再施加一个“刹车”力。PID就是根据“角度差”和“角速度”来计算出电机应该输出多大的力PWM占空比。执行决策出来的PWM信号通过STM32的定时器输出驱动电机驱动模块如TB6612、DRV8833从而控制电机的转速和方向。执行要快、要准不能有大的延迟。所以软件篇的任务就是教你如何在STM32上搭建起这个“读取MPU6050 - 进行PID运算 - 输出PWM控制电机”的实时循环。我会从最基础的工程创建、传感器驱动开始一步步带你走到能让小车稳稳站立的PID调参。我们不追求一步登天而是确保每一步你都能理解、能复现、能调试。2. 搭建你的开发环境与工程框架在写第一行代码之前先把“战场”布置好。一个清晰的工程结构能让你在调试时少掉一半的头发。2.1 硬件与软件准备清单你需要准备好以下东西硬件STM32核心板F1系列如STM32F103C8T6就足够性价比高资料多。平衡小车套件包含底盘、两个带编码器的减速电机、轮子。MPU6050模块六轴陀螺仪加速度计务必购买带芯片的模块软件解算会简单很多。电机驱动模块TB6612或DRV8833比古老的L298N效率高、发热小。电源建议两路供电一路如7.4V锂电池给电机驱动另一路可通过降压模块到5V/3.3V给STM32和传感器。避免电机启动时的电压波动导致单片机复位。杜邦线、开关、可能还需要一个蓝牙模块用于无线调试和遥控。软件Keil MDK-ARM或STM32CubeIDE前者是传统经典后者是ST官方基于Eclipse的免费工具集成CubeMX配置外设非常直观。本教程以STM32CubeIDE为例因为它能图形化配置减少底层寄存器操作的困扰。STM32CubeMX如果独立使用需要配合Keil/IAR。在CubeIDE中已内置。它是配置引脚、时钟、外设的神器。串口调试助手如XCOM、SSCOM用于查看程序打印的调试信息。逻辑分析仪或示波器非必需但强烈推荐用于观察PWM波形、编码器脉冲是排查硬件问题的终极手段。2.2 使用STM32CubeMX创建基础工程启动CubeMX选择芯片型号例如STM32F103C8Tx。配置时钟树对于F103通常使用外部高速时钟HSE然后通过PLL倍频到72MHz系统时钟。在Clock Configuration选项卡中选择HSE将PLL输入源设为HSE然后调整倍频系数使系统时钟SYSCLK达到72MHz。APB1总线时钟定时器常用最高36MHzAPB2总线时钟最高72MHz。配置关键外设SYS在Debug下拉菜单中选择Serial Wire。这开启了SWD下载调试接口必须配置否则可能无法下载程序。GPIO根据你的模块连接预先配置一些关键引脚为输出如控制电机驱动的使能脚或输入如编码器接口但编码器通常用定时器编码器模式。USART1这是最常用的调试串口。模式选择Asynchronous配置好波特率如115200。引脚通常是PA9(TX) PA10(RX)。记得在NVIC Settings中开启全局中断。I2C1用于连接MPU6050。模式选择I2C配置为标准模式100kHz或快速模式400kHz。引脚通常是PB6(SCL) PB7(SDA)。同样开启中断。TIM2/TIM3/TIM4我们需要至少3个定时器。两个定时器用于编码器接口模式选择两个通用定时器如TIM2, TIM4将Combined Channels设置为Encoder Mode。这会将对应通道的GPIO自动配置为编码器输入。编码器A相接CH1B相接CH2。一个定时器用于生成PWM选择一个高级定时器如TIM1或通用定时器如TIM3。选择一个通道如CH1, CH2为PWM Generation CHx。我们将用两个通道分别控制两个电机的速度。PWM频率建议在10kHz-20kHz之间太高了驱动模块可能发热太低了电机噪音大、控制不细腻。例如系统时钟72MHz预分频器PSC设为71自动重载值ARR设为999则PWM频率 72MHz / (711) / (9991) 1kHz。注意ARR值决定了PWM的分辨率值越大调速越细腻。另一个定时器用于系统时基和中断通常使用Systick系统滴答定时器即可CubeMX默认已配置。我们将用它来产生一个固定周期如5ms或10ms的中断在这个中断里执行整个控制循环读传感器、PID计算、更新PWM。生成工程代码转到Project Manager选项卡设置工程名、路径选择Toolchain/IDE为STM32CubeIDE。在Code Generator中勾选Generate peripheral initialization as a pair of ‘.c/.h’ files per peripheral这样每个外设的代码会独立成文件结构清晰。点击GENERATE CODE然后Open Project。现在一个包含所有基础外设初始化代码的工程就创建好了。main.c里已经生成了MX_GPIO_InitMX_USART1_UART_InitMX_I2C1_InitMX_TIMx_Init等函数并在main函数中按顺序调用。3. 编写核心驱动与控制逻辑有了工程骨架接下来就要填充“肌肉”和“神经”。3.1 MPU6050数据读取与姿态解算MPU6050通过I2C通信。ST的HAL库提供了HAL_I2C_Mem_Read和HAL_I2C_Mem_Write函数可以方便地读写芯片内部寄存器。初始化与配置在main函数初始化部分之后调用一个MPU6050_Init()函数。这个函数需要唤醒MPU6050写PWR_MGMT_1寄存器。设置陀螺仪和加速度计量程例如±2000°/s 和 ±4g。配置数字低通滤波器DLPF滤除高频噪声。开启或关闭相关中断初始调试可以不开启。数据读取编写MPU6050_Read_Data()函数。一次性读取加速度计和陀螺仪的六个原始值每个16位。通常需要将原始值根据量程转换为实际物理量如加速度单位g角速度单位°/s。姿态解算 - 关键一步得到原始数据后需要解算出我们需要的俯仰角Pitch。这里有几种方法复杂度递增仅用加速度计通过atan2(AccY, AccZ)可以计算出一个角度。但加速度计对震动非常敏感小车一动这个角度会剧烈抖动无法单独使用。互补滤波这是最适合入门的方法。原理很简单加速度计测得的角度长期可靠静态或低速时但动态响应差陀螺仪积分得到的角度短期精确但会随时间漂移。互补滤波就是将两者“互补”一下。公式类似角度 0.98 * (上一角度 陀螺仪角速度 * 采样时间) 0.02 * 加速度计角度。这个0.98和0.02是权重系数可以根据实际情况调整。在5ms的中断里这个计算量很小。卡尔曼滤波更优但更复杂的算法能给出更平滑、更准确的估计。如果你是初学者强烈建议先从互补滤波开始它能让你快速看到效果建立信心。你需要将解算出的角度单位通常是度或弧度和角速度直接从陀螺仪读取或做简单处理存储到全局变量中供PID控制器使用。3.2 编码器速度读取编码器用于测量电机的实际转速是实现速度闭环让小车按指定速度行走和位置闭环让小车走到指定位置的基础。即使只做平衡读取速度也能让控制更稳定。硬件连接将编码器的A、B相分别接到已配置为编码器模式的定时器的CH1和CH2引脚。编码器供电接3.3V或5V地线接好。读取计数值在CubeMX生成的代码中编码器定时器已经配置好。你只需要在需要的时候比如在5ms定时中断里读取计数寄存器TIMx-CNT的值。计算速度定义一个全局变量记录上一次的计数值last_count。在本次中断中读取当前计数值current_count。速度 (current_count - last_count) / (编码器线数 * 减速比 * 采样时间)。注意处理计数器溢出对于向上/向下计数模式和方向current_count可能小于last_count表示反转。将last_count更新为current_count。得到的速度单位是“每秒钟的编码器脉冲数”或换算成“轮子转速RPM”。对于PID控制使用一个统一的相对单位即可比如“脉冲数/控制周期”。3.3 PID控制器实现PID是比例、积分、微分的缩写。对于平衡小车我们通常使用串级PID外环是角度环内环是速度环。角度环直立环这是核心没有它车就站不起来。输入是期望角度通常为0即直立和当前角度来自MPU6050解算的偏差输出是一个目标速度。P项用于“扶正”D项用于“抑制摆动”I项在平衡中一般不用或用很小的值来补偿微小偏差。角度环PD控制输出 Kp_angle * 角度偏差 Kd_angle * 角速度。注意这里的微分项直接用了陀螺仪测得的角速度这比用角度差分更准确、抗噪声更好被称为“不完全微分”。速度环速度环输入是角度环输出的目标速度和编码器测得的实际速度的偏差输出是PWM的增量。P项用于“追速度”I项用于“消除静差”让小车能稳定在某个位置而不是慢慢漂走D项一般不用。速度环PI控制输出增量 Kp_speed * 速度偏差 Ki_speed * 速度偏差积分。这个增量会叠加到角度环输出的PWM基础上。代码结构 在5ms定时中断服务函数里按顺序执行void HAL_SYSTICK_Callback(void) { // 假设你用SysTick定时5ms static uint32_t control_cnt 0; control_cnt; if(control_cnt 5) { // 5ms * 5 25ms控制一次可根据需要调整 control_cnt 0; // 1. 读取传感器数据 Get_MPU6050_Data(angle, gyro); // 获取角度和角速度 Get_Motor_Speed(speed_left, speed_right); // 获取左右轮速度 // 2. 角度环PD计算 angle_error 0 - angle; // 期望角度为0 target_speed Kp_angle * angle_error Kd_angle * gyro; // 3. 速度环PI计算 (以左轮为例) speed_error_left target_speed - speed_left; // 这里target_speed可能需要根据转向需求做左右轮分配 speed_integral_left speed_error_left; // 积分限幅防止积分饱和 if(speed_integral_left INTEGRAL_MAX) speed_integral_left INTEGRAL_MAX; if(speed_integral_left -INTEGRAL_MAX) speed_integral_left -INTEGRAL_MAX; pwm_increment_left Kp_speed * speed_error_left Ki_speed * speed_integral_left; // 4. 综合输出PWM pwm_left BALANCE_PWM_BASE pwm_increment_left; // 平衡基础PWM 速度环修正 // PWM限幅 if(pwm_left PWM_MAX) pwm_left PWM_MAX; if(pwm_left PWM_MIN) pwm_left PWM_MIN; // 5. 更新电机PWM __HAL_TIM_SET_COMPARE(htim3, TIM_CHANNEL_1, pwm_left); // 以左轮接TIM3 CH1为例 // 右轮同理... } }3.4 电机控制与PWM输出PID计算出了最终的PWM值现在要输出给电机驱动。方向控制电机驱动模块通常有两个输入信号AIN1/AIN2或IN1/IN2控制方向PWMA控制速度。我们可以用STM32的两个GPIO引脚控制方向一个定时器通道控制PWM。正转AIN11, AIN20, PWM有效。反转AIN10, AIN21, PWM有效。刹车AIN11, AIN21。停止AIN10, AIN20。 在代码中根据计算出的pwm_left的正负来决定方向引脚的电平并取其绝对值作为PWM占空比。PWM输出使用__HAL_TIM_SET_COMPARE(htimx, TIM_CHANNEL_x, pwm_value)函数来快速更新PWM占空比。pwm_value的值应在0到自动重载值ARR之间。4. 调试、参数整定与常见问题排查这是最考验耐心也最能学到东西的环节。别指望一次成功准备好和你的小车“斗智斗勇”。4.1 分步调试法绝对不要把所有代码写完、所有参数设上就指望小车能站。要像做实验一样一步一步验证。验证传感器先不接电机只连接MPU6050和串口。编写代码通过串口每秒打印出解算出的角度和角速度。用手缓慢转动模块观察数据变化是否平滑、方向是否正确。确保互补滤波或卡尔曼滤波工作正常静态时角度稳定快速转动时响应及时。验证电机与编码器将小车架起来轮子悬空。编写一个简单的测试程序让电机以一个固定的PWM正转、反转。观察轮子转向是否正确。同时读取编码器值并打印转动轮子看计数值是否增减方向判断是否正确。开环测试尝试最简单的“比例控制”。在定时中断里只使用角度环的P项PWM输出 Kp * 角度偏差。给小车一个初始角度用手扶一个角度然后松手。观察小车的反应。如果小车往倒的方向加速倒下说明Kp的符号错了这是最常见的问题。检查你的角度正方向定义、电机正方向定义是否一致。通常小车前倾角度为正时轮子应该向前转产生向前的加速度来“追”上车身。如果小车有往回“扶”的趋势但非常无力慢慢倒下说明Kp太小。如果小车剧烈地来回摆动振荡说明Kp太大。这个阶段的目标是找到一个能让小车在松手后有明显“挣扎”动作的Kp值。加入微分D项在Kp大致可用的基础上加入Kd * 角速度。微分项的作用是抑制振荡相当于“阻尼”。从小值开始慢慢增加Kd。效果小车的摆动应该会迅速减弱趋于稳定。如果Kd太大小车会反应迟钝像“冻住”一样然后缓慢倒下。注意角速度的符号也很关键。通常角速度的符号与角度偏差的变化率符号一致。如果加入Kd后小车反而抖得更厉害可能是Kd符号错了。尝试站立当Kp和Kd调到一个不错的状态时尝试让小车自己站立。用手扶住接近直立位置轻轻松手。理想情况是小车能在原地轻微地前后调整保持不倒。此时一定要做好小车突然失控冲出去的心理和物理准备放在空旷地方远离贵重物品。加入速度环当小车能基本站稳后你会发现它可能会慢慢地朝一个方向漂移。这是因为角度环的平衡点可能不是绝对的0度或者有微小不对称。这时引入速度环的I项积分项来消除这个静差。先给Kp_speed一个很小的值Ki_speed也为0。观察小车漂移是否减缓。然后慢慢增加Ki_speed让小车能稳定在一点不漂移。速度环的P和I值一定要小它们的作用是微调如果太大会干扰甚至破坏角度环的平衡。4.2 常见问题与排查清单问题小车一上电就疯狂旋转或猛冲。排查电机方向检查电机接线确认PWM增大时轮子转向是否符合预期。可以单独写一个电机测试程序。角度符号确认MPU6050安装方向。前倾时解算出的角度是正还是负这个符号必须和电机“向前转”的定义匹配。PID参数符号Kp和Kd的符号是否正确。这是最可能的原因。传感器数据异常通过串口打印原始加速度计和陀螺仪数据看是否在合理范围内。检查I2C通信是否正常接线是否松动。问题小车有平衡趋势但来回振荡越来越厉害最后倒下。排查Kp太大减小Kp。Kd太小或为0增加Kd值引入阻尼。控制周期不稳定确保你的PID计算是在一个严格定时的中断中执行的。用示波器或点灯法测量中断周期是否恒定。传感器噪声大检查MPU6050的DLPF数字低通滤波器设置是否开启并尝试提高滤波强度。检查电路电源是否干净传感器是否固定牢固。问题小车能站住但反应迟钝轻轻一推就倒恢复很慢。排查Kp太小增加Kp提高“扶正”的力度。Kd太大减小Kd过大的阻尼会抑制必要的快速调整。机械结构问题重心是否太高轮子是否打滑电池等重物应尽量放低靠近轮轴。问题小车站一会儿后开始缓慢向一个方向漂移。排查引入速度环这是速度环I项要解决的核心问题。机械不对称两个电机的性能、齿轮间隙可能有微小差异尝试给两个电机设置一个微小的“死区”补偿或不同的基础PWM值。传感器零漂MPU6050的陀螺仪可能存在零漂。在小车静止时读取一段时间的角速度并求平均将这个平均值作为零点偏移量在每次读数时减去它。问题代码运行一段时间后卡死或复位。排查堆栈溢出中断函数里不要做太复杂的计算或调用太多函数。检查.map文件看看堆栈使用情况。中断冲突确保不同中断的优先级设置合理特别是SysTick、定时器、串口中断之间。电源问题电机启动瞬间电流很大可能导致单片机电压被拉低而复位。务必确保电机电源和单片机电源隔离或使用大电容缓冲。调试平衡小车是一个系统工程。硬件、软件、参数、机械环环相扣。我的经验是日志串口打印是你最好的朋友。把关键数据角度、角速度、PWM输出、编码器速度实时打印出来用绘图软件如SerialPlot可视化你能非常直观地看到系统的响应比盲目调参高效十倍。保持耐心从单环开始逐步增加复杂度每一次失败都能让你更理解这套系统是如何工作的。当你看到它颤颤巍巍又倔强地立起来的那一刻所有的折腾都值了。