原文:https://tutorial.cytron.io/2012/06/22/pid-for-embedded-design/
作者:Kong Wai Weng
译者:0xAA55
PID控制系统
PID控制系统 由于其简单而有效的算法,它是目前而言最成熟也是被应用最广泛的工业控制系统之一。
本文将介绍 PID控制系统 的基础概念以及如何在嵌入式系统里将其实现。
介绍
闭环控制系统在嵌入式开发里一直都是一个关键话题,其使用 执行器 和 传感器 ,配合软件上的算法进行工作。
其中典型例子之一是对于开环控制的情况,电机的转动速度可通过改变 PWM输出的高低电平占空比 来控制,但因为没有反馈,电机的实际转动速度是无法被确保的。
在不同的情形下,使用相同的 PWM输出值 去控制电机,得到的实际转速往往是不一样的。比如,即使是同一个 PWM输出值 ,在电机负载较低的时候,其可以达到较高的转速;反之,在负载较高的时候,其转速会降低。
此时我们应该设计闭环控制系统,通过获取反馈来得到电机的实际转速,从而合理控制 PWM输出值 来得到正确的转速。
PID控制系统 除了能控制 转速 以外,还可以控制其它各种各样的参数,比如 位置 、 温度 、 水位 、 稳定性 等。
本文将介绍如何应用 PID控制系统 实现精准的 位置 控制。
开环控制的问题——直流电机转动位置的控制
在我们设计 PID控制器 前,我们需要先了解问题。在这个例子中,我们想要将电机轴从初始位置移动到目标位置。
文中有一些常用的术语,被用于描述PID控制系统,其中主要有:
-
控制变量 (CV) - 控制循环的输出,比如本文中用于控制电机力度的PWM信号长度。
-
处理变量 (PV) - 控制系统传感器反馈的信息,比如本文中,电机轴的转动位置是可以被获取到的。
-
设置点 (SP) - 我们的控制系统想要达到的目标,比如本文中电机轴的目标角度。
-
误差 (E) - 误差指的是处理变量与设置点的差。换句话说,就是从当前位置到达目标位置之间的距离(角度)。
PID控制器的实现
PID控制器的名字里,P指的是 比例值 ( proportional ),I指的是 积分值 ( integral ),然后D指的是 微分值 ( derivative )。下文将会分别介绍这三个控制系统,并会介绍其合并后的效果。
比例值控制器(P控制器)
当电机轴还远没有达到 目标位置 的时候,我们希望增加供电来让电机更快转动到 目标位置 。当电机轴靠近了 目标位置 ,我们会减少供电来让它的转动慢下来。而当电机轴到达了 目标位置 ,它应该要能停下来。如果电机轴超出了 目标位置 ,我们就得让电机反转,使其回到 目标位置 。
简而言之就叫 比例值控制器 ,因为我们使用 误差值 作为给电机供电的大小。
可参照以下式子理解 比例值控制器 的工作原理:
误差值 = 设置点 - 处理变量
控制变量 = Kp * 误差值
可以从下示例代码观察如何用C语言来实现 比例值控制器 :
// 无限循环
while(1)
{
// 取得当前位置
current_position = read_current_position();
// 计算误差
error = target_position - current_position;
// 计算控制变量
pwm = Kp * error;
// 限制控制变量的范围在 +- 255 以内
if (pwm > 255) pwm = 255;
else if (pwm < -255) pwm = -255;
// 如果控制变量为正,让电机顺时针转动
if (pwm > 0) motor_cw(pwm);
// 如果控制变量为负,让电机逆时针转动
else if (pwm < 0) motor_ccw(-pwm);
// 如果控制变量为零,停止转动
else motor_stop();
}
Kp 的数值应该被小心选择以优化系统,较低的数值会让系统运行平滑但响应缓慢:
而较高的数值则虽然响应迅速,但会导致电机转动过头,在它稳定之前会引起振动:
过高的数值则会导致它反应过大、无限循环震动无法稳定下来:
积分值控制器(I控制器)
从 P控制器 的图表可以看出,当它稳定下来后,并不能到达 目标位置 。这是因为 当前位置 还没有达到 目标位置 ,但是靠近了的时候,其 误差值 过小,计算出来的 PWM输出值 让电机的力度无法对抗摩擦阻力和重力。当系统稳定下来后,这个小额的误差被称作 稳态误差 。
为了解决 P控制器 的 稳态误差 问题,此处引入了 I控制器 的概念。根据其名字 积分值控制器 ,它的 积分值 相当于从系统开始运行后不断累加的 误差值 。
积分值 = ∑(误差值)
计算的全部结果被乘以常量 Ki ,并被加到 P控制器 的输出。和 P控制器 不同, I控制器 几乎不会被单独使用,而是通常和 P控制器 或者 PD控制器 合并使用。当系统已经稳定在小数值的 稳态误差 状态后, 积分值 始终能累加 误差值 直到 控制变量 足够大,使其带动 处理变量 到达 设置点 。 PI控制器 的公式如下:
误差值 = 设置点 - 处理变量
积分值 = 积分值 + 误差值
控制变量 = (Kp * 误差值) + (Ki * 积分值)
可以从下示例代码观察如何用C语言来实现 积分值控制器 :
// 无限循环
while(1)
{
// 取得当前位置
current_position = read_current_position();
// 计算误差
error = target_position - current_position;
// 计算积分值
integral = integral + error;
// 计算控制变量
pwm = (Kp * error) + (Ki * integral);
// 限制控制变量的范围在 +- 255 以内
if (pwm > 255) pwm = 255;
else if (pwm < -255) pwm = -255;
// 如果控制变量为正,让电机顺时针转动
if (pwm > 0) motor_cw(pwm);
// 如果控制变量为负,让电机逆时针转动
else if (pwm < 0) motor_ccw(-pwm);
// 如果控制变量为零,停止转动
else motor_stop();
}
就像 P控制器 , Ki 的数值需要被小心选择。数值太小, 稳态误差 被纠正得太慢;数值太大,系统变得不稳定并开始振动。
因为 积分值 可以在没有到达 设置点 的时候增加得特别快,有的程序在 控制变量 的数值到达 饱和值 的时候停止累加 积分值 。
微分值控制器(D控制器)
举个例子,比如你在驾驶一辆车,然后你想要让车尽快地做到正好停止在距离当前位置100m远的位置。如果此时你的车正在以10km每小时的速度前行,此时你会想加快速度来到达目标。而如果你的当前速度是100km每小时,你的速度太快所以你会需要开始刹车来让车能停在100m远的位置来防止冲过头。这就是 微分值控制器 所做之事。
任何数值的微分值描述其随时间改变的速度。在 PID控制系统 里, 微分值 指的是 误差值 的变化。他可以按照以下的公式来描述:
微分值 = 误差值 - 上一次的误差值
其中的 上一次的误差值 指的是上一次循环处理时的误差值。
负的 微分值 指示误差信号的改良(降低)。例如,如果上次的误差是20,而这次的误差是10, 积分值 是 -10 。当这些负值被乘上一个常数 Kd 然后加到循环的输出后,当快要达到目标位置的时候,它能让系统慢下来。
就像 I控制器 那样, D控制器 很少被单独使用,而是通常和 P控制器 或者 PI控制器 一起用。 PD控制器 的公式如下所示:
上一次的误差值 = 误差值
误差值 = 设置点 - 处理变量
微分值 = 误差值 - 上一次的误差值
控制变量 = (Kp * 误差值) + (Kd * 微分值)
对应的C代码实现如下所示:
// 无限循环
while(1)
{
// 取得当前位置
current_position = read_current_position();
// 计算误差
error = target_position - current_position;
// 计算微分值
derivative = error + last_error;
// 计算控制变量
pwm = (Kp * error) + (Kd * derivative);
// 限制控制变量的范围在 +- 255 以内
if (pwm > 255) pwm = 255;
else if (pwm < -255) pwm = -255;
// 如果控制变量为正,让电机顺时针转动
if (pwm > 0) motor_cw(pwm);
// 如果控制变量为负,让电机逆时针转动
else if (pwm < 0) motor_ccw(-pwm);
// 如果控制变量为零,停止转动
else motor_stop();
// 保存当前误差值用于下一次循环时使用的“上一次的误差值”
last_error = error;
}
D控制器 的阻尼效果允许系统在拥有较高的 Kp 和 Ki 值时不会冲过头。因此,它可以让系统在应对 设置点 的变化的时候有更好的反应时间。然而, Kd 的值如果过高,依然会带来反面效果。 D控制器 会在控制系统里增加反馈系统的噪音。如果 Kd 数值过大,而反馈循环有噪声,则整个系统会变得很 逗比 。
三合一 PID控制器
通过把 P控制器 、 I控制器 、 D控制器 结合到一起,我们可以把每个控制器的优势都发挥出来。我们有 P控制器 的快速响应, I控制器 解决 稳态误差 , D控制器 添加阻尼来减少过冲。
实际上只需要把这三个控制器的数值累加起来即可,如下所示的公式:
上一次的误差值 = 误差值
误差值 = 设置点 - 处理变量
积分值 = 积分值 + 误差值
微分值 = 误差值 - 上一次的误差值
控制变量 = (Kp * 误差值) + (Ki * 积分值) + (Kd * 微分值)
对应的C代码实现如下所示:
// 无限循环
while(1)
{
// 取得当前位置
current_position = read_current_position();
// 计算误差
error = target_position - current_position;
// 计算积分值
integral = integral + error;
// 计算微分值
derivative = error + last_error;
// 计算控制变量
pwm = (Kp * error) + (Ki * integral) + (Kd * derivative);
// 限制控制变量的范围在 +- 255 以内
if (pwm > 255) pwm = 255;
else if (pwm < -255) pwm = -255;
// 如果控制变量为正,让电机顺时针转动
if (pwm > 0) motor_cw(pwm);
// 如果控制变量为负,让电机逆时针转动
else if (pwm < 0) motor_ccw(-pwm);
// 如果控制变量为零,停止转动
else motor_stop();
// 保存当前误差值用于下一次循环时使用的“上一次的误差值”
last_error = error;
}
|