Skip to content

第7章 割り込みとポーリングの比較

この章では、第6章と同じタクトスイッチとLEDの配線を使用し、 スイッチを押すたびにLEDの点灯と消灯を切り替えます。

同じ結果を、次の2つの方法で実現します。

  • プログラムから入力値を繰り返し確認するポーリング
  • 入力の変化をきっかけに関数が呼び出される割り込み

配線と動作結果をそろえて比較することで、 プログラム全体の動きの違いを確認します。

この章でできるようになること

Section titled “この章でできるようになること”
  • 押下時の1から0への変化を説明できる
  • ポーリングでスイッチの押下を検出できる
  • Pin.irq()でGPIOの割り込みを設定できる
  • コールバック関数とメインループの役割を説明できる
  • ポーリングと割り込みの違いを比較できる
  • チャタリング対策が必要な理由を説明できる
  • 割り込み処理を短く保つ理由を説明できる

第6章で完成した配線を、そのまま使用します。 この章で追加する部品や配線はありません。

分類使用するもの数量
制御用ボードピンヘッダー実装済みのRaspberry Pi Pico WまたはPico WH1
配線ブレッドボード1
入力部品一般的な4端子タクトスイッチ1
出力回路赤色LEDと330Ω抵抗1組
配線ジャンパーワイヤー第6章の配線
PC接続データ通信対応Micro USBケーブル1

第6章の図6-1 GP14のスイッチ入力とGP15のLED出力と 接続が一致していることを確認します。

GPIO接続入出力
GP14タクトスイッチを経由してGND内部プルアップ付き入力
GP15330Ω抵抗と赤色LEDを経由してGNDデジタル出力

この回路では、スイッチを離すとGP14が1、 押すと0になります。

配線を確認するときは、Pico WからUSBケーブルを外します。

第6章では、スイッチを押している間だけLEDを点灯しました。 この章では、スイッチを1回押すたびにLEDの状態を切り替えます。

操作LEDの変化
1回目に押す消灯から点灯へ切り替わる
2回目に押す点灯から消灯へ切り替わる
3回目に押す消灯から点灯へ切り替わる

押している時間ではなく、押した瞬間を1回として扱う必要があります。 そこで、GP14が1から0へ変化したことを検出します。

信号が変化する境目をエッジと呼びます。

信号の変化呼び方この回路での操作
0 → 1立ち上がりエッジスイッチを離す
1 → 0立ち下がりエッジスイッチを押す

最初に、ポーリングで立ち下がりを検出します。 次のプログラムをch07_01_polling_toggle.pyとしてPC側へ保存し、実行します。

ch07_01_polling_toggle.py
from machine import Pin
import time
SWITCH_PIN = 14
LED_PIN = 15
DEBOUNCE_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回ずつ切り替われば成功です。

if last_value == 1 and current_value == 0:

last_valueには前回読み取った値、 current_valueには今回読み取った値が入ります。

両方を比較することで、押している間の0ではなく、 1 → 0へ変化した瞬間だけを検出します。

now = time.ticks_ms()

ticks_ms()は、時間の経過をミリ秒単位で確認するときに使用します。 前回の押下からの経過時間は、ticks_diff()で求めます。

time.ticks_diff(now, last_press_ms) >= DEBOUNCE_MS

このプログラムでは、前回の押下から200ミリ秒未満の変化を無視します。 機械式スイッチのチャタリングによって、 1回の操作を複数回と判断することを抑えます。

割り込みは、あらかじめ指定した出来事が発生したときに、 通常の処理へ一時的に割り込んで、決められた関数を呼び出す仕組みです。

GPIOの入力変化に応じて呼び出される関数を、 この章では割り込みハンドラーまたはコールバック関数と呼びます。

switch.irq(trigger=Pin.IRQ_FALLING, handler=switch_pressed)

主な指定を確認します。

記述意味
switch.irq()switchへ割り込みを設定する
trigger=Pin.IRQ_FALLING1 → 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 = now

Pin.irq()から呼び出される関数は、 引数を1つ受け取る形で定義します。 ここでは、その引数をpinとしています。

globalは、関数の外で作成した変数へ 関数の中から値を代入するときに使用します。

割り込みハンドラーでは、LEDを直接切り替えず、 switch_eventTrueにします。 実際のLED制御はメインループが担当します。

割り込みハンドラーはいつ呼び出されるか分からないため、 必要な処理だけを短時間で行い、すぐに通常処理へ戻る構成にします。

ポーリング版を停止してから、 次のプログラムをch07_02_interrupt_toggle.pyとしてPC側へ保存し、実行します。

ch07_02_interrupt_toggle.py
from machine import Pin
import time
SWITCH_PIN = 14
LED_PIN = 15
DEBOUNCE_MS = 200
switch = Pin(SWITCH_PIN, Pin.IN, Pin.PULL_UP)
led = Pin(LED_PIN, Pin.OUT)
led.off()
switch_event = False
last_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の点灯と消灯が切り替われば成功です。

