STM32 PID算法详解:从原理到电机实战
2026/8/4 7:44:41 网站建设 项目流程

如果你能让电机稳定转在 100rpm,而不是靠运气转在 90~110rpm,你就入门了自动控制。PID 就是干这个的——占自动控制领域 95% 以上的控制器。

这篇文章融合了野火、稚晖君、Crazyflie飞控等精品教程,用 STM32 + 编码器电机为例,讲透 PID。


1. 什么是 PID?先看闭环长啥样

开环:你给电机 50% PWM,它可能转 100rpm,也可能因为带负载只转 60rpm,你不知道。

闭环:加一个传感器(编码器测速),实时告诉你现在是多少,与想要是多少的差值叫误差 e,PID就是根据误差来算该给多少输出。

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比1

3. 位置式 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试探Ki=0, Kd=0,Kp从 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抑超调(可选,速度环很多不用D)Kd从0开始,慢慢加,看超调是否减小。注意:编码器噪声大时,D会把噪声放大,电机嗡嗡响,这时候要加一阶低通滤波

// 微分先低通滤波 float derivative = (err - err_last) * 0.7 + last_derivative * 0.3;

最终参数示例(JGB37-520电机 10ms周期):速度环:Kp=1.5, Ki=0.2, Kd=0.05, out_max=7200(PWM满幅) 位置环:Kp=2.0, Ki=0, Kd=1.0(位置环常用PD)

8. 高级优化:工程必备的4个补丁

  1. 积分分离:误差 > 阈值时,只用PD,积分不参与,避免大误差时积分猛冲。上面代码已实现。

  2. 抗积分饱和(Anti-Windup):输出达到极限时,停止同向积分。上面代码:只有输出没饱和,才积分。

  3. 微分踢(Derivative Kick):目标值突变时,误差微分巨大,输出尖峰。改成对测量值微分,而不是对误差微分。

    // 改进前:d = err - err_last // 改进后:d = -(actual - actual_last) // 只对实际值微分,目标突变不会导致尖峰
  4. 不完全微分:在微分项上加一阶低通,滤波高频噪声。

9. 排坑指南

  1. PID周期不固定:HAL_Delay在主循环调PID,周期抖动导致Ki, Kd失效。必须用定时器中断固定周期

  2. 编码器读数反向:PID输出正向变大,电机反转,系统正反馈直接飞车。解决:要么改电机线,要么err = actual - target取反。

  3. PWM限幅后不处理积分:输出限幅了,积分还在累加,退出饱和区超慢。必须抗饱和。

  4. 调参时一改改3个:永远只动一个参数,观察曲线,再动下一个。

  5. 不打匿名上位机:人眼看电机转速不准。建议用野火PID调试助手或VOFA+,USB串口发曲线,实时看超调。

10. 总结

  • P:管现在,快; I:管过去,消静差; D:管未来,防超调。

  • 位置式:算绝对位置,带积分,容易饱和,适合位置/角度/温度。

  • 增量式:算增量,安全抗饱和,适合速度/电机。

  • 级联:外环角度P,内环角速度PID,无人机/平衡车必用。

  • 调参:先P后I再D,周期固定要记牢,积分限幅不能少。


系列完结

参考:野火《PID控制算法》文档 9 ,位置式与增量式对比 1,调参口诀 2

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询