ロータリーエンコーダ

RPi.GPIOを使った外部機器からの入力。押しボタンスイッチの他に何か無いか?と思って探すとArduinoで使ったロータリーエンコーダが出てきたので、これを試してみる。
秋月で取り扱っている2相出力のエンコーダで、ツマミを回すとA相、B相の各端子からパルスが出力される。パルスはA相とB相でずれて出力されるようになっていて、回転方向によってずれかたが異なるから、それでどちらに回されたのかを知ることができる。

このロータリーエンコーダはクリックのある場所で停止するようになっていて、停止位置では端子がどこにも接続されていない開放状態となるので、抵抗でプルアップしておく必要がある。ブレッドボードで使い勝手が良いように、小型ユニバーサル基板を使ってプルアップ抵抗を予め付けておいた。

R0011719回転方向の検出はA相、B相の状態にそれぞれ番号を付けておき、状態が変化した時に新しい番号と以前の番号を比較して、増加していれば右回転(C.W)、減少していれば左回転(C.C.W)と判断する。この時、3→0と0→3は別にチェックしておく。

rotary_chart

プログラムは以下の通り。
プッシュスイッチの時と同じように変化点からのディレイでチャタリングを除去しているが、ディレイを長くするとツマミを高速で回した時に取りこぼしが発生してしまうので、時間を短くしてある。
プッシュスイッチよりはチャタリングの発生時間が短いらしく、これでも問題はなかった。ちなみに時間を計る為に使用している time.clock() はPythonのバージョン3.3で取り除かれてしまったので、3.3で試す時は別の時間計測関数を使う必要がある。 ※

※2013/03/16追記 : time.clock()が正しい値を返さない場合がある(原因不明)事が分かったので、time.time()に変更

#!/usr/bin/env python
# -*- coding:utf-8 -*-

import RPi.GPIO as GPIO
import time

class pin:
    def __init__(self, port):
        self.port = port
        self.dt = time.time()
        GPIO.setup(self.port, GPIO.IN, GPIO.PUD_OFF)
        self.stat = GPIO.input(self.port)
    def get(self):
        stat = GPIO.input(self.port)
        dt = time.time()
        if stat != self.stat and dt > self.dt + 0.01:
            self.stat = stat
            self.dt = dt
        return self.stat        

class rotary:
    def __init__(self, port_a, port_b, val, lo, hi):
        self.ports = [pin(port_a), pin(port_b)]
        self.val = val
        self.lo = lo
        self.hi = hi
        self.stat = 0
        self.sa = self.ports[0].get()
        self.sb = self.ports[1].get()
    def get(self):
        sa = self.ports[0].get()
        sb = self.ports[1].get()
        if sa != self.sa or sb != self.sb:
            self.sa = sa
            self.sb = sb
            if sa == GPIO.HIGH:
                if sb == GPIO.HIGH:
                    stat = 0    # H/H
                else:
                    stat = 3    # H/L
            else:
                if sb == GPIO.HIGH:
                    stat = 1    # L/H
                else:
                    stat = 2    # L/L
            if stat - self.stat == 1 or self.stat - stat == 3:    # RIGHT
                if self.val < self.hi:
                    self.val = self.val + 1
            elif self.stat - stat == 1 or stat - self.stat == 3:  # LEFT
                if self.val > self.lo:
                    self.val = self.val - 1
            self.stat = stat
        return self.val

if __name__ == "__main__":
    GPIO.setmode(GPIO.BCM)
    val = 25
    re = rotary(24, 25, val, 0, 50)
    try:
        while True:
            v = re.get()
            if(val != v):
                print '%02d' % v,'-' * (v - 1),'*','-' * (51 - v)
                val = v
    except KeyboardInterrupt:
        print '\nbreak'
        GPIO.cleanup()

ルート権限でプログラムを実行し、ロータリーエンコーダのつまみを左右に動かすと。以下のように表示されるはず。
rotary_out

チャタリング除去時間を短くしても、高速でつまみを回すと取りこぼしが発生する場合があったので、Pythonではこれ以上は難しいかも知れない。C言語を使って直接GPIOにアクセスすればもっと確実にデータを取得できるが、高速で回した時に1パルス分の取りこぼしも許されない用途というのはあまり考えられないし、その場合は別な手段を考えるべきだろう。

ロータリーエンコーダ」への2件のフィードバック

  1. 割り込み頻度が不規則になりがちだろうと思っていたので、
    ロータリーエンコーダの読み取りで「読み飛ばし」がバリバリ
    発生してしまうのではないかと思っていたんですが、ちゃんと
    拾えるんですね。

    ちなみに、プッシュボタンではなく、ロータリーエンコーダの
    場合は、チャタリングを起こしても位置情報が微小範囲(±1)
    でパタパタするだけで、致命的に位置情報をロストするような
    ことにはならないはずなので、ソフトウェア上で数値処理的
    にスレショルドを設けてあげれば、タイマは使わなくても
    「バタバタしない数値」が拾えるのではと思います。
    (分解能は多少落ちますが)

    テキスト文字がスクロールしている画面にビビビと反応して
    しまいました。
    昔のベーマガ時代によくあった、テキスト文字スクロールタイプ
    のゲームを思い出してしまって…

    • 実はロータリーエンコーダからの読み取りを試す時に、どこまでチャタリング
      除去ディレイを短くしても大丈夫なのか?というのも試していたんですが
      ゼロにしても全く問題なく読めてしまいました(笑)。
      読み取り間隔が遅いので、それだけでも大丈夫だったのではないかと思います。
      でもどのロータリーエンコーダでも大丈夫なのかは分からなかったので
      一応ディレイ有りとして書きました。

      ロータリーエンコーダを左右に動かすと画面がスクロールしていくテストは
      私もなんかBASICパソコン時代のゲームみたいだなぁと思ってました(笑)。

コメントを残す