STM32 PID算法详解:从原理到电机实战
如果你能让电机稳定转在 100rpm而不是靠运气转在 90~110rpm你就入门了自动控制。PID 就是干这个的——占自动控制领域 95% 以上的控制器。这篇文章融合了野火、稚晖君、Crazyflie飞控等精品教程用 STM32 编码器电机为例讲透 PID。1. 什么是 PID先看闭环长啥样开环你给电机 50% PWM它可能转 100rpm也可能因为带负载只转 60rpm你不知道。闭环加一个传感器编码器测速实时告诉你现在是多少与想要是多少的差值叫误差 ePID就是根据误差来算该给多少输出。PID 定义对误差进行 比例P 积分I 微分D 运算2. P I D 各自是干嘛的P - 比例项此刻误差有多大我就用多大力气误差越大输出越大像弹簧拉得越远回弹力越大。Kp越大响应越快越接近目标但过大会振荡在目标左右反复横跳。Kp过小响应慢永远到不了目标有稳态误差。先调P让系统能动起来朝目标走。I - 积分项把过去的误差累加起来消除稳态误差如果一直有 1rpm 的小偏差P 觉得太小懒得管I 会默默把这个小偏差越积越大最终逼着 P 动起来把静差消灭掉。Ki越大消静差越快但容易超调累积太多导致积分饱和/失控。Ki是慢变量调小一点。经典比喻P是近视眼只看现在I是记仇的会计把所有历史欠账都记着必须还清。D - 微分项预测未来提前刹车看误差的变化趋势。如果误差在快速减小说明马上就要到目标了D提前踩刹车防止冲过头。Kd越大阻尼越强超调越小但过大对噪声敏感电机嗡嗡抖。Kd像老司机预判误差变化越快 brake 越狠。三项组合口诀野火版工程版参数整定找最佳从小到大顺序查先是比例后积分最后再把微分加。曲线振荡很频繁比例度盘要放大Kp减小曲线漂浮绕大弯比例度盘往小扳Kp增大曲线偏离回复慢积分时间往下降Ki增大曲线波动周期长积分时间再加长Ki减小曲线振荡频率快先把微分降下来动差大来波动慢微分时间应加长理想曲线两个波前高后低 4比13. 位置式 vs 增量式STM32 该用哪个维度位置式 (Positional)增量式 (Incremental)公式输出绝对位置u(k)输出增量Δu(k)需累加历史需要累计所有误差计算量大只需近3次误差不累加安全性计算机故障输出突变到极限危险故障时只影响增量电机保持原位安全积分饱和严重需额外限幅和抗饱和天然抗饱和但有截断误差适用执行机构不带积分舵机、平衡小车直立、温控执行机构带积分步进电机、速度闭环电机STM32场景角度环、位置环、温度控制速度环、电机转速控制结论STM32 做速度环→ 用增量式 PI很多时候 D 都不需要。STM32 做位置环/角度环→ 用位置式 PID必须加积分限幅和输出限幅。代码推导位置式推导把连续积分变累加微分变差分。增量式推导位置式 u(k) - u(k-1)把累加项消掉只剩3项公式见图。4. STM32 硬件系统电机 编码器 定时器被控对象直流减速电机 霍尔编码器反馈编码器脉冲TIM2 编码器模式计数或外部中断法10ms读一次差值就是速度。执行TIM1 PWM 输出到 TB6612 驱动改变占空比改转速。控制周期绝对不能在main while(1)里用HAL_Delay跑PID必须放定时器中断里固定周期如 10ms。否则采样时间 T 不固定Ki, Kd 全乱。5. 工程级 PID 代码直接抄去用这是比网上 90% 教程都健壮的版本加入积分限幅、输出限幅、抗积分饱和、微分滤波。// pid.h typedef struct { float Kp, Ki, Kd; float target; // 目标值 float actual; // 实际值 float err, err_last, err_prev; // 本次、上次、上上次误差 float integral; // 积分累加 float integral_max; // 积分限幅 float out_max, out_min; // 输出限幅 float output; // 输出 } PID_t; // 初始化 void PID_Init(PID_t *pid, float Kp, float Ki, float Kd, float i_max, float out_max) { pid-Kp Kp; pid-Ki Ki; pid-Kd Kd; pid-integral_max i_max; pid-out_max out_max; pid-out_min -out_max; pid-err pid-err_last pid-err_prev 0; pid-integral 0; pid-output 0; } /* 位置式PID带抗积分饱和 */ float PID_Positional(PID_t *pid, float actual) { pid-actual actual; pid-err pid-target - pid-actual; // 积分分离 抗饱和误差太大时不积分输出饱和时不积分同向误差 // 防止积分饱和 if (pid-output pid-out_max pid-output pid-out_min) { // 误差过大时取消积分快速趋近 if (fabs(pid-err) 200) // 阈值根据实际范围改 pid-integral pid-err; } // 积分限幅 if (pid-integral pid-integral_max) pid-integral pid-integral_max; if (pid-integral -pid-integral_max) pid-integral -pid-integral_max; float p_out pid-Kp * pid-err; float i_out pid-Ki * pid-integral; float d_out pid-Kd * (pid-err - pid-err_last); pid-output p_out i_out d_out; // 输出限幅 if (pid-output pid-out_max) pid-output pid-out_max; if (pid-output pid-out_min) pid-output pid-out_min; pid-err_last pid-err; return pid-output; } /* 增量式PID速度环专用无积分累加自动抗饱和 */ float PID_Incremental(PID_t *pid, float actual) { pid-actual actual; pid-err pid-target - pid-actual; float increment pid-Kp * (pid-err - pid-err_last) pid-Ki * pid-err pid-Kd * (pid-err - 2*pid-err_last pid-err_prev); pid-output increment; // 在原输出上增加 // 输出限幅 if (pid-output pid-out_max) pid-output pid-out_max; if (pid-output pid-out_min) pid-output pid-out_min; pid-err_prev pid-err_last; pid-err_last pid-err; return pid-output; }定时器中断调用核心PID_t motor_speed_pid; int encoder_cnt 0; void TIM4_IRQHandler(void) // 10ms进来一次 { if(TIM_GetITStatus(TIM4,TIM_IT_Update)) { int speed Encoder_Get(); // 读编码器差值 实际速度 float pwm PID_Incremental(motor_speed_pid, (float)speed); Motor_SetPWM((int)pwm); // 设置TIM1 CCR TIM_ClearITPendingBit(TIM4,TIM_IT_Update); } }6. 高阶级联PID——平衡车、无人机的灵魂单级PID搞不定无人机。无人机从倾斜5度回到水平需要两个过程先需要一个转速再产生力矩。级联结构外环角度环P或PD输入目标角度 - 实际角度输出期望角速度。特点慢不需要I响应快。内环角速度环PID输入外环给的期望角速度 - 陀螺仪实测角速度输出电机PWM。特点快有I抗风扰。// 伪代码四轴串级PID float outer_pid PID_Positional(angle_pid, actual_angle); // 外环角度 - 期望角速度 angle_rate_pid.target outer_pid; // 内环目标 外环输出 float inner_pid PID_Positional(rate_pid, actual_gyro_rate); // 内环 Motor_Output(inner_pid);为什么外环常用P外环有I会让内环目标一直累加响应慢且容易饱和。稚晖君、PX4飞控开源代码外环都是纯P [5]。调参顺序先内环后外环。内环没调稳外环调得再好也飞不稳。内环增益从小到大到开始抖动回退20%。7. STM32 实战调参步骤私藏版以增量式速度环控制电机 100rpm 为例Step 1纯P试探Ki0, Kd0Kp从 0.1 开始慢慢加。看阶跃响应用串口打印或野火上位机。Kp太小到60rpm就上不去了稳态误差大Kp合适能到90rpm有轻微超调Kp太大来回振荡 80~120记下此时的 Kp减小 30% 作为最终 Kp。Step 2加I消静差保持Kp不变Ki从0开始慢慢加如0.01。Ki太小还是有静差Ki合适稳态正好100rpm无超调Ki太大超调变大系统变“肉”来回晃悠才稳定积分限幅一定要加设integral_max out_max / Ki * 0.5防止启动瞬间误差大积分直接爆表。Step 3加D抑超调可选速度环很多不用DKd从0开始慢慢加看超调是否减小。注意编码器噪声大时D会把噪声放大电机嗡嗡响这时候要加一阶低通滤波// 微分先低通滤波 float derivative (err - err_last) * 0.7 last_derivative * 0.3;最终参数示例JGB37-520电机 10ms周期速度环Kp1.5, Ki0.2, Kd0.05, out_max7200PWM满幅 位置环Kp2.0, Ki0, Kd1.0位置环常用PD8. 高级优化工程必备的4个补丁积分分离误差 阈值时只用PD积分不参与避免大误差时积分猛冲。上面代码已实现。抗积分饱和Anti-Windup输出达到极限时停止同向积分。上面代码只有输出没饱和才积分。微分踢Derivative Kick目标值突变时误差微分巨大输出尖峰。改成对测量值微分而不是对误差微分。// 改进前d err - err_last // 改进后d -(actual - actual_last) // 只对实际值微分目标突变不会导致尖峰不完全微分在微分项上加一阶低通滤波高频噪声。9. 排坑指南PID周期不固定用HAL_Delay在主循环调PID周期抖动导致Ki, Kd失效。必须用定时器中断固定周期。编码器读数反向PID输出正向变大电机反转系统正反馈直接飞车。解决要么改电机线要么err actual - target取反。PWM限幅后不处理积分输出限幅了积分还在累加退出饱和区超慢。必须抗饱和。调参时一改改3个永远只动一个参数观察曲线再动下一个。不打匿名上位机人眼看电机转速不准。建议用野火PID调试助手或VOFAUSB串口发曲线实时看超调。10. 总结P管现在快 I管过去消静差 D管未来防超调。位置式算绝对位置带积分容易饱和适合位置/角度/温度。增量式算增量安全抗饱和适合速度/电机。级联外环角度P内环角速度PID无人机/平衡车必用。调参先P后I再D周期固定要记牢积分限幅不能少。系列完结参考野火《PID控制算法》文档 9 位置式与增量式对比 1调参口诀 2