PID調節C語言解析

前言

老師佈置了個任務,用編程實現PID調節,鑑於我們專業都學過C語言和VB,於是我就想拿Kotlin練練手.
上網搜索一番別人怎麼用C實現的,找到個比較詳細的,但是展示了太多數據,我這裏整理一下基礎知識.
Kotlin的實現在這裏

簡要解析

參考資料,來自這裏
通過誤差信號控制被控量,而控制器本身就是比例、積分、微分三個環節的加和。這裏我們規定(在t時刻):
- 輸入量爲rin(t);
- 輸出量爲rout(t);
- 偏差量爲err(t)=rin(t)-rout(t);
- 如果偏差爲0,則比例環節不起作用,只有存在偏差時,比例環節才起作用。
- 而微分信號則反應了偏差信號的變化規律,或者說是變化趨勢,根據偏差信號的變化趨勢來進行超前調節,從而增加了系統的快速性。。

位置型PID的C語言實現

第一步:定義PID變量結構體,代碼如下:

struct _pid{
    float SetSpeed;            //定義設定值
    float ActualSpeed;        //定義實際值
    float err;                //定義偏差值
    float err_last;            //定義上一個偏差值
    float Kp,Ki,Kd;            //定義比例、積分、微分系數
    float voltage;          //定義電壓值(控制執行器的變量)
    float integral;            //定義積分值
}pid;

控制算法中所需要用到的參數在一個結構體中統一定義,方便後面的使用。

第二部:初始化變量,代碼如下:

void PID_init(){
    printf("PID_init begin \n");
    pid.SetSpeed=0.0;
    pid.ActualSpeed=0.0;
    pid.err=0.0;
    pid.err_last=0.0;
    pid.voltage=0.0;
    pid.integral=0.0;
    pid.Kp=0.2;
    pid.Ki=0.015;
    pid.Kd=0.2;
    printf("PID_init end \n");
}

統一初始化變量,尤其是Kp,Ki,Kd三個參數,調試過程當中,對於要求的控制效果,可以通過調節這三個量直接進行調節。

第三步:編寫控制算法,代碼如下:

float PID_realize(float speed){
    pid.SetSpeed=speed;
    pid.err=pid.SetSpeed-pid.ActualSpeed;
    pid.integral+=pid.err;
    pid.voltage=pid.Kp*pid.err+pid.Ki*pid.integral+pid.Kd*(pid.err-pid.err_last);
    pid.err_last=pid.err;
    pid.ActualSpeed=pid.voltage*1.0;
    return pid.ActualSpeed;
}

注意:這裏用了最基本的算法實現形式,沒有考慮死區問題,沒有設定上下限,只是對公式的一種直接的實現,後面的介紹當中還會逐漸的對此改進。

到此爲止,PID的基本實現部分就初步完成了。下面是測試代碼:

int main(){
    printf("System begin \n");
    PID_init();
    int count=0;
    while(count<1000)
    {
        float speed=PID_realize(200.0);
        printf("%f\n",speed);
        count++;
    }
    return 0;
}

增量型PID的C語言實現

這就是離散化PID的增量式表示方式,由公式可以看出,增量式的表達結果和最近三次的偏差有關,這樣就大大提高了系統的穩定性。需要注意的是最終的輸出結果應該爲

   u(K)+增量調節值;
include<stdio.h>
struct _pid{
    float SetSpeed;           //定義設定值
    float ActualSpeed;        //定義實際值
    float err;                //定義偏差值
    float err_next;           //定義上一個偏差值
    float err_last;           //定義最上前的偏差值
    float Kp,Ki,Kd;           //定義比例、積分、微分系數
}pid;

void PID_init(){
    pid.SetSpeed=0.0;
    pid.ActualSpeed=0.0;
    pid.err=0.0;
    pid.err_last=0.0;
    pid.err_next=0.0;
    pid.Kp=0.2;
    pid.Ki=0.015;
    pid.Kd=0.2;
}

float PID_realize(float speed){
    pid.SetSpeed=speed;
    pid.err=pid.SetSpeed-pid.ActualSpeed;
    float incrementSpeed=pid.Kp*(pid.err-pid.err_next)+pid.Ki*pid.err+pid.Kd*(pid.err-2*pid.err_next+pid.err_last);
    pid.ActualSpeed+=incrementSpeed;
    pid.err_last=pid.err_next;
    pid.err_next=pid.err;
    return pid.ActualSpeed;
}

int main(){
    PID_init();
    int count=0;
    while(count<1000)
    {
        float speed=PID_realize(200.0);
        printf("%f\n",speed);
        count++;
    }
    return 0;
}

總結:系統的穩定性大大提高

積分分離的PID控制算法C語言實現

在普通PID控制中,引入積分環節的目的,主要是爲了消除靜差,提高控制精度。但是在啓動、結束或大幅度增減設定時,短時間內系統輸出有很大的偏差,會造成PID運算的積分積累,導致控制量超過執行機構可能允許的最大動作範圍對應極限控制量,從而引起較大的超調,甚至是震盪,這是絕對不允許的。

爲了克服這一問題,引入了積分分離的概念,其基本思路是 當被控量與設定值偏差較大時,取消積分作用; 當被控量接近給定值時,引入積分控制,以消除靜差,提高精度。其具體實現代碼如下:

pid.Kp=0.2;
pid.Ki=0.04;
pid.Kd=0.2;  //初始化過程
if(abs(pid.err)>200)
{
    index=0;
    }else{
    index=1;
    pid.integral+=pid.err;
}
    pid.voltage=pid.Kp*pid.err+index*pid.Ki*pid.integral+pid.Kd*(pid.err-pid.err_last);    //算法具體實現過程

