PID 算法

0、前言

我这个文章慢慢更新,我现在也是学习阶段,后面会慢慢更新

1、 PID的概述

在我们学习PID之前在程序经常会写

if(温度<温度阈值)
{
    加热
}
else
{
    不加热
}

他就是直接判断当前的测量值是否跟我的阈值相同,如果相同做什么,不相同就做什么。这样虽然可以做出判断保证这个事件有被处理但是不精确,就比如这个加热模块,我要保证这个温度保持在这个阈值上,如果我当前温度大于阈值我要停止加热,但是由于我保险丝或者加热器还有温度,这个温度就会影响我现在这个温度,导致我们测量的温度会比我们想要的温度高那么一点点,反之亦然。

在我们PID算法中,我们就会尽量减少这种情况,通过控制器减少这种误差,让他保持在我们想要的数据附件。

PID:Proportional(比例)、Integral(积分)、Differential(微分)的缩写。

我们下面说的都是对电机测速

1、P 比例控制

比例控制是一种最简单的控制方式。其控制器的输出与输入误差信号成比例关系。当仅有比例控制时系统输出存在稳态误差。比例项输出:

file

这个公式的意思是Pout是输出值、Kp是系数我们规定的, e(t)是误差值

我们规定一个我们的期望值(target),还有一个我们单片机测量的值(measure)

e(t) = target - measure

比例控制系统快速响应

如果只用P算法 输出的Pout直接作用给PWM,所以因为线性关系Kp直接决定这个调节的速度。

file

说明

  • Kp越大说明 他没有达到目标他就越容易接近目标、但是相反如果在目标附近他就会产生很大的增幅。
  • 如果这个系统存在外力,就是反作用于系统的元素,会导致系统存在静态误差(就是我的增量被我的反作用于系统的东西抵消了,如:我给了系统5N的力让他靠近目标值,但是这个系统还存在5N的摩擦力刚好抵消)

所以,单独的比例控制,在很多时候并不能满足要求。

2、I 积分控制

刚刚说P算法存在静态误差,I算法就是解决这个静态误差的,积分就是把每一次的误差值e(t)加起来,然后慢慢的累加起来,抵消静态误差

file

Ki 是自己设定的值

积分控制系统的准确性,消除累积的误差,输出到达目标值

积分项是误差对时间的积分,随着时间的增加,积分项会增大。

这样,即便误差很小,积分项也会随着时间的增加而加大,它推动控制器的输出增大使稳态误差进一步减小,直到等于零。

因此,比例积分(PI)控制器,可以使系统在进入稳态后无稳态误差。

不同积分增益 Ki 下, 受控变量的阶跃响应(Kp, Kd 维持稳定值)

file

相对于P算法来说 I 算法也是对输出起到促进作用,但是作用的强度低于P算法。

3、D 微分控制

D 算法就是相对于P和I算法的,作用是减少P和I的震荡效果

file

这次误差-上次误差

在微分控制中,控制器的输出与输入误差信号的微分成正比关系。微分调节就是偏差值的变化率。使用微分调节能够实现系统的超前控制。如果输入偏差值线性变化,则在调节器输出侧叠加一个恒定的调节量。大部分控制系统不需要调节微分时间。因为只有时间滞后的系统才需要附加这个参数。

也就是说,他是调节我们变化的快慢的,准确说是抑制,但P算法和I算法快速调节的时候他会减慢他们调节的程度。也就是减少震荡

file

4、pid算法

将P、I、D三个算法加起来就是我们的PID算法

file

file

2、位置式 PID 算法

位置式PID是当前系统的实际位置,与你想要达到的预期位置的偏差,进行PID控制

file

