---
# System prepended metadata

title: 旋轉編碼器的計數
tags: [Arduino, ESP32]

---

# 旋轉編碼器的計數

旋轉編碼器的兩個腳位 CLK 和 DT 因為相差半個週期, 而且轉一格後一定會停在兩個腳都是高電位的地方, 順時鐘旋轉時電位變化如下：

|CLK|DT|
|---|---|
|1|1|
|0|1|
|0|0|
|1|0|
|1|1|

如果逆時鐘旋轉, 就會是相反的順序：

|CLK|DT|
|---|---|
|1|1|
|1|0|
|0|0|
|0|1|
|1|1|

理論上, 只要檢查 CLK 發生電位變化時, DT 的電位與 CLK 的電位是否相同, 就可以據此判斷是順時鐘還是逆時鐘旋轉半格 , 例如以下是順時鐘旋轉的時候, 兩個腳的電位不同：

|CLK|DT|電位變化處|
|---|---|---|
|1|1|
|0|1|CLK != DT|
|0|0|
|1|0|CLK != DT|
|1|1|

但如果是逆時間旋轉, 反而是 CLK 電位變化時兩個腳的電位相同：

|CLK|DT|電位變化處|
|---|---|---|
|1|1|
|1|0|
|0|0|CLK == DT|
|0|1|
|1|1|CLK == DT|

不過這個方法仰賴偵測電位變化, 但旋轉編碼器是機械裝置, 可能因為彈跳而導致電位變化, 就會讓計算旋轉格數錯誤。有位 [Ben Buxton]((http://www.buxtronix.net/2011/10/rotary-encoders-done-properly.html)) 寫了一個[簡潔有效的程式庫](https://github.com/buxtronix/arduino/tree/master/libraries/Rotary)解決了彈跳的問題, 他把順逆時鐘旋轉的電位變化轉化成一個狀態機：

狀態編號<br>(順時鐘)|CLK|DT|狀態編號<br>(逆時鐘)|
|---|---|---|---|
0|1|1|0
2|0|1|5
3|0|0|6
1|1|0|4
0|1|1|0

從狀態 0 開始, 就會依據電位變化決定走左邊順時鐘或是右邊順時鐘的狀態變化路線, 即使發生彈跳, 也只會在前後狀態間來回, 不會從順時鐘變成逆時鐘,或是從逆時鐘變成順時鐘。只要順著狀態變化從 1 或是 5 回到狀態 0, 就表示順時鐘或是逆時鐘旋轉一格了。

舉例來說, 一開始是狀態 0, 發現電位變成 01, 表示往順時鐘方向轉, 狀態變成 2, 這時即使有彈跳, 也只是在 2 和 0 兩個狀態間變化, 最終穩定成 2。如果電位繼續變化成 00, 就近入狀態 3, 再變化成 10, 就進入狀態 1, 最後電位變化回 11 回到狀態 0, 表示旋轉 1 格。

為了方便程式查詢狀態變化, 可以轉化成以下的表格：

|CLK DP<br>狀態|00|01|10|11|
|---|---|---|---|---|
|0|0x00|0x02|0x04|0x00|
|1|0x03|0x01|0x01|0x10|
|2|0x03|0x02|0x02|0x02|
|3|0x03|0x02|0x01|0x03|
|4|0x06|0x04|0x04|0x00|
|5|0x06|0x05|0x05|0x20|
|6|0x06|0x05|0x04|0x06|

表格中以 16 進位數字表示狀態變化, 高 4 位元表示有轉一格, 0x10 表示順時鐘方向, 0x20 表示逆時鐘方向轉一格, 低的 4 位元就是新的狀態。例如, 目前狀態 2, 兩個腳電位是 00, 那狀態就轉移到 3。若目前狀態是 1, 兩個腳電位是 11, 就表示順時鐘轉一格, 並且狀態回到 0。

以下是實際的展示程式：

```arduino
const byte CLK_PIN = 19;  // CLK 接腳
const byte DT_PIN = 21;   // DT 接腳
int counter = 0;          // 計數值
int clk = 0;             // 暫存 CLK 腳的目前值
int dt = 0;            // 暫存 CLK 腳的前次值
int state = 0;

void setup() {
  Serial.begin(115200);
  pinMode(CLK_PIN, INPUT);
  pinMode(DT_PIN, INPUT);
}

int smt[7][4] = {
  {0x00,0x02,0x04,0x00},
  {0x03,0x01,0x01,0x10},
  {0x03,0x02,0x02,0x02},
  {0x03,0x02,0x01,0x03},
  {0x06,0x04,0x04,0x00},
  {0x06,0x05,0x05,0x20},
  {0x06,0x05,0x04,0x06}
};

void loop() {
  clk = digitalRead(CLK_PIN);
  dt = digitalRead(DT_PIN);
  state = smt[state][clk << 1 | dt];
  if ((state & 0xF0) == 0x20){
    Serial.printf("%d\n", counter--);
  }
  if ((state & 0xF0) == 0x10){
    Serial.printf("%d\n", counter++);
  }
  state = state & 0x0F;
}
```
