专家PID仿真
用Simulink中的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就差不多了