RPi python で rotary encoder

# 1.はじめに
RPi Python で rotary encoderを使う覚書。
方法はいくつかある。けど、ライブラリ使わずGPIOの入力データを見ながら自分で制御した方がわかりやすい。
## 1-1. [Pi HUT](https://thepihut.com/blogs/raspberry-pi-tutorials/how-to-use-a-rotary-encoder-with-the-raspberry-pi)のやりかた
~~分かりそうで分かりにくい気も。~~
判りにくいのは私の能力不足で、判りやすいです。シンプルでいい感じですが、メインループの処理時間依存なので、メインループの処理時間が増えていくと、取りこぼしが出てしまいます（出るはず）。
ので、割り込み処理でクリックをイベントとして取得する必要が出てくるでしょう。

## 1-2. circuit pythonの [rotaryioライブラリ](https://circuitpython.readthedocs.io/en/latest/shared-bindings/rotaryio/index.html)を使う。
circuit pythonのrotaryioライブラリ使うのが一番便利そう。ただし、circuit pythonはgpioの取り扱いが独自のbusioだったはずなので注意。あとcircuit pythonライブラリは信用してるけど、このライブラリは未確認。

## 1-3. とりあえず信号を確認しつつ愚直に処理します。
ここではこのやり方を試してみます。
＜＜注意＞＞
・ピンアサインは一緒だと思いますが、waveshareのエンコーダーは使っていないのでもしかするともしかして、ちがうかも。
・エンコーダーのステートの順番ももしかするともしかして、ちがうかも。

追記
waveshareのロータリージョイントブレイクアウトボードの[回路図](https://www.waveshare.com/w/upload/2/25/Rotation-Sensor-Schematic.pdf)はこんな感じでした。
![waveshare rotary.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/496179/8d94023f-d224-c30b-bcb6-3d50173c2d5f.png)

## 1-4. 探せば他にも、、、
多分いっぱいあると思います。

# 2.接続


接続は3PINだけでOK。


|エンコーダ|RPi|
| ---- |-----|
|GND|GND|
|CLK(SIA)|GPIO17|
|DT(SIB)|GPIO18|

※これは、GPIO 17,18をpull UPする場合。pull downする場合は　+と5Vを接続（すれば行けると思うけど未確認）
※プッシュスイッチを使うなら、SWを任意のGPIOにつないでpull upでinputを読めば動きそう。

# 3.考え方と動作確認
ロータリーエンコーダーは１クリックごとにA,B端子の状態×２の組み合わせで4つのステートパターンを繰り返します。

例えば、右回り

|  step  |  CLK  |  DT  |
| ---- | ---- |-----|
|0(基準)|1|1|
|1|0|1|
|2|0|0|
|3|1|0|
|0|1|1|

左回り

|  step  |  CLK  |  DT  |
| ---- | ---- |-----|
|0(基準)|1|1|
|1|1|0|
|2|0|0|
|3|0|1|
|0|1|1|

※stepの2と3は入れ替わる可能性あり。pull up時とpull down時では反転。

今回はCLKとDTがそれぞれ(1,1)になったとき、その前のステータスが(0,1)か(1,0)かで回転方法を判断。また、チャタリング防止のためにステータス読み込みサイクルにsleepをかけます。

## 3-1.基準状態のステータスの確認

```python
from RPi import GPIO

A = 17
B = 18

GPIO.setmode(GPIO.BCM)
GPIO.setup(A, GPIO.IN, pull_up_down=GPIO.PUD_UP) #今回はpull upで回路を組みます。
GPIO.setup(B, GPIO.IN, pull_up_down=GPIO.PUD_UP)

print(GPIO.input(A))
print(GPIO.input(B))
```
CLKとDTはA,Bと読み替えます。
ロータリーエンコーダーをつないで、スクリプトを実行、A,Bのステートが基準状態で両者1であることを確認。
何度かエンコーダをクリックさせては実行し、基準状態がいつも1であることを確認。

## 3-2.クリックの遷移状態のステータスを確認。

```python
from RPi import GPIO

A = 17
B = 18

GPIO.setmode(GPIO.BCM)
GPIO.setup(A, GPIO.IN, pull_up_down=GPIO.PUD_UP)
GPIO.setup(B, GPIO.IN, pull_up_down=GPIO.PUD_UP)

while True:
    Astate = GPIO.input(A)
    Bstate = GPIO.input(B)
    if Astate == 0:
        print("Astate = " + str(Astate))
        print("Bstate = " + str(Bstate))

```

このスクリプトで、クリック中にステータスが遷移している順番が見えます。

## 3-3. 遷移状態に合わせて回転方向を判断してカウント

```python
from RPi import GPIO
from time import sleep

A = 17    #CLKを接続
B = 18    #DTを接続

GPIO.setmode(GPIO.BCM)
GPIO.setup(A, GPIO.IN, pull_up_down=GPIO.PUD_UP)
GPIO.setup(B, GPIO.IN, pull_up_down=GPIO.PUD_UP)

counter = 0  #エンコーダー積算
times = 0    #エンコーダークリック回数
AstateOld = 1
BstateOld = 1

while True:
    Astate = GPIO.input(A)
    Bstate = GPIO.input(B)

    if Astate == 1 and AstateOld == 0:
        print("AstateOld = " + str(AstateOld))
        print("BstateOld = " + str(BstateOld))
        BstateOld = Bstate
    
        if AstateOld == 0 and BstateOld == 0:
            counter += 1
        elif AstateOld == 0 and BstateOld == 1:
            counter -= 1
        times += 1
                
        print("counter  = " + str(counter))
        print("times    = " + str(times))
    
    AstateOld = Astate   #今回のステートを前回のデータとして記録
    BstateOld = Bstate   #
        
    sleep(0.001)  #チャタリング防止。１クリックで数カウントする場合は伸ばす。

```
※sleep行をなくしてもらうとおかしなカウントになると思います。
これは各ステートに落ち着く瞬間、スイッチがチャタリングを起こしているためです。今回は（私のエンコーダでは）チャタリングが1msec以内に収まっていたのでsleep(0.001)でチャタリングを防止できました。もう少しラフなエンコーダーの場合はsleepを伸ばせばよいのですが、伸ばしすぎると途中のステートを読めなくなる可能性があるので厄介です。


# 4.まとめ
とりあえずチャタリング防止して回転方向チェックしてカウントできました。何かの装置に実装する際、メインループの処理速度依存になるとうまく動かないケースも出てくるので、割り込み処理を使うのが一般的かと思います。