PID 是应用广泛的闭环控制算法之一,其通过三个控制变量让系统朝目标量接近。
这三个变量分别是比例控制、积分控制、微分控制,叠加到输入中从而控制系统的行为
并联型 PID
结构体
PID 结构体由以下变量构成:
1
2
3
4
5
6
7
8
9
|
typedef struct
{
double Kp; // 比例增益
double Ki; // 积分增益
double Kd; // 微分增益
double integral; // 积分累积值
double preError; // 上一次的误差值
double outputLimit; // 输出限幅
} PIDController;
|
其中 Kp Ki Kd 都是其量的放大系数。
Kp 比例增益
Kp 是最基本的量
其与误差的乘积形成的最基本的输出将系统往目标值拉。
Ki 积分增益
Ki 控制 pid 的积分输出
Ki 过大会造成大幅震荡。
Kd 微分增益
Kd 增高阻尼作用加强,但是 Kd 过高微笑的误差变化也会使 PID 产生较大的输出(这种微小的变化可能来自反馈信号的干扰),使得系统不稳定
就是太敏感了
integral 是积分的累积值,方便进行误差积分累加。在之后的计算中有如下形式:
1
|
pid->integral += error * dt;
|
prevError 存储上次计算的误差,在微分计算时候需要:
1
2
|
derivative = pid->Kd * ((error - pid->preError) / dt);
pid->preError = error;
|
outputLimit 限制积分和输出限幅,防止输出过大烧坏电机。
PID 核心有三个部分:
比例控制(Proportion) $K_p\times err$
比例控制是最基本的输出量,其将误差乘以一个比例增益,当误差逐渐缩小的时候其的值也逐渐缩小其也逐渐接近目标点,在无外部干扰的情况下其最终会到达目标。
但是比例控制是有局限性的,如果整个系统还有外部干扰,在临近情况会出现外部干扰与 PID 控制值相抵的情况,而无法到达目标值,这种情况称为稳态误差。
积分控制(Integral)$\int_{t}^{0}err(\tau)d\tau$
这里的 $\tau$ 是积分变量,代表积分过程从 0 到 t 时间的变化
积分会一直累加之前的误差,直至误差为 0。
当这个累积值持续累加并乘以积分增益产生持续增长的输出能够辅助比例控制的输出量以消除稳态误差。
当系统稳定的时候,其的量还是稳定不变的,可以有一个持续的输出来克服阻力。
微分控制(Derivative) $K_{d}\frac{derr}{dt}(t)$
这里的 $\frac{derr}{dt}$ 表现为 $\frac{error - prevError}{dt}$
微分控制实际就是一个导数,当误差减小的时候其的值为负数可以抵消掉超调。
当接近目标时候,此时误差在减小的情况下其是一个负值,在系统以很快的速度接近目标点的时候提供阻尼作用阻止误差的变化。其可以避免震荡减少超调。
最后将这三个量求和就是 PID 的输出值:
$output = Proportion + Integral + Derivative$
实际代码
上面是最基本的并联 PID 的模型,对应到代码中如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
|
typedef struct
{
double Kp; // 比例增益
double Ki; // 积分增益
double Kd; // 微分增益
double integral; // 积分累积值
double preError; // 上一次的误差值
double outputLimit; // 输出限幅
} PIDController;
double pidCompute(PIDController *pid, double setPoint, double measured, double dt)
{
double error;
double proportional, integral, derivative;
double output;
// 计算误差
error = setPoint - measured;
// 计算比例系数
proportional = pid->Kp * error;
// 积分累积值为误差对时间的累积,需要持续保存
pid->integral += error * dt;
// 限制积分累积的最大值
// 防止积分累积过多烧坏电机
if (pid->integral > pid->outputLimit)
pid->integral = pid->outputLimit;
if (pid->integral < -pid->outputLimit)
pid->integral = -pid->outputLimit;
// 积分系数:为积分增益 * 积分的累积项
integral = pid->Ki * pid->integral;
/*
* 微分系数
* (error - pid->preError) / dt 为对误差求导 也就是微分吗???
* 误差和时间关系的微分 * 微分增益
*/
derivative = pid->Kd * ((error - pid->preError) / dt);
output = proportional + integral + derivative;
if (output > pid->outputLimit)
output = pid->outputLimit;
if (output < -pid->outputLimit)
output = -pid->outputLimit;
return output;
}
|
不过我们主要关注几个变量:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
// 计算误差
error = setPoint - measured;
// 计算比例系数
proportional = pid->Kp * error;
// 积分累积值为误差对时间的累积,需要持续保存
pid->integral += error * dt;
// 积分系数:为积分增益 * 积分的累积项
integral = pid->Ki * pid->integral;
/*
* 微分系数
* (error - pid->preError) / dt 为对误差求导
* 误差和时间关系的微分 * 微分增益
*/
derivative = pid->Kd * ((error - pid->preError) / dt);
output = proportional + integral + derivative;
|
还是很好理解的,以上还有个总结公式:
$U(s) = K_{P} + K_{d}s + K_{i}\frac{1}{s}$
这里的 $s$ 是 拉普拉斯算子(Laplace operator) 有点抽象,不做太多深究。当然,我们通过离散化的表达和拉普拉斯算子没什么关系
我们的代码对应如下:
$output = K_{p}\cdot err(t) + K_{i}\int err(t)dt + K_{d}\frac{derr(t)}{dt}$
混合型 PID
$U(t) = K_{P}(err(t)+\frac{1}{T_{i}}\int e(t)dt + T_{d}\frac{de(t)}{dt})$
混合型的 PID 将 $K_{i}$ 、$K_{d}$ 替换为了更好理解的物理量 $T_{i}$、$T_{d}$。
这里依次讲解下这两个新的量:
$T_{i}$ 积分时间常数
$T_{i} = \frac{K_{p}}{K_{i}}$
$Ti$
$T_{d}$ 微分时间常数
$T_{d} = \frac{K_{p}}{K_{d}}$