4、直流有刷电机闭环控制:PID控制原理、位置式PID与增量式PID、速度闭环控制实验
各位同学,欢迎来到第四章。前面我们讲了开环控制,说白了就是给多大电压转多快,但实际中负载一变,转速就掉下来了。这时候,我们就需要闭环控制。今天要聊的PID,是电机控制里最经典、最实用的算法,没有之一。
我刚开始做电机驱动时,觉得PID不就是个比例积分微分嘛,网上代码一搜一大把。结果真到项目里,电机要么抖得像筛子,要么响应慢得像蜗牛。后来才明白,PID这东西,参数调好了是神器,调不好就是灾难。今天我就把这几年的经验掰开揉碎了讲给你听。
4.1 PID控制原理——说白了就是“纠偏”
PID的全称是比例-积分-微分控制。你想想看,我们要让电机转速稳定在1000转/分,但实际只有800转,差了200转。怎么办?
- 比例(P):差多少补多少。差200转,我就加对应的电压。但比例太大容易超调,电机冲过头又回调,来回震荡。
- 积分(I):消除静差。有时候比例控制下,电机就是差那么几转上不去,积分项会慢慢累积误差,直到把这点偏差吃掉。
- 微分(D):预测未来。如果误差突然变大,微分项会提前刹车,防止系统震荡。
公式长这样:
u(t) = Kp * e(t) + Ki * ∫e(t)dt + Kd * de(t)/dt
其中e(t)是目标值与实际值的偏差。嗯,这里要注意,积分项是累积误差,如果积分时间太长,就会出现“积分饱和”——误差累积太大,撤不回来了。我在项目中遇到过,电机启动时积分项疯狂累积,等转速到了目标值,积分项还在输出,结果转速冲过头了。
核心要点:PID的本质就是根据过去(积分)、现在(比例)、未来(微分)的误差来调整输出。说白了,就是让电机听话。
4.2 位置式PID与增量式PID——两种实现方式
实际写代码时,PID有两种实现方式:位置式和增量式。很多新手搞不清区别,我简单说一下。
4.2.1 位置式PID
位置式PID直接计算当前时刻的控制量u(k)。公式如下:
u(k) = Kp * e(k) + Ki * ∑e(i) + Kd * [e(k) - e(k-1)]
这里∑e(i)是从开始到现在的所有误差之和。你想想看,如果系统跑了一天,这个累积和会非常大。一旦出现故障,输出u(k)可能瞬间跳变,电机就会猛冲一下。我曾经在调试时遇到过,位置式PID的积分项累积到几万,一复位电机直接飞车,吓得我赶紧切了电源。
警告:位置式PID的积分项需要做限幅处理,否则积分饱和会带来严重的安全隐患。我建议在代码里加一个积分上限,比如限制在±1000以内。
4.2.2 增量式PID
增量式PID只计算当前时刻相对于上一时刻的控制量增量Δu(k)。公式如下:
Δu(k) = Kp * [e(k) - e(k-1)] + Ki * e(k) + Kd * [e(k) - 2*e(k-1) + e(k-2)]
最终输出:u(k) = u(k-1) + Δu(k)
增量式的优点很明显:
- 没有积分累积,不会出现积分饱和
- 输出变化平缓,即使e(k)突变,Δu(k)也不会太大
- 容易实现手动/自动切换,切换时无冲击
我个人习惯在速度闭环控制中用增量式PID。为什么?因为速度控制对平滑性要求高,增量式天然就有“软启动”的效果。位置式PID更适合位置控制,比如机械臂的关节角度控制。
小技巧:如果你刚开始调PID,建议先用增量式。它更安全,不容易出事故。等经验丰富了,再根据场景选择位置式。
4.3 速度闭环控制实验——手把手带你调参数
理论讲完了,咱们来点实际的。下面是一个基于STM32的直流有刷电机速度闭环控制实验。硬件很简单:STM32F103、L298N驱动、带编码器的直流电机、12V电源。
4.3.1 硬件连接
| STM32引脚 | 连接对象 | 说明 |
|---|---|---|
| PA0 | 编码器A相 | 定时器2通道1 |
| PA1 | 编码器B相 | 定时器2通道2 |
| PA6 | PWM输出 | 定时器3通道1 |
| PB0 | 电机方向1 | GPIO输出 |
| PB1 | 电机方向2 | GPIO输出 |
4.3.2 代码实现——增量式PID速度控制
下面是我常用的增量式PID代码框架。你直接复制到工程里,改一下参数就能用。
// pid.h
typedef struct {
float Kp;
float Ki;
float Kd;
float target; // 目标值
float actual; // 实际值
float error; // 当前误差
float last_error; // 上一次误差
float prev_error; // 上上次误差
float output; // 输出值
float delta; // 增量
} PID_TypeDef;
// pid.c
void PID_Init(PID_TypeDef *pid, float Kp, float Ki, float Kd) {
pid->Kp = Kp;
pid->Ki = Ki;
pid->Kd = Kd;
pid->target = 0;
pid->actual = 0;
pid->error = 0;
pid->last_error = 0;
pid->prev_error = 0;
pid->output = 0;
pid->delta = 0;
}
float PID_Calc(PID_TypeDef *pid, float target, float actual) {
pid->target = target;
pid->actual = actual;
// 更新误差
pid->prev_error = pid->last_error;
pid->last_error = pid->error;
pid->error = pid->target - pid->actual;
// 计算增量
pid->delta = pid->Kp * (pid->error - pid->last_error) +
pid->Ki * pid->error +
pid->Kd * (pid->error - 2*pid->last_error + pid->prev_error);
// 累加输出
pid->output += pid->delta;
// 输出限幅,防止PWM溢出
if (pid->output > 999) pid->output = 999;
if (pid->output < -999) pid->output = -999;
return pid->output;
}
4.3.3 参数整定——我的“三步法”
调PID参数是门手艺活。我总结了一个三步法,适合新手快速上手:
- 先调P:Ki和Kd设为0,只给Kp。从小到大慢慢加,直到电机出现轻微震荡。然后取当前Kp值的60%作为最终Kp。比如Kp加到10时开始震荡,那就取6。
- 再加I:保持Kp不变,从0开始加Ki。观察稳态误差是否消除。如果电机出现低频震荡,说明Ki太大了,减半再试。我一般Ki取Kp的1/10到1/5。
- 最后加D:如果系统响应慢或者超调大,加一点Kd。Kd通常很小,从0.1开始试。加多了电机反而会高频抖动。
经验之谈:我曾经调一个高速电机,Kp加到20都不震荡,但响应还是慢。后来发现是编码器分辨率太低,反馈延迟太大。换了高分辨率编码器后,Kp降到5就搞定了。所以,硬件基础决定PID的上限。
4.3.4 实验步骤
- 下载代码到STM32,打开串口助手,波特率115200。
- 发送命令:
SET 1000,设置目标转速为1000转/分。 - 观察串口打印的实际转速。如果震荡,减小Kp;如果响应慢,增大Ki。
- 用手捏住电机轴,模拟负载变化。看转速能否快速恢复。
- 记录不同参数下的响应曲线,对比效果。
注意:实验时手不要碰到电机轴旋转部分,小心受伤。另外,PWM占空比不要长时间超过90%,否则驱动芯片容易过热。
4.4 避坑指南——我踩过的那些坑
最后分享几个我实际项目中遇到的坑,希望能帮你少走弯路。
- 积分饱和:我曾经在位置式PID中没做积分限幅,电机启动时积分累积到几千,等转速到了目标值,积分项还在输出,结果转速冲过头了。解决方案:加积分限幅,或者改用增量式PID。
- 微分噪声放大:Kd对高频噪声非常敏感。如果编码器信号有毛刺,微分项会把噪声放大,导致电机抖动。我建议在编码器输入加一个RC低通滤波,或者在代码里做一阶低通滤波。
- PWM频率选择:直流有刷电机的PWM频率一般选10kHz-20kHz。频率太低,电机会有啸叫声;频率太高,驱动芯片开关损耗大。我习惯用16kHz,人耳听不到,效率也合适。
- 死区补偿:电机在低速时,由于摩擦力,PWM占空比太小电机不转。这时候需要加一个死区补偿,比如PWM输出小于5%时直接给5%。
好了,这一章的内容就到这里。PID控制是电机控制的基石,你把它吃透了,后面学FOC、伺服控制都会轻松很多。下一章我们讲电流闭环控制,到时候会用到更高级的PI控制器。记得动手做实验,光看是学不会的。