タイマー割り込みによるLチカ再入門

MicroPython
タイマー割り込みによるLチカ再入門

Raspberry Pi Pico(Pico W や Pico 2 も含む)を買ってみて、とりあえずLチカはできたけれど、「この先どう発展させればいいんだろう?」と感じたことはありませんか?

私も最初は、MicroPythonでLEDをチカチカさせて満足していましたが、だんだん「ずっと while True でLEDだけ点滅させているのはもったいないな」と思うようになりました。

そんなときに便利なのがタイマー割り込みです。タイマー割り込みを使うと、メイン処理とは別のところでLEDを自動的に点滅させることができます。しかも、Raspberry Pi Pico / Pico W / Pico 2 どれでもほぼ同じコードで動かせます。

この記事では、

  • 割り込みなしのLチカ
  • タイマー割り込みで直接LEDをトグルするLチカ
  • フラグ方式で安全に扱うタイマー割り込みLチカ
  • 割り込みの基本ルールと3パターンの比較

という流れで、MicroPythonでタイマー割り込みによるLチカを初心者向けに分かりやすく解説します。

まずは実際にどういう動きをするかイメージしておきましょう。LED(発光ダイオード)がゆっくり点灯・消灯を繰り返して「チカチカ」します。

視覚的にパチッと反応があるので、初めての電子工作でも手応えを感じやすい実験です。

完成イメージ

見た目の動きは、ふつうのLチカとほとんど同じです。違いは「どう制御しているか」のという中身です。

ピコさん
ピコさん
「ただのLチカじゃん」と思うのはまだ早いよ
この記事では、同じ動きでも中身の仕組みを変えていくから、じっくり読んでいってね。ちょいむずかも。

準備:必要な道具と環境

パーツ一覧

品名役割代替案
Raspberry Pi Pico Wマイコン本体Pico(無印)でも可
Micro USBケーブルデータ通信・電源供給Type-A to Micro USB(データ通信対応)
ブレッドボード配線用の土台小型でもOK
ジャンパワイヤ(オス-オス)接続ケーブル色違いで用意すると便利
LED(赤・黄・緑など)点灯テスト用単色1つでも可
抵抗(220〜330Ω)電流制限470Ωでも可

動作環境(筆者の検証環境)

  • PC:MacBook Air (M1, macOS 15.5 arm64)
  • エディタ:Thonny 4.1.6(Python 3.10.11, Tk 8.6.13)
  • デバイス:Raspberry Pi Pico W(MicroPython

もちろん、Windows や Linux 環境でも動作します。MicroPythonを導入していれば同じコードで実行可能です。

配線図

Lチカ配線図
Lチカ用ピン番号

安全上の注意

  • LEDには極性(+と−)があります。長い脚が+側です。
  • 抵抗を必ず直列に入れましょう。 入れ忘れるとLEDが焼ける危険があります。
  • 静電気が強い日や金属テーブル上では、手で基板を触る前に放電しておきましょう。

まずは割り込みなしでLチカしてみよう

まずは一番基本となる割り込みなしのLチカから復習しておきます。ここが分かっていると、タイマー割り込み版の理解もスムーズになります。

今回は、外付けLEDをGPIO 15番ピンに接続した例で書いてみます。Pico / Pico W / Pico 2 どれでも、配線とピン番号が合っていれば同じように動作します。

from machine import Pin
from time import sleep_ms

# ==========================
# 設定値(あとで変えるならここだけ)
# ==========================
LED_PIN = 15              # LED をつないだ GPIO 番号
TIMER_INTERVAL_MS = 500   # 点滅間隔(ミリ秒)

# ==========================
# メイン処理
# ==========================
def main():
    led = Pin(LED_PIN, Pin.OUT)
    led_state = 0
    led.value(led_state)

    while True:
        # LED の ON/OFF をトグル
        led_state = 1 - led_state
        led.value(led_state)

        # 一定時間待つ(これで点滅間隔を決める)
        sleep_ms(TIMER_INTERVAL_MS)

このコードはとても素直で、一定時間待ってLEDの状態を反転させるだけです。MicroPython入門としては、このスタイルがまず押さえておきたい形です。

ただしこの方法は、プログラムがずっとLチカだけを担当している状態になります。他の処理を入れたいときには、同じループの中にコードを詰め込む必要があり、だんだん見通しが悪くなりがちです。

ピコさん
ピコさん
sleep_ms(TIMER_INTERVAL_MS) のときに
ただ待っているだけ
このコードだよ。

そこで次は、この「待ち時間」をタイマー割り込みに任せる形に変えていきます。

タイマー割り込みでLEDを直接トグルするLチカ

次のステップは、Raspberry Pi Pico のハードウェアタイマーを使って、一定周期ごとにLEDを点滅させる方法です。MicroPythonではmachine.Timerクラスを使います。

ここではまず、タイマー割り込みのコールバック関数の中で直接LEDのON/OFFを切り替えるシンプルなパターンを見ていきます。

from machine import Pin, Timer

# ==========================
# 設定値(あとで変えるならここだけ)
# ==========================
LED_PIN = 15               # LED をつないだ GPIO 番号
TIMER_INTERVAL_MS = 500    # 点滅間隔(ミリ秒)

# ==========================
# グローバル変数
# ==========================
led = None
led_state = 0
timer = None

# ==========================
# タイマー割り込みハンドラ
# ==========================
def toggle_led(timer_obj):
    # timer_obj には、この割り込みを発生させた Timer オブジェクトが渡される
    global led_state
    led_state = 1 - led_state  # 0/1 を切り替え
    led.value(led_state)

# ==========================
# メイン処理
# ==========================
def main():
    global led, timer

    # LED 初期化
    led = Pin(LED_PIN, Pin.OUT)
    led.value(0)

    # タイマー初期化(周期ごとに toggle_led を呼ぶ)
    timer = Timer()
    timer.init(
        period=TIMER_INTERVAL_MS,   # 割り込み周期 [ms]
        mode=Timer.PERIODIC,        # 周期モード(繰り返し)
        callback=toggle_led         # 呼び出す関数
    )

    # メイン側は他の処理を書ける(ここでは何もしないで放置)
    while True:
        pass

ここでの主役は、toggle_led() 関数と timer.init() のセットです。

  • def toggle_led(timer_obj):
    タイマー割り込みが発生したときに自動で呼ばれる関数(割り込みハンドラ)です。ここに「一定時間ごとにやってほしい処理」を書きます。この例ではLEDのON/OFFトグルだけを行っています。
  • timer.init(..., callback=toggle_led)
    periodで「何ミリ秒ごとに」、modeで「一回きり or 周期的」、callbackで「どの関数を呼ぶか」を指定しています。
    「このタイマーが鳴ったら toggle_led() を呼んでね」と登録しているイメージです。

timer_obj って何?

toggle_led(timer_obj)timer_obj は、MicroPython の Timer がコールバック関数に自動で渡してくる引数です。

  • timer = Timer() で作ったTimerオブジェクトそのものが、timer_obj として渡されます。
  • タイマーを複数使う場合は、if timer_obj is timer0: のようにして、「どのタイマーからの割り込みか」を判別するのに使えます。
  • 今回のようにタイマーは1つだけで、ハンドラの中で特にタイマー情報を使わない場合は、「形だけ受け取っている引数」と考えてOKです。

ポイントとして、MicroPython の Timer「引数を1つ取る関数」をコールバックとして受け取る仕様になっています。そのため、

  • def toggle_led(): のように引数なしにするとエラーになる
  • たとえ中で使わなくても、def toggle_led(timer_obj): のように引数を1つ受け取る形にしておく必要がある

もし「使わない引数があるのは気持ち悪いな…」と感じる場合は、timer_obj の代わりに _timer のような名前にして、「使わないけど形だけ受け取っているよ」という意図を表現する書き方もよく使われます。

ここまで来ると、メイン側のwhile True: passは、極端な例として「何もしていない」状態です。それでもLEDはタイマー割り込みだけでチカチカし続けるので、LED点滅をバックグラウンドに丸投げしているイメージがつかみやすいと思います。

ピコさん
ピコさん
timer_obj は「どのタイマーから来たか」の情報だよ。
今回みたいに1個しか使わないなら、あまり気にせず書式どおり受け取るだけでもOK!

上記の「タイマー割り込みでLEDを直接トグルするLチカ」には注意点があります。それは割り込みハンドラ内で直接LEDを制御していることです。LED程度なら良いのですが、ここに処理を盛り込みすぎると、割り込みが長くなりすぎて不安定の原因になります。

そこで登場するのが、次に紹介するフラグ方式です。

フラグ方式で安全にタイマー割り込みLチカ

割り込みの世界でよく言われる基本ルールのひとつが、「割り込みハンドラは短く、すぐ返す」です。タイマー割り込みの中で重い処理をすると、他の割り込みやメイン処理に悪影響が出ることがあります。

そこでおすすめなのが、割り込みハンドラの中では「やってほしいことのフラグだけを立てる」という書き方です。LEDのON/OFFはメインループ側で行います。

from machine import Pin, Timer

# ==========================
# 設定値
# ==========================
LED_PIN = 15
TIMER_INTERVAL_MS = 1000  # 少しゆっくり 1秒間隔

# ==========================
# グローバル変数
# ==========================
led = Pin(LED_PIN, Pin.OUT)
led_state = 0

# 割り込みで触るのは「フラグだけ」
led_toggle_requested = False

# ==========================
# タイマー割り込みハンドラ
# ==========================
def timer_isr(timer_obj):
    global led_toggle_requested
    # ← ここでは「トグルしてね」というフラグを立てるだけ
    led_toggle_requested = True

# ==========================
# メイン処理
# ==========================
def main():
    global led_state, led_toggle_requested

    timer = Timer()
    timer.init(
        period=TIMER_INTERVAL_MS,
        mode=Timer.PERIODIC,
        callback=timer_isr
    )

    while True:
        # フラグが立っていたら、ここで実際の処理を行う
        if led_toggle_requested:
            led_toggle_requested = False  # フラグをクリア
            led_state = 1 - led_state
            led.value(led_state)

        # ここに他の処理を書いてもOK
        # 例: ボタン入力やセンサー読み取りなど
        # 必要なら軽く sleep を入れてもよい

このパターンでは、timer_isr()は本当に短い処理しかしていません。フラグをTrueにするだけなので、すぐにreturnしてくれます。

一方、LEDのON/OFFはメインループ側でまとめて行います。将来的に「ログを取る」「別のLEDも同時に制御する」「状態変数を増やす」といった拡張も、メイン側を編集するだけで済むので、とても保守しやすい構成です。

ピコさん
ピコさん
割り込みでは「フラグだけ立てる」パターンを覚えておくと、
センサーやボタンにも応用しやすいよ!

タイマー割り込みを本格的に使っていきたいなら、このフラグ方式をベースに考えるのがおすすめです。

割り込みを使うときの基本ルール

ここで、Raspberry Pi Pico に限らず、マイコンで割り込みを扱うときの基本ルールを整理しておきます。Pico / Pico W / Pico 2 どれでも同じ考え方でOKです。

  • 割り込みハンドラは「即返す」が基本
    長い処理を入れず、できるだけ短く終わらせること。
  • sleep系関数は使わない
    sleep()sleep_ms()などの待ち処理は、割り込み内ではNGです。
  • printは最小限に
    デバッグ用のprint()も時間がかかることがあるので、常用は避けた方が無難です。
  • 共有するデータはシンプルに
    真偽値のフラグや、小さなカウンタなどにしておくと安全です。
  • 複雑な処理はメインループで
    センサーの処理、表示の更新、状態管理などはメイン側に集約します。
ピコさん
ピコさん
重い処理はメインループ割り込みはきっかけだけ」って覚えておくと、
設計で迷いにくいよ!

今回のフラグ方式は、まさにこのルールを守りやすくするための書き方です。最初は少しコードが長く見えるかもしれませんが、慣れるとこちらの方が安心して拡張できます。

3つのLチカを比較してみよう

ここまでで、

  • ① 割り込みなしのLチカ
  • ② タイマー割り込みで直接LEDをトグル
  • ③ フラグ方式のタイマー割り込み

という3パターンを見てきました。それぞれの特徴をざっくり比較してみます。

① 割り込みなしのLチカ

  • メリット: コードが短くて直感的。MicroPython入門として最適。
  • デメリット: sleep_ms()で待っている間は何もできない。並行処理には向かない。

② 直接トグルするタイマー割り込み

  • メリット: LED点滅をハードウェアタイマーに任せられる。メイン側に別の処理を書きやすい。
  • デメリット: 調子に乗って割り込み内に処理を詰め込むと、不安定になりやすい。

③ フラグ方式のタイマー割り込み

  • メリット: 割り込みはフラグだけを扱うので安全。メインループでまとめて状態管理しやすい。
  • デメリット: コード量は増える。最初は構造を理解するのに少し慣れが必要。
ピコさん
ピコさん
実際の作品づくりでは、③のフラグ方式をベースにしておくと、
あとから機能追加しやすいよ!

Pico / Pico W / Pico 2 どれを使っていても、この考え方はそのまま応用できます。ハードが変わっても、MicroPythonのコードはほぼ流用できるのも嬉しいポイントです。

まとめ:タイマー割り込みLチカから次のステップへ

今回は、Raspberry Pi Pico(Pico W / Pico 2 含む)とMicroPythonを使って、

  • 割り込みなしの基本Lチカ
  • タイマー割り込みで直接トグルするLチカ
  • フラグ方式で安全なタイマー割り込みLチカ
  • 割り込み使用時の基本ルールと3パターン比較

という流れで、タイマー割り込みによるLチカを一通り体験しました。

「割り込み」と聞くと難しそうに感じるかもしれませんが、実際には「一定時間ごとに呼ばれる関数を登録するだけ」と考えると、かなりハードルが下がると思います。

次の一歩としては、

  • タイマー割り込みとPWMを組み合わせて、明るさ変化+点滅を同時に制御する
  • ボタンやセンサーと組み合わせて、一定間隔で状態をチェックする仕組みを作る
  • 複数のタイマーを使って、違う周期の処理を同時に走らせてみる

といったチャレンジもおすすめです。

まずはこの記事の3パターンを全部動かしてみて、周期やピン番号を自分なりに変えて遊んでみてください。タイマー割り込みの感覚がつかめてくると、Raspberry Pi Pico で作れる電子工作のアイデアが一気に広がっていきますよ。

コメント