總結:系統的快速性得到了提高。

抗積分飽和的PID控制算法C語言實現

所謂的積分飽和現象是指如果系統存在一個方向的偏差,PID控制器的輸出由於積分作用的不斷累加而加大,從而導致執行機構達到極限位置,若控制器輸出U(k)繼續增大,執行器開度不可能再增大,此時計算機輸出控制量超出了正常運行範圍而進入飽和區。一旦系統出現反向偏差,u(k)逐漸從飽和區退出。進入飽和區越深則退出飽和區時間越長。在這段時間裏,執行機構仍然停留在極限位置而不隨偏差反向而立即做出相應的改變,這時系統就像失控一樣,造成控制性能惡化,這種現象稱爲積分飽和現象或積分失控現象。
防止積分飽和的方法之一就是抗積分飽和法,該方法的思路是在計算u(k)時,首先判斷上一時刻的控制量u(k-1)是否已經超出了極限範圍: 如果u(k-1)>umax,則只累加負偏差; 如果u(k-1)

struct _pid{
    float SetSpeed;           //定義設定值
    float ActualSpeed;        //定義實際值
    float err;                //定義偏差值
    float err_last;           //定義上一個偏差值
    float Kp,Ki,Kd;           //定義比例、積分、微分系數
    float voltage;            //定義電壓值(控制執行器的變量)
    float integral;           //定義積分值
    float umax;
    float umin;
}pid;

void PID_init(){
    printf("PID_init begin \n");
    pid.SetSpeed=0.0;
    pid.ActualSpeed=0.0;
    pid.err=0.0;
    pid.err_last=0.0;
    pid.voltage=0.0;
    pid.integral=0.0;
    pid.Kp=0.2;
    pid.Ki=0.1;       //注意,和上幾次相比,這裏加大了積分環節的值
    pid.Kd=0.2;
    pid.umax=400;
    pid.umin=-200;
    printf("PID_init end \n");
}
float PID_realize(float speed){
    int index;
    pid.SetSpeed=speed;
    pid.err=pid.SetSpeed-pid.ActualSpeed;

   if(pid.ActualSpeed>pid.umax)  //灰色底色表示抗積分飽和的實現
    {

       if(abs(pid.err)>200)      //藍色標註爲積分分離過程
        {
            index=0;
        }else{
            index=1;
            if(pid.err<0)
            {
              pid.integral+=pid.err;
            }
        }
    }else if(pid.ActualSpeed<pid.umin){
        if(abs(pid.err)>200)      //積分分離過程
        {
            index=0;
        }else{
            index=1;
            if(pid.err>0)
            {
                pid.integral+=pid.err;
            }
        }
    }else{
        if(abs(pid.err)>200)                    //積分分離過程
        {
            index=0;
        }else{
            index=1;
            pid.integral+=pid.err;
        }
    }

    pid.voltage=pid.Kp*pid.err+index*pid.Ki*pid.integral+pid.Kd*(pid.err-pid.err_last);

    pid.err_last=pid.err;
    pid.ActualSpeed=pid.voltage*1.0;
    return pid.ActualSpeed;
}

總結:系統的穩定時間相對前幾次來講縮短了不少。

梯形積分的PID控制算法C語言實現

先看一下梯形算法的積分環節公式
作爲PID控制律的積分項,其作用是消除餘差,爲了儘量減小余差,應提高積分項運算精度,爲此可以將矩形積分改爲梯形積分,具體實現的語句爲:

pid.voltage=pid.Kp*pid.err+index*pid.Ki*pid.integral/2+pid.Kd*(pid.err-pid.err_last);  //梯形積分

總結:精度進一步提高。

變積分的PID控制算法C語言實現

變積分PID可以看成是積分分離的PID算法的更一般的形式。在普通的PID控制算法中,由於積分系數ki是常數,所以在整個控制過程中,積分增量是不變的。但是,系統對於積分項的要求是,系統偏差大時,積分作用應該減弱甚至是全無,而在偏差小時,則應該加強。積分系數取大了會產生超調,甚至積分飽和,取小了又不能短時間內消除靜差。因此,根據系統的偏差大小改變積分速度是有必要的。
變積分PID的基本思想是設法改變積分項的累加速度,使其與偏差大小相對應:偏差越大,積分越慢; 偏差越小,積分越快。

這裏給積分系數前加上一個比例值index:

當abs(err)<180時,index=1;

當180<abs(err)<200時,index=(200-abs(err))/20;

當abs(err)>200時,index=0;

最終的比例環節的比例係數值爲ki*index;
具體PID實現代碼如下:

pid.Kp=0.4;
pid.Ki=0.2;    //增加了積分系數
pid.Kd=0.2;

float PID_realize(float speed){
    float index;
    pid.SetSpeed=speed;
    pid.err=pid.SetSpeed-pid.ActualSpeed;

    if(abs(pid.err)>200)           //變積分過程
    {
        index=0.0;
    }else if(abs(pid.err)<180){
        index=1.0;
        pid.integral+=pid.err;
    }else{
        index=(200-abs(pid.err))/20;
        pid.integral+=pid.err;
    }
    pid.voltage=pid.Kp*pid.err+index*pid.Ki*pid.integral+pid.Kd*(pid.err-pid.err_last);

    pid.err_last=pid.err;
    pid.ActualSpeed=pid.voltage*1.0;
    return pid.ActualSpeed;
}

總結:系統的穩定速度非常快
我在另一篇博客https://blog.csdn.net/qq_37258787/article/details/79603352使用Kotlin語言實現上述功能,並進行了簡單的數據可視化展示.

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章