0xAA55 发表于 2021-2-1 05:44:17

【翻译】嵌入式设计之PID控制系统

原文: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;
        }



### 总结

`PID控制系统` 是一种非常简单而又高效的控制系统,被广泛应用于工业生产。而且, `PID控制系统` 的实现很简单,但调整起来则不容易。调整 `PID参数` 的过程 ( `Kp` , `Ki` 和 `Kd`)是一个不断地试错和纠正的过程。没有能直接算出这三个值的方法,除非整个系统是基于数学进行建模和模拟的。经验在这个地方是一个非常重要的方面,通过不断微调参数可以积累很多的经验。感兴趣的话请多多参与技术宅的结界的交流。

## 参考

(http://en.wikipedia.org/wiki/PID_controller)

(http://www.expertune.com/tutor.html)

(http://www.newportus.com/products/techncal/techncal.htm)

(http://www.dprg.org/tutorials/2003-10a/motorcontrol.pdf)




watermelon 发表于 2021-2-2 14:03:00

牛皮,工作量很饱满啊!
页: [1]
查看完整版本: 【翻译】嵌入式设计之PID控制系统