在工業自動化和控制中,PID控制器已經成為最可靠的控制算法之一,可以實現穩定任何系統的輸出響應。PID 代表比例積分微分。這三種類型的控制機制組合在一起,會產生一個誤差信號,這個誤差信號被用作反饋來控制最終應用程序。PID 控制器可以在廣泛的工業和商業應用中找到,例如用于調節壓力、線性運動和許多其他變量。PID溫度控制器是您可以在 Internet 上找到的最常見的應用程序。如果沒有 PID 控制器,手動完成這項工作可能是一個乏味的過程。在這個先進的數字電子設備和微控制器時代,在任何系統中設計和實施 PID 控制器變得更加容易。
什么是 PID 控制器,它是如何工作的?
正如我們在介紹部分告訴您的, PID是比例、積分和微分的首字母縮寫詞。但這甚至意味著什么,有沒有更簡單的方法來理解它?就在這里。為此,讓我們以我們在之前的一個項目中使用 Arduino 的 DIY 智能吸塵機器人為例 。對我來說,這是一個非常酷的項目,在電路和控制機制方面非常簡單。但它的主要缺點是它沒有任何基于 PID 的控制機制。現在,假設機器人正在清潔自己并且靠近樓梯,它有一個接近傳感器在檢測到這種情況并切斷電機電源的機器人下方,但由于慣性,機器人不會立即停止。如果發生這種情況,機器人很可能會從樓梯上絆倒。現在,假設你有一輛機器人汽車,你想把它停在某個位置,如果沒有 PID,這可能會非常困難,因為如果你只是切斷電源,汽車絕對會因為它的動量而錯過目標。
現在我們知道了這個概念,我們可以繼續前進并理解一些高級部分。如果您在線搜索 PID 控制器,您將得到的第一個結果來自PID 控制器 - 維基百科,在這篇文章中,您會找到一個框圖和一個方程。但是這個等式甚至意味著什么,我們如何在我們的微控制器中實現它?好問題,現在繼續,你會明白如何,
該控制器以錯誤的處理方式命名,然后被求和,然后發送到工廠/過程中。讓我解釋!在框圖中,您可以看到在比例路徑中,誤差乘以常數Kp。在積分路徑中,誤差乘以常數Ki然后積分,在導數路徑中,誤差乘以Kd,然后微分。之后,將三個值相加以產生輸出。現在在控制器中,Kp、Kd 和 Ki 參數稱為增益。并且它們被調整或調整以滿足一組特定的要求,并且通過更改這些值,您可以調整您的系統對這些不同參數(P、I 或 D 參數)中的每一個的敏感程度。讓我通過單獨檢查每個參數來解釋它。
P 控制器:
假設系統中的錯誤隨著時間的推移而變化,正如您在紅線中觀察到的那樣。在比例控制器中,輸出是由增益 Kp 定義的誤差。可以看到,當誤差很大時,輸出會產生很大的輸出,當誤差為零時,輸出誤差為零,當誤差為負時,輸出為負
I-控制器:
在積分控制器中,隨著誤差值隨時間變化,積分將開始對誤差開始求和,并將其與常數 Ki 相乘。在這種類型的控制器中,很容易看出積分結果是曲線下方的區域,其中藍色區域為正區域,黃色區域為負區域。在復雜系統中,積分控制器用于消除控制系統中的恒定誤差。不管常數誤差有多小,最終,誤差的總和將足以調整控制器的輸出。在上圖中,錯誤用綠線表示。
D-控制器:
在微分控制器中,影響輸出信號的是誤差的變化率。當誤差變化相對緩慢時,我們可以使用正弦波的起始位置作為示例。如上圖所示(由綠線表示),導數輸出將很小。并且誤差變化越快,輸出就越大。
現在,您可以將三個輸出相加,您就有了 PID 控制器。但通常您不需要所有三個控制器一起工作,相反,我們可以通過將設定點設置為零來移除任何人。例如,我們可以通過將 D 值設置為零來獲得 PI 控制器,否則我們可以通過將 I 參數設置為零來獲得 PD 控制器。現在我們有了一個清晰的想法,我們可以進入實際的硬件示例。
什么是編碼器電機,它是如何工作的?
編碼器電機的概念非常簡單:它是一個附有編碼器的有刷直流電機。在上一篇文章中,我們已經詳細討論了旋轉編碼器,如果您想了解更多有關該主題的信息,可以查看。
在編碼器電機中,旋轉編碼器安裝在直流電機上,直流電機通過跟蹤電機軸的速度或位置向系統提供反饋。有許多不同類型的電機可用,所有這些電機都可以具有不同類型的編碼器配置,例如增量或絕對、光學、空心軸、磁性等,不勝枚舉。不同類型的電機適用于不同類型的應用。不僅直流電機,許多伺服電機、步進電機和交流電機都帶有內置編碼器。在上圖中,您可以看到N20 永磁式編碼電機,它在附加變速箱的幫助下將輸出 RPM 降低到 15。您還可以看到兩個霍爾傳感器附在 PCB 上。這些霍爾傳感器獲取電機旋轉的方向,在微控制器的幫助下,我們可以很容易地讀取它。
構建啟用 PID 的編碼器電機控制器所需的組件
在這一點上,我們對 PID 控制器的工作有了一個很好的了解,并且我們也知道了我們的最終目標。基于此,我們決定使用 Arduino 和其他一些互補組件來構建電路。這些補充組件的列表如下所示。
Arduino 納米 - 1
N20 編碼電機 - 1
BD139 - 2
BD140 - 2
BC548 - 2
100R 電阻 - 2
4.7K電阻 - 2
面包板
跳線
電源
用于測試啟用 PID 的編碼器電機控制器的示意圖
啟用 PID 的編碼器電機控制器的完整原理圖如下所示。這個電路的工作原理很簡單,下面就來介紹一下。
電路非常簡單。首先,在原理圖中,我們有N20 編碼器電機,它有六個引腳,引腳標記為M1、M2,用于為電機供電,因為這是一個非常小的電機,額定電壓為 3.3V。接下來,我們有用于為編碼器電路供電的VCC和GND引腳。要為編碼器電路供電,您必須給它+5V,否則編碼器電路將無法正常工作。接下來,我們有PIN_A和PIN_B的電機。這兩個引腳直接連接到編碼器。通過讀取這些引腳的狀態,我們可以很容易地測量轉速,這個 15RPM N20 電機的齒輪比為 1:2098,這意味著主電機軸需要旋轉 2098 次,輔助軸旋轉一次。PIN_A和PIN_B連接到Arduino的引腳 9 和引腳 10,引腳 9 和 10 都是支持 PWM 的引腳;所選引腳必須具有 PWM 功能,否則代碼將不起作用。PID 控制器通過控制 PWM 來控制電機。
接下來,我們有我們的H橋電機驅動器,電機驅動器的制作使得我們只需使用Arduino的兩個引腳即可控制電機,甚至可以防止電機誤觸發。
支持 PID 的編碼器電機控制器的 Arduino 代碼
此項目中使用的完整代碼可在此頁面底部找到。添加所需的頭文件和源文件后,您應該可以直接編譯Arduino代碼而不會出現任何錯誤。您可以從下面給出的鏈接下載PID 控制器庫,或者您可以使用板管理器方法安裝該庫。
為 Arduino 下載 PID 控制器庫
ino中的代碼說明。文件?如下。首先,我們首先包含所有必需的庫。在這個程序中,我們只使用PID 控制器庫,?所以我們需要首先包含它。之后,我們定義讀取編碼器和驅動電機所需的所有必要引腳。完成后,我們定義 Kp、Ki 和 Kd 的所有值。
?
#include/* ENCODER_A 和 ENCODER_B 引腳用于讀取編碼器 * 來自微控制器的數據,來自編碼器的數據 * 來得非常快,所以這兩個引腳必須啟用中斷 * 引腳 */ #define ENCODER_A 2 #define ENCODER_B 3 /* MOTOR_CW 和 MOTOR_CCW 引腳用于驅動 H 橋 * H 橋然后驅動電機,這兩個引腳必須 * 啟用 PWM,否則代碼將無法工作。 */ #define MOTOR_CW 9 #define MOTOR_CCW 10
?
接下來,我們為代碼定義了__Kp、__Ki和__Kd值。這三個常量負責為我們的代碼設置輸出響應。在這一點上,請注意,對于這個項目,我使用了試錯法來設置常量,但還有其他方法可以很好地完成這項工作。
?
/*在本節中,我們定義了增益值 * 我設置的比例、積分和微分控制器 * 在試錯法的幫助下獲得增益值。 */ #define __Kp 260 // 比例常數 #define __Ki 2.7 // 積分常數 #define __Kd 2000 // 導數常數
?
接下來,我們定義了此代碼中所需的所有必要變量。首先,我們有encoder_count?變量,用于計算產生的中斷數;因此它計算圈數。接下來,我們定義了一個 unsigned?int類型變量整數值,用于存儲我們放入串行監視器中的值。接下來,我們定義了一個 char 類型的變量incomingByte來臨時存儲傳入的串行數據。接下來,我們在這段代碼中定義了最重要的變量,就是motor_pwm_value變量,通過PWM算法計算出數據后存儲在這個變量中。定義這些變量后,我們為PID控制器。一旦我們這樣做了,我們就可以進入我們的setup()函數。
?
volatile long int encoder_count = 0; // 存儲當前編碼器計數 無符號整數整數值 = 0; // 存儲傳入的序列值。最大值為 65535 char 傳入字節;// 一個一個地解析并存儲每個字符 int motor_pwm_value = 255; // 在 PID 計算數據存儲在這個變量中之后。 PIDController pid_controller;
?
在設置函數中,我們將ENCODER_A和ENCODER_B引腳分配為輸入,并將MOTOR_CW和MOTOR_CCW引腳定義為輸出。接下來,我們將ENCODER_A分配為中斷,在上升沿,這將調用函數encoder();?接下來的三行再次是最重要的,因為我們使用begin()方法啟用了 PID 控制器,并且我們還使用 Kp、Ki 和 Kd 值調整了控制器。最后,我們為 PID 控制器輸出設置了限制。
?
無效設置(){ 序列號.開始(115200);// 調試串口 pinMode(ENCODER_A,輸入);// ENCODER_A 作為輸入 pinMode(ENCODER_B,輸入);// ENCODER_B 作為輸入 pinMode(MOTOR_CW,輸出);// MOTOR_CW 作為輸出 pinMode(MOTOR_CCW,輸出);// MOTOR_CW 作為輸出 /* 將中斷附加到 Arduino 的 ENCODER_A 引腳,當脈沖處于上升沿時調用函數 encoder()。 */ attachInterrupt(digitalPinToInterrupt(ENCODER_A),編碼器,RISING); pidcontroller.begin(); //初始化PID實例 pidcontroller.tune(__Kp , __Ki , __Kd); // 調整 PID,參數:kP, kI, kD pidcontroller.limit(-255, 255); // 將 PID 輸出限制在 -255 到 255 之間,這對于消除積分飽和很重要! }
?
接下來,我們有我們的loop()部分。在循環部分,我們首先檢查串行是否可用。如果序列號可用,我們解析整數值并將其保存到整數值變量中。接下來,我們有一個'?/n'字符進入。我們把它放在incomingByte變量中,并用if語句檢查這個變量,如果為真,我們繼續循環,接下來我們用pidcontroller設置目標點.setpoint(整數值);并傳遞我們剛剛從串行接收到的整數值。接下來,我們打印接收到的值進行調試。
我們有motor_pwm_value變量,我們計算 PID 值并將其放入該變量中。如果該值大于零,我們調用?motor_ccw(motor_pwm_value)函數并傳入該值,否則,我們調用motor_cw(abs(motor_pwm_value))函數。這標志著我們循環部分的結束。
?
無效循環(){ 而 (Serial.available() > 0) { integerValue = Serial.parseInt(); // 存儲整數值 傳入字節 = Serial.read(); // 存儲 /n 字符 pidcontroller.setpoint(整數值);// PID 控制器試圖“達到”的“目標”, Serial.println(integerValue); // 打印傳入的值以進行調試 if (incomingByte == '\n') // 如果我們收到換行符,我們將繼續循環 繼續; } motor_pwm_value = pidcontroller.compute(encoder_count); //讓PID計算值,返回計算出的最優輸出 Serial.print(motor_pwm_value); // 打印計算值以供調試 序列號.print(""); if (motor_pwm_value > 0) // 如果 motor_pwm_value 大于零,我們順時針旋轉電機 MotorCounterClockwise(motor_pwm_value); else // 否則,我們逆時針方向移動它 MotorClockwise(abs(motor_pwm_value)); Serial.println(encoder_count);// 打印最終的編碼器計數。 }
?
接下來,我們有編碼器功能。當ENCODER_B中出現 tan 上升沿中斷時調用此函數。如果為真,我們使用if (digitalRead(ENCODER_B) == HIGH) 再次檢查該語句。一旦為真,我們的計數器變量就會增加。否則,它會遞減。
?
無效編碼器(){ if (digitalRead(ENCODER_B) == HIGH) // 如果 ENCODER_B 為高,則增加計數 編碼器計數++;// 增加計數 else // 否則減少計數 編碼器計數——;// 減少計數 }
?
接下來,我們有使電機順時針旋轉的功能。調用此函數時,它會檢查該值是否大于 100。如果是這樣,我們按順時針方向旋轉電機,否則我們停止電機。
?
void motor_cw(int power){ 如果(功率 > 100){ 模擬寫入(MOTOR_CW,電源); 數字寫入(MOTOR_CCW,低); } // 兩個引腳都設置為低 別的 { 數字寫入(MOTOR_CW,低); 數字寫入(MOTOR_CCW,低); } }
?
逆時針旋轉電機的功能也是如此。調用此函數時,我們檢查該值并逆時針旋轉電機。
?
void motor_ccw(int power){ 如果(功率 > 100){ 模擬寫入(MOTOR_CCW,電源); 數字寫入(MOTOR_CW,低); } 別的 { 數字寫入(MOTOR_CW,低); 數字寫入(MOTOR_CCW,低); } }
?
這標志著我們編碼部分的結束。
測試啟用 PID 的電機控制器
以下設置用于測試電路。如您所見,我使用了一個帶有一些雙面膠帶的電箱來固定電機,并且我使用了一個小型降壓轉換器模塊為電機供電,因為電機在 3.3V 上運行。
您還可以看到,我們已將 USB 電纜與 Arduino 連接,用于設置 PID 控制器的設定值。我們還通過 USB 從 Arduino 獲取調試信息。在這種情況下,它給出了當前的編碼器計數。下圖將使您更好地了解該過程。
?
#include
/* ENCODER_A 和 ENCODER_B 引腳用于讀取編碼器
來自微控制器的數據,來自編碼器的數據
來得非常快,所以這兩個引腳必須啟用中斷
引腳
*/
#define ENCODER_A 2
#define ENCODER_B 3
/* MOTOR_CW 和 MOTOR_CCW 引腳用于驅動 H 橋
然后 H 橋驅動電機,這兩個引腳必須
啟用 PWM,否則代碼將無法工作。
*/
#define MOTOR_CW 9
#define MOTOR_CCW 10
/*在本節中,我們定義了增益值
我設置的比例、積分和微分控制器
在試錯法的幫助下獲得增益值。
*/
#define __Kp 260 // 比例常數
#define __Ki 2.7 // 積分常數
#define __Kd 2000 // 導數常數
volatile long int encoder_count = 0; // 存儲當前編碼器計數
無符號整數整數值 = 0; // 存儲傳入的序列值。最大值為 65535
char 傳入字節;// 一個一個地解析和存儲每個單獨的字符
int motor_pwm_value = 255; // 在 PID 計算數據存儲在這個變量中之后。
PIDController PID控制器;
無效設置(){
序列號.開始(115200);// 調試串口
pinMode(ENCODER_A,輸入);// ENCODER_A 作為輸入
pinMode(ENCODER_B,輸入);// ENCODER_B 作為輸入
pinMode(MOTOR_CW,輸出);// MOTOR_CW 作為輸出
pinMode(MOTOR_CCW,輸出);// MOTOR_CW 作為輸出
/* 將中斷附加到 Arduino 的 ENCODER_A 引腳,當
脈沖處于上升沿,稱為函數 encoder()。
*/
attachInterrupt(digitalPinToInterrupt(ENCODER_A),編碼器,RISING);
pidcontroller.begin(); //初始化PID實例
pidcontroller.tune(260, 2.7, 2000); // 調整 PID,參數:kP, kI, kD
pidcontroller.limit(-255, 255); // 將 PID 輸出限制在 -255 到 255 之間,這對于消除積分飽和很重要!
}
無效循環(){
而 (Serial.available() > 0) {
integerValue = Serial.parseInt(); // 存儲整數值
傳入字節 = Serial.read(); // 存儲 /n 字符
if (incomingByte == '\n') // 如果我們收到換行符,我們將繼續循環
繼續;
}
pidcontroller.setpoint(整數值);// PID 控制器試圖“達到”的“目標”,
Serial.println(integerValue); // 打印傳入的值以進行調試
motor_pwm_value = pidcontroller.compute(encoder_count); //讓PID計算值,返回計算出的最優輸出
Serial.print(motor_pwm_value); // 打印計算值以供調試
序列號.print("");
if (motor_pwm_value > 0) // 如果 motor_pwm_value 大于零,我們順時針旋轉電機
motor_ccw(motor_pwm_value);
else // 否則我們按逆時針方向移動它
motor_cw(abs(motor_pwm_value));
Serial.println(encoder_count);// 打印最終的編碼器計數。
}
無效編碼器(){
if (digitalRead(ENCODER_B) == HIGH) // 如果 ENCODER_B 為高,則增加計數
編碼器計數++;// 增加計數
else // 否則減少計數
編碼器計數——;// 減少計數
}
void motor_cw(int power){
如果(功率 > 100){
模擬寫入(MOTOR_CW,電源);//如果值大于100,則旋轉電機
數字寫入(MOTOR_CCW,低);// 使另一個引腳為低電平
}
別的 {
// 兩個引腳都設置為低
數字寫入(MOTOR_CW,低);
數字寫入(MOTOR_CCW,低);
}
}
void motor_ccw(int power){
如果(功率 > 100){
模擬寫入(MOTOR_CCW,電源);
數字寫入(MOTOR_CW,低);
}
別的 {
數字寫入(MOTOR_CW,低);
數字寫入(MOTOR_CCW,低);
}
}
評論
查看更多