1. 程式人生 > >增量式PID計算公式4個疑問與理解

增量式PID計算公式4個疑問與理解

PID控制是一個二階線性控制器

    定義:通過調整比例、積分和微分三項引數,使得大多數的工業控制系統獲得良好的閉環控制性能。

    優點

            a. 技術成熟

       b. 易被人們熟悉和掌握

       c. 不需要建立數學模型

       d. 控制效果好

       e. 魯棒性

      通常依據控制器輸出與執行機構的對應關係,將基本數字PID演算法分為位置式PID和增量式PID兩種。

1  位置式PID控制演算法

基本PID控制器的理想算式為

PID控制 - lql990832 - lql990832的部落格               (1)

 

式中

u(t)——控制器(也稱調節器)的輸出;

e(t)——控制器的輸入(常常是設定值與被控量之差,即e(t)=r(t)-c(t));

Kp——控制器的比例放大係數;

Ti ——控制器的積分時間;

Td——控制器的微分時間。

u(k)為第k次取樣時刻控制器的輸出值,可得離散的PID算式

                              

PID控制 - lql990832 - lql990832的部落格       (2)

式中 PID控制 - lql990832 - lql990832的部落格,   PID控制 - lql990832 - lql990832的部落格  。

    由於計算機的輸出u(k)直接控制執行機構(如閥門),u(k)的值與執行機構的位置(如閥門開

度)一一對應,所以通常稱式(2)為位置式PID控制演算法。

    位置式PID控制演算法的缺點:當前取樣時刻的輸出與過去的各個狀態有關,計算時要對e(k)進

行累加,運算量大;而且控制器的輸出u(k)對應的是執行機構的實際位置,如果計算機出現故

障,u(k)的大幅度變化會引起執行機構位置的大幅度變化。

  2  增量式PID控制演算法

增量式PID是指數字控制器的輸出只是控制量的增量Δu(k)。採用增量式演算法時,計算機輸出的控制量Δu(k)對應的是本次執行機構位置的增量,而不是對應執行機構的實際位置,因此要求執行機構必須具有對控制量增量的累積功能,才能完成對被控物件的控制操作。執行機構的累積功能可以採用硬體的方法實現;也可以採用軟體來實現,如利用算式 u(k)=u(k-1)+Δu(k)程式化來完成。

由式(2)可得增量式PID控制算式

PID控制 - lql990832 - lql990832的部落格  (3)

式中 Δe(k)=e(k)-e(k-1)

進一步可以改寫成

PID控制 - lql990832 - lql990832的部落格                                                (4)

式中 PID控制 - lql990832 - lql990832的部落格PID控制 - lql990832 - lql990832的部落格 、PID控制 - lql990832 - lql990832的部落格

一般計算機控制系統的取樣週期T在選定後就不再改變,所以,一旦確定了Kp、Ti、Td,只要使用前後3次測量的偏差值即可由式(3)或式(4)求出控制增量。

增量式演算法優點:①算式中不需要累加。控制增量Δu(k)的確定僅與最近3次的取樣值有關,容易通過加權處理獲得比較好的控制效果;②計算機每次只輸出控制增量,即對應執行機構位置的變化量,故機器發生故障時影響範圍小、不會嚴重影響生產過程;③手動—自動切換時衝擊小。當控制從手動向自動切換時,可以作到無擾動切換。

對於增量式PID計算公式4個疑問與理解

  

一開始見到PID計算公式時總是疑問為什麼是那樣子?為了理解那幾道公式,當時將其未簡化前的公式“活生生”地算了一遍,現在想來,這樣的演算過程固然有助於理解,但假如一開始就帶著對疑問的答案已有一定看法後再進行演算則會理解的更快!

  首先推薦白志剛的《由入門到精通—吃透PID 2.0版》看完一、二章之後,建議你先通過實踐練習然後再回來看接下來的所有章節,這樣你對這本書的掌握會更加牢固、節省時間。

  PID就是對輸入偏差進行比例積分微分運算,運算的疊加結果去控制執行機構。實踐練習中,如何把這一原理轉化為程式?為什麼是用那幾個error進行計算?

  以下是我摘錄的一段PID程式,我曾用其對智慧車的速度進行閉環控制:

  P:Proportional  比例

  I:Integrating 積分

  D:Differentiation 微分

  Pwm_value:輸出Pwm暫空比的值

  Current_error:當前偏差  last_error:上次偏差   prev_error:上上次偏差

  增量式PID計算公式: 

  P=Kp*(current_error﹣last_error);

  D=Kd*(current_error﹣2*last_error﹢prev_error);

  I=Ki*current_error;

  PID_add=Pwm_value+P﹢I﹢D;

