【組み込み】タイマ割り込みで周期処理を作る方法|sleep待ちとの違いと実装の考え方

  • URLをコピーしました!

今回は、組み込み開発でよく使うタイマ割り込みによる周期処理について解説します。

LED点滅、センサの定期取得、ウォッチドッグ監視、通信タイムアウト管理など、組み込みでは「一定間隔で処理したい」場面が多くあります。
このとき、単純に sleep や待ちループで時間を空ける方法もありますが、実機ではそれだけでは扱いづらいケースが少なくありません。

そこで重要になるのが、ハードウェアタイマと割り込みを使って周期処理を作る考え方です。
この記事では、sleep 待ちとの違いを整理しつつ、タイマ割り込みで周期処理を組む基本パターンと、実装時に気をつけたいポイントを紹介します。

目次

周期処理とは

周期処理とは、一定時間ごとに同じ処理を繰り返すことです。
たとえば以下のような処理が該当します。

  • 10msごとにボタン状態を読む
  • 100msごとに温度センサを取得する
  • 1秒ごとにステータスLEDを切り替える
  • 一定周期で通信のタイムアウトを監視する

PC向けアプリではスレッドやイベントループで扱うことが多いですが、組み込みではタイマ割り込みを基準に動かすのが基本になります。

sleep待ちとタイマ割り込みの違い

sleep待ちの特徴

sleep や単純な待ちループは、書き方が分かりやすいのが利点です。

while (1) {
    read_sensor();
    sleep_ms(100);
}

この形は動作イメージをつかみやすい一方で、次のような問題があります。

  • 待っている間にCPUが他の処理をしにくい
  • 処理時間のぶんだけ周期がずれやすい
  • 複数の周期処理を同時に扱いにくい
  • 割り込みや非同期イベントとの設計がかみ合いにくい

たとえば read_sensor() に20msかかると、100ms周期のつもりでも実際には120msごとの実行になりやすくなります。

タイマ割り込みの特徴

一方、タイマ割り込みでは、ハードウェアタイマが一定周期で割り込みを発生させます。
そのため、メインループの状態に関係なく、時間の基準を比較的安定して作れます。

volatile uint32_t tick_ms = 0;

void timer_isr(void)
{
    tick_ms++;
}

このように1msごとのカウンタを作っておけば、メイン側で「何ms経過したか」を基準に処理を分けられます。

タイマ割り込みで周期処理を作る基本パターン

実務では、割り込み内では最小限の処理だけを行い、実際の仕事はメインループで実行する形がよく使われます。

1msティックを作る

volatile uint32_t tick_ms = 0;

void timer_isr(void)
{
    tick_ms++;
}

メインループ側で周期判定する

int main(void)
{
    uint32_t last_led = 0;
    uint32_t last_sensor = 0;

    while (1) {
        uint32_t now = tick_ms;

        if ((now - last_led) >= 1000) {
            last_led = now;
            toggle_led();
        }

        if ((now - last_sensor) >= 100) {
            last_sensor = now;
            read_sensor();
        }
    }
}

この方法なら、1つのタイマを基準に複数の周期処理を管理できます。
100ms周期と1000ms周期を同時に動かす場合でも、別々に sleep を書く必要がありません。

実装時に押さえたいポイント

割り込みの中で重い処理をしない

タイマ割り込みの中でセンサ通信や文字列処理まで実行すると、次の割り込みに影響が出やすくなります。
割り込み内は、フラグ更新やカウンタ加算など短い処理にとどめるのが基本です。

volatile uint8_t sensor_request = 0;

void timer_isr(void)
{
    static uint16_t cnt = 0;
    cnt++;

    if (cnt >= 100) {
        cnt = 0;
        sensor_request = 1;
    }
}

メインループでは、そのフラグを見て実際の処理を行います。

周期ぴったりで動くとは限らない

タイマ割り込みを使っても、処理時間が長すぎれば遅延は発生します。
たとえば100msごとに呼びたい処理が150msかかるなら、周期は維持できません。

この場合は次の見直しが必要です。

  • 処理を軽くする
  • 周期を長くする
  • 複数回に分割する
  • DMAや別タスクへ逃がす

周期処理の設計では、「何msで呼ぶか」だけでなく、1回の処理が何msかかるかもセットで考える必要があります。

時刻比較は加算より差分で考える

周期判定では now >= last + 100 のように書くより、now - last >= 100 の形が安全です。
タイマカウンタはいつかオーバーフローするため、差分比較のほうが長時間運用に向いています。

精度が必要な処理と、だいたいでよい処理を分ける

LED点滅のように多少ずれても困らない処理と、通信タイムアウトやPWM制御のように精度が重要な処理は同じではありません。
前者はメインループで十分ですが、後者は専用タイマ機能やハードウェア機能を使うべき場面もあります。

「とりあえず全部1ms割り込みで管理する」と決め打ちせず、要求精度に応じて実装を分けるのが現実的です。

どんなときにsleep待ちが向いているか

sleep がまったく使えないわけではありません。
単機能の簡単な検証コードや、他に並行処理がない小さなサンプルでは十分役立ちます。

ただし、以下のような条件が増えるとタイマ割り込みベースの設計が有利になります。

  • 複数の周期処理を同時に動かしたい
  • UART受信や外部入力と並行したい
  • 応答性を落としたくない
  • 長時間安定して動かしたい

まとめ

タイマ割り込みを使った周期処理は、組み込みで時間管理を行うための基本パターンです。
sleep 待ちは簡単ですが、処理時間の影響を受けやすく、複数処理の両立にも向きません。

実装するときは、次の点を意識すると安定しやすくなります。

  • タイマ割り込みでは短い処理だけを行う
  • 実際の処理はメインループ側で実行する
  • 周期判定は時刻の差分で行う
  • 処理時間と要求周期のつり合いを見る
  • 精度が必要な処理は専用機能も検討する

周期処理は地味ですが、システム全体の安定性に直結する部分です。
まずは1msティックとフラグ制御の基本形を押さえておくと、センサ取得や通信管理など他の処理にも応用しやすくなります。

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

この記事を書いた人

エンジニア。20代。組み込みエンジニアとして働き始めるも、働き方や業務内容に限界を感じ、 AI,Web3エンジニアを目指して勉強中。 エンジニアとして思うことや、学んだことを発信します。

目次