🎃

ロータリーエンコーダー買ってみた

2023/05/05に公開

aliexpressで購入したロータリーエンコーダーは誤作動が多く使いにくかったので、先日共立電子で見かけたロータリーエンコーダーを購入しました。

追記

共立電子もクソアイテムを売ってやがります。
クソアイテムを掴まされて数時間を無駄にするよりも、多少高くても秋月電子で購入する方が良いです。

製品情報

  • アルプスアルパイン社
  • インクリメンタル型 25ステップ

kicad情報

シンボル
フットプリント

サンプルコード

クリックで展開
main.py
# main.py
from machine import Pin,Timer
import machine
import utime
from tm1637 import TM1637

ledPin = Pin(25,Pin.OUT)
ledPin.off()

aPin = Pin(28,Pin.IN,Pin.PULL_UP)
bPin = Pin(27,Pin.IN,Pin.PULL_UP)

preState = (1, 1) # aPinとbPinは初期状態では(HIGH,HIGH)
last = utime.ticks_ms()
direction:int = -1 #-1:左,1:右,0:回転なし

temp = 0

# TM1637
tmClkPin=Pin(19,Pin.IN)
tmDioPin=Pin(20,Pin.IN)
tm=TM1637(clk=tmClkPin,dio=tmDioPin)

# エンコーダーの状態変化時に呼ばれるコールバック関数
def encoder_callback(pin:Pin):
    global preState,count,direction,last,temp
    now=utime.ticks_ms()
    # チャタリング防止のため、前回割り込み時との時間差が10ms以下の時はアクションしない
    if utime.ticks_diff(now,last)<10:
        return
    # しばらく放置した後は、directionとpreStateを初期化する
    if utime.ticks_diff(now,last)>500:
        direction=0
        preState=(1,1)
    last = now

    curState = (aPin.value(),bPin.value())
    if preState==(0,0):
        if curState==(0,0):
            direction = direction+0
        elif curState==(0,1):
            direction = direction+1
        elif curState==(1,0):
            direction = direction-1
        elif curState==(1,1):
            return
    
    elif preState==(0,1):
        if curState==(0,0):
            direction = direction-1
        elif curState==(0,1):
            direction = direction+0
        elif curState==(1,0):
            return
        elif curState==(1,1):
            direction = direction+1
    
    elif preState==(1,0):
        if curState==(0,0):
            direction = direction+1
        elif curState==(0,1):
            return
        elif curState==(1,0):
            direction = direction+0
        elif curState==(1,1):
            direction = direction-1

    elif preState==(1,1):
        if curState==(0,0):
            return
        elif curState==(0,1):
            direction = direction-1
        elif curState==(1,0):
            direction = direction+1
        elif curState==(1,1):
            direction = direction+0
    
    preState = curState
    if direction<0:
        print('left')
        temp=temp-1
    elif direction>0:
        print('right')
        temp=temp+1
    print(temp)
    tm.number(temp)

aPin.irq(trigger=Pin.IRQ_FALLING|Pin.IRQ_RISING,handler=encoder_callback)
bPin.irq(trigger=Pin.IRQ_FALLING|Pin.IRQ_RISING,handler=encoder_callback)

解説

ロータリーエンコーダーからはAとB二つの端子が出ており、タクトスイッチと同様にHIGH or LOWのシグナルを取得できます。

プルアップ抵抗をつけてやる(ラズパイやarduinoなら内部プルアップ抵抗が使えます)点やチャタリング対策が必要な点などは、タクトスイッチと同じです。

回転した時に割り込みでコールバック関数を呼びたいので、aPinとbPinにはFalligとRisingでコールバック関数を呼ぶようにしました。

右回転か左回転を判別するには、直前のA相とB相の状態と今のA相とB相の状態を呼ぶ必要があります。

状態としては、(aピン,bピン)=(0,0)or(0,1)or(1,0)or(1,1)の4通り。直前状態と直後状態の組み合わせは4*4=16通りあります。
それぞれの場合において、右回転or左回転を判断してあげれば良いでしょう。(なお、(0,0)→(1,1)に移行するといった通常はありえない変化も含みます)

改良

実際はそれほど綺麗な変化をするわけではなく、動作も100%安定ではありません

そこで、「右回りor左回りのパターンを3回検知したら、右回りor左回りを1カウントする」というふうに改良しました。
これだと、反応性は多少悪くなりますが、誤動作をする確率はグッと減ります。

改良版
main.py
# main.py
from machine import Pin
import utime
from tm1637 import TM1637

aPin = Pin(28,Pin.IN,Pin.PULL_UP)
bPin = Pin(27,Pin.IN,Pin.PULL_UP)

# TM1637
tmClkPin=Pin(19,Pin.IN)
tmDioPin=Pin(20,Pin.IN)
tm=TM1637(clk=tmClkPin,dio=tmDioPin)

def encoder_callback(pin:Pin):
    global preState,count,direction,last,temp
    now=utime.ticks_ms()
    # チャタリング防止のため、前回割り込み時との時間差が10ms以下の時はアクションしない
    if utime.ticks_diff(now,last)<10:
        return
    # しばらく放置した後は、directionとpreStateを初期化する
    if utime.ticks_diff(now,last)>500:
        direction=0
        preState=(1,1)
    last = now

    curState = (aPin.value(),bPin.value())
    if preState==(0,0):
        if curState==(0,0):
            direction = direction+0
        elif curState==(0,1):
            direction = direction+1
        elif curState==(1,0):
            direction = direction-1
        elif curState==(1,1):
            return
    
    elif preState==(0,1):
        if curState==(0,0):
            direction = direction-1
        elif curState==(0,1):
            direction = direction+0
        elif curState==(1,0):
            return
        elif curState==(1,1):
            direction = direction+1
    
    elif preState==(1,0):
        if curState==(0,0):
            direction = direction+1
        elif curState==(0,1):
            return
        elif curState==(1,0):
            direction = direction+0
        elif curState==(1,1):
            direction = direction-1

    elif preState==(1,1):
        if curState==(0,0):
            return
        elif curState==(0,1):
            direction = direction-1
        elif curState==(1,0):
            direction = direction+1
        elif curState==(1,1):
            direction = direction+0
    
    preState = curState
    if direction<0:
        print('left')
        temp=temp-1
    elif direction>0:
        print('right')
        temp=temp+1
    print(temp)
    tm.number(temp)

'''
def encoderToggle(_):
    global count, preState
    curState = (aPin.value(),bPin.value())
    if preState == curState:
        pass
    if curState == (1,1):count = count + 1
    elif curState == (1,0):count = count - 1
    elif curState == (0,1):count = count - 1
    elif curState == (0,0):count = count + 1
    print(f'{curState},{count}')
    preState = curState
'''

rightPatterns:tuple = (
    ((1,1),(1,0),(0,0)),
    ((1,0),(0,0),(0,1)),
    ((0,0),(0,1),(1,1)),
    ((0,1),(1,1),(1,0))
)

leftPatterns:tuple = (
    ((1,1),(0,1),(0,0)),
    ((0,1),(0,0),(1,0)),
    ((0,0),(1,0),(1,1)),
    ((1,0),(1,1),(0,1))
)

count:int = 0
prepre:tuple = (0,0)
pre:tuple = (0,0)

def encoderToggle(_):
    global count, prepre,pre
    cur = (aPin.value(),bPin.value())
    if cur == pre:
        return
    pattern = (prepre,pre,cur)
    if pattern in leftPatterns:
        count = count-1
        print('left')
    elif pattern in rightPatterns:
        count = count+1
        print('right')
    prepre = pre
    pre = cur
    tm.number(count)


aPin.irq(trigger=Pin.IRQ_FALLING|Pin.IRQ_RISING,handler=encoderToggle)
bPin.irq(trigger=Pin.IRQ_FALLING|Pin.IRQ_RISING,handler=encoderToggle)

参考

http://elm-chan.org/docs/tec/te04.html

Discussion