這一段話給了我啟發:

首先對其進行分析,

一、為什麼是PID_add=Pwm_value+(P﹢I﹢D)而不是PID_add=P+I+D?

如左圖,有一個人前往目的地A,他用眼睛視覺感測器目測到距離目的地還有100m,即當前與目的地的偏差為100,他向雙腳輸出Δ=100J的能量,跑呀跑,10s之後,他又目測了一次,此時距離為40m,即current_error=40,他與10s前的偏差last_error=10對比,即current_error—last_error=—60,這是個負數,他意識到自己已經比較接近目的地,可以不用跑那麼快,於是輸出Δ=100+(—60)=40J的能量,40J的能量他剛好以4m/s的速度跑呀跑,10s之後,他發現已經到達目的點,此時current_error=0,大腦經過思考得出current_error—last_error=0—40=—40,兩腳獲得的能量Δ=40+(—40)=0,即他已經達到目的地,無需再跑。在剛才的敘述中,可知增量式P+I+D輸出的是一個增量,將該增量與調節量相加後的到的才是最終輸出量,P+I+D反應的是之前的輸出量是在當前的狀態中是該增加還是該減少。

 

二、純比例控制P=Kp*(current_error﹣last_error),怎樣理解﹙current_error﹣last_error ﹚?

  PID中純比例控制就是把被控制量的偏差乘以一個係數作為調節器的輸出,在增量式PID中,反映在程式上的,我們被控制量就是error,而實際上,例如在速度控制中error=目標速度﹣當前速度,所以明確目的:我們通過控制error趨近於0,最終使得當前速度趨近於目標速度。

如右圖,假如考試時有這麼一種題:函式經過時間Δt,由y1變化為y2時,問y增長的比例為多少?你很容易地得出答案:K=﹙y2-y1﹚/Δt;

 

以速度控制為例,若y為error,得右圖,在時間t1到t2的過程中,我們可以得到輸出控制量error變化的趨勢為(current_error—last_error)/Δt。得到偏差的變化趨勢後,乘以Kp使輸出量與error相對變化。這個道理猶如類比電子電路中,聲音訊號經過功放管放大輸出的訊號與輸入訊號相對應變化。

 

三、微分控制:

然而,通常情況下,我們的被控制量並非純比例式地變化,如下圖:

