在之前一篇博客中简单介绍了PID算法的基本原理和位置式算法的实现过程,由于部分推导过程已经在上一篇文章做过介绍,所以推导过程本文不再赘述,重点将对离散增量式PID的算法进行实现。
先看一下增量式PID的离散公式如下: Δ u ( k ) = K p ( e ( k ) − e ( k − 1 ) ) + K i e ( k ) + K d ( e ( k ) − 2 e ( k − 1 ) + e ( k − 2 ) ) \Delta u(k)=K_p(e(k)-e(k-1))+K_ie(k)+K_d \Big( e(k)-2e(k-1)+e(k-2) \Big) Δu(k)=Kp(e(k)−e(k−1))+Kie(k)+Kd(e(k)−2e(k−1)+e(k−2))
K p K_p Kp:比例系数 K i K_i Ki:积分系数 K d K_d Kd:微分系数 e ( k ) e(k) e(k):偏差
对于所谓的位置式,增量式的算法,这两者只是在算法的实现上的存在差异,本质的控制上对于系统控制的影响还是相同,单纯从输入和输出的角度来比较,具体如下表所示;
位置式PID增量式PID输入 e ( k ) e(k) e(k) e ( k ) , e ( k − 1 ) , e ( k − 2 ) e(k),e(k-1),e(k-2) e(k),e(k−1),e(k−2)输出 u ( k ) u(k) u(k) u ( k − 1 ) + Δ u ( k ) u(k-1) + \Delta u(k) u(k−1)+Δu(k)这里简单的说明一下;
位置式:位置式算法较为简单,直接输入当前的偏差 e ( k ) e(k) e(k),即可得到输出 u ( k ) u(k) u(k);增量式:增量式算法需要保存历史偏差, e ( k − 1 ) , e ( k − 2 ) e(k-1),e(k-2) e(k−1),e(k−2),即在第 k k k次控制周期时,需要使用第 k − 1 k-1 k−1和第 k − 2 k-2 k−2次控制所输入的偏差,最终计算得到 Δ u ( k ) \Delta u(k) Δu(k),此时,这还不是我们所需要的PID输出量;所以需要进行累加; u ( k ) = u ( k − 1 ) + Δ u ( k ) u(k) = u(k-1) + \Delta u(k) u(k)=u(k−1)+Δu(k) 不难发现第一次控制周期时,即 k = 1 k=1 k=1时; u ( k ) = u ( 0 ) + Δ u ( k ) u(k) = u(0) + \Delta u(k) u(k)=u(0)+Δu(k) 由以上公式我们可以推导出下式; u ( k − 1 ) = ∑ i = 1 k − 1 Δ u ( i ) u(k-1)=\displaystyle\sum_{i=1}^{k-1}\Delta u(i) u(k−1)=i=1∑k−1Δu(i)所以可以看出,最终PID的输出量 u ( k ) u(k) u(k),满足以下公式; u ( k ) = ∑ i = 1 k Δ u ( i ) u(k)=\displaystyle\sum_{i=1}^{k}\Delta u(i) u(k)=i=1∑kΔu(i)
可见增量式算法,就是所计算出的PID增量的历史累加和;
下面从一个简单的例子中去理解一下增量式PID,这里依然举一个不是很恰当的例子; 如果是位置式PID算法的话:
隆哥对一个直流电机进行调速,设定了转速为 1000;这时由于反馈回来的速度和设定的速度偏差为 e k e_k ek;经过位置式PID计算得到 u ( k ) u(k) u(k); u ( k ) u(k) u(k)作为Process的输入值(可以是PWM的占空比),最终Process输出相应的PWM驱动直流电机;反馈装置检测到电机转速,然后重复以上步骤;整体框图如下所示;
对于增量式PID来说;
隆哥对一个直流电机进行调速,设定了转速为 1000;这时由于反馈回来的速度和设定的速度偏差为 e k e_k ek,系统中保存上一次的偏差 e k − 1 e_{k-1} ek−1和上上次的偏差 e k − 2 e_{k-2} ek−2,这三个输入量经过增量PID计算得到 Δ u ( k ) \Delta u(k) Δu(k);系统中还保存了上一次的PID输出的 u ( k − 1 ) u(k-1) u(k−1),所以 u ( k − 1 ) u(k-1) u(k−1)加上增量 Δ u ( k ) \Delta u(k) Δu(k),就是本次控制周期的PID输出—— u ( k ) u(k) u(k); u ( k ) u(k) u(k)作为Process的输入值(可以是PWM的占空比),最终Process输出相应的PWM驱动直流电机;反馈装置检测到电机转速,然后重复以上步骤;整体框图如下所示;
所以这里不难发现,所谓增量式PID,它的特点有:
需要输入历史的偏差值;计算得到的是PID输出增量,因此每一次需要累加历史增量最为当前的PID输出;下面简单介绍一下如何实现增量式PID算法;
这里直接使用了TI公司的PID算法,做了积分抗饱和;具体可以参考controlSUITE\libs\app_libs\motor_control\math_blocks\v4.2\pid_grando.h
具体代码如下所示;
pid_grando.h
/* ================================================================================= File name: PID_GRANDO.H ===================================================================================*/ #ifndef __PID_H__ #define __PID_H__ typedef struct { _iq Ref; // Input: reference set-point _iq Fbk; // Input: feedback _iq Out; // Output: controller output _iq c1; // Internal: derivative filter coefficient 1 _iq c2; // Internal: derivative filter coefficient 2 } PID_TERMINALS; // note: c1 & c2 placed here to keep structure size under 8 words typedef struct { _iq Kr; // Parameter: reference set-point weighting _iq Kp; // Parameter: proportional loop gain _iq Ki; // Parameter: integral gain _iq Kd; // Parameter: derivative gain _iq Km; // Parameter: derivative weighting _iq Umax; // Parameter: upper saturation limit _iq Umin; // Parameter: lower saturation limit } PID_PARAMETERS; typedef struct { _iq up; // Data: proportional term _iq ui; // Data: integral term _iq ud; // Data: derivative term _iq v1; // Data: pre-saturated controller output _iq i1; // Data: integrator storage: ui(k-1) _iq d1; // Data: differentiator storage: ud(k-1) _iq d2; // Data: differentiator storage: d2(k-1) _iq w1; // Data: saturation record: [u(k-1) - v(k-1)] } PID_DATA; typedef struct { PID_TERMINALS term; PID_PARAMETERS param; PID_DATA data; } PID_CONTROLLER; /*----------------------------------------------------------------------------- Default initalisation values for the PID objects -----------------------------------------------------------------------------*/ #define PID_TERM_DEFAULTS { \ 0, \ 0, \ 0, \ 0, \ 0 \ } #define PID_PARAM_DEFAULTS { \ _IQ(1.0), \ _IQ(1.0), \ _IQ(0.0), \ _IQ(0.0), \ _IQ(1.0), \ _IQ(1.0), \ _IQ(-1.0) \ } #define PID_DATA_DEFAULTS { \ _IQ(0.0), \ _IQ(0.0), \ _IQ(0.0), \ _IQ(0.0), \ _IQ(0.0), \ _IQ(0.0), \ _IQ(0.0), \ _IQ(1.0) \ } /*------------------------------------------------------------------------------ PID Macro Definition ------------------------------------------------------------------------------*/ #define PID_MACRO(v) \ \ /* proportional term */ \ v.data.up = _IQmpy(v.param.Kr, v.term.Ref) - v.term.Fbk; \ \ /* integral term */ \ v.data.ui = _IQmpy(v.param.Ki, _IQmpy(v.data.w1, (v.term.Ref - v.term.Fbk))) + v.data.i1; \ v.data.i1 = v.data.ui; \ \ /* derivative term */ \ v.data.d2 = _IQmpy(v.param.Kd, _IQmpy(v.term.c1, (_IQmpy(v.term.Ref, v.param.Km) - v.term.Fbk))) - v.data.d2; \ v.data.ud = v.data.d2 + v.data.d1; \ v.data.d1 = _IQmpy(v.data.ud, v.term.c2); \ \ /* control output */ \ v.data.v1 = _IQmpy(v.param.Kp, (v.data.up + v.data.ui + v.data.ud)); \ v.term.Out= _IQsat(v.data.v1, v.param.Umax, v.param.Umin); \ v.data.w1 = (v.term.Out == v.data.v1) ? _IQ(1.0) : _IQ(0.0); \ #endif // __PID_H__example
/* Instance the PID module */ PID pid1={ PID_TERM_DEFAULTS, PID_PARAM_DEFAULTS, PID_DATA_DEFAULTS }; main() { pid1.param.Kp = _IQ(0.5); pid1.param.Ki = _IQ(0.005); pid1.param.Kd = _IQ(0); pid1.param.Kr = _IQ(1.0); pid1.param.Km =_IQ(1.0); pid1.param.Umax= _IQ(1.0); pid1.param.Umin= _IQ(-1.0); } void interrupt periodic_interrupt_isr() { pid1.Ref = input1_1; // Pass _iq inputs to pid1 pid1.Fbk = input1_2; // Pass _iq inputs to pid1 PID_MACRO(pid1); // Call compute macro for pid1 output1 = pid1.Out; // Access the output of pid1 }本文简单总结了位置式PID算法和增量式PID算法的差异,参考了TI公司的增量式PID算法实现,对于不同的控制对象可以根据系统要求选择合适的PID算法;
由于作者能力和水平有限,文中难免存在错误和纰漏,请不吝赐教。
小麦大叔 认证博客专家 签约作者 有梦想的咸鱼 更多干货,欢迎关注公众号:[小麦大叔]一个热衷技术的工程师的原创分享,涉及内容包括但不限于嵌入式、物联网、单片机、编程技术、PCB、硬件设计等等。来交个朋友?