别再只调PWM占空比了!给STM32智能小车加上PID速度控制,让行驶更稳

张开发
2026/4/3 17:34:14 15 分钟阅读
别再只调PWM占空比了!给STM32智能小车加上PID速度控制,让行驶更稳
STM32智能小车PID速度控制实战从PWM开环到闭环优化的完整指南当你第一次让STM32智能小车跑起来时那种成就感无与伦比。但很快你会发现单纯依靠PWM占空比控制电机转速小车行驶时快时慢转弯角度也难以精确控制。这就像驾驶一辆没有油门反馈的汽车——你踩下踏板却不知道实际速度是多少。本文将带你从开环PWM控制跃升到闭环PID速度控制让你的智能小车真正听话。1. 为什么PWM开环控制不够用很多初学者在刚开始接触智能小车项目时会直接使用PWM信号控制电机转速。这种方法简单直接通过调整占空比来改变电机两端的平均电压从而控制转速。但实际应用中这种开环控制方式存在几个致命缺陷负载敏感当小车遇到坡度或阻力变化时电机转速会明显波动电池电压影响随着电池电量下降相同PWM占空比对应的实际转速会降低启动特性差电机从静止到目标转速需要较长时间响应延迟明显无法精确控制难以实现两个电机完全同步导致小车走偏// 典型的开环PWM控制代码示例 void Motor_Control(int left_speed, int right_speed) { TIM_SetCompare1(TIM3, left_speed); // 左电机PWM TIM_SetCompare2(TIM3, right_speed); // 右电机PWM }提示开环控制就像蒙着眼睛调节水龙头——你只能凭感觉无法准确知道实际水流大小。2. PID控制基础与增量式算法选择2.1 PID控制的三要素PID控制器通过比例(P)、积分(I)、微分(D)三个环节的组合实现对系统的精确控制比例环节根据当前误差大小进行调节快速响应但可能产生稳态误差积分环节累积历史误差消除稳态误差但可能引起超调微分环节预测误差变化趋势抑制振荡提高稳定性对于智能小车这种嵌入式应用我们通常选择增量式PID而非位置式PID原因如下比较项增量式PID位置式PID计算量较小较大内存占用较少较多抗积分饱和天然避免需要特殊处理执行机构冲击较小可能较大代码实现只需保存上次误差需要累积所有历史误差2.2 增量式PID的数学表达增量式PID算法的离散化公式为Δu(k) Kp×[e(k)-e(k-1)] Ki×e(k) Kd×[e(k)-2e(k-1)e(k-2)]其中Δu(k)本次控制量的增量e(k)、e(k-1)、e(k-2)当前、前一次、前两次的误差Kp、Ki、Kd比例、积分、微分系数typedef struct { float target_val; // 目标值 float actual_val; // 实际值 float err; // 当前误差 float err_last; // 上一次误差 float err_before_last;// 上上次误差 float Kp, Ki, Kd; // PID参数 } PID_IncTypeDef; float PID_Inc_Realize(PID_IncTypeDef *pid, float actual_val) { pid-actual_val actual_val; pid-err pid-target_val - pid-actual_val; float increment pid-Kp * (pid-err - pid-err_last) pid-Ki * pid-err pid-Kd * (pid-err - 2*pid-err_last pid-err_before_last); pid-err_before_last pid-err_last; pid-err_last pid-err; return increment; }3. 硬件系统搭建与编码器测速3.1 电机编码器选型与接口要实现闭环速度控制首先需要测量电机的实际转速。常用的方案有光电编码器精度高但成本较高霍尔传感器成本低但精度有限电机自带编码器集成度高推荐使用对于STM32F103C8T6我们可以使用定时器的编码器接口模式来高效读取编码器信号void Encoder_Init(TIM_TypeDef *TIMx) { TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; TIM_ICInitTypeDef TIM_ICInitStructure; // 时基配置 TIM_TimeBaseStructInit(TIM_TimeBaseStructure); TIM_TimeBaseStructure.TIM_Prescaler 0; TIM_TimeBaseStructure.TIM_Period 0xFFFF; TIM_TimeBaseStructure.TIM_CounterMode TIM_CounterMode_Up; TIM_TimeBaseInit(TIMx, TIM_TimeBaseStructure); // 编码器接口配置 TIM_EncoderInterfaceConfig(TIMx, TIM_EncoderMode_TI12, TIM_ICPolarity_Rising, TIM_ICPolarity_Rising); TIM_ICStructInit(TIM_ICInitStructure); TIM_ICInitStructure.TIM_ICFilter 6; // 适当滤波 TIM_ICInit(TIMx, TIM_ICInitStructure); TIM_Cmd(TIMx, ENABLE); } int16_t Encoder_Get(TIM_TypeDef *TIMx) { int16_t cnt TIM_GetCounter(TIMx); TIM_SetCounter(TIMx, 0); return cnt; }3.2 速度计算与滤波处理编码器脉冲计数需要转换为实际转速RPM或cm/s。假设编码器每转产生N个脉冲采样周期为T秒则转速计算为转速(RPM) (Δcount × 60) / (N × T)在实际应用中原始速度信号通常含有噪声需要进行滤波处理。常用的简单滤波方法包括移动平均滤波计算最近n个采样值的平均值一阶低通滤波y(k) α×x(k) (1-α)×y(k-1)#define ENCODER_RESOLUTION 11 // 编码器线数×4(四倍频) #define SAMPLE_PERIOD 0.02f // 20ms采样周期 float Speed_Calculate(int16_t encoder_cnt) { static float speed_filtered 0; float speed_raw (encoder_cnt * 60.0f) / (ENCODER_RESOLUTION * SAMPLE_PERIOD); // 一阶低通滤波α0.3 speed_filtered 0.3 * speed_raw 0.7 * speed_filtered; return speed_filtered; }4. PID参数整定与调试技巧4.1 参数整定三步法PID参数整定是一个经验性很强的过程推荐采用以下步骤先调P将I和D设为0逐渐增大P直到系统出现等幅振荡再调I取振荡周期的一半作为积分时间Ki Kp×(T/Ti)最后调D通常取Td Ti/4Kd Kp×Td对于智能小车电机控制典型参数范围参考参数作用典型范围影响效果Kp响应速度0.5~5.0值大则响应快但易振荡Ki消除稳态误差0.01~0.5值大则消除快但易超调Kd抑制振荡0.001~0.1值大则抑制强但易敏感4.2 实际调试中的经验技巧分段调试先调试低速(20%PWM)再逐步提高速度独立调试先单独调试一个电机再同步两个电机安全保护限制PID输出范围防止过冲损坏电机可视化辅助通过串口发送数据到上位机绘制曲线// 带限幅的PID控制实现 void Motor_PID_Update(PID_IncTypeDef *pid, float speed_actual) { float pwm_change PID_Inc_Realize(pid, speed_actual); float new_pwm pid-actual_pwm pwm_change; // 输出限幅 if(new_pwm MAX_PWM) new_pwm MAX_PWM; if(new_pwm MIN_PWM) new_pwm MIN_PWM; pid-actual_pwm new_pwm; TIM_SetComparex(TIMx, (uint16_t)new_pwm); }注意调试时建议先将Ki和Kd设为0仅调整Kp使系统有基本响应后再逐步加入I和D作用。5. 系统集成与性能优化5.1 控制周期选择与实时性保障控制系统的性能很大程度上取决于控制周期的选择周期太短计算负担重可能无法完成所有处理周期太长控制响应慢性能下降对于STM32F103C8T6智能小车项目推荐速度环10-20ms使用基本定时器中断位置环50-100ms如果需要// 定时器中断服务函数示例 void TIM2_IRQHandler(void) { if(TIM_GetITStatus(TIM2, TIM_IT_Update) ! RESET) { // 1. 读取编码器值 int16_t cnt_left Encoder_Get(TIM4); int16_t cnt_right Encoder_Get(TIM5); // 2. 计算实际速度 float speed_left Speed_Calculate(cnt_left); float speed_right Speed_Calculate(cnt_right); // 3. PID计算并更新PWM Motor_PID_Update(pid_left, speed_left); Motor_PID_Update(pid_right, speed_right); TIM_ClearITPendingBit(TIM2, TIM_IT_Update); } }5.2 双电机同步控制策略为了实现小车直线行驶两个电机的转速必须保持同步。常用策略包括主从控制一个电机作为主控制器另一个跟踪其转速交叉耦合控制考虑两个电机转速差进行补偿统一速度给定相同目标速度依赖PID自身调节// 双电机同步控制示例 void Chassis_Control(float target_speed, float turn_rate) { // 计算左右轮目标速度 float left_target target_speed - turn_rate * WHEEL_BASE / 2; float right_target target_speed turn_rate * WHEEL_BASE / 2; // 更新PID目标值 pid_left.target_val left_target; pid_right.target_val right_target; // 实际控制由定时器中断完成 }6. 进阶优化方向当基本PID控制实现后可以考虑以下优化方向提升性能变参数PID根据速度大小自动调整PID参数前馈控制加入电压补偿等前馈量提高响应速度模糊PID实现参数的自适应调整抗积分饱和采用积分分离或积分限幅策略运动轨迹规划实现S曲线加减速减少机械冲击// 变参数PID实现示例 void PID_Param_Adjust(PID_IncTypeDef *pid, float speed) { if(fabs(speed) LOW_SPEED_THRESHOLD) { pid-Kp KP_LOW; pid-Ki KI_LOW; pid-Kd KD_LOW; } else { pid-Kp KP_NORMAL; pid-Ki KI_NORMAL; pid-Kd KD_NORMAL; } }在最近的一个智能小车项目中我发现当电池电压低于7.4V时电机响应特性会明显变化。通过在PID计算中加入电压补偿因子成功解决了低电量时控制性能下降的问题。具体做法是将PID输出乘以(当前电压/额定电压)的平方因为电机转矩与电压平方成正比。这个小技巧让小车在不同电量下都能保持稳定的行驶性能。

更多文章