比例表示變化趨勢,微分則表示變化趨勢的變化率,對映到一個影象曲線中即為導數的變化!圖3中若求曲線中x2至x1某點的斜率,當Δt足夠小時,則可近似為(y2—y1)/Δt ,可知x3到x1導數的變化為﹛﹙y3—y2﹚—(y2—y1﹚﹜/Δt =﹙y3—2*y2﹢y1﹚/Δt 。將不同時間的y1、y2、y3對映為prev_error、last_error、current_error;則error變化趨勢的變化為﹛﹙current_error—last_error﹚﹣﹙last_error—prev_error﹚﹜/Δt=﹛﹙current_error—2*last_error﹢prev_error﹚﹜/Δt,可得微分D=Kd*(current_error﹣2*last_error﹢prev_error)。 在系統中加入微分放映系統偏差訊號的變化率,能預知偏差變化的趨勢,具有超前控制作用,提前處理偏差。

 

 

四、積分控制:

積分控制可以消除偏差,體現在公式中較容易理解,當前的偏差差經過係數Ki的放大後對映為輸出控制量,即I=Ki*current_error。P只要前後偏差之差為0,即current_error—last_current=0,則不進行調節,D只要前後偏差變化率為0,即(current_error﹣2*last_error﹢prev_error)=0,則不進行調節。而對於積分只要偏差存在,調節就始終進行,因此積分可以消除誤差度,但在在某些情況下,一定範圍內的誤差是允許的,而如果此時積分調節始終存在,可能會導致系統穩定性下降,如右圖,此時可通過弱化積分系數Ki使系統穩定。

 

以下為arduino 程式

我的pid 調節思路:

#include <PID_v1.h>
#include "constants.h"
#include "output_px4.h"
#include "deal_src04.h"

#define yellowboxleft 282

int thisleft;
int thisfront;

int current_height;
int current_disFront;
int current_disLeft;
int current_disRight;
int current_disLeftlast;


int current_disback;
long long ic_left = 0;
long long ic_front = 0;
int dc_left[2] = {0};
int dc_front[2] = {0};

long step_1start;
long step_2start;
long step_4start;
long step_6start;
long step_3start;


int sign = 0;
int signb = 0;
char signA;//0 for the blue box, 1 for the yellow box.


void getDistances() {
  int height = getHeight();
  if (height != 0) {
    current_height = height;
  }
  //print_channel_input();
  //delay(50);
  current_disFront = getDisFront();
  // delay(50);
  current_disRight = getDisRight();
  current_disLeft = getDisLeft();
  Serial3.print("height:   ");
  Serial3.print(current_height);
  //  Serial3.print("   front:   ");
  //  Serial3.print(current_disFront);
  //  Serial3.print("   left:   ");
  //  Serial3.print(current_disLeft);
  Serial3.print("   mission_step:   ");
  Serial3.print(mission_step);
  Serial3.println();

  //Serial3.println(current_height);
  //Serial.println(current_height);
}

void initSerial()
{
  Serial3.begin(9600);
  Serial2.begin(9600);
  Serial.begin(9600);
}

void setup() {
  // put your setup code here, to run once:
  initSerial();
  init_servo();
  init_input();
  init_timer();

  Throw_Servo.writeMicroseconds(1200);//舵機初始化  上球
  delay(100);
  Throw_Servo.writeMicroseconds(1600);
}

void loop() {
  // put your main code here, to run repeatedly:
  //print_channel_input();
  getDistances();
  print_channel_input();
  //Throw_Servo.writeMicroseconds(1680);
  //delay(2000);
  //Throw_Servo.writeMicroseconds(1550);
  //delay(4000);
  //Throw_Servo.writeMicroseconds(1000);
  //delay(2000);
  //Throw_Servo.writeMicroseconds(2000);
  //delay(2000);
  //Throw_Servo.writeMicroseconds(-1500);
  //delay(2000);
  //Throw_Servo.writeMicroseconds(1750);
  switch (mission_step)
  {
      int left_speed;
      int front_speed;
    case 0://任務未開始
      setOutput(Roll_Middle, Pitch_Middle, Thro_Middle, Yaw_Middle);
      thisleft = current_disLeft;
      //thisfront=current_disFront;

      break;
    case 1://第一步,起飛懸停

      Throw_Servo.writeMicroseconds(1550);

      dc_left[1] = current_disLeft - thisleft - dc_left[0];
      //dc_front[1] = current_disFront - thisfront - dc_front[0];

      if (sign >= 1)
      {
        left_speed = (int)((current_disLeft - thisleft) * 3  + dc_left[1] * 0.1);
        front_speed = (int)((current_disFront - thisfront) * 3 + dc_front[1] * 0.1);


        if (left_speed > 50) {
          left_speed = 50;
        }
        if (left_speed < -50) {
          left_speed = -50;
        }
        if (front_speed > 50) {
          front_speed = 50;
        }
        if (front_speed < -50) {
          front_speed = -50;
        }

        setOutput(Roll_Middle , Pitch_Middle, Thro_Middle - 150, Yaw_Middle);
        setOutput(Roll_Middle, Pitch_Middle, Thro_Middle + (Default_Height - current_height) * 0.4, Yaw_Middle);
        if ((current_height >= Default_Height - 15) && (current_height <= Default_Height + 100))
        {
          setOutput(Roll_Middle , Pitch_Middle, Thro_Middle, Yaw_Middle);
          mission_step = 2;
          step_2start = millis();
          sign = 0;
          signb = 0;
        }
      }
      else
      {
        if ((current_height >= 1200))
        {
          if (signb == 0)
          {
            step_1start =  millis();
            signb = signb + 10;
          }
          setOutput(Roll_Middle, Pitch_Middle, Thro_Middle, Yaw_Middle);
          if (millis() >= (step_1start + 12400))
          {
            sign = sign + 10;
          }
        }
        else
        {
          setOutput(Roll_Middle , Pitch_Middle, Thro_Middle + (1500 - current_height) * 0.15, Yaw_Middle);
        }
      }
      break;
    case 2://第二步,懸停1s
      setOutput(Roll_Middle, Pitch_Middle, Thro_Middle + (Default_Height - current_height) * 0.4, Yaw_Middle);
      if (millis() >= (step_2start + 6200))
      {
        mission_step = 3;
        step_3start = millis();
      }
      break;
    case 3://第三步,到達目的地
      ic_left = ic_left + current_disLeft;
      ic_front = ic_front + current_disFront;
      dc_left[1] = current_disLeft - Default_Left - dc_left[0];
      dc_front[1] = current_disFront - Default_Front - dc_front[0];

      if (current_disLeft - Default_Left >= 30 || current_disLeft - Default_Left <= 30)
      {
        left_speed = (int)((current_disLeft - Default_Left) * 2 + (0.001 * (ic_left / (millis() - step_3start))) + dc_left[1] * 0.02);
        front_speed = (int)((current_disFront - Default_Front) * 2 + (0.001 * (ic_front / (millis() - step_3start))) + dc_front[1] * 0.02);
      }
      else
      {
        left_speed = (int)((current_disLeft - Default_Left) * 1  + dc_left[1] * 0.02);
        front_speed = (int)((current_disFront - Default_Front) * 1 + dc_front[1] * 0.02);
      }


      if (left_speed > 130) {
        left_speed = 130;
      }
      if (left_speed < -130) {
        left_speed = -130;
      }
      if (front_speed > 130) {
        front_speed = 130;
      }
      if (front_speed < -130) {
        front_speed = -130;
      }

      setOutput(Roll_Middle - left_speed , Pitch_Middle - front_speed, Thro_Middle, Yaw_Middle);
      if (current_disLeft <= (Default_Left + 25))
      {
        if (current_disFront <= (Default_Front + 25))
        {
          sign = sign + 1;
          if (sign >= 4)
          {
            mission_step = 6;
            step_6start = millis();
            sign = 0;
            dc_left[0] = 0;
            dc_front[0] = 0;
            ic_left = 0;
            ic_front = 0;
          }

        }
      }

      if (current_disLeft - Default_Left>=30||current_disLeft - Default_Left<=-30)
      { 
      dc_left[1] = current_disLeft - yellowboxleft - dc_left[0];
        dc_front[1] = current_disFront - Default_Front - dc_front[0];
        left_speed = (int)((current_disLeft - yellowboxleft) * 2  + dc_left[1] * 0.02);
        front_speed = (int)((current_disFront - Default_Front) * 2 + dc_front[1] * 0.02);

        dc_left[1] = current_disLeft - yellowboxleft - dc_left[0];
        dc_front[1] = current_disFront - Default_Front - dc_front[0];
        if ( current_disLeftlast - current_disLeft  >= 40)
        {
          current_disLeft = current_disLeftlast;
        }

      }

        ic_left = ic_left + current_disLeft;
        ic_front = ic_front + current_disFront;

        if(current_disLeft - Default_Left>=30||current_disLeft - Default_Left<=-30)
        {
                  left_speed = (int)((current_disLeft - yellowboxleft) * 3  +(0.001*(ic_left/(millis()- step_3start)))+ dc_left[1]*0.02);
                  front_speed = (int)((current_disFront - Default_Front) * 4 + (0.001*(ic_front/(millis()- step_3start)))+ dc_front[1]*0.02);
         }
          else
          {
           left_speed = (int)((current_disLeft - yellowboxleft) * 1  +(0.001*(ic_left/(millis()- step_3start)))+ dc_left[1]*0.02);
           front_speed = (int)((current_disFront - Default_Front) * 1 + (0.001*(ic_front/(millis()- step_3start)))+ dc_front[1]*0.02);
           }


        if (left_speed > 130) {
          left_speed = 130;
        }
        if (left_speed < -130) {
          left_speed = -130;
        }
        if (front_speed > 130) {
          front_speed = 130;
        }
        if (front_speed < -130) {
          front_speed = -130;
        }

        setOutput(Roll_Middle - left_speed , Pitch_Middle - front_speed, Thro_Middle + (Default_Height - current_height) * 0.4, Yaw_Middle);
        current_disLeftlast = current_disLeft;
        if ((current_disLeft <= (yellowboxleft + 20)) && (current_disLeft >= (yellowboxleft - 20)))
        {
          if ((current_disFront <= (Default_Front + 15)) && (current_disFront <= (Default_Front - 15)))
          {
            //sign = sign + 1;

            mission_step = 6;
            step_6start = millis();
            sign = 0;
            dc_left[0] = 0;
            dc_front[0] = 0;
            ic_left = 0;
            ic_front = 0;


          }
        }
        
        dc_left[0] = dc_left[1];
        dc_front[0] = dc_front[1];
        break;


      case 6://第六步,懸停1s,扔球
        setOutput(Roll_Middle, Pitch_Middle, Thro_Middle, Yaw_Middle);
        Throw_Servo.writeMicroseconds(2000);
        if (millis() >= (step_6start + 5200))
        {
          mission_step = 7;
          dc_left[0] = 0;
          dc_front[0] = 0;
          dc_left[1] = 0;
          dc_front[1] = 0;

        }
        break;
      case 7://第七步,返回

        dc_left[1] = current_disLeft - Start_Left - dc_left[0];
        dc_front[1] = current_disFront - Start_Front - dc_front[0];

        left_speed = (int)((current_disLeft - Start_Left) * 3  + dc_left[1] * 0.1);
        front_speed = (int)((current_disFront - Start_Front) * 3 + dc_front[1] * 0.1);

        if (left_speed > 100) {
          left_speed = 100;
        }
        if (left_speed < -100) {
          left_speed = -100;
        }
        if (front_speed > 100) {
          front_speed = 100;
        }
        if (front_speed < -100) {
          front_speed = -100;
        }
        setOutput(Roll_Middle - left_speed, Pitch_Middle - front_speed, Thro_Middle, Yaw_Middle);
        if (current_disFront >= (Start_Front - 20))
        {
          if (current_disLeft >= (Start_Left - 20)||(current_disback >= (Start_back - 20)))
          {
            sign = sign + 1;
            if (sign >= 2)
            {
              mission_step = 8;
              sign = 0;
            }
          }
        }
        break;
      case 8://第八步,降落


        dc_left[1] = current_disLeft - Start_Left - dc_left[0];
        dc_front[1] = current_disFront - Start_Front - dc_front[0];


        left_speed = (int)((current_disLeft - Start_Left) * 3  + dc_left[1] * 0.1);
        front_speed = (int)((current_disFront - Start_Front) * 3 + dc_front[1] * 0.1);


        if (left_speed > 50) {
          left_speed = 50;
        }
        if (left_speed < -50) {
          left_speed = -50;
        }
        if (front_speed > 50) {
          front_speed = 50;
        }
        if (front_speed < -50) {
          front_speed = -50;
        }


        setOutput(Roll_Middle - left_speed, Pitch_Middle, Thro_Middle - 150, Yaw_Middle);

        Throw_Servo.writeMicroseconds(1680);
        break;
      default:
        setOutput(Roll_Middle, Pitch_Middle, Thro_Middle, Yaw_Middle);
        break;
      }
  }