PID控制器简述(附代码)
直观的来看,当误差突然增大时,此时的e0-e1会很大,D项的输出也相应增大,从而使系统快速响应(理论上讲是这样,不过产生误差往往是在很短的时间内,这就导致大部分时间e0-e1都是负的,从而减小系统输出)。I是积分系数,同样,我们假设误差为e,积分可以理解为简单的累加。但是只用P会存在稳态误差(随着e的减小,输出也越来越小,例如无人机在上升时,到达目标位置之后,还需要抵抗重力,因此也需要电机持续输出
本文主要是通过简单易懂的表述讲解一下PID(位置式)。PID是一种常用且算法简单的控制器,在现实中的应用很广泛,通过本文,可以让一个小白对PID有一定了解(本文仅是写给小白看的,所以很多地方并不严谨)。
PID(比例 - 积分 - 微分)控制器是一种广泛应用的反馈控制算法,其控制规律可以表示为:
其中,u(t)是控制器的输出,e(t)是设定值与实际输出的偏差,Kp是比例系数,Ki是积分系数,Kd是微分系数。
P
P是比例系数,假设你的误差为e,那么输出则为Kp*e。(这里的Kp包括后文的Ki,Kd都是系数,可以视为常量)但是只用P会存在稳态误差(随着e的减小,输出也越来越小,例如无人机在上升时,到达目标位置之后,还需要抵抗重力,因此也需要电机持续输出,输出为0显然会时无人机高度下降),所以我们就要引入I。
如图,是一个简单的系统,目标为1,仅用P控制
I
I是积分系数,同样,我们假设误差为e,积分可以理解为简单的累加。假设输出为Y,则Y+=Ki*e。在程序运行中将会对误差不断进行累加输出,即便很小的误差也能经过累加有一个相对较大的输出。从而可以消除稳态误差。不过需要注意的是,Y会随着累加而过大,可能使系统不稳定,因此我们在使用I的过程中会给其输出Y一个限制,当Y>imax时Y=imax。但是,随着I的加入系统可能会出现超调现象。
系统同上,这次我们加入了I控制

D
微分系数,设当前的误差是e0,上一次程序运行时的误差是e1,则微分项的输出为Kd*(e0-e1)。直观的来看,当误差突然增大时,此时的e0-e1会很大,D项的输出也相应增大,从而使系统快速响应(理论上讲是这样,不过产生误差往往是在很短的时间内,这就导致大部分时间e0-e1都是负的,从而减小系统输出)。而误差减小时,D项的输出会变成负数,减小系统整体的输出,在系统快到达目标位置时可以稳定系统,有一定预防超调(超出目标值)的作用。
这次我们将D项系数增大,可以看到在刚开始极短的时间内,系统响应变快,但随着误差的变小(D项为负),输出减小,系统到达目标1时的时间要晚于PI控制。

代码实例(分为位置式pid和增量式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;
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;
}
fp32 PID_calc(pid_type_def *pid, fp32 ref, fp32 set)
{
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;
}
////_____.h____////
#define LimitMax(input, max) \
{ \
if (input > max) \
{ \
input = max; \
} \
else if (input < -max) \
{ \
input = -max; \
} \
}
enum PID_MODE
{
PID_POSITION = 0,
PID_DELTA
};
typedef struct
{
uint8_t mode;
fp32 Kp;
fp32 Ki;
fp32 Kd;
fp32 max_out; //最大输出
fp32 max_iout; //最大积分输出
fp32 lvbo;
fp32 set;
fp32 fdb;
fp32 out;
fp32 Pout;
fp32 Iout;
fp32 Dout;
fp32 Dbuf[3]; //微分项 0最新
fp32 error[4]; //误差项 0最新
} pid_type_def;
extern void PID_init(pid_type_def *pid, uint8_t mode, const fp32 PID[3], fp32 max_out, fp32 max_iout);
extern fp32 PID_calc(pid_type_def *pid, fp32 ref, fp32 set);
简单说一下增量式pid,增量式pid根据相邻两个采样时刻的误差来计算控制量的增量Delta u(k) ,是一种递推式算法。确定控制量增量仅与最近 3 次的采样值有关,计算出的控制量是在上一次控制量基础上的增加量(或减少量) 。计算公式为Delta u(k)=K_{p}(e(k)-e(k - 1))+K_{i}e(k)+K_{d}(e(k)-2e(k - 1)+e(k - 2)) 。算式无需累加,计算量小;计算机故障影响范围小;手动 - 自动切换冲击小;能避免积分饱和现象。但存在积分截断效应,有稳态误差,溢出影响大 。增量式pid不会对过去的误差一直累加,所以可以避免积分饱和现象.
本文是在作者学习之后根据自己的理解写出,作者能力有限,有问题还望指正。
更多推荐



所有评论(0)