之前用CRT顯示器的時候,調整顯示器的時候用一個圓盤轉動和點擊的方法就能夠實現選擇菜單和修改設置項的值,比多個按鈕的方式方便不少。git
鼠標滾輪也是這種操做方法,旋轉+點擊,只是方向不一樣。最近在網上買了旋轉編碼器模塊,想把它用到實際製做中。在網上找了不少資料,測試發現其中的代碼或多或少都有問題。因而決定本身研究一下旋轉編碼器的原理,只涉及高低電平應該會比較簡單。函數
我買的旋轉編碼器模塊有5個引腳,分別是VCC, GND, SW, CLK, DT。其中VCC和GND用來接電源和地,按縮寫SW應該是Switch(開關)、CLK是Clock(時鐘)、DT是Data(數據)。oop
網上的資料雖然代碼不是很理想,但介紹的原理基本是沒問題的。旋轉編碼器的操做是旋轉和按壓轉軸,在按下轉軸的時候SW引腳的電平會變化,旋轉的時候每轉動一步CLK和DT的電平是有規律的變化。在只接電源的狀況下先測一下各類操做時引腳電平的變化,沒有示波器只好用萬用表測電壓。測試
點擊:SW(紅)+GND(黑)時按下和鬆開按鈕沒有任何變化,VCC(紅)+SW(黑)鬆開時錶針指向0,按下時高電平。據此能夠推測SW平時爲高阻態,按下時接地。用Arduino檢測的方法是設置鏈接SW的引腳爲INPUT並上拉輸出高電平,檢測到引腳爲低電平則表示按鈕按下,如下代碼能夠正確檢測出按鈕的變化。ui
//定義引腳鏈接 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的值:spa
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的值都是同樣的,而且相鄰兩步之間的值是不同的,符合用萬用表測量的結果。3d
萬用表測量時發現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 }
順時針旋轉一步:code
順時針旋轉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字型的,用手旋轉的時候確實會出現跳動的狀況,裝上旋鈕帽以後應該會避免這種狀況。
旋轉編碼器能夠用於須要精確調整值(電位器不許確),操做菜單等場合。後續會使用旋轉編碼器製做一些小東西,也歡迎你們分享旋轉編碼器相關代碼。