e(t)为误差值,Kp为比例系数,Kp/Ti为积分系数,Kp/TD为微分系数
比例项Kp:控制器比例项输出值和误差值保持线性关系,误差值放大一倍则输出值也同样放大一倍,误差值缩小一倍则输出值也缩小一倍。只依靠比例项进行控制的方法称为比例控制,比例控制可以很简单的实现控制器的基本功能,但往往存在静差以及过大引起系统振荡的问题。
积分项Ki:控制器积分项输出值与误差值的积分值成线性关系,即误差值的累计值乘以一个常数。积分项可以加速系统趋近设定值的过程,但积分增益过大容易引起积分超调的现象
微分项Kd:微分项的大小和输出值的变化量成正相关,微分项计算误差的一阶导数,并和一个常数相乘,得到微分项的输出值。微分项可以对系统的改变做出反应,对系统的短期改变很有帮助。
二、离散化PID(位置型PID)
由于数字系统是离散的,在单片机中实现PID控制算法时,需要将PID控制器的输出表达式改写成离散形式,其具体的做法就是将输出u(t)和误差e(t)由函数改成数组u(k)和e(k),积分换成求和,微分换成差分。离散化后的PID表达式如下:
- PID控制器可以分为增量式PID控制器和位置式PID控制器
位置式PID控制器:误差值直接决定最后的输出
增量式PID控制器:用误差值来控制每次输出的改变量∆u
∆u(k)=u(k)-u(k-1)
表达式为:
- 不需要累加计算累加,输出增量只和前三次误差采样值有关,参数更容易调节
- 每次只输出控制增量,故发生故障时产生的影响较小
使用增量式PID时需要记忆上一次的输出值,将上一次的输出值和增量相加才能得到本次输出值。
该枚举用于表示PID的模式(一个位置型PID、一个差分型PID)
C enum PID_MODE { PID_POSITION = 0, PID_DELTA }; |
该结构体用于储存PID相关的参数
C typedef struct { uint8_t mode; //PID模式 //PID 三个参数 fp32 Kp; fp32 Ki; fp32 Kd;
fp32 max_out; //最大输出的限制 fp32 max_iout; //最大积分输出的限制
fp32 set; //设定的目标值 fp32 fdb; //当前的PID值
fp32 out; //输出的控制值 fp32 Pout; //Pout Iout Dout 为中间三项的阶段成果 fp32 Iout; fp32 Dout; fp32 Dbuf[3]; //微分项 0最新 1上一次 2上上次 fp32 error[3]; //误差项 0最新 1上一次 2上上次
} pid_type_def; |
PID初始化函数
C extern void PID_init(pid_type_def *pid, uint8_t mode, const fp32 PID[3], fp32 max_out, fp32 max_iout); |
PID计算函数
C extern fp32 PID_calc(pid_type_def *pid, fp32 ref, fp32 set); |
PID清空函数
C extern void PID_clear(pid_type_def *pid); |
用于保障输出值不会越界
C #define LimitMax(input, max) \ { \ if (input > max) \ { \ input = max; \ } \ else if (input < -max) \ { \ input = -max; \ } \ } |
PID初始化函数
C void PID_init(pid_type_def *pid, uint8_t mode, const fp32 PID[3], fp32 max_out, fp32 max_iout) { if (pid == NULL || PID == NULL) { return; } pid->mode = mode; //以后调参数,主要以调Kp、Ki、Kd为主 pid->Kp = PID[0]; pid->Ki = PID[1]; pid->Kd = PID[2]; pid->max_out = max_out; pid->max_iout = max_iout; pid->Dbuf[0] = pid->Dbuf[1] = pid->Dbuf[2] = 0.0f; pid->error[0] = pid->error[1] = pid->error[2] = pid->Pout = pid->Iout = pid->Dout = pid->out = 0.0f; } |
pid->Kp的含义为pid.h文件中的Kp(其他代码含义与此相同)
PID计算函数
运行过程中,如果PID结构体指针不存在将直接返回0;
如果存在,首先将更新误差值目标值参考值
之后当使用普通PID时将直接计算PID的值,对应计算其比例项、微分项、积分项。积分项会限制范围,加起来后得到输出,输出也有范围限制
使用差分PID时,同样计算比例项、微分项、积分项,在对应加起来得到输出
C fp32 PID_calc(pid_type_def *pid, fp32 ref, fp32 set) //1.PID结构体指针 2.读取到的参考值 3.设定的目标值 { if (pid == NULL) { return 0.0f; }
pid->error[2] = pid->error[1]; pid->error[1] = pid->error[0]; pid->set = set; pid->fdb = ref; pid->error[0] = set - ref; if (pid->mode == PID_POSITION) { pid->Pout = pid->Kp * pid->error[0]; pid->Iout += pid->Ki * pid->error[0]; pid->Dbuf[2] = pid->Dbuf[1]; pid->Dbuf[1] = pid->Dbuf[0]; pid->Dbuf[0] = (pid->error[0] - pid->error[1]); pid->Dout = pid->Kd * pid->Dbuf[0]; LimitMax(pid->Iout, pid->max_iout); pid->out = pid->Pout + pid->Iout + pid->Dout; LimitMax(pid->out, pid->max_out); } else if (pid->mode == PID_DELTA) { pid->Pout = pid->Kp * (pid->error[0] - pid->error[1]); pid->Iout = pid->Ki * pid->error[0]; pid->Dbuf[2] = pid->Dbuf[1]; pid->Dbuf[1] = pid->Dbuf[0]; pid->Dbuf[0] = (pid->error[0] - 2.0f * pid->error[1] + pid->error[2]); pid->Dout = pid->Kd * pid->Dbuf[0]; pid->out += pid->Pout + pid->Iout + pid->Dout; LimitMax(pid->out, pid->max_out); } return pid->out; } |
举例:ref可以是从电调中读取的电机转速,set是目标的电机转速
PID清空函数
C void PID_clear(pid_type_def *pid) { if (pid == NULL) { return; }
pid->error[0] = pid->error[1] = pid->error[2] = 0.0f; pid->Dbuf[0] = pid->Dbuf[1] = pid->Dbuf[2] = 0.0f; pid->out = pid->Pout = pid->Iout = pid->Dout = 0.0f; pid->fdb = pid->set = 0.0f; } |