以前用CRT顯示器的時候,調整顯示器的時候用一個圓盤轉動和點擊的方法就可以實現選擇菜單和修改設置項的值,比多個按鈕的方式方便很多。
鼠標滾輪也是這種操作方法,旋轉+點擊,只是方向不同。最近在網上買了旋轉編碼器模塊,想把它用到實際制作中。在網上找了很多資料,測試發現其中的代碼或多或少都有問題。於是決定自己研究一下旋轉編碼器的原理,只涉及高低電平應該會比較簡單。
我買的旋轉編碼器模塊有5個引腳,分別是VCC, GND, SW, CLK, DT。其中VCC和GND用來接電源和地,按縮寫SW應該是Switch(開關)、CLK是Clock(時鍾)、DT是Data(數據)。
網上的資料雖然代碼不是很理想,但介紹的原理基本是沒問題的。旋轉編碼器的操作是旋轉和按壓轉軸,在按下轉軸的時候SW引腳的電平會變化,旋轉的時候每轉動一步CLK和DT的電平是有規律的變化。在只接電源的情況下先測一下各種操作時引腳電平的變化,沒有示波器只好用萬用表測電壓。
點擊:SW(紅)+GND(黑)時按下和松開按鈕沒有任何變化,VCC(紅)+SW(黑)松開時表針指向0,按下時高電平。據此可以推測SW平時為高阻態,按下時接地。用Arduino檢測的方法是設置連接SW的引腳為INPUT並上拉輸出高電平,檢測到引腳為低電平則表示按鈕按下,以下代碼可以正確檢測出按鈕的變化。
//定義引腳連接 int SW= 4; // SW->D4 bool lastButtonStatus = false; void setup() { pinMode(SW, INPUT); digitalWrite(SW, HIGH);//連接按鈕的引腳設為上拉 Serial.begin(9600); } void loop() { bool buttonStatus = !digitalRead(SW);//高電平時未按下,狀態為false if (buttonStatus != lastButtonStatus) { Serial.println(buttonStatus ? "pressed" : "released"); lastButtonStatus = buttonStatus;//保存當前狀態 } delay(100); }
旋轉:CLK(紅)+GND(黑),每旋轉一次(和方向無關),電平轉換一次,DT(紅)+GND(黑),變化情況和上一種情況一致,並且CLK和DT的電平保持一致。VCC(紅)+CLK(黑),VCC(紅)+DT(黑)也是同樣的情況。CLK(紅)+DT(黑)或者CLK(黑)+DT(紅)時,每次旋轉(和方向無關)指針都會輕微擺動然后歸零,並且相鄰兩步指針的擺動方向相反。結論:每次旋轉CLK和DAT引腳的電平都會變化,電平變化有時間差,但無法區分往哪個方向旋轉。
編寫測試代碼,在按下按鈕的時候讀取CLK和DT的值:
1 //定義引腳連接 2 int CLK = 2;//CLK->D2 3 int DT = 3;//DT->D3 4 int SW = 4;//SW->D4 5 6 void setup() 7 { 8 pinMode(SW, INPUT); 9 digitalWrite(SW, HIGH);//連接按鈕的引腳設為上拉 10 pinMode(CLK, INPUT); 11 pinMode(DT, INPUT); 12 Serial.begin(9600); 13 } 14 15 void loop() 16 { 17 if (!digitalRead(SW)) //讀取到按鈕按下時讀取CLK和DT的值 18 { 19 int clkValue = digitalRead(CLK);//讀取CLK引腳的電平 20 int dtValue = digitalRead(DT);//讀取DT引腳的電平 21 Serial.print("CLK:"); 22 Serial.print(clkValue); 23 Serial.print("; DT:"); 24 Serial.println(dtValue); 25 delay(1000); 26 } 27 }
測試發現不管順時針還是逆時針旋轉,每次按下按鈕之后讀取的CLK和DT的值都是一樣的,並且相鄰兩步之間的值是不一樣的,符合用萬用表測量的結果。
萬用表測量時發現CLK和DT的變化有一定的時間差,可以用Arduino在CLK電平變化的瞬間讀取DT的值,可能會找到其中的規律。改成通過中斷0監控CLK上的電平變化,讀取CLK和DT的電平值:
1 //定義引腳連接 2 int CLK = 2;//CLK->D2 3 int DT = 3;//DT->D3 4 const int interrupt0 = 0;// Interrupt 0 在 pin 2 上 5 6 void setup() 7 { 8 pinMode(CLK, INPUT); 9 pinMode(DT, INPUT); 10 attachInterrupt(interrupt0, ClockChanged, CHANGE);//設置中斷0的處理函數,電平變化觸發 11 Serial.begin(9600); 12 } 13 14 void loop() 15 { 16 } 17 18 //中斷處理函數 19 void ClockChanged() 20 { 21 int clkValue = digitalRead(CLK);//讀取CLK引腳的電平 22 int dtValue = digitalRead(DT);//讀取DT引腳的電平 23 Serial.print("CLK:"); 24 Serial.print(clkValue); 25 Serial.print("; DT:"); 26 Serial.println(dtValue); 27 }
順時針旋轉一步:
順時針旋轉3步(用橫線分隔):
逆時針旋轉3步(用橫線分隔):
根據以上測試結果,每旋轉一次觸發的中斷次數不一致,可能是硬件本身引起的,類似按鈕抖動。多次測試之后,查看每次變化的最后一組值,順時針旋轉時CLK和DT的值不一致,逆時針旋轉時CLK和DT的值一致。修改代碼,順時針時對計數值加1,逆時針時對計數值減1,按下按鈕時計數值清零。
1 //定義引腳連接 2 int CLK = 2;//CLK->D2 3 int DT = 3;//DT->D3 4 int SW = 4;//SW->D4 5 const int interrupt0 = 0;// Interrupt 0 在 pin 2 上 6 int count = 0;//計數值 7 int lastCLK = 0;//CLK歷史值 8 9 void setup() 10 { 11 pinMode(SW, INPUT); 12 digitalWrite(SW, HIGH); 13 pinMode(CLK, INPUT); 14 pinMode(DT, INPUT); 15 attachInterrupt(interrupt0, ClockChanged, CHANGE);//設置中斷0的處理函數,電平變化觸發 16 Serial.begin(9600); 17 } 18 19 void loop() 20 { 21 if (!digitalRead(SW) && count != 0) //讀取到按鈕按下並且計數值不為0時把計數器清零 22 { 23 count = 0; 24 Serial.print("count:"); 25 Serial.println(count); 26 } 27 } 28 29 //中斷處理函數 30 void ClockChanged() 31 { 32 int clkValue = digitalRead(CLK);//讀取CLK引腳的電平 33 int dtValue = digitalRead(DT);//讀取DT引腳的電平 34 if (lastCLK != clkValue) 35 { 36 lastCLK = clkValue; 37 count += (clkValue != dtValue ? 1 : -1);//CLK和DT不一致時+1,否則-1 38 Serial.print("count:"); 39 Serial.println(count); 40 } 41 }
測試發現大多數時候可以正確輸出:
偶爾旋轉不是很順暢會出現跳動的情況,這時候能感覺到旋鈕在兩步之間跳動了一下。看到有人說在引腳和地之間接上濾波電容會好一些,實際測試發現並沒有改善。推測由於旋鈕是D字型的,用手旋轉的時候確實會出現跳動的情況,裝上旋鈕帽之后應該會避免這種情況。
旋轉編碼器可以用於需要精確調整值(電位器不准確),操作菜單等場合。后續會使用旋轉編碼器制作一些小東西,也歡迎大家分享旋轉編碼器相關代碼。