• PID控制算法


    目录

    1 what?  什么是pid算法

    2 why? 为什么要使用pid算法?PID算法的作用?

    2.1 KP控制

    2.2 KD控制

    2.3 KI控制

    3 how? 如何使用pid算法(过程+编码)

    3.1  口诀

    3.2  算法选择

    3.3  代码实现


    写在前面的话

            最近业务需要,电机控制相关,所以打算对PID算法进行重新学习。本文整理PID算法的概念,作用以及代码实现,有需要的同学可以自己编译文章最后的代码进行试验。        

    1 what?  什么是pid算法

            PID即:Proportional(比例)、Integral(积分)、Differential(微分)的缩写。顾名思义,PID控制算法是结合比例、积分和微分三种环节于一体的控制算法,同时,pid算法还是一个闭环反馈控制算法。因此,要求硬件必须具有闭环反馈单元,也就是得有反馈。比如控制电机的转速,就得有测量转速的传感器,并将反馈信号(转速)反馈到控制单元。

            在工业过程中,连续控制系统的理想PID控制算法为:

            

    Kp

    比例增益,Kp与比例度成倒数关系;

    Tt

    积分时间常数;

    TD

    微分时间常数;

    u(t)

    PID控制器的输出信号;

    e(t)

    给定值r(t)与测量值之差。

            可以认为,PID的流程本质:通过误差反馈信号控制被控量,而控制器本身就是比例、积分、微分三个环节的效果加和。

    2 why? 为什么要使用pid算法?PID算法的作用?

            比如,控制“恒温器”,让水温保持在45℃。

            也许你在想:

                    小菜一碟,小于45℃就让它加热,大于45°c就断电,几行代码用STM32 分分钟写出来。

                    没错 ! 在场景要求不高的情况下,确实可以这么干~ But! 如果换一种说法,你就知道问题出在哪里了:

            如果控制对象是一辆汽车呢?如果希望汽车的车速保持在 100km/h 巡航,你还敢这样干么?

    设想一下,假如汽车的定速巡航电脑在某一时间测到车速是90km/h。

            域控制器接到欠速反馈之后,立刻命令发动机:加速!

    结果,发动机那边突然来了个100%全油门,汽车急加速到了130km/h。

            域控制器接到超速速反馈之后,立刻命令制动系统:刹车!

            这样多来几次,地上会多几条胎印,如果车上有人,估计还会留下一摊呕吐物。实际应用过程中,没有一下厂家敢这么干。

            所以,在大多数场合中,用“开关量”来控制一个物理量,就显得比较简单粗暴了。有时候,是无法保持稳定的。因为单片机、传感器不是无限快的,采集、控制需要时间,也就是我们所常说的,滞后性。

            而且,控制对象具有惯性。比如你把恒温器开关拔掉,它的“余热”(即热惯性)可能还会使水温继续升高一小会。

            这时,就需要一种『算法』:

    它可以将需要控制的物理量带到目标附近

    它可以“预见”这个量的变化趋势

    它也可以消除因为散热、阻力等因素造成的静态误差

            于是,当时的数学家们发明了这一历久不衰的算法——这就是PID。

    2.1 KP控制

            需要控制定速巡航速度,有它现在的『当前值80km/h』,也有我们期望的『100km/h』。

    当『当前值』 <  『目标值』:且两者差距不大时,发动机“轻轻地”加速一下。

    当『当前值』 <  『目标值』:速度降低很多,两者差距大,发动机“稍稍用力”加速一下。

    当『当前值』 <  『目标值』:要是当速度比目标速度低得多,就让发动机“开足马力”加速,尽快让速度到达巡航速度附近。

    当『当前值』 >  『目标值』:这个时候,松开油门,不用加速了。

            这就是P的作用,跟开关控制方法相比,是不是“温文尔雅”了。但是kP越大,调节作用越激进,kP调小会让调节作用更保守。

    2.2 KD控制

            仍然是巡航的场景,刚才有了KP的作用。不难发现,只有KP,车速时加速时减速,晃晃悠悠,整个系统不是特别稳定,总是在“抖动”。

    设想一个弹簧:现在在平衡位置上。拉它一下,然后松手。这时它会震荡起来。因为阻力很小,它可能会震荡很长时间,才会重新停在平衡位置。

    请想象一下:要是把上图所示的系统浸没在水里,同样拉它一下 :这种情况下,重新停在平衡位置的时间就短得多。

            我们需要一个控制作用,让被控制的物理量的“变化速度”趋于0,即类似于“阻尼”的作用。

            因为,当比较接近目标时,P的控制作用就比较小了。越接近目标,P的作用越温柔。什么意思呢,就像巡航速度快接近设定的巡航速度,这个时候,理想速度和实际速度得差值变得更小,再乘上一个kp,这个值得影响变得更小,晃动的程度也变小了。

            但是有很多内在的或者外部的因素,使控制量发生小范围的摆动。

            kD的作用就是让这个小范围摆动的物理量趋于0,只要什么时候,这个量具有了速度,kD就向相反的方向用力,尽力刹住这个变化。

            kD参数越大,向速度相反方向刹车的力道就越强。(反应到车速场景上来说,就是油门的加减)。

    2.3 KI控制

            这个参数的话,用巡航场景不是特别好理解,可以借助水温的场景进行理解。

            还是说水温的时,比如冬天你讲恒温器放到东北的室外,仍然要把水温烧到45°c。

            但是有个尴尬的情况:在KP的作用下,水温慢慢升高。直到升高到35℃时,天气太冷,水散热的速度,和P控制的加热的速度相等了。这就尴尬了。

    KP这样想:我和目标已经很近了,只需要轻轻加热就可以了。

    KD这样想:加热和散热相等,温度没有波动,我好像不用调整什么。

            于是,水温永远只能达到35°c,但这些都只是计算机的逻辑。作为一个有思想,有常识的人类,我们明白,如果要将这水烧到45°c,我们还需要继续加热,可是加热 的力度应该多大呢。

            数学家们设置了一个积分量,只要偏差存在,就不断地对偏差进行积分(累加),并反应在调节力度上。

            这样一来,即使35℃和45℃相差不太大,但是随着时间的推移,只要没达到目标温度,这个积分量就不断增加。系统就会慢慢意识到:还没有到达目标温度,该增加功率啦!

            到了目标温度后,假设温度没有波动,积分值就不会再变动。这时,加热功率仍然等于散热功率。但是,温度是妥妥的45℃。所以说KI的作用就是,减小静态情况下的误差,让受控物理量尽可能接近目标值。

            kI的值越大,积分时乘的系数就越大,积分效果越明显。

    3 how? 如何使用pid算法(过程+编码)

    3.1口诀

    参数整定找最佳,从小到大顺序查,

    先是比例后积分,最后再把微分加,

    曲线振荡很频繁,比例度盘要放大,

    曲线漂浮绕大湾,比例度盘往小扳,

    曲线偏离回复慢,积分时间往下降,

    曲线波动周期长,积分时间再加长,

    曲线振荡频率快,先把微分降下来,

    动差大来波动慢,微分时间应加长,

    理想曲线两个波,前高后低四比一,

    一看二调多分析,调节质量不会低

    3.2  算法选择

            最简单的闭环控制只有 P 控制,将当前结果反馈回来,再与目标相比,为正的话,就减速,为负的话就加速。pid 是比例(P)、积分(I)、微分(D)控制算法。但并不是必须同时具备这三种算法,也可以是 PD,PI,甚至只有 P 算法控制。PID 算法的结构

    比例控制 P:

            采用 P 比例控制,能较快地克服扰动的影响,作用于输出值较快的场景,但不能很好稳定在一个理想的数值。

            它适用于一阶惯性对象,负荷变化不大,工艺要求不高、如用于压力、液位、串级副控回路,控制要求不高、被控参数允许在一定范围内有余差的场景。如:热水器水位控制等。

    比例积分控制(PI):

            比例积分控制也是应用最广泛的控制算法之一。积分能在比例控制的基础上消除余差;

            它适用于被控参数不允许有余差的场景。如:油库供油管流量控制系统等。

    比例微分控制(PD):

            微分控制具有超前预判的功能,对于惯性较大的对象,为了使控制及时,常常希望能根据被控变量变化的快慢来控制。响应快,偏差小,能增加系统稳定性,有超前控制作用,可以克服对象的惯性

            微分作用与偏差变化率成比例,即它是根据偏差变化趋势产生控制作用,因而有“预先控制”的性质。俗称超前调节。微分作用的超前特性,只对广义对象的容量滞后有效。而对很大的纯滞后无效。

    例积分微分控制规律(PID):

            PID 控制是一种较理想的控制规律,它在比例的基础上引入积分,可以消除余差,再加入微分作用,又能提高系统的稳定性。它适用于控制通道时间常数或容量滞后较大、控制要求较高的场合。如过热蒸汽温度控制,PH值控制等。

    3.3 代码实现

            代码的实现过程中,使用了位式和增量式两种算法进行实现,下面的代码直接可以使用,有需要的小伙伴可以自行下载实验,对于仿真的实验后续如果有空也会更新上来。

            其实算法的实现过程,可以根据具体的场景进行变化,比如同一个过程,但是涉及到不同的场景,比如开车:加速+巡航+减速,在不同场景中,我们可以叠加不同的算法,也叫做算法分离。

    1. #include
    2. #include
    3. /*
    4. 对于位式和增量式那种算法好:具体场景具体分析
    5. */
    6. typedef struct
    7. {
    8. float kp; // 比例系数
    9. float ki; // 积分系数
    10. float kd; // 微分系数
    11. float err_last; // 上次误差
    12. float err_sum; // 误差累计
    13. float result;
    14. }pid_pos_typedef;
    15. typedef struct
    16. {
    17. float kp; // 比例系数
    18. float ki; // 积分系数
    19. float kd; // 微分系数
    20. float err_last; // 上一次的误差
    21. float err_pree; // 上二次的误差
    22. float result;
    23. }pid_delta_typedef;
    24. void pid_delta_init(pid_delta_typedef* pid, float kp, float ki, float kd);
    25. float pid_delta_calc(pid_delta_typedef* pid, float currVal, float objVal);
    26. void pid_pos_init(pid_pos_typedef* pid, float kp, float ki, float kd);
    27. float pid_pos_calc(pid_pos_typedef* pid, float currVal, float objVal);
    28. // 位置式pid算法初始化
    29. void pid_pos_init(pid_pos_typedef* pid, float kp, float ki, float kd)
    30. {
    31. pid->kp = kp;
    32. pid->ki = ki;
    33. pid->kd = kd;
    34. pid->result = 0;
    35. pid->err_last = 0;
    36. pid->err_sum = 0;
    37. }
    38. // 位置式pid算法计算
    39. float pid_pos_calc(pid_pos_typedef* pid, float currVal, float objVal)
    40. {
    41. float err_c = objVal - currVal; // 当前误差
    42. pid->err_sum += err_c; // 误差累计
    43. pid->result = pid->kp * err_c + pid->ki * pid->err_sum + pid->kd * (err_c - pid->err_last);
    44. pid->err_last = err_c;
    45. return pid->result;
    46. }
    47. // 增量式pid算法初始化
    48. void pid_delta_init(pid_delta_typedef* pid, float kp, float ki, float kd)
    49. {
    50. pid->kp = kp;
    51. pid->ki = ki;
    52. pid->kd = kd;
    53. pid->result = 0;
    54. pid->err_last = 0;
    55. pid->err_pree = 0;
    56. }
    57. // 增量式算法计算
    58. float pid_delta_calc(pid_delta_typedef* pid, float currVal, float objVal)
    59. {
    60. float err_c; // 当前误差
    61. float err_p; // p误差
    62. float err_i; // i误差
    63. float err_d; // d误差
    64. float increment; // 增量
    65. err_c = objVal - currVal;
    66. err_p = err_c - pid->err_last;
    67. err_i = err_c;
    68. err_d = err_c - 2 * pid->err_last + pid->err_pree;
    69. increment = pid->kp * err_p + pid->ki * err_i + pid->kd * err_d;
    70. pid->err_pree = pid->err_last;
    71. pid->err_last = err_c;
    72. pid->result += increment;
    73. return pid->result;
    74. }
    75. void mydelay(int ms){
    76. int i;
    77. for(;i<1000000*ms;i++)
    78. {
    79. }
    80. }
    81. int main(void)
    82. {
    83. unsigned int i = 0;
    84. float currVal = 0; // 当前值
    85. float objVal = 10; // 目标值
    86. pid_delta_typedef pid_delta;
    87. pid_pos_typedef pid_pos;
    88. pid_delta_init(&pid_delta, 0.2, 0.001, 0.0001);
    89. pid_pos_init(&pid_pos, 0.2, 0.001, 0.0001);
    90. printf("------ Start. \n");
    91. while(1)
    92. {
    93. i++;
    94. /*从这边可以对比出到第是哪一种算法会更加好*/
    95. //currVal = pid_delta_calc(&pid_delta, currVal, objVal);
    96. currVal = pid_pos_calc(&pid_pos, currVal, objVal);
    97. printf("[%d] objVal[%f] currVal:%f \n", i, objVal, currVal);
    98. if(currVal>9.998990){
    99. mydelay(100);//方便观察
    100. }
    101. if(currVal >= 9.999)
    102. break;
    103. }
    104. printf("------ End. \n");
    105. return 0;
    106. }
  • 相关阅读:
    阿里云云安全中心支持哪些功能?费用价格是否值得买?
    用cpolar发布Ubuntu上的网页(1)
    基于vue和node.js的志愿者招募网站设计
    【JVM深层系列】「官方技术翻译」《A FIRST LOOK INTO ZGC》初探JVM-ZGC垃圾回收器
    Docker 安装(方法4):使用二进制文件压缩包安装
    强化学习 (三) 动态规划
    中文编程开发语言编程实际案例:程序控制灯电路以及桌球台球室用这个程序计时计费
    ubuntu 18 VMWare 如何 命令行 扩展磁盘
    弘辽科技:拼多多爆单技巧是什么?拼多多怎么快速出单?
    如何提高Hbase的读取效率
  • 原文地址:https://blog.csdn.net/IT_luosong/article/details/126019987