第7章 割り込みとポーリングの比較
この章の目的
Section titled “この章の目的”この章では、第6章と同じタクトスイッチとLEDの配線を使用し、 スイッチを押すたびにLEDの点灯と消灯を切り替えます。
同じ結果を、次の2つの方法で実現します。
- プログラムから入力値を繰り返し確認するポーリング
- 入力の変化をきっかけに関数が呼び出される割り込み
配線と動作結果をそろえて比較することで、 プログラム全体の動きの違いを確認します。
この章でできるようになること
Section titled “この章でできるようになること”- 押下時の
1から0への変化を説明できる - ポーリングでスイッチの押下を検出できる
Pin.irq()でGPIOの割り込みを設定できる- コールバック関数とメインループの役割を説明できる
- ポーリングと割り込みの違いを比較できる
- チャタリング対策が必要な理由を説明できる
- 割り込み処理を短く保つ理由を説明できる
使用するもの
Section titled “使用するもの”第6章で完成した配線を、そのまま使用します。 この章で追加する部品や配線はありません。
| 分類 | 使用するもの | 数量 |
|---|---|---|
| 制御用ボード | ピンヘッダー実装済みのRaspberry Pi Pico WまたはPico WH | 1 |
| 配線 | ブレッドボード | 1 |
| 入力部品 | 一般的な4端子タクトスイッチ | 1 |
| 出力回路 | 赤色LEDと330Ω抵抗 | 1組 |
| 配線 | ジャンパーワイヤー | 第6章の配線 |
| PC接続 | データ通信対応Micro USBケーブル | 1 |
開始前の確認
Section titled “開始前の確認”第6章の図6-1 GP14のスイッチ入力とGP15のLED出力と 接続が一致していることを確認します。
| GPIO | 接続 | 入出力 |
|---|---|---|
GP14 | タクトスイッチを経由してGND | 内部プルアップ付き入力 |
GP15 | 330Ω抵抗と赤色LEDを経由してGND | デジタル出力 |
この回路では、スイッチを離すとGP14が1、
押すと0になります。
配線を確認するときは、Pico WからUSBケーブルを外します。
7-1 押した瞬間を検出する
Section titled “7-1 押した瞬間を検出する”第6章では、スイッチを押している間だけLEDを点灯しました。 この章では、スイッチを1回押すたびにLEDの状態を切り替えます。
| 操作 | LEDの変化 |
|---|---|
| 1回目に押す | 消灯から点灯へ切り替わる |
| 2回目に押す | 点灯から消灯へ切り替わる |
| 3回目に押す | 消灯から点灯へ切り替わる |
押している時間ではなく、押した瞬間を1回として扱う必要があります。
そこで、GP14が1から0へ変化したことを検出します。
信号が変化する境目をエッジと呼びます。
| 信号の変化 | 呼び方 | この回路での操作 |
|---|---|---|
0 → 1 | 立ち上がりエッジ | スイッチを離す |
1 → 0 | 立ち下がりエッジ | スイッチを押す |
7-2 ポーリング版を実行する
Section titled “7-2 ポーリング版を実行する”最初に、ポーリングで立ち下がりを検出します。
次のプログラムをch07_01_polling_toggle.pyとしてPC側へ保存し、実行します。
from machine import Pinimport time
SWITCH_PIN = 14LED_PIN = 15DEBOUNCE_MS = 200
switch = Pin(SWITCH_PIN, Pin.IN, Pin.PULL_UP)led = Pin(LED_PIN, Pin.OUT)led.off()
last_value = switch.value()last_press_ms = None
while True: current_value = switch.value()
if last_value == 1 and current_value == 0: now = time.ticks_ms()
if ( last_press_ms is None or time.ticks_diff(now, last_press_ms) >= DEBOUNCE_MS ): led.toggle() last_press_ms = now
last_value = current_value time.sleep(0.01)スイッチを短く押すたびに、外付けLEDの点灯と消灯が 1回ずつ切り替われば成功です。
直前の値と比較する
Section titled “直前の値と比較する”if last_value == 1 and current_value == 0:last_valueには前回読み取った値、
current_valueには今回読み取った値が入ります。
両方を比較することで、押している間の0ではなく、
1 → 0へ変化した瞬間だけを検出します。
チャタリングの影響を抑える
Section titled “チャタリングの影響を抑える”now = time.ticks_ms()ticks_ms()は、時間の経過をミリ秒単位で確認するときに使用します。
前回の押下からの経過時間は、ticks_diff()で求めます。
time.ticks_diff(now, last_press_ms) >= DEBOUNCE_MSこのプログラムでは、前回の押下から200ミリ秒未満の変化を無視します。 機械式スイッチのチャタリングによって、 1回の操作を複数回と判断することを抑えます。
7-3 割り込みとは
Section titled “7-3 割り込みとは”割り込みは、あらかじめ指定した出来事が発生したときに、 通常の処理へ一時的に割り込んで、決められた関数を呼び出す仕組みです。
GPIOの入力変化に応じて呼び出される関数を、 この章では割り込みハンドラーまたはコールバック関数と呼びます。
switch.irq(trigger=Pin.IRQ_FALLING, handler=switch_pressed)主な指定を確認します。
| 記述 | 意味 |
|---|---|
switch.irq() | switchへ割り込みを設定する |
trigger=Pin.IRQ_FALLING | 1 → 0の立ち下がりで割り込みを発生させる |
handler=switch_pressed | 割り込み時にswitch_pressed()を呼び出す |
handlerへ指定する関数名には、丸括弧を付けません。
ここでは関数をその場で実行するのではなく、
呼び出す関数を割り込み設定へ登録しています。
7-4 割り込みハンドラーの役割
Section titled “7-4 割り込みハンドラーの役割”この章の割り込みハンドラーは、 スイッチ操作があったことを変数へ記録します。
def switch_pressed(pin): global switch_event, last_irq_ms
now = time.ticks_ms() if ( last_irq_ms is None or time.ticks_diff(now, last_irq_ms) >= DEBOUNCE_MS ): switch_event = True last_irq_ms = nowPin.irq()から呼び出される関数は、
引数を1つ受け取る形で定義します。
ここでは、その引数をpinとしています。
globalは、関数の外で作成した変数へ
関数の中から値を代入するときに使用します。
割り込みハンドラーでは、LEDを直接切り替えず、
switch_eventをTrueにします。
実際のLED制御はメインループが担当します。
割り込みハンドラーはいつ呼び出されるか分からないため、 必要な処理だけを短時間で行い、すぐに通常処理へ戻る構成にします。
7-5 割り込み版を実行する
Section titled “7-5 割り込み版を実行する”ポーリング版を停止してから、
次のプログラムをch07_02_interrupt_toggle.pyとしてPC側へ保存し、実行します。
from machine import Pinimport time
SWITCH_PIN = 14LED_PIN = 15DEBOUNCE_MS = 200
switch = Pin(SWITCH_PIN, Pin.IN, Pin.PULL_UP)led = Pin(LED_PIN, Pin.OUT)led.off()
switch_event = Falselast_irq_ms = None
def switch_pressed(pin): global switch_event, last_irq_ms
now = time.ticks_ms() if ( last_irq_ms is None or time.ticks_diff(now, last_irq_ms) >= DEBOUNCE_MS ): switch_event = True last_irq_ms = now
switch.irq(trigger=Pin.IRQ_FALLING, handler=switch_pressed)
while True: if switch_event: switch_event = False led.toggle()
time.sleep(0.01)スイッチを短く押すたびに、ポーリング版と同じように LEDの点灯と消灯が切り替われば成功です。
メインループの役割
Section titled “メインループの役割”割り込みハンドラーがswitch_eventをTrueにすると、
メインループがその値を確認してLEDを切り替えます。
if switch_event: switch_event = False led.toggle()処理後にFalseへ戻すことで、
同じイベントを繰り返し処理しないようにします。
このメインループはGP14の入力値を直接読み続けていません。 割り込みハンドラーから受け取った、 「スイッチ操作があった」という情報を処理しています。
7-6 プログラム全体の動きを比較する
Section titled “7-6 プログラム全体の動きを比較する”図7-1 同じスイッチ操作をポーリングと割り込みで検出する流れ
| 比較項目 | ポーリング | 割り込み |
|---|---|---|
| 入力を検出する側 | メインループ | GPIOの変化をきっかけにハンドラーが呼ばれる |
| 入力値の確認 | GP14を繰り返し読む | 指定したエッジで反応する |
| メインループ | 入力の確認とLED制御を行う | 通知されたイベントを処理する |
| 確認間隔 | プログラムで決める | 一定間隔でGP14を読む必要はない |
| 短い入力 | 確認間隔によっては見落とす | 指定した変化を検出しやすい |
| プログラム構造 | 順番を追いやすい | ハンドラーとメインループの関係が増える |
ポーリングと割り込みには、それぞれ適した場面があります。 割り込みを使えば常に優れている、という関係ではありません。
温湿度を10秒ごとに測定するような定期処理は、 ポーリングで分かりやすく作れます。 一方、いつ発生するか分からない短い入力を検出したい場合は、 割り込みが候補になります。
使用するデバイスが割り込み信号を出せるか、 プログラムがどの程度の反応速度を必要とするかも確認して選びます。
7-7 割り込み処理で注意すること
Section titled “7-7 割り込み処理で注意すること”割り込みハンドラーは、通常の処理の途中で呼び出される可能性があります。 次の方針で、短く単純にします。
- ハンドラーの中で長時間待たない
print()を繰り返さない- 複雑な計算や通信を行わない
- イベントの発生を記録し、続きはメインループで処理する
- メインループと共有する変数を必要最小限にする
この章ではPin.irq()の既定設定であるソフト割り込みを使用します。
hard=Trueを指定するハード割り込みには、
メモリー確保などに、より厳しい制約があります。
本研修では扱いません。
変更してみる
Section titled “変更してみる”2つのプログラムで、チャタリングを無視する時間を変更します。
DEBOUNCE_MS = 200をDEBOUNCE_MS = 500へ変更する- プログラムを実行する
- スイッチを素早く2回押し、反応を確認する
- ポーリング版と割り込み版の両方で試す
- 確認後、
DEBOUNCE_MS = 200へ戻す
無視する時間を長くしすぎると、 意図した連続操作まで無視されます。 チャタリング対策の値は、使用するスイッチと必要な操作速度に合わせて決めます。
- 第6章と同じ配線を使用できる
- 押下が
1 → 0の立ち下がりであると説明できる - ポーリング版で、押すたびにLEDを切り替えられる
- 割り込み版で、押すたびにLEDを切り替えられる
-
Pin.IRQ_FALLINGの意味を説明できる - 割り込みハンドラーとメインループの役割を説明できる
- 2つのプログラムの入力検出方法を比較できる
- チャタリング対策の時間を変更して結果を確認できる
よくある問題と確認方法
Section titled “よくある問題と確認方法”| 状況 | 主な確認箇所 |
|---|---|
| 押してもLEDが切り替わらない | 第6章の配線、GP14、GP15、GND、実行中のプログラム |
| 離したときに反応する | Pin.IRQ_FALLINGを使用しているか |
| 1回で複数回切り替わる | DEBOUNCE_MS、ticks_diff()の条件 |
| 素早く2回押すと2回目が反応しない | DEBOUNCE_MSが長すぎないか |
NameErrorが表示される | 変数名、関数名、globalの記述 |
TypeErrorが表示される | ハンドラーがpin引数を1つ受け取る形か |
| 割り込み版を止めた後も反応がおかしい | Thonnyの停止後にソフトリセットし、プログラムを再実行 |
配線を確認するときは、先にUSBケーブルを外します。
- エッジは、デジタル信号が
0と1の間で変化する境目です - この回路では、押下を
1 → 0の立ち下がりとして検出します - ポーリングは、プログラム側から入力値を繰り返し確認します
- 割り込みは、指定した入力変化をきっかけにハンドラーを呼び出します
Pin.irq()で割り込みの条件とハンドラーを登録します- 割り込みハンドラーは短く保ち、続きの処理をメインループへ渡します
- 機械式スイッチでは、チャタリングによる連続反応を抑える必要があります
- ポーリングと割り込みは、入力の特徴と必要な反応速度に応じて使い分けます
- MicroPython公式「machine.Pin」(v1.26.0)
- MicroPython公式「割り込みハンドラーの作成」(v1.26.0)
- MicroPython公式「time」(v1.26.0)
次の第8章では、可変抵抗の値をアナログ入力として読み取り、 PWM出力でLEDの明るさを変化させます。