Raspberry Pi Picoのノンブロッキング処理とは?time.sleepとの違いを初心者向けに解説

目次

はじめに

前回は、Raspberry Pi Picoを使って、

1回スイッチを押すだけで、A → Bの順番に所定の時間LEDを点灯させる

ところまで作りました。最終的な目的は、LEDではなくリレーモジュールを使い、外部の12V回路を順番に通電させることです。ただし、前回のコードには実用上の大きな課題があります。それは、

time.sleep()でpicoの処理が止まってしまう。つまり所定の時間、プログラムが動けない状態になっている
という点です。そこで今回は

  • 状態遷移
  • ノンブロッキング処理
  • キャンセル処理

を導入します。

前回コードの問題点

前回は以下のような処理でした。

led_a.on()
time.sleep(5)
led_a.off()

led_b.on()
time.sleep(3)
led_b.off()

これは非常にわかりやすいです。しかし、time.sleep(5)の間、Picoは基本的に待っているだけです。
つまり、その5秒間は、

  • スイッチ入力を見られない
  • キャンセル操作を作ったとしてもを受け付けられない
  • 他の処理ができない

という問題があります。実験なら問題ありませんが、実用回路としては何かあっても止められないため危険です。

ノンブロッキングとは?

ノンブロッキングとは、

待ち時間中でも処理全体を止めない考え方

time.sleep()で止めるのではなく、

現在時刻を見て、必要な時間が経過したかを判断する

Raspberry Pi PicoのMicroPythonでは、主に time.ticks_ms() を使えば実現できます。

start_time = time.ticks_ms()

if time.ticks_diff(time.ticks_ms(), start_time) >= 5000:
    print("5秒経過")

このようにすると、5秒経過を確認しながら、同時にスイッチ入力も監視できます。

状態遷移とは?

状態遷移とは、

今この装置が何をしている途中なのかを分けて管理する考え方

今回なら、装置の状態は次のように分けられます。

待機中

AをON中

AとBの間の待ち時間

BをON中

完了

待機中へ戻る

これをプログラム上では、state という変数で管理することにしました。

今回の動作仕様

今回は以下の動作にしました。

スタートスイッチを押す

Aを5秒ON

AをOFF

1秒待つ

Bを3秒ON

BをOFF

待機状態に戻る

さらに、途中でキャンセルスイッチを押したら、

AもBも即OFF

待機状態に戻る

配線・使用部品

前回から不変です。下記参照ください。

あわせて読みたい
Raspberry Pi Picoでリレーを順番制御|スイッチ1回で複数操作を自動化 はじめに 知人から、少し面白い相談を受けました。とある機器を特殊なモードへ入れるために、 スイッチAを押す(通電する) 数秒保持 次にスイッチBを押す(通電する) さら...

スイッチを2回押すと動作をキャンセルするコード

from machine import Pin
import time

button = Pin(15, Pin.IN, Pin.PULL_UP)

led_a = Pin(16, Pin.OUT)
led_b = Pin(17, Pin.OUT)

led_a.off()
led_b.off()

STATE_WAIT = 0
STATE_A_ON = 1
STATE_B_ON = 2

state = STATE_WAIT

last_button = 1
start_time = 0


def stop_all():
    led_a.off()
    led_b.off()


while True:
    current_button = button.value()
    now = time.ticks_ms()

    # 押された瞬間を検出
    button_pressed = last_button == 1 and current_button == 0

    if button_pressed:
        time.sleep(0.02)  # チャタリング対策

        if button.value() == 0:

            # 待機中なら開始
            if state == STATE_WAIT:
                print("START")

                led_a.on()
                led_b.off()

                start_time = time.ticks_ms()
                state = STATE_A_ON

            # 動作中ならキャンセル
            else:
                print("CANCEL")

                stop_all()
                state = STATE_WAIT

    # Aを5秒ON
    if state == STATE_A_ON:
        if time.ticks_diff(now, start_time) >= 5000:
            print("A OFF, B ON")

            led_a.off()
            led_b.on()

            start_time = time.ticks_ms()
            state = STATE_B_ON

    # Bを10秒ON
    elif state == STATE_B_ON:
        if time.ticks_diff(now, start_time) >= 10000:
            print("END")

            stop_all()
            state = STATE_WAIT

    last_button = current_button
    time.sleep(0.01)

このコードのポイント

1. 1つのボタンで「開始」と「キャンセル」を兼用している

今回のコードでは、スタート用とキャンセル用でスイッチを分けていません。
つまり、同じボタンでも状態によって役割が変わります。

if state == STATE_WAIT:
# 待機中なら開始
else:
# 動作中ならキャンセル
2. 「押された瞬間」だけを検出している
button_pressed = last_button == 1 and current_button == 0

これは、前回は押されていない、今回は押されているという変化を見ています。
プルアップ入力なので、

  • 押していない:1
  • 押した:0

そのため、1 → 0 に変化した瞬間だけを「押された」と判断しています。
これにより、押しっぱなしで何度もSTART/CANCELが発生するのを防げます。

3. stateで現在の動作段階を管理している
STATE_WAIT = 0
STATE_A_ON = 1
STATE_B_ON = 2

今回の状態は3つです。プログラムは常に、”今どの状態か?”を見ながら次の動作を決めています。

4. time.sleep()で長時間止めていない

Aを5秒ON、Bを10秒ONにしていますが、

time.sleep(5)
time.sleep(10)

とは書いていません。代わりに、

time.ticks_diff(now, start_time)

で経過時間を確認しています。この方法なら、AやBがONしている途中でもボタン入力を確認できます。だから、動作中にもう一度ボタンを押すとキャンセルできます。

5. stop_all()で安全に全出力をOFFにしている
def stop_all():
led_a.off()
led_b.off()

キャンセル時や終了時には、この関数を呼び出しています。

stop_all()
state = STATE_WAIT

出力を止めてから待機状態へ戻すので、安全側の処理になります。将来的にLEDをリレーモジュールへ置き換える場合も、この考え方は重要です。

まとめ

今回は、前回の順番制御を実用寄りに改善しました。重要なのは次の3つです。

考え方内容
状態遷移今どの動作中かを管理する
ノンブロッキング待ち時間中も処理を止めない
キャンセル処理途中でも安全に停止できる

最初は少し難しく見えますが、これは電子工作だけでなく、

  • 家電制御
  • 自動車制御
  • 産業機器
  • ロボット
  • IoT機器

でも使われる基本的な考え方ですので記憶の片隅に残しましょう。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

コメント

コメントする

目次