2019年11月28日木曜日

ラズベリーパイでradikoラジオをつくる: その5 ロータリエンコーダで音量調整

ロータリエンコーダを使ってみる

今回使うのは、BOURNSのPEC16という1回転で24パルスのロータリエンコーダです。デテント付きと言って、軸を廻すとクリック感のあるタイプを選びました。データシートにはPRO AUDIOとあって、何のことかと思いましたが廻して納得。フィーリングが心地よく、ひょっとしたらデジタルミキサなどのプロ用音響機器に採用されているのかもと思わせるものでした。



ところでロータリエンコーダの軸は、断面がD形(半月形)になっていて、つまみが入手しにくい印象です。今回はRSで購入しましました。記事を書きながら「D形シャフト用つまみ」を改めて調べてみたら、マーベル社の製品が千石で扱いがあるようです。
ロータリエンコーダの中にはスイッチが2個入っていて、回転に合わせてスイッチが断続します。波形の変化から回転方向がわかります。下図でDはデテントの固定位置、CWは時計回り、CCWは反時計回りを示しています。


ロータリエンコーダの接続はこんな感じです。ENC-A, ENC-BをラズパイのGPIOで読み取ります。ラズパイ側のプルアップ抵抗は無しです。


ロータリエンコーダの接点を表示するスクリプト

まずはPythonでスクリプトを作成しました。ENC-A, ENC-Bのどちらかのレベルが変動したら、接点を読み取り標準出力に表示します。

#!/usr/bin/python
# -*- coding: utf-8 -*-
import time
import RPi.GPIO as GPIO_RPi
# GPIO pin assign
SW_ENCA= 22
SW_ENCB= 23

def callback(callback):
    re = (GPIO_RPi.input(SW_ENCA) &1)
    re += (GPIO_RPi.input(SW_ENCB) &1)<<1
    print(re)

def main():
    GPIO_RPi.setwarnings(False)
    GPIO_RPi.setmode(GPIO_RPi.BCM)
    # GPIO initialize
    GPIO_RPi.setup(SW_ENCA, GPIO_RPi.IN)
    GPIO_RPi.setup(SW_ENCB, GPIO_RPi.IN)
    GPIO_RPi.add_event_detect(SW_ENCA, GPIO_RPi.BOTH, callback=callback, bouncetime=5)
    GPIO_RPi.add_event_detect(SW_ENCB, GPIO_RPi.BOTH, callback=callback, bouncetime=5)
    try:
        while(True):
            time.sleep(0.1)
    except KeyboardInterrupt:
        print("break")
        GPIO_RPi.cleanup()

if __name__ == "__main__":
    main()
早速スクリプトを実行させて、右に2ステップ、左に3ステップ廻してみました。右に廻すと0→1→3→2、左は1→0→2→3の順番で変化します。見た感じ、チャタリングは無いように見えます。


ロータリエンコーダで音量を調整

読み取ったデータからスクリプトでロータリエンコーダの回転方向を判定し、音量調整に利用します。回転方向判定は、ChaN氏のページのテーブル変換を参考にしました。ただ、そのままでは軸を1クリック廻すごとに4回動いたと判定されてしまうため、1回になるようテーブルに手を入れています。
ロータリエンコーダの回転を検知する毎にamixerコマンドを発行すれば、音量が変わります。このコマンドで音量は0から255までの数値で指定できますが、減衰量は1/256ずつ直線的に変化すると思っていましたが、実際にやってみるとそうではないようです。この変化のルールがどうなっているのか調べたところ、同じamixerコマンドでその答えが表示されることがわかりました。この表示によると0dBから-51dBまで0.2dBステップで調整できることになっています。


ラジオのボリュームとして使いやすいよう16段階で音量調整できるようにします。減衰量は配列に入れておき、ロータリエンコーダが動く都度読み出します。減衰量のデータは適当に作ったので音量変化が妥当になるよう見直しが必要ですね。参考までスクリプトを示します。今回の製作で初めてPythonに触れたので不適切なところがあるかも、と言い訳しておきます。
#!/usr/bin/python
# -*- coding: utf-8 -*-
import time
import subprocess
import RPi.GPIO as GPIO_RPi
# GPIO pin assign
SW_ENCA= 22
SW_ENCB= 23
re = 0
position = 13

def callback(callback):
    global re
    global position
    #dir = [0,1,-1,0,-1,0,0,1,1,0,0,-1,0,-1,1,0]
    dir = [0,1,0,0,-1,0,0,0,0,0,0,0,0,0,0,0]
    re = (re &3)<<2
    re += (GPIO_RPi.input(SW_ENCA) &1)
    re += (GPIO_RPi.input(SW_ENCB) &1)<<1
    n = dir[re & 15]
    if n==1:
        print('CW')
        if position < 15:
            position += n
        print(position)
        vol_cont(position)
    elif n==-1:
        print('CCW')
        if position > 0:
            position += n
        print(position)
        vol_cont(position)

def vol_cont(position):
    vol = [0,115,125,135,145,155,165,175,185,195,205,215,225,235,245,255]
    cmd = ["amixer", "-c0", "sset", "PCM", str(vol[position]), "unmute"]
    ret = subprocess.Popen(cmd)

def main():
    GPIO_RPi.setwarnings(False)
    GPIO_RPi.setmode(GPIO_RPi.BCM)
    # GPIO initialize
    GPIO_RPi.setup(SW_ENCA, GPIO_RPi.IN)
    GPIO_RPi.setup(SW_ENCB, GPIO_RPi.IN)
    GPIO_RPi.add_event_detect(SW_ENCA, GPIO_RPi.BOTH, callback=callback, bouncetime=5)
    GPIO_RPi.add_event_detect(SW_ENCB, GPIO_RPi.BOTH, callback=callback, bouncetime=5)
    try:
        while(True):
            time.sleep(0.1)
    except KeyboardInterrupt:
        print("break")
        GPIO_RPi.cleanup()

if __name__ == "__main__":
    main()

0 件のコメント:

コメントを投稿