PID控制器简述(附代码)
PID控制器简述(附代码)
PID控制器是自动化控制领域的重要概念,广泛应用于各种需要精确控制的场景。本文将从P、I、D三个部分详细解释PID控制器的工作原理,并通过示意图帮助理解。最后还附有C语言实现的PID控制器代码,包括初始化和计算函数。
PID调参(1)
本文主要是通过简单易懂的表述讲解一下PID。PID是一种常用且算法简单的控制器,在现实中的应用很广泛,通过本文,可以让一个小白对PID有一定了解(本文仅是写给小白看的,所以很多地方并不严谨)。
P
P是比例系数,假设你的误差为e,那么输出则为Kp*e。但是只用P会存在稳态误差(随着e的减小,输出也越来越小,例如无人机在上升时,到达目标位置之后,还需要抵抗重力,因此也需要电机持续输出,输出为0显然会时无人机高度下降),所以我们就要引入I。
如图,是一个简单的系统,目标为1,仅用P控制
I
I是积分系数,同样,我们假设误差为e,积分可以理解为简单的累加。假设输出为Y,则Y+=Kp*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控制。
代码实例
///___.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);
本文是在作者学习之后根据自己的理解写出,作者能力有限,有问题还望指正。