見出し画像

Raspberry Pi Pico 学習記録 タイマーを使った長押し判定に挑戦

課題

プッシュスイッチの長押し操作をタイマーを使って処理する

おさらい

もうだいぶ前になってしまったが、発売されて間もない頃に Raspberry Pi Pico を入手して、プッシュスイッチの操作を処理するプログラムを書いた。

次に、メニューを表示して設定を変更したいと思った。設定値が消えないようにもしたかった。

そこで、16文字x2行のキャラクターディスプレイを実装したり、EEPROM を実装してみたりした。

なんとか必要な機能は実現できた。

しかし、数値を変えようとプッシュスイッチをポチポチ押しているとき、やっていられないと思った。そこで、ロータリーエンコーダーを使うことを考えた。素早く回転したときに取りこぼしてしまうことを除けば、ロータリーエンコーダーを使うことはそれほど難しくはなかった。

しかし、ブレッドボードではなく、何かしら形にしようと考えたときに、ロータリーエンコーダーは少し大袈裟な気がした。

そこで、プッシュスイッチの長押しをやってみることにした。

すぐには長押しの実現方法が思いつかなかったために、今になってしまったのだ。

長押しの検出方法

スイッチを押してからの時間を計る方法もあると思うが、どうせやるならとタイマーを使うことにした。

環境

Raspberry Pi Pico & Thonny & MicroPython

用意したもの

ブレッドボード
Raspberry Pi Pico
USBケーブル
タクトスイッチ2個(プッシュスイッチ)
抵抗2個
セラミックコンデンサー2個

抵抗とコンデンサーの定数は前の記事に書いたとおり。表示部は、省略。

コード

#スイッチ長押し

#スイッチの操作を割り込みで処理
#スイッチを押したらすぐに反映
#チャタリングと思われる割り込みは無視
#長押しに対応

import machine
from machine import Timer,Pin
import utime
import time

tim = Timer()

def callback(pin):
    global sw
    global irq_flags
    
    state = machine.disable_irq()
    irq_flags = pin.irq().flags()
    sw = pin
    machine.enable_irq(state)

def mycallback(t):
    global long_press
    long_press = True
    
def main():
    global sw
    global irq_flags
    global long_press
    
    sw = None
    long_press = False
    
    value = 127
    
    #Switch1-
    sw1 = Pin(0, Pin.IN, Pin.PULL_DOWN)
    #Switch2+
    sw2 = Pin(1, Pin.IN, Pin.PULL_DOWN)

    red = Pin(12, Pin.OUT)
    yellow = Pin(13, Pin.OUT)
    green = Pin(14, Pin.OUT)    
    
    #割り込みの設定
    sw1.irq(trigger=Pin.IRQ_RISING|Pin.IRQ_FALLING, handler=callback)
    sw2.irq(trigger=Pin.IRQ_RISING|Pin.IRQ_FALLING, handler=callback)

    #割り込み処理をしない時間
    interval = 50000
    
    #割り込みのタイミングの基準
    ref_time = utime.ticks_us()
    
    while True:
        
        #割り込み発生
        if sw and irq_flags == 8:
            sw_value = sw.value()
            time_diff = utime.ticks_diff(utime.ticks_us(), ref_time)
            
            if time_diff > interval:#チャタリング防止のための時間経過後
                if sw_value == 1:
                    if long_press == False:
                        tim.init(mode=Timer.ONE_SHOT, period=1000, callback=mycallback)
                        print('Timer Started.')
                    if sw1.value() == 1:
                        if value > 0:
                            value += -1
                    elif sw2.value() == 1:
                        if value < 255:
                            value += 1
            else:
                print('... Too fast ...')
                                
            sw = None
            ref_time = utime.ticks_us()
            
        elif sw and irq_flags == 4:
            #チャタリングだったとしても、スイッチが解除されたらリセット
            long_press = False
            print('Timer Stopped')
            tim.deinit()
            sw = None
            
        while long_press == True:
            print('LONG PRESS PROCESS', sw1.value(), sw2.value(), value, utime.ticks_diff(utime.ticks_us(), ref_time))
            if sw1.value() == 1 and sw2.value() == 1:
                value = 127
                long_press = False
            else:
                if sw1.value() == 1:
                    if value > 0:
                        value += -1
                elif sw2.value() == 1:
                    if value < 255:
                        value += 1
            time.sleep(0.01)
                            
        print(value, utime.ticks_diff(utime.ticks_us(), ref_time))
        time.sleep(0.01)
                        

if __name__ == "__main__":
    main()
    

コードの覚書

割り込みの設定
長押しとは直接関係なく、スイッチの操作を知るために必要。

 sw1.irq(trigger=Pin.IRQ_RISING|Pin.IRQ_FALLING, handler=callback)
 sw2.irq(trigger=Pin.IRQ_RISING|Pin.IRQ_FALLING, handler=callback)

Rising Edge と Falling Edge の両方でトリガーを掛ける。
トリガーがあったら、callback という名称の関数?を呼び出す。

callback 関数は、次のように作った。

def callback(pin):
    global sw
    global irq_flags
    
    state = machine.disable_irq()
    irq_flags = pin.irq().flags()
    sw = pin
    machine.enable_irq(state)

sw および irq_flags という変数をグローバル変数として定義。
state = の行と最後の行は、割り込み後に状態を復元するためのコード。正直、何をやっているかはよくわかっていない。
callback 関数の引数として pin。pin は、割り込みのフラグを要素として持っている。フラグといっても、Rising Edge かとか Falling Edge かとかがわかる程度のもの。pin はもちろん、どのピンかという情報も持っている。
ここでは、それぞれ irq_flags および sw という変数に収めておく。
もしかしたら、変数への代入は、まったく意味のないことかもしれないと思う。

タイマーの割り込み

タイマーを設定するのは、次の1行。

tim.init(mode=Timer.ONE_SHOT, period=1000, callback=mycallback)

Timer.ONE_SHOT で指定した時間が経過したときに、1回だけ割り込みを掛ける。
period は 1000 msec とした。これでスイッチを押してから1秒経ったら長押しと判定するようにできる。
タイマーの割り込みがあったら、mycallback という関数を呼び出す。

mycallback 関数は、次のようにした。

def mycallback(t):
    global long_press
    long_press = True

長押しという意味で、long_press というグローバル変数を定義。
この関数は、その変数を True に設定するだけ。

main() について書く。

グローバル変数や初期値を設定。
スイッチ1およびスイッチ2の定義
red, yellow, green の定義は消し忘れた。これらは不使用。
スイッチの割り込みの設定。

while True: からのコード

スイッチが押されると割り込みが発生し、callback 関数が呼び出される。この時、irq_flags にフラグが取り込まれる。

irq_flags = pin.irq().flags()

スイッチを接続した入力端子を、プルダウンと定義した。よって、押されたら L > H に変化し、離したら H > L に戻る。
irq_flags は
IRQ_RISING の場合:8
IRQ_FALLING の場合:4

irq_flags が 8 のときは、スイッチが押されたということなので、変数 long_press をみて長押し状態かそうでないかを確認する。
long_press が  False の場合は、長押しではないので、長押し検出用にタイマーを起動する。

tim.init(mode=Timer.ONE_SHOT, period=1000, callback=mycallback)

スイッチは2つ用意した。1つは、数値を減らす「マイナススイッチ」、もう1つは、数値を増やす「プラススイッチ」だ。
タイマー起動と同時に、数値も増減する。
プラススイッチの場合、数字を増やし、マイナススイッチの場合は、数字を減らす。
数値の範囲を 0 - 255 に制限した。

irq_flags が 4 のときは、スイッチが離されたということになるので、タイマーを解除する。

tim.deinit()

タイマーを解除するときは、チャタリングかどうかは気にする必要はないみたいだった。

長押しで数値を連続的に変化

長押しとはどういうことかと考える。
長押しとは、ある一定の時間押し続けると、スイッチを離すまで、数値が連続的に変化するということだ。
そこで、long_press が True の場合、数値を増やし続けるか、減らし続けるか、やめるように言われるまでループする。

while long_press == True:

やめ時は、数値が上限や下限に達した時、または、スイッチから指を離したときである。
ただし、上限や下限に達した場合は、ループから抜けないで、数値の変化が止まるだけである。
スイッチから指を離したという割り込みが掛かった時に初めてループを抜ける。それは、long_press が False になったときである。irq_flags が 4 のとき、long_press は False になる。
この連続的数値変化のループでは、0.01  秒のスリープを入れている。
while ループにも、0.01 秒のスリープを入れて回している。

理由は、やってみたらスリープを入れないとうまく動かなかったからである。本当は、何かやり方があるのかもしれない。

説明を書いていて気づいたのだが、同時押しでデフォルト値にセットする仕込みもやっていた。

if sw1.value() == 1 and sw2.value() == 1:
                value = 127

Shell に表示される情報

タイマー割り込みのエッセンスだけにしたかったので、表示素子を省略した。結果、スイッチを押しても反応がない。そこで、Shell に情報を表示するようにした。

何もしていないとき
128 982186

1項目目が、数値。2項目目が、スイッチを押してからの時間経過?

長押しを検知したとき
LONG PRESS PROCESS 0 1 128 1003574

LONG PRESS PROCESS で長押しの処理中であることを表示。
次の2つが、スイッチ1とスイッチ2の状態。
次が、数値で、その次が、押してからの時間経過?

問題点

問題はいろいろあると思う。これが正解とは思っていない。

今見たら、長押しモード中は、プラスのスイッチからマイナスのスイッチに変えても、長押しモードが続いているようで、増減を反転しても高速で数字が変化する。
ま、それもありかと。

後書き

独学なので、正解がわからない。

ネットで情報を探すが、まずLチカみたいないやつばかりが出てくる。
自分はLチカをやりたいわけじゃない。Lチカで終わるつもりはないのである。で、情報を見つけ出すのに苦労する。

まずは、Raspberry Pi Pico とか タイマー で検索する。

検索ワードに MicroPython を追加したら知りたい情報が出てくるのに、今になって気づいた。

タイマーを使ったLチカの場合は、タイマーを周期的に起動することになるわけで、自分の目的には合わない。

timer.init(freq=2, mode=Timer.PERIODIC, callback=tica2)

mode=Timer.PERIODIC と書いてあるが、それが「周期的」という定義だ。

あちらこちらを探し回って、別の ONESHOT というやつをみつけた。

mode Timer.ONESHOTまたはTimer,PERIODIC という記述を見つける。

この記事では lambda 式を使っているが、自分がやろうとしていることには応用できないようだった。多分。

設定は簡単、以下のような感じで周波数を設定し、PERIODICかONE_SHOTか指定してcallback関数を登録すれば、即座にタイマを使えます。

と言っている。

PERIODIC か ONE_SHOT かを指定する。設定は、簡単、なんて言う。

設定は簡単?いやいや、簡単なんて、わかっている人が言うセリフ。

ま、結局、次の1行でいけるとわかったのだった。

tim.init(mode=Timer.ONE_SHOT, period=1000, callback=mycallback)

オフィシャルな情報のどこを参照したら良いのか、Timer.PERIODIC 以外に
 Timer.ONESHOT  というものがあることがどこをみればわかるのか、今もまだわかっていない自分だった。

t.koba

この記事が気に入ったらサポートをしてみませんか?