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