在之前一篇博客中简单介绍了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) |
这里简单的说明一下;
所以可以看出,最终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算法的话:
对于增量式PID来说;
整体框图如下所示;
所以这里不难发现,所谓增量式PID,它的特点有:
下面简单介绍一下如何实现增量式PID算法;
previous02_error := 0 //上上次偏差
previous01_error := 0 //上一次偏差
integral := 0 //积分和
pid_out := 0 //pid增量累加和
//循环
//采样周期为dt
loop:
//setpoint 设定值
//measured_value 反馈值
error := setpoint − measured_value //计算得到偏差
proportion := error - previous01_error //计算得到比例输出
integral := error × dt //计算得到积分累加和
derivative := (error − 2*previous01_error + previous02_error) / dt //计算得到微分
pid_delta := Kp × error + Ki × integral + Kd × derivative //计算得到PID增量
pid_out := pid_out + pid_delta //计算得到PID输出
//保存当前的偏差和上一次偏差作为下一次采样所需要的历史偏差
previous02_error := previous01_error
previous01_error := error //保存当前偏差为下一次采样时所需要的历史偏差
wait(dt) //等待下一次采用
goto loop
这里直接使用了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算法;
由于作者能力和水平有限,文中难免存在错误和纰漏,请不吝赐教。