PID 控制算法:从原理到实践

3.2k 词

PID(比例-积分-微分)是应用最广泛的控制算法。算法简单、鲁棒性好,适用于温度、压力、流量、液位等几乎所有现场。但它的不足在于:对非线性、时变不确定的系统难以建立精确模型,参数整定也较为繁琐。

三个环节

比例环节(P)

成比例地反映偏差信号 e(t),偏差一旦产生,控制器立即产生控制作用以减小偏差。

  • Kp 越大,比例作用越强,动态响应越快
  • 但实际系统有惯性——Kp 过大时,输出可能因惯性越过目标值
  • 仅有比例控制时,系统会存在稳态误差

积分环节(I)

输出与误差信号的积分成正比,主要用于消除静差

只要有误差存在,积分就持续累加,直到误差为零。但引入积分后系统稳定裕度减小,曲线波动增大,需要较长时间才能趋于平整。

积分作用消除静差的原理:只要误差存在就积分 → 输出增大/减小 → 误差归零 → 积分停止 → 输出稳定。

微分环节(D)

反映偏差信号的变化趋势,在偏差变得太大之前引入早期修正信号

  • 比例和积分是”事后调节”——发生误差后才调节
  • 微分是”事前预防”——发现有变化趋势就提前干预,防止过冲和超调

Kd 从小往大调,不能太强,否则会引起振荡。微分只能作为 P 和 I 的补充,不能起主导作用。

两种实现形式

位置式 PID

输出直接对应执行机构的位置(如阀门开度):

1
u(k) = Kp·e(k) + Ki·Σe(k) + Kd·[e(k) - e(k-1)]
  • 输出与整个历史状态有关,计算量大
  • 适用于执行机构不带积分部件的对象(如电液伺服阀)

增量式 PID

输出控制量的增量

1
2
Δu(k) = Kp·[e(k)-e(k-1)] + Ki·e(k) + Kd·[e(k)-2e(k-1)+e(k-2)]
u(k) = u(k-1) + Δu(k)
  • 只与当前拍和前两拍误差有关,累积误差更小
  • 计算机故障时误动作影响小(执行机构有记忆功能,保持原位)
  • 易于实现手动/自动无扰动切换
  • 适用于执行机构带积分部件的对象(如步进电机)

程序优化技巧

积分分离

问题:启动、结束或大幅增减设定值时,短时间产生很大偏差 → 积分累积 → 超调甚至振荡。

解法:偏差大时取消积分作用,接近目标时才引入积分。

1
2
3
4
5
6
7
if (abs(err) > THRESHOLD) {
use_integral = false; // 偏差大,不用积分
} else {
use_integral = true;
integral += err; // 偏差小,正常积分
}
u = Kp*err + use_integral*Ki*integral + Kd*(err - err_last);

抗积分饱和

问题:执行机构达到极限位置后,积分仍在累加 → 反向偏差出现时退出饱和区很慢 → 系统”失控”。

解法:判断上一时刻输出是否超限,超限后只累加反方向偏差:

1
2
3
4
5
6
7
if (u_last > umax) {
if (err < 0) integral += err; // 已饱和,只累加负偏差
} else if (u_last < umin) {
if (err > 0) integral += err; // 已饱和,只累加正偏差
} else {
integral += err; // 正常范围,正常累加
}

PID 整定方法

手动试凑法

参数 增大效果 过大风险
Kp 加快响应 超调、稳定性下降
Ki(减小 Ti) 消除静差加快 振荡
Kd 减小超调、加快响应 对干扰敏感

调参顺序:先 P → 再 I → 最后 D(从小往大一点一点加)

衰减曲线法

  1. 积分时间放最大,微分时间放零,纯 P 运行
  2. 慢慢减小比例度,直到出现 4:1(或 10:1)衰减比
  3. 根据衰减周期查表计算各参数

适用于变化较快的压力、流量、液位系统,但负荷变化大时需重新整定。

ROS 动态调参(dynamic_reconfigure)

不用重新编译就能在线调 PID 参数:

1. 创建 cfg 文件

1
2
3
4
5
6
7
8
9
#!/usr/bin/env python
PACKAGE = "my_controller"
from dynamic_reconfigure.parameter_generator_catkin import *

gen = ParameterGenerator()
gen.add("KP", double_t, 0, "KP_param", 0.0, 0, 500)
gen.add("KI", double_t, 0, "KI_param", 0.0, 0, 500)
gen.add("KD", double_t, 0, "KD_param", 0.0, 0, 500)
exit(gen.generate(PACKAGE, "dynamic_PID", "pid"))
1
chmod a+x pid.cfg

2. 代码中配置回调

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <my_controller/PIDConfig.h>

dynamic_reconfigure::Server<my_controller::PIDConfig>* reconf_server_;

void init(ros::NodeHandle& nh) {
reconf_server_ = new dynamic_reconfigure::Server<PIDConfig>(
ros::NodeHandle("~/pid"));
reconf_server_->setCallback(
boost::bind(&MyController::reconfigCB, this, _1));
}

void reconfigCB(PIDConfig& config) {
kp_ = config.KP;
ki_ = config.KI;
kd_ = config.KD;
}

之后用 rosrun rqt_reconfigure rqt_reconfigure 就能滑条调参了。

PWM 与力矩指令转换

电机控制中,PID 输出最终要转为 PWM 占空比。

电机基本方程(忽略电感):

$$E = IR + K_e\omega$$
$$T = K_t I$$

合并得:

$$T = \frac{K_t(E - K_e\omega)}{R}$$

PWM 平均电压:$E = V_d \times m$(m 为占空比),代入:

$$m = \frac{T \cdot R + K_e\omega}{K_t \cdot V_d}$$

获取电机参数

  • 电阻 R:万用表测任意两相电阻,值的一半为每相电阻
  • 电感 L:RCL 测量仪 1kHz 测任意两相,值的一半
  • 反电动势系数 Ke:示波器测手拨电机产生的正弦波,$K_e = \frac{V_{peak}}{\omega}$
  • 转矩系数 Kt:$K_t = \frac{60}{2\pi \cdot PP} K_e$(PP 为极对数)

实际中还需考虑粘性阻力、PWM 频率、电流纹波、续流二极管等因素。

MATLAB 仿真示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
clear; clc;
target = [10*ones(100,1); ones(100,1)];
real = zeros(200,1);

Kp = 0.22; Ki = 0.47; Kd = 0.03;
integral = 0; pre_err = 0;

for i = 2:length(target)
err = target(i) - real(i-1);
integral = integral + err;
u = Kp*err + Ki*integral + Kd*(err - pre_err);
pre_err = err;
real(i) = u;
end

plot(target, 'b'); hold on; plot(real, 'r');
legend('目标', '实际');