割り込みハンドラーがswitch_eventTrueにすると、 メインループがその値を確認してLEDを切り替えます。

if switch_event:
switch_event = False
led.toggle()

処理後にFalseへ戻すことで、 同じイベントを繰り返し処理しないようにします。

このメインループはGP14の入力値を直接読み続けていません。 割り込みハンドラーから受け取った、 「スイッチ操作があった」という情報を処理しています。

7-6 プログラム全体の動きを比較する

Section titled “7-6 プログラム全体の動きを比較する”

上段ではポーリング版がメインループからGP14を繰り返し読み、立ち下がりを検出してLEDを切り替える。下段ではメイン処理中にGP14の立ち下がりが発生すると割り込みハンドラーがイベントを記録し、メインループがLEDを切り替える。同じ配線と結果でも入力の検出方法が異なる。

図7-1 同じスイッチ操作をポーリングと割り込みで検出する流れ

比較項目ポーリング割り込み
入力を検出する側メインループGPIOの変化をきっかけにハンドラーが呼ばれる
入力値の確認GP14を繰り返し読む指定したエッジで反応する
メインループ入力の確認とLED制御を行う通知されたイベントを処理する
確認間隔プログラムで決める一定間隔でGP14を読む必要はない
短い入力確認間隔によっては見落とす指定した変化を検出しやすい
プログラム構造順番を追いやすいハンドラーとメインループの関係が増える

ポーリングと割り込みには、それぞれ適した場面があります。 割り込みを使えば常に優れている、という関係ではありません。

温湿度を10秒ごとに測定するような定期処理は、 ポーリングで分かりやすく作れます。 一方、いつ発生するか分からない短い入力を検出したい場合は、 割り込みが候補になります。

使用するデバイスが割り込み信号を出せるか、 プログラムがどの程度の反応速度を必要とするかも確認して選びます。

7-7 割り込み処理で注意すること

Section titled “7-7 割り込み処理で注意すること”

割り込みハンドラーは、通常の処理の途中で呼び出される可能性があります。 次の方針で、短く単純にします。

  • ハンドラーの中で長時間待たない
  • print()を繰り返さない
  • 複雑な計算や通信を行わない
  • イベントの発生を記録し、続きはメインループで処理する
  • メインループと共有する変数を必要最小限にする

この章ではPin.irq()の既定設定であるソフト割り込みを使用します。 hard=Trueを指定するハード割り込みには、 メモリー確保などに、より厳しい制約があります。 本研修では扱いません。

2つのプログラムで、チャタリングを無視する時間を変更します。

  1. DEBOUNCE_MS = 200DEBOUNCE_MS = 500へ変更する
  2. プログラムを実行する
  3. スイッチを素早く2回押し、反応を確認する
  4. ポーリング版と割り込み版の両方で試す
  5. 確認後、DEBOUNCE_MS = 200へ戻す

無視する時間を長くしすぎると、 意図した連続操作まで無視されます。 チャタリング対策の値は、使用するスイッチと必要な操作速度に合わせて決めます。

  • 第6章と同じ配線を使用できる
  • 押下が1 → 0の立ち下がりであると説明できる
  • ポーリング版で、押すたびにLEDを切り替えられる
  • 割り込み版で、押すたびにLEDを切り替えられる
  • Pin.IRQ_FALLINGの意味を説明できる
  • 割り込みハンドラーとメインループの役割を説明できる
  • 2つのプログラムの入力検出方法を比較できる
  • チャタリング対策の時間を変更して結果を確認できる
状況主な確認箇所
押してもLEDが切り替わらない第6章の配線、GP14、GP15、GND、実行中のプログラム
離したときに反応するPin.IRQ_FALLINGを使用しているか
1回で複数回切り替わるDEBOUNCE_MSticks_diff()の条件
素早く2回押すと2回目が反応しないDEBOUNCE_MSが長すぎないか
NameErrorが表示される変数名、関数名、globalの記述
TypeErrorが表示されるハンドラーがpin引数を1つ受け取る形か
割り込み版を止めた後も反応がおかしいThonnyの停止後にソフトリセットし、プログラムを再実行

配線を確認するときは、先にUSBケーブルを外します。

  • エッジは、デジタル信号が01の間で変化する境目です
  • この回路では、押下を1 → 0の立ち下がりとして検出します
  • ポーリングは、プログラム側から入力値を繰り返し確認します
  • 割り込みは、指定した入力変化をきっかけにハンドラーを呼び出します
  • Pin.irq()で割り込みの条件とハンドラーを登録します
  • 割り込みハンドラーは短く保ち、続きの処理をメインループへ渡します
  • 機械式スイッチでは、チャタリングによる連続反応を抑える必要があります
  • ポーリングと割り込みは、入力の特徴と必要な反応速度に応じて使い分けます

次の第8章では、可変抵抗の値をアナログ入力として読み取り、 PWM出力でLEDの明るさを変化させます。