本系列旨在以我本身写的PID lib为例,讲一下PID的几点基本优化,PID的基本原理网上有不少资料,所以本系列将不会涉及PID的基本实现原理,在这里特别推荐Matlab tech talk的PID教程:https://ww2.mathworks.cn/videos/series/understanding-pid-control.html。html
因为笔者大一在读,尚未学习自动控制原理等课程,所以本系列将不会从自控原理角度展开,相反的,本系列将试图从“直觉”展开,经过直观的描述让你们从直觉上感觉并理解PID的一些包括微分先行、积分分离等基础的优化。git
因为笔者水平有限,文中不免存在一些不足和错误之处,诚请各位批评指正。github
(一)中主要讲解代码结构与代码使用,算法有关内容于(二)开始讲解算法
该PID lib所有代码详见:https://github.com/CharlesW1970/PID_Libraryide
该lib经过pid结构体保存于pid运算有关的参数数据,经过枚举表示其余有关量:函数
//PID结构体 typedef struct _PID_TypeDef { float Target; float LastNoneZeroTarget; float Kp; float Ki; float Kd; float Measure; float Last_Measure; float Err; float Last_Err; float Pout; float Iout; //Iout = ITerm_0 + ITerm_1 +....+ ITerm_n float Dout; float ITerm; //ITerm = Err * Ki float Output; float Last_Output; float MaxOut; float IntegralLimit; float DeadBand; float ScalarA; //变积分公式参数 float ScalarB; //ITerm = Err*((A-abs(err)+B)/A) when B<|err|<A+B uint8_t Improve; //用于使能优化 PID_ErrorHandler_t ERRORHandler; void (*PID_param_init)( struct _PID_TypeDef *pid, uint16_t maxOut, uint16_t integralLimit, float deadband, float Kp, float ki, float kd, float A, float B, uint8_t improve); void (*PID_reset)( struct _PID_TypeDef *pid, float Kp, float ki, float kd); } PID_TypeDef; //PID优化功能枚举 typedef enum pid_Improvement_e { NONE = 0X00, //无 Integral_Limit = 0x01, //积分限幅 Derivative_On_Measurement = 0x02, //微分先行 Trapezoid_Intergral = 0x04, //梯形积分 Proportional_On_Measurement = 0x08, //该系列不涉及 OutputFilter = 0x10, //输出滤波 ChangingIntegralRate = 0x20, //变积分 ErrorHandle = 0x80, //异常处理 } PID_Improvement_e; //异常状况枚举,这里只写了电机堵转保护一种 typedef enum errorType_e { PID_ERROR_NONE = 0x00U, Motor_Blocked = 0x01U } ErrorType_e; //异常状况结构体 typedef struct { uint64_t ERRORCount; ErrorType_e ERRORType; } PID_ErrorHandler_t;
在使用以前须要先调用PID_Init函数进行参数初始化和函数链接学习
void PID_Init( PID_TypeDef *pid, uint16_t max_out, uint16_t intergral_limit, float deadband, float kp, float Ki, float Kd, float A, float B, uint8_t improve) { pid->PID_param_init = f_PID_param_init; pid->PID_reset = f_PID_reset; //链接Kp Ki Kd参数重设函数 pid->PID_param_init(pid, max_out, intergral_limit, deadband, kp, Ki, Kd, A, B, improve); //链接并调用参数初始化函数 } static void f_PID_param_init( PID_TypeDef *pid, uint16_t max_out, uint16_t intergral_limit, float deadband, float kp, float Ki, float Kd, float Changing_Integral_A, float Changing_Integral_B, uint8_t improve) { //参数初始化 pid->DeadBand = deadband; pid->IntegralLimit = intergral_limit; pid->MaxOut = max_out; pid->Target = 0; pid->Kp = kp; pid->Ki = Ki; pid->Kd = Kd; pid->ITerm = 0; pid->ScalarA = Changing_Integral_A; pid->ScalarB = Changing_Integral_B; pid->Improve = improve; //异常处理初始化 pid->ERRORHandler.ERRORCount = 0; pid->ERRORHandler.ERRORType = PID_ERROR_NONE; pid->Output = 0; }
PID_Calculate函数与网上大多数代码大致结构相同,只是添加了不一样的优化函数,具体优化在各函数(如:f_PID_ErrorHandle、f_Trapezoid_Intergral)中实现,PID_Calculate函数具体代码以下:优化
float PID_Calculate(PID_TypeDef *pid, float measure, float target) { if (pid->Improve & ErrorHandle) { //异常处理 f_PID_ErrorHandle(pid); if (pid->ERRORHandler.ERRORType != PID_ERROR_NONE) { //电机堵转保护 pid->Output = 0; return 0; } } //偏差更新 pid->Measure = measure; pid->Target = target; pid->Err = pid->Target - pid->Measure; //死区内进行计算 if (ABS(pid->Err) > pid->DeadBand) { //计算比例、微分输出与该周期积分项结果 pid->Pout = pid->Kp * pid->Err; pid->ITerm = pid->Ki * pid->Err; pid->Dout = pid->Kd * (pid->Err - pid->Last_Err); //判断是否使能梯形积分 if (pid->Improve & Trapezoid_Intergral) f_Trapezoid_Intergral(pid); //判断是否使能变积分 if (pid->Improve & ChangingIntegralRate) f_Changing_Integral_Rate(pid); //判断是否使能积分限幅 if (pid->Improve & Integral_Limit) f_Integral_Limit(pid); //判断是否使能微分先行 if (pid->Improve & Derivative_On_Measurement) f_Derivative_On_Measurement(pid); //计算积分输出 pid->Iout += pid->ITerm; //计算pid总输出 pid->Output = pid->Pout + pid->Iout + pid->Dout; //判断是否使能输出滤波 if (pid->Improve & OutputFilter) f_OutputFilter(pid); //输出限幅 f_Output_limit(pid); } //数据保存供下一周期调用 pid->Last_Measure = pid->Measure; pid->Last_Output = pid->Output; pid->Last_Err = pid->Err; return pid->Output; }
这里给出以发布在GitHub上的示例,具体不在详细讲解ui
//pid函数链接 PID_Init(&PID_Example, 9600, 5000, 3, 1, 5, 0.3, 0.3, 100, 100, ErrorHandle | Integral_Limit | OutputFilter); //修改kp ki kd PID_Example.PID_reset(&PID_Example, 3, 1, 0); //计算 PID_Calculate(&PID_Example, measure, target);
该篇对该lib结构和使用就讲到这里,下一篇将会开始算法讲解。code