PID算法理论与C语言的实现

一、参考的资料链接:
1、PID参数整定方法论: https://blog.csdn.net/weixin_43455581/article/details/91365468
2、理论算法实现(带实例源码): https://www.jianshu.com/p/eaf807cd7085
///
二、理论知识解析(重要):
1、什么是PID:PID,即比例Proportion、积分Integral和微分Derivative三个单词的缩写。
2、PID运算原理图:
       PID算法理论与C语言的实现_第1张图片
      Setpoint:理论目标值;Output:实际输出值;Error:理论与实际的误差;Process:PID算法对误差的计算后,输出的补偿值。
3、算法基础公式:
       PID算法理论与C语言的实现_第2张图片
       该系统由模拟 PID 控制器和被控对象组成。上面框图中, r(t) 是给定值, y(t) 是系统的实际输出值,给定值与实际输出值构成控制偏差e(t) = r(t) − y(t)。e(t) 作为 PID 控制的输入, u(t)作为 PID 控制器的输出和被控对象的输入。 所以模拟 PID 控制器的控制规律为:
        PID算法理论与C语言的实现_第3张图片
      Kp:控制器的比例系数;Ti:控制器的积分时间,也称为积分系数;Td:控制器的微分时间,也称为微分系数
4、对P、I、D的解析:
      比例控制(P):比例控制器实际上就是个放大倍数可调的放大器,即△P=Kp×e,式中Kp为比例增益,即Kp可大于1,也可小于1;e为控制器的输入,也就是测量值与给定值之差,又称为偏差。对于大多数模拟控制器而言,都不采用比例增益Kp作为刻度,而是用比例度来刻度,即δ=1/Kc×100%。也就是说比例度与控制器的放大倍数的倒数成比例;控制器的比例度越小,它的放大倍数越大,偏差放大的能力越大,反之亦然。
      积分控制(I):控制器的积分作用就是为了消除自控系统的余差而设置的。所谓积分,就是随时间进行累积的意思,即当有偏差输入e存在时,积分控制器就要将偏差随时间不断累积起来,也就是积分累积的快慢与偏差e的大小和积分速度成正比。只要有偏差e存在,积分控制器的输出就要改变,也就是说积分总是起作用的,只有偏差不存在时,积分才会停止。
      微分控制(D):在常规PID控制器中,微分作用的输出变化与微分时间和偏差变化的速度成比例,而与偏差的大小无关,偏差变化的速度越大,微分时间越长,则微分作用的输出变化越大。但如果微分作用过强,则可能由于变化太快而由其自身引起振荡,使控制器输出中产生明显的“尖峰”或“突跳”。为了避免这一扰动,在PID调节器和DCS中可使用微分先行PID运算规律,即只对测量值PV进行微分,当人工改变控制器的给定值SP时,不会造成控制器输出的突变,避免了改变SP的瞬间给控制系统带来的扰动。如TDC-3000,则在常规PID算法中增加一个软开关,组态时供用户选择控制器对偏差、还是测量值进行微分。
///
三、代码的实现与技术细节
1、C语言测试源码: (增量式算法,实时修正,没有积分效应)
     
#include 
#include 

typedef struct PID_data{
	long long SetPoint;  //目标输出值//
	long long SumError;  //当前误差//
	
	double P_value;   //比例增益参数//
	double I_value;   //积分参数//
	double D_value;   //微分参数//
	
	long long LastError;  //上一个误差值//
	long long PrevError;  //再上一个误差值//
}PID;
/*PID 参数结构体对象的初始化函数*/
char PID_Arg_Init(PID *pid_arg)
{
	if(NULL == pid_arg)
		return -1;
	
	pid_arg->SetPoint = 0;
	pid_arg->SumError = 0;
	
	pid_arg->P_value = 0;
	pid_arg->I_value = 0;
	pid_arg->D_value = 0;
	
	pid_arg->LastError = 0;
	pid_arg->PrevError = 0;
}
/*PID 增量式计算,通过当前的实际值,返回补偿值*/
long long PID_IncFunction(PID *pid_arg,long long InPoint)
{
	long long ret_val;
	if(NULL == pid_arg)
	{
		perror("NULL == PID data!!\n");
		return 0;
	}
	
	/*这里开始计算*/
	pid_arg->SumError = pid_arg->SetPoint - InPoint;  //1 计算出当前实际输出与目标的误差值//
	ret_val = (long long)((pid_arg->P_value)*(pid_arg->SumError));  //2 比例补偿计算//
	ret_val -= (long long)((pid_arg->I_value)*(pid_arg->LastError));  //3 积分滞后调节//
	ret_val += (long long)((pid_arg->D_value)*(pid_arg->PrevError));  //4 微分超前调节//
	
	pid_arg->PrevError = pid_arg->LastError;
	pid_arg->LastError = pid_arg->SumError;
	
	return ret_val;
}

