參考資料:https://www.arduino.cn/thread-2423-1-1.html
1. 所需的材料
- 旋轉編碼器(KY-040)
- Arduino UNO開發板
- 字符型圖形點陣液晶1602
- 電位器10k
- 面包板
2. 連接導線旋轉編碼器是如何工作的?
旋轉編碼器是一種機電換能器,意味着它將機械運動轉換為電子脈沖。它由旋鈕組成,當旋轉時,旋鈕將逐步移動並產生一系列脈沖序列,每個步驟具有預定義的寬度。有許多類型的編碼器,每個編碼器都有自己的工作機制,稍后我們將了解這些類型,但現在讓我們只關注KY040增量編碼器,因為我們將它用於我們的教程。
編碼器的內部機械結構如下所示。它基本上由圓盤(灰色)和放置在該圓盤頂部的導電墊(銅色)組成。這些導電焊盤以相同的距離放置,如下所示。輸出引腳固定在該圓盤的頂部,這樣當旋鈕旋轉時,導電墊與輸出引腳接觸。這里有兩個輸出引腳,輸出A和輸出B,如下圖所示。
輸出引腳A和輸出B產生的輸出波形分別以藍色和綠色顯示。當導電焊盤直接位於引腳下方時,它會變高,導致導通時間,當導電焊盤移開時,引腳變低,導致上面所示波形的關閉時間。現在,如果我們計算脈沖數,我們將能夠確定編碼器移動了多少步。
現在可能會出現這樣的問題:當一個脈沖信號足以計算旋轉旋鈕時所采取的步數時,為什么我們需要兩個脈沖信號。這是因為我們需要確定旋鈕旋轉的方向。如果您看一下這兩個脈沖,您會注意到它們都是90°異相。因此,當順時針旋轉旋鈕時,輸出A將首先變高,當旋鈕逆時針旋轉時,輸出B將首先變高。
3. 旋轉編碼器的類型
市場上有很多種類型的旋轉編碼器,設計工程師可以根據自己的應用選擇一種。最常見的類型如下所示
- 增量編碼器
- 絕對值編碼器
- 磁編碼器
- 光學編碼器
- 激光編碼器
這些編碼器基於輸出信號和傳感技術進行分類,增量編碼器和絕對值編碼器基於輸出信號進行分類,磁、光和激光編碼器基於傳感技術進行分類。這里使用的編碼器是增量型編碼器。
4. KY-040旋轉編碼器引腳和說明
KY-040增量式旋轉編碼器的引腳分布如下所示
前兩個引腳(接地和Vcc)用於為編碼器供電,通常使用+ 5V電源。除了以順時針方向和逆時針方向旋轉旋鈕外,編碼器還有一個開關(低電平有效),按下內部的旋鈕可以按下該開關。來自此開關的信號通過引腳3(Switch)獲得。最后它有兩個輸出引腳,產生如上所述的波形。
正傳波形
反轉波形
5. Arduino與旋轉編碼器的連接電路圖
旋轉編碼器與Arduino連接的完整電路圖如下圖所示
旋轉編碼器有5個引腳,順序如上圖的標簽所示。前兩個引腳是接地和Vcc,它連接到Arduino的地和+ 5V引腳。編碼器的開關連接到數字引腳D8,並通過1k電阻拉高。兩個輸出引腳分別連接到D9和D8。
要顯示通過旋轉Rotary編碼器增加或減少的變量值,我們需要一個顯示模塊。這里使用的是常用的字符型圖形點陣液晶1602。我們已將連接的顯示屏設置成4位工作模式,並使用Arduino的+ 5V引腳為其供電。電位計用於調整LCD顯示屏的對比度。完整的電路可以在面包板上進行搭建,一旦完成所有的連接后,效果看起來類似下圖。
說明,采用單片機內部的上拉輸入可能帶來較大功耗,因為單片機內部電阻較大,arduino實現的基本原理為已知每圈脈沖數為2500,則當計數大道2500時,計數一圈,若為stm32則進行溢出中斷。通過A、B相的高低電平關系,判斷AB相的先后關系,進而判斷轉向。
為了區分正反轉及檢測零點,通常包括三個部分:A相,B相和Z相,A相與B相相差1/4周期(相位差90度),可以用來區分正轉還是反轉;Z相為單圈脈沖,碼盤轉一圈產生一次,可以用作編碼器的參考零位,如下圖:
6. 編寫用於旋轉編碼器的Arduino程序
如果您了解旋轉編碼器的工作原理,那么編程Arduino開發板以便將旋轉編碼器連接到它是相當容易的。我們只需要讀取脈沖數來確定編碼器的轉動數,以及首先檢查哪個脈沖高,以找到編碼器旋轉的方向。在本篇文章中,我們將在LCD第一行上顯示增加或減少的數字以及在第二行上顯示編碼器的方向。
由於我們使用了一個LCD顯示屏,因此包含了Arduino IDE中默認存在的液晶庫。然后我們定義用於連接LCD和Arduino的引腳。最后,我們初始化這些引腳上的LCD顯示屏。
#include <LiquidCrystal.h> //Default Arduino LCD Library is included const int rs = 7, en = 6, d4 = 5, d5 = 4, d6 = 3, d7 = 2; //Mention the pin number for LCD connection LiquidCrystal lcd(rs, en, d4, d5, d6, d7); lcd.begin(16, 2); //Initialise 16*2 LCD
接下來在setup函數中,我們在LCD屏幕上顯示介紹消息,然后等待2秒鍾,以便用戶可以讀完該消息。這是為了確保LCD能夠正常工作。
#include <LiquidCrystal.h> //Default Arduino LCD Library is included const int rs = 7, en = 6, d4 = 5, d5 = 4, d6 = 3, d7 = 2; //Mention the pin number for LCD connection LiquidCrystal lcd(rs, en, d4, d5, d6, d7); lcd.begin(16, 2); //Initialise 16*2 LCD
Rotary編碼器有三個輸出引腳,對於Arduino來說,它們都是INPUT引腳。這三個引腳分別是開關(Switch)、輸出A(Output A)和輸出B(Output B)。這些引腳使用pinMode函數聲明為Input,如下所示。
//pin Mode declaration pinMode (Encoder_OuputA, INPUT); pinMode (Encoder_OuputB, INPUT); pinMode (Encoder_Switch, INPUT);
在void setup()函數中,我們讀取輸出A引腳的狀態以檢查引腳的最后狀態。然后,我們將使用此信息與新值進行比較,以檢查哪個引腳(輸出A或輸出B)變高。
Previous_Output = digitalRead(Encoder_OuputA); //Read the inital value of Output A
最后在loop函數內,我們必須將輸出A和輸出B的值與先前輸出進行比較,以檢查哪一個先變高。這可以通過簡單地將A和B的當前輸出值與先前輸出進行比較來完成,如下所示。
if (digitalRead(Encoder_OuputA) != Previous_Output) { if (digitalRead(Encoder_OuputB) != Previous_Output) { Encoder_Count ++; lcd.clear(); lcd.print(Encoder_Count); lcd.setCursor(0, 1); lcd.print("Clockwise"); }
在上面的代碼中,如果輸出B已從先前的輸出改變,則執行第二個if條件。在這種情況下,編碼器變量的值遞增,LCD顯示編碼器以順時針方向旋轉。類似地,如果if條件不符合,則在隨后的其他條件中,我們遞減變量並顯示編碼器沿逆時針方向旋轉。代碼如下所示。
else { Encoder_Count--; lcd.clear(); lcd.print(Encoder_Count); lcd.setCursor(0, 1); lcd.print("Anti - Clockwise"); } }
最后,在loop函數結束時,我們必須使用當前輸出值更新先前的輸出值,以便可以使用相同的邏輯重復循環。
Previous_Output = digitalRead(Encoder_OuputA);
另一個可選方法是檢查編碼器上的開關是否被按下。這可以通過檢查旋轉編碼器上的開關銷來監控。該引腳是低電平有效引腳,意味着按下該按鈕時它將變為低電平。如果沒有按下引腳保持高電平,我們也使用了一個上拉電阻,以確保在未按下開關時保持高電平,從而避免浮空狀態。
if (digitalRead(Encoder_Switch) == 0) { lcd.clear(); lcd.setCursor(0, 1); lcd.print("Switch pressed"); }
Arduino控制旋轉編碼器的工作過程
一旦硬件和代碼准備就緒,只需將代碼上傳到Arduino開發板板,然后向Arduino供電。您可以通過USB電纜為其供電,也可以使用12V適配器。上電時,LCD應顯示介紹消息,然后變為空白。現在旋轉旋轉編碼器,您應該看到值根據旋轉方向遞增或遞減。第二行將顯示編碼器是以順時針方向還是逆時針方向旋轉。下圖顯示了實際的工作過程:
此外,當按下按鈕時,第二行將顯示按下按鈕。這只是一個旋轉編碼器與Arduino連接的示例程序,檢查它是否按預期工作。現在,您應該可以將編碼器用於任何項目並進行相應的編程。
7.3 完整代碼
/* Interfacing Rotary Encoder with Arduino Power LCD and Rotary encoder from the +5V pin of Arduino LCD RS -> pin 7 LCD EN -> pin 6 LCD D4 -> pin 5 LCD D5 -> pin 4 LCD D6 -> pin 3 LCD D7 -> pin 2 Encoder Switch -> pin 10 Encoder Output A -> pin 9 Encoder Output B -> pin 8 */ int Encoder_OuputA = 9; int Encoder_OuputB = 8; int Encoder_Switch = 10; int Previous_Output; int Encoder_Count; #include <LiquidCrystal.h> //Default Arduino LCD Librarey is included const int rs = 7, en = 6, d4 = 5, d5 = 4, d6 = 3, d7 = 2; //Mention the pin number for LCD connection LiquidCrystal lcd(rs, en, d4, d5, d6, d7); void setup() { lcd.begin(16, 2); //Initialise 16*2 LCD lcd.print(" Rotary Encoder "); //Intro Message line 1 lcd.setCursor(0, 1); lcd.print(" With Arduino "); //Intro Message line 2 delay(2000); lcd.clear(); //pin Mode declaration pinMode (Encoder_OuputA, INPUT); pinMode (Encoder_OuputB, INPUT); pinMode (Encoder_Switch, INPUT); Previous_Output = digitalRead(Encoder_OuputA); //Read the inital value of Output A } void loop() { //aVal = digitalRead(pinA); if (digitalRead(Encoder_OuputA) != Previous_Output) { if (digitalRead(Encoder_OuputB) != Previous_Output) { Encoder_Count ++; lcd.clear(); lcd.print(Encoder_Count); lcd.setCursor(0, 1); lcd.print("Clockwise"); } else { Encoder_Count--; lcd.clear(); lcd.print(Encoder_Count); lcd.setCursor(0, 1); lcd.print("Anti - Clockwise"); } } Previous_Output = digitalRead(Encoder_OuputA); if (digitalRead(Encoder_Switch) == 0) { lcd.clear(); lcd.setCursor(0, 1); lcd.print("Switch pressed"); } }
8. 補充其他的網友的代碼
#define PinA 2 //外部中斷0 #define PinZ 3 //外部中斷1 #define PinB 8 //編碼器的OUT_B信號連接到數字端口8 //變量初始化 unsigned long time1 = 0; // 時間標記 float time_cw; float time_ccw; long count = 0; const float d = 75.7 / 1000; //輪子的直徑 const float pi = 3.141592654;//圓周率 int num = 0;//圈數 double t;//一圈的運動時間 float velocity; double time3;//外部中斷1產生時的時間,即捕捉到Z相的置零信號時,用於在loop循環內進行一圈時間長短的計算 void setup() { pinMode(PinA, INPUT_PULLUP);//因為編碼器信號為歐姆龍E6B2-CWZ6C,為開漏輸出,因此需要上拉電阻,此處采用arduino的內部上拉輸入模式,置高 pinMode(PinB, INPUT_PULLUP);//同上 pinMode(PinZ, INPUT_PULLUP);//同上 attachInterrupt(0, Encode, FALLING);//脈沖中斷函數:捕捉A相信號,並判斷A、B相先后順序 attachInterrupt(1, Set_state , FALLING);//用於在捕捉到Z的零信號時,進行狀態置零 Serial.begin (9600); } void loop() { double distance; //正轉 if (count == 2500) { // Serial.println("ok");//調試用 num = num + 1; time_cw = millis(); t = time_cw - time3; t = t / 1000; distance = num * d * pi; velocity = d * pi / t; Serial.print("The wheel has run "); Serial.print(distance); Serial.println("m."); Serial.print("The cw_speed is "); Serial.print(velocity); Serial.println("m/s."); Serial.print("The time is "); Serial.print(t); Serial.println("s."); } //反轉 if (count == -2500) { // Serial.println("ok");//調試用 num = num + 1; time_ccw = millis(); t = time_ccw - time3; t = t / 1000; distance = num * d * pi; velocity = d * pi / t; Serial.print("The wheel has run "); Serial.print(distance); Serial.println("m."); Serial.print("The ccw_speed is "); Serial.print(velocity); Serial.println("m/s."); Serial.print("The time is "); Serial.print(t); Serial.println("s."); } } // 編碼器計數中斷子程序 void Encode() { //為了不計入噪音干擾脈沖, //當2次中斷之間的時間大於5ms時,計一次有效計數 if ((millis() - time1) > 5) { //當編碼器碼盤的OUTA脈沖信號下跳沿每中斷一次, if ((digitalRead(PinA) == LOW) && (digitalRead(PinB) == HIGH)) { count--; } else { count++; } } time1 == millis(); } void Set_state() { count = 0; time3 = millis();//發生中斷時的時間 }