• 1 什么是增量式PID?
  • 2 举个例子
    • 2.1 位置式PID
    • 2.2 增量式PID
  • 3 伪算法
  • 4 C语言实现
  • 5 总结

在之前一篇博客中简单介绍了PID算法的基本原理位置式算法的实现过程,由于部分推导过程已经在上一篇文章做过介绍,所以推导过程本文不再赘述,重点将对离散增量式PID的算法进行实现。

1 什么是增量式PID?

先看一下增量式PID的离散公式如下:

[公式]

[公式]:比例系数
[公式]:积分系数
[公式]:微分系数
[公式]:偏差

对于所谓的位置式增量式的算法,这两者只是在算法的实现上的存在差异,本质的控制上对于系统控制的影响还是相同,单纯从输入和输出的角度来比较,具体如下表所示;

这里简单的说明一下;

  • 位置式:位置式算法较为简单,直接输入当前的偏差 [公式],即可得到输出[公式]
  • 增量式:增量式算法需要保存历史偏差,[公式],即在第[公式]次控制周期时,需要使用第[公式]和第[公式]次控制所输入的偏差,最终计算得到 [公式],此时,这还不是我们所需要的PID输出量;所以需要进行累加;

[公式]

不难发现第一次控制周期时,即[公式]时;

[公式]

由以上公式我们可以推导出下式;

[公式]

所以可以看出,最终PID的输出量[公式],满足以下公式;

[公式]

可见增量式算法,就是所计算出的PID增量的历史累加和;

2 举个例子

2.1 位置式PID

下面从一个简单的例子中去理解一下增量式PID,这里依然举一个不是很恰当的例子; 如果是位置式PID算法的话:

  • 隆哥对一个直流电机进行调速,设定了转速为 1000
  • 这时由于反馈回来的速度和设定的速度偏差为 [公式]
  • 经过位置式PID计算得到[公式]
  • [公式]作为Process的输入值(可以是PWM的占空比),最终Process输出相应的PWM驱动直流电机;
  • 反馈装置检测到电机转速,然后重复以上步骤;

整体框图如下所示;

2.2 增量式PID

对于增量式PID来说;

  • 隆哥对一个直流电机进行调速,设定了转速为 1000
  • 这时由于反馈回来的速度和设定的速度偏差为 [公式],系统中保存上一次的偏差[公式]和上上次的偏差[公式],这三个输入量经过增量PID计算得到[公式]
  • 系统中还保存了上一次的PID输出[公式],所以[公式]加上增量[公式],就是本次控制周期的PID输出——[公式]
  • [公式]作为Process的输入值(可以是PWM的占空比),最终Process输出相应的PWM驱动直流电机;
  • 反馈装置检测到电机转速,然后重复以上步骤;

整体框图如下所示;

所以这里不难发现,所谓增量式PID,它的特点有:

  • 需要输入历史的偏差值;
  • 计算得到的是PID输出增量,因此每一次需要累加历史增量最为当前的PID输出;

下面简单介绍一下如何实现增量式PID算法;

3 伪算法

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

4 C语言实现

这里直接使用了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     
}
 

5 总结

本文简单总结了位置式PID算法增量式PID算法的差异,参考了TI公司的增量式PID算法实现,对于不同的控制对象可以根据系统要求选择合适的PID算法

由于作者能力和水平有限,文中难免存在错误和纰漏,请不吝赐教。