int main(void)
{
	PID pid_test;
	PID_Arg_Init(&pid_test);  //初始化PID参数设置//
	
	/*配置PID相关参数*/
	pid_test.SetPoint = 20000;
	pid_test.P_value = 0.53;
	pid_test.I_value = 0.15;
	pid_test.D_value = 0.1;
	
	long long temp=0;  //模拟实际输出的值//
	long long add = 0;  //每次误差调整的值//
	int i=0;  //统计调节次数//
	
	while(temp != pid_test.SetPoint)
	{
		i++;
		add = PID_IncFunction(&pid_test,temp);
		temp += add;
		
		printf("add:%lld\n",add);
		printf("temp:%lld\n",temp);
		printf("PID.tmp:%lld\n",pid_test.SetPoint);
		printf("PID.error:%lld\n\n",pid_test.LastError);
	}
	
	printf("need int counter %d\n",i);
	
	return 0;
}

2、别人的代码段:(其实根据我手写的例子,再去看代码段就差不多了

      位置型:位置式PID控制的输出与整个过去的状态有关,用到了误差的累加值;而增量式PID的输出只与当前拍和前两拍的误差有关,因此位置式PID控制的累积误差相对更大。位置式PID适用于执行机构不带积分部件的对象,如电液伺服阀。
    
     
typedef struct
{
  float Kp;                       //比例系数Proportional
  float Ki;                       //积分系数Integral
  float Kd;                       //微分系数Derivative

  float Ek;                       //当前误差
  float Ek1;                      //前一次误差 e(k-1)
  float Ek2;                      //再前一次误差 e(k-2)
  float LocSum;                   //累计积分位置
}PID_LocTypeDef;

/************************************************
函数名称 : PID_Loc
功    能 : PID位置(Location)计算
参    数 : SetValue ------ 设置值(期望值)
            ActualValue --- 实际值(反馈值)
            PID ----------- PID数据结构
返 回 值 : PIDLoc -------- PID位置
作    者 : strongerHuang
*************************************************/
float PID_Loc(float SetValue, float ActualValue, PID_LocTypeDef *PID)
{
  float PIDLoc;                                  //位置

  PID->Ek = SetValue - ActualValue;
  PID->LocSum += PID->Ek;                         //累计误差

  PIDLoc = PID->Kp * PID->Ek + (PID->Ki * PID->LocSum) + PID->Kd * (PID->Ek1 - PID->Ek);

  PID->Ek1 = PID->Ek;  return PIDLoc;
}

 

     增量型:增量式PID控制输出的是控制量增量,并无积分作用,因此该方法适用于执行机构带积分部件的对象,如步进电机等。由于增量式PID输出的是控制量增量,如果计算机出现故障,误动作影响较小,而执行机构本身有记忆功能,可仍保持原位,不会严重影响系统的工作。
    
typedef struct
{
  float Kp;                       //比例系数Proportional
  float Ki;                       //积分系数Integral
  float Kd;                       //微分系数Derivative

  float Ek;                       //当前误差
  float Ek1;                      //前一次误差 e(k-1)
  float Ek2;                      //再前一次误差 e(k-2)
}PID_IncTypeDef;

/************************************************
函数名称 : PID_Inc
功    能 : PID增量(Increment)计算
参    数 : SetValue ------ 设置值(期望值)
            ActualValue --- 实际值(反馈值)
            PID ----------- PID数据结构
返 回 值 : PIDInc -------- 本次PID增量(+/-)
作    者 : strongerHuang
*************************************************/
float PID_Inc(float SetValue, float ActualValue, PID_IncTypeDef *PID)
{
  float PIDInc;                                  //增量

  PID->Ek = SetValue - ActualValue;
  PIDInc = (PID->Kp * PID->Ek) - (PID->Ki * PID->Ek1) + (PID->Kd * PID->Ek2);

  PID->Ek2 = PID->Ek1;
  PID->Ek1 = PID->Ek;  return PIDInc;
}

 

    
///
 
附加说明:本人承接Linux系统的嵌入式软件开发项目,CODESYS的runntime组件开发。欢迎加微:wxk101633(备注:委托开发)

你可能感兴趣的:(运动控制算法,C语言)