特点:

  • 所以每次输出均与过去状态有关,计算时要对ek进行累加,工作量大
  • 如果误差值一直是正的或者负的,位置式PID在积分项会一直累积,所以在使用时,必须对积分项进行限幅,同时也要对输出进行限幅。当偏差开始反向变化,位置式PID在积分项需要一段时间才能从最大值减下来,造成输出的滞后。(可以使用积分分离的思想,就是在保持稳定之前用pd算法,在保持稳定之后加入I算法

pid.c

#include "pid.h"
#include "main.h"

// pid结构体的初始化
void PIDStructInit(_PID *pid,float target,float p,float i,float d)
{
    pid->p = p;
    pid->i = i;
    pid->d = d;
    pid->target = target;
    pid->err_curr = 0;
    pid->err_last = 0;
    pid->err_sum = 0;
}

// pid 位置式计算
float PID_Postion(_PID *pid,float current)
{
    volatile float Pout;   // 计算的输出值
    pid->err_curr = pid->target - current;   // 计算当前误差
    pid->err_sum += pid->err_curr;   //积分
    Pout = (pid->p * pid->err_curr) + (pid->i * pid->err_sum) + (pid->d * (pid->err_curr - pid->err_last));   // 计算pid的值
    printf("P = %.3f   I = %.3f   D = %.3f\r\n",(pid->p * pid->err_curr) , (pid->i * pid->err_sum) , (pid->d * (pid->err_curr - pid->err_last)));
    pid->err_last = pid->err_curr;   // 记录上一次的误差为当前这一次
    return Pout;
}

pid.h

#ifndef _PID_H
#define _PID_H
typedef struct _PID
{
    volatile float p;
    volatile float i;
    volatile float d;
    float err_curr;   // 当前误差
    float err_last;  // 上次误差
    float err_sum;   // 误差和  积分
    float target;    // 目标值
}_PID;
void PIDStructInit(_PID *pid,float target,float p,float i,float d);  // 结构体初始化
float PID_Postion(_PID *pid,float current);   // 计算位置pid的值
#endif

3、增量式 PID 算法

增量式 PID 是对近几次的数据进行对比,并不像位置式 PID 对之前所有的误差值进行积分,这样就避免了位置式PID的缺陷,但是在实际开发中位置式和增量式要根据但是环境进行选择

file

特点

  • ▲u(k)对应的是近几次位置误差的增量,而不是对应与实际位置的偏差,没有误差累加
  • 积分是不需要累加前面所有的误差值的,增量Δu(k)的确定仅与最近3次的采样值有关,所以在系统发生问题时,增量式不会严重影响系统的工作
typedef struct _PID
{
    volatile float p;
    volatile float i;
    volatile float d;
    float err_curr;   // 当前误差
    float err_last;  // 上次误差
    float err_sum;   // 误差和  积分
    float err_last_last;  // 上上次的误差
    float target;    // 目标值
    float Out;  // 增量 输出的值
}_PID;

// pid结构体的初始化
void PIDStructInit(_PID *pid,float target,float p,float i,float d)
{
    pid->p = p;
    pid->i = i;
    pid->d = d;
    pid->target = target;
    pid->err_curr = 0;
    pid->err_last = 0;
    pid->err_last_last = 0;
    pid->err_sum = 0;
    pid->Out  = 0;
}

// 增量式PID 计算
float PID_Incremental(_PID *pid,float current)
{
    volatile float Pout;   // 计算的输出值
    pid->err_curr = pid->target - current;   // 计算当前误差
    Pout = (pid->p * (pid->err_curr - pid->err_last)) + (pid->i * pid->err_last) + (pid->d * ((pid->err_curr - 2* pid->err_last + pid->err_last_last)));   // 计算pid的值
    printf("P = %.3f   I = %.3f   D = %.3f\r\n",(pid->p * (pid->err_curr - pid->err_last)) , (pid->i * pid->err_last) , (pid->d * ((pid->err_curr - 2* pid->err_last + pid->err_last_last))));
    pid->err_last_last = pid->err_last;   // 上上次更新为上次
    pid->err_last = pid->err_curr;     // 将上次的值更新为这次
    pid->Out += Pout;
    return pid->Out;
}

4、什么时候用增量式、什么时候用位置式

我的理解就是

  • 如果你要的是速度这种线性变化的可以用增量式。
  • 如果你要的是那种向我要知道那种位置我当前的位置信息,随机变化很大的可以用位置式

5、 我现在搞好的 pid 驱动(后面还会完善)

// pid.c
#include "pid.h"
#include "main.h"

// pid结构体的初始化
void PIDStructInit(_PID *pid,float target,float p,float i,float d)
{
    pid->p = p;
    pid->i = i;
    pid->d = d;
    pid->target = target;
    pid->err_curr = 0;
    pid->err_last = 0;
    pid->err_last_last = 0;
    pid->err_sum = 0;
    pid->Out  = 0;
}

// pid 位置式计算
float PID_Postion(_PID *pid,float current)
{
    volatile float Pout;   // 计算的输出值
    pid->err_curr = pid->target - current;   // 计算当前误差
    pid->err_sum += pid->err_curr;   //积分
    Pout = (pid->p * pid->err_curr) + (pid->i * pid->err_sum) + (pid->d * (pid->err_curr - pid->err_last));   // 计算pid的值
    printf("P = %.3f   I = %.3f   D = %.3f\r\n",(pid->p * pid->err_curr) , (pid->i * pid->err_sum) , (pid->d * (pid->err_curr - pid->err_last)));
    pid->err_last = pid->err_curr;   // 记录上一次的误差为当前这一次

    return Pout;
}

// 增量式PID 计算
float PID_Incremental(_PID *pid,float current)
{
    volatile float Pout;   // 计算的输出值
    pid->err_curr = pid->target - current;   // 计算当前误差
    Pout = (pid->p * (pid->err_curr - pid->err_last)) + (pid->i * pid->err_last) + (pid->d * ((pid->err_curr - 2* pid->err_last + pid->err_last_last)));   // 计算pid的值
    printf("P = %.3f   I = %.3f   D = %.3f\r\n",(pid->p * (pid->err_curr - pid->err_last)) , (pid->i * pid->err_last) , (pid->d * ((pid->err_curr - 2* pid->err_last + pid->err_last_last))));
    pid->err_last_last = pid->err_last;   // 上上次更新为上次
    pid->err_last = pid->err_curr;     // 将上次的值更新为这次
    pid->Out += Pout;
    return pid->Out;
}
// pid.h

#ifndef _PID_H
#define _PID_H

typedef struct _PID
{
    volatile float p;
    volatile float i;
    volatile float d;
    float err_curr;   // 当前误差
    float err_last;  // 上次误差
    float err_sum;   // 误差和  积分
    float err_last_last;  // 上上次的误差
    float target;    // 目标值
    float Out;  // 增量 输出的值
}_PID;

void PIDStructInit(_PID *pid,float target,float p,float i,float d);  // 结构体初始化

float PID_Postion(_PID *pid,float current);   // 计算位置pid的值

float PID_Incremental(_PID *pid,float current);    // 增量式PID 计算
#endif
博客内容均系原创,未经允许严禁转载!
您可以通过 RSS 订阅本站文章更新,订阅地址:https://blognas.hwb0307.com/feed/什么是 RSS ?

评论

  1. jq
    Android Chrome
    1 年前
    2023-12-06 20:11:24

    大哥牛逼。

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