はじめに
前回は、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
↓
待機状態に戻る
配線・使用部品
前回から不変です。下記参照ください。

スイッチを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機器
でも使われる基本的な考え方ですので記憶の片隅に残しましょう。
コメント