专家PID仿真

Simulink中的S函数进行专家PID仿真

传送们:
编写简单的S函数
专家PID

S函数代码

#include "math.h"
//********************************PID算法部分************************************//
#define OUT_MIN		-1000
#define OUT_MAX		1000

//积分限幅
#define INERGRAL_MAX 200
#define INERGRAL_MIN -200

#define DEAD_BAND 25 //死区控制线
double Error;//输入偏差
/****************************************************************************************/
//定义PID结构体//
/****************************************************************************************/

typedef struct
{
	volatile double    Proportion; // 比例常数 Proportional Const
	volatile double    Integral; // 积分常数 Integral Const
	volatile double    Derivative;// 微分常数 Derivative Const
	volatile double      Error1; // Error[n-1]
	volatile double      Error2; // Error[n-2]
	volatile double      iError;  // Error[n]
	volatile double		 Error_sum;//积分值
	volatile double		 iIncpid;//PID输出值
	volatile double      inc_iError;//Error[n]增量
	volatile double      inc_Error1;//Error[n-1]增量
	volatile double		 Error_abs_Max;//偏差绝对值最大值
	volatile double		 Error_abs_Mid;//偏差绝对值最中值
	volatile double		 Error_abs_Min;//偏差绝对值最小值
} PID;


//PID指针
PID pid_increase;
PID* sptr_increase=&pid_increase;

//PID初始化
void PID_Init(PID *sptr)
{
	sptr->Derivative = 0;//Kd
	sptr->Proportion = 0;//Kp
	sptr->Integral = 0;//Ki
	sptr->Error2 = 0;
	sptr->Error1 = 0;
	sptr->iError = 0;
	sptr->Error_sum = 0;
	//sptr->index=1;
	sptr->iIncpid = 0;
	sptr->inc_iError = 0;
	sptr->inc_Error1 = 0;
	sptr->Error_abs_Max = 200;
	sptr->Error_abs_Mid = 100;
	sptr->Error_abs_Min = 50;

}

//PID输出限幅处理
double PID_OutputLimit(double output)
{
	double x;
	x = output;
	if (x <= OUT_MIN)
	{
		x = (double)OUT_MIN;
	}
	else if (x >= OUT_MAX)

	{
		x = (double)OUT_MAX;
	}
	return x;
}

/****************************************************************************************/																												//								位置式专家
//										//pwm=Kp*e(k)+Ki*∑e(k)+Kd[e(k)-e(k-1)]	
/****************************************************************************************/
double Exper_Pid(double iError, PID* sptr)
{
	double result = 0;
	sptr->iError = iError;  //传入当前误差
	sptr->inc_iError = sptr->iError - sptr->Error1;//得到这次增量
	sptr->inc_Error1 = sptr->Error1 - sptr->Error2;//得到上次增量
	sptr->iError = iError;                                    // 计算当前误差

	sptr->Error_sum += sptr->iError;//积分项	
		///当输出限幅的时候,积分累加部分也应同时进行限幅,以防输出不变而积分项继续累加,也即所谓的积分饱和过深。
	//积分量限幅
	if (sptr->Error_sum > INERGRAL_MAX)
	{
		sptr->Error_sum = INERGRAL_MAX;
	}
	if (sptr->Error_sum < INERGRAL_MIN)
	{
		sptr->Error_sum = INERGRAL_MIN;
	}

	if (fabs(sptr->iError) > sptr->Error_abs_Max)//误差超过设定的最大值(M1)
	{
		if (sptr->iError > 0)  result = OUT_MAX;
		if (sptr->iError < 0)  result = OUT_MIN;
	}

	if (fabs(sptr->iError) <= sptr->Error_abs_Min)//若误差小于设定的最小值(M3)
	{
		if (fabs(sptr->iError) > DEAD_BAND) //死区控制
		{
			result = 1*sptr->Proportion * sptr->iError// P
				+ 1*sptr->Integral * sptr->Error_sum;                // I
		}
		else result = 0;
	}
	if (((sptr->iError*sptr->inc_iError < 0) && (sptr->inc_iError*sptr->inc_Error1 > 0)) || (sptr->iError == 0))
		//说明误差正在减小,或者为零,此时维持原输出
	{
		result = sptr->iIncpid;//保持上一次输出
	}

	if (((sptr->iError*sptr->inc_iError < 0) && (sptr->inc_iError*sptr->inc_Error1 < 0)))//误差处于极值状态
	{
		if (fabs(sptr->iError) > sptr->Error_abs_Mid)    result = 4*sptr->Integral*sptr->Error_sum;
		if (fabs(sptr->iError) < sptr->Error_abs_Mid)    result = 3*sptr->Integral*sptr->Error_sum;
	}
    
	if ((sptr->iError*sptr->inc_iError > 0) || (sptr->inc_iError == 0))//误差在变大,或维持长值
	{
		if (fabs(sptr->iError) > sptr->Error_abs_Mid)    result = 3 * (sptr->Integral*sptr->Error_sum + sptr->Proportion*sptr->iError + sptr->Derivative*sptr->inc_iError);

		if (fabs(sptr->iError) < sptr->Error_abs_Mid)    result = 1.2*(sptr->Integral*sptr->Error_sum + sptr->Proportion*sptr->iError + sptr->Derivative*sptr->inc_iError);
	}
    
	//更新值
	sptr->iIncpid = result;
	sptr->Error2 = sptr->Error1;
	sptr->Error1 = sptr->iError;
	result = PID_OutputLimit(result);//PID输出限幅								
	return(result);          // 返回计算值

}
//下面是主要编写S函数的部分


#define S_FUNCTION_NAME  exper_pid   //修改s函数名称,此处为test
#define S_FUNCTION_LEVEL 2      //不需要改使用LEVEL 2,可以提供更过API函数
#define SAMPLE_TIME 0.01        //自己设置的采样时间

#include "simstruc.h"//必须包含的头文件

//**输出函数:**
static void mdlInitializeSizes(SimStruct *S)
{
    ssSetNumSFcnParams(S, 3); /*设置参数个数,这里为3,分别设置PID的三个参数分别是P I D */
    if (ssGetNumSFcnParams(S) != ssGetSFcnParamsCount(S)) /*判断参数个数,这里为3 ,在S-Function的对话框里面设置3个*/
    {
        return;
    }
    
    ssSetNumContStates(S, 0);//设置连续状态的个数,缺省为0;
    ssSetNumDiscStates(S, 0);//设置离散状态的个数,缺省为0;

    if (!ssSetNumInputPorts(S, 1)) return;//设置输入变量的个数,这里为1
    ssSetInputPortWidth(S, 0, 1);//指定输入端口的宽度。0号,1维
    ssSetInputPortRequiredContiguous(S, 0, true); //指定进入端口的信号元素必须是连续的。
    //连续指的是:指定端口的信号元素必须占用内存的连续区域。以便于访问信号指针即可访问信号的元素,可以使用ssGetInputPortSignal访问。
    //指定进入指定端口的信号元素必须占用内存的连续区域。以便于访问信号指针即可访问信号的元素ssGetInputPortSignal
   
    
    //端口的输入用于mdlOutputs或mdlGetTimeOfNextVarHit函数。设置直接馈通标志(1=yes,0=no)   
    ssSetInputPortDirectFeedThrough(S, 0, 1);
 
    if (!ssSetNumOutputPorts(S, 1)) return;设置输出变量的个数
    ssSetOutputPortWidth(S, 0, 1);//指定输入端口的宽度。0号,1维

    ssSetNumSampleTimes(S, 1);//设定的样本采样次数,此处为1次
    ssSetNumRWork(S, 0);//用于mdlInitializeSizes将real_T工作向量元素的数量指定为0,
    ssSetNumIWork(S, 0);//可以不用管,一些清零操作
    ssSetNumPWork(S, 0);//可以不用管,一些清零操作
    ssSetNumModes(S, 0);//可以不用管,一些清零操作
    //如果该选项设置为使用默认SIM状态,并且S函数不使用PWorks,则Simulink将S函数视为内置块。
    ssSetNumNonsampledZCs(S, 0);

    ssSetOptions(S, 0);//有许多选项,使用or连接,采取默认值
}

//指定采样时间函数
static void mdlInitializeSampleTimes(SimStruct *S)
{
    ssSetSampleTime(S, 0, SAMPLE_TIME);//指定采样时间SAMPLE_TIME也就是0.01 秒,索引从0开始的采样时间段
    ssSetOffsetTime(S, 0, 0.0);//使用此宏mdlInitializeSizes指定从0开始的采样时间的偏移量。

}



#define MDL_INITIALIZE_CONDITIONS   /* Change to #undef to remove function */
#if defined(MDL_INITIALIZE_CONDITIONS)
  /* Function: mdlInitializeConditions ========================================
   * Abstract:
   *    In this function, you should initialize the continuous and discrete
   *    states for your S-function block.  The initial states are placed
   *    in the state vector, ssGetContStates(S) or ssGetRealDiscStates(S).
   *    You can also perform any other initialization activities that your
   *    S-function may require. Note, this routine will be called at the
   *    start of simulation and if it is present in an enabled subsystem
   *    configured to reset states, it will be call when the enabled subsystem
   *    restarts execution to reset the states.
   */
//初始化连续和离散状态,此处不使用,使用ssGetContStates(S) or ssGetRealDiscStates(S).访问状态
//例如 real_T *xdisc    = ssGetRealDiscStates(S);
  static void mdlInitializeConditions(SimStruct *S)
  {
  }
#endif /* MDL_INITIALIZE_CONDITIONS */



#define MDL_START  /* Change to #undef to remove function */
#if defined(MDL_START) 

//此函数在模型执行开始时调用一次。自己需要初始化的函数可以放在这里面。*/
  static void mdlStart(SimStruct *S)
  {
      PID_Init(sptr_increase);//PID参数初始化
  }
#endif /*  MDL_START */



//用于计算S函数的输出,算法一般是在这里实现的。
static void mdlOutputs(SimStruct *S, int_T tid)
{

  real_T *para1 = mxGetPr(ssGetSFcnParam(S, 0));  //获得参数1,P
  real_T *para2 = mxGetPr(ssGetSFcnParam(S, 1));  //获得参数2,I
  real_T *para3 = mxGetPr(ssGetSFcnParam(S, 2));  //获得参数2,D
 
  const real_T *u1 = (const real_T*) ssGetInputPortSignal(S,0);  //获得输入u1
  real_T       *y1 = ssGetOutputPortSignal(S,0);  //输出y1
  sptr_increase->Proportion=(double)*para1;
  sptr_increase->Integral=(double)*para2;
  sptr_increase->Derivative=(double)*para3;
  Error=(int)*u1;
  *y1=(double)Exper_Pid(Error,sptr_increase);//PID输出
   
}



#define MDL_UPDATE  /* Change to #undef to remove function */
#if defined(MDL_UPDATE)
  /* Function: mdlUpdate ======================================================
   * Abstract:
   *    This function is called once for every major integration time step.
   *    Discrete states are typically updated here, but this function is useful
   *    for performing any tasks that should only take place once per
   *    integration step.
   */ 
   //离散状态通常在此更新,做任何任务都应该更新一次,需要k'kssSetNumDiscStates宏指定它具有离散状态。
  static void mdlUpdate(SimStruct *S, int_T tid)
  {
  }
#endif /* MDL_UPDATE */



#define MDL_DERIVATIVES  /* Change to #undef to remove function */
#if defined(MDL_DERIVATIVES)
  /* Function: mdlDerivatives =================================================
   * Abstract:
   *    In this function, you compute the S-function block's derivatives.
   *    The derivatives are placed in the derivative vector, ssGetdX(S).
   */
  //计算S函数块的导数。
  //*导数被放置在导数向量ssGetdX(S)中
  static void mdlDerivatives(SimStruct *S)
  {
  }
#endif /* MDL_DERIVATIVES */



/* Function: mdlTerminate =====================================================
 * Abstract:
 *    In this function, you should perform any actions that are necessary
 *    at the termination of a simulation.  For example, if memory was
 *    allocated in mdlStart, this is the place to free it.
 */
  //函数结束时执行的操作,例如释放内存等必要操作
static void mdlTerminate(SimStruct *S)
{
    Error=0;
    PID_Init(sptr_increase);
}


/*=============================*
 * Required S-function trailer *
 *=============================*/

#ifdef  MATLAB_MEX_FILE    /* Is this file being compiled as a MEX-file? */
#include "simulink.c"      /* MEX-file interface mechanism */
#else
#include "cg_sfun.h"       /* Code generation registration function */
#endif

搭建Simulink简单模型

在这里插入图片描述

控制效果:
可以说非常好了,超调量很小,反应时间也很迅速,不到1s就达到稳定值了。


在这里插入图片描述

调试由于参数较多,需要确定输出的最值和积分的最值,和设定误差的最大最小,和中值。
 
然后结合普通PID的调试技巧,先将Ki置零,调节Kp,然后根据误差曲线的变化规律和输出曲线,判断误差处于那个状态。在进去里面调节增益系数。期间可以对设定误差的最值和中值进行微调以边更适合系统。
 
当Kp调试差不多了,在调试Ki就差不多了
————————————————
版权声明:本文为CSDN博主「Silent Knight」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/weixin_46185705/article/details/114365012