问小白 wenxiaobai
资讯
历史
科技
环境与自然
成长
游戏
财经
文学与艺术
美食
健康
家居
文化
情感
汽车
三农
军事
旅行
运动
教育
生活
星座命理

PID控制器简述(附代码)

创作时间:
作者:
@小白创作中心

PID控制器简述(附代码)

引用
CSDN
1.
https://m.blog.csdn.net/jiangrenkuo/article/details/145849916

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);

本文是在作者学习之后根据自己的理解写出,作者能力有限,有问题还望指正。

© 2023 北京元石科技有限公司 ◎ 京公网安备 11010802042949号