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 かを指定する。設定は、簡単、なんて言う。
設定は簡単?いやいや、簡単なんて、わかっている人が言うセリフ。
ま、結局、次の1行でいけるとわかったのだった。
tim.init(mode=Timer.ONE_SHOT, period=1000, callback=mycallback)
オフィシャルな情報のどこを参照したら良いのか、Timer.PERIODIC 以外に
Timer.ONESHOT というものがあることがどこをみればわかるのか、今もまだわかっていない自分だった。
t.koba
この記事が気に入ったらサポートをしてみませんか?