はじめに
初心者さんISRは“通知だけ”にするのは分かったんですが、その後の処理はどう管理するんですか?



そこで重要になるのが“状態遷移設計”なんだ。組み込みではかなり基本の考え方だよ
前回の記事では、
👉 「割り込みは処理を書く場所ではない」
という考え方を整理しました。


では、
ISRが通知したイベントは、
その後どこで処理されるのでしょうか?
答えは、
👉 状態遷移(ステートマシン)
です。
割り込みと状態遷移を正しく組み合わせることで、
組み込みソフトは一気に安定します。
この記事では、
- なぜ状態遷移が必要なのか
- 割り込みとどう組み合わせるのか
- なぜイベント駆動設計が強いのか
を、組み込み実務目線で整理していきます。
割り込みの基本についてはこちらの記事で解説しています。


割り込み処理解説シリーズ
本記事は「割り込み処理解説」シリーズの1つです。
割り込み処理解説シリーズの全体像はこちら


割り込み記事シリーズ一覧です。
- 割り込みとは何か?ポーリングとの違いから理解する【組み込み入門】
- 割り込み処理でやってはいけないこと5選|組み込み設計の落とし穴
- 割り込みは「処理を書く場所」ではない|組み込み設計の本質
- 割り込み×状態遷移設計|イベント駆動型組み込みの基本パターン(この記事)
- 割り込み優先度設計の考え方|リアルタイム性を壊さないために
- 割り込みとRTOSなし構成の違いとは?設計思想の本質を整理する
なぜ割り込みだけでは足りないのか?
例えば初心者がやりがちなコード。
void UART_IRQHandler(void) {
parse_packet();
}小規模なサンプルでは、
これでも動いてしまいます。
しかし実際には、
かなり危険な構造です。
なぜ危険なのか?
ISRへロジックを書き始めると、
- 状態が見えなくなる
- 処理の流れが追えなくなる
- タイミング依存化する
- 仕様変更に弱くなる
などの問題が起きます。
特に危険なのは、
👉 「今システムが何をしているか分からなくなる」
ことです。
割り込み設計の危険性についてはこちらの記事で解説しています。


割り込みは“きっかけ”にすぎない
割り込みの役割は、
👉 「イベント発生を知らせること」
です。
制御の中心ではありません。
つまり、
- UART受信した
- ボタン押された
- タイマ満了した
という“イベント”だけ通知します。
そして、
👉 「その後どう動くか」
を決めるのが状態遷移です。
割り込みでイベント発生を知らせることの重要性はこちらの記事で解説しています。


状態遷移(ステートマシン)とは?
状態遷移とは、
👉 「今どの状態にいるか」を明示的に管理する設計
です。
例えば:
typedef enum {
STATE_IDLE,
STATE_RECEIVING,
STATE_PROCESSING
} system_state_t;システムは常に、
どれか1つの状態にいます。
状態遷移でよく使われるenumについてはこちらの記事で解説しています。


状態を持つと何が良いのか?
例えば:
- 待機中なのか
- 受信中なのか
- 処理中なのか
が明確になります。
つまり、
👉 「システムの現在地」
が見えるようになります。
これがかなり重要です。
理想的な構造
イベント駆動型設計では、
一般的に以下のような構造になります。


割り込みは、
イベントを投げるだけ。
ロジックの中心は、
状態遷移側に置きます。
シンプルなコード例
ISR側
volatile uint8_t uart_event = 0;
void UART_IRQHandler(void) {
uart_event = 1;
}ISRでは通知だけ行います。
メインループ側
system_state_t state = STATE_IDLE;
while(1) {
if(uart_event) {
uart_event = 0;
switch(state) {
case STATE_IDLE:
state = STATE_RECEIVING;
break;
case STATE_RECEIVING:
state = STATE_PROCESSING;
break;
default:
break;
}
}
}重要なのは、
👉 ロジックがすべてメイン側にある
ことです。
なぜこの構造が強いのか?
理由はかなり多いです。
① ISRが軽くなる
ISRで重い処理を書かないため、
- 他割り込み遅延
- リアルタイム性悪化
- ネスト問題
などを減らせます。
ISRは短いほど安全です。
② 状態が可視化される
状態を持つことで、
👉 「今どこにいるか」
が明確になります。
これはデバッグで非常に強いです。
例えば状態が無いと…
if(a) {
if(b) {
if(c) {
}
}
}このように、
条件分岐だけで制御が増えていきます。
すると、
- 処理フローが読めない
- 仕様追加で破綻
- バグ解析困難
になりやすいです。
状態を持つと整理しやすい
例えば:
IDLE
↓
RECEIVING
↓
PROCESSING
↓
IDLEのように、
状態変化として整理できます。
これはかなり大きいです。
③ 仕様変更に強い
実務では、
- タイムアウト追加
- エラー処理追加
- 再送制御追加
など、
仕様変更が頻繁に発生します。
状態遷移設計では、
👉 「状態を追加する」
形で拡張しやすくなります。
④ デバッグしやすい
組み込みでは、
👉 「今どの状態だったか」
が分かるだけで、
かなり解析しやすくなります。
例えばログに:
STATE_RECEIVING → STATE_PROCESSING
と出るだけでも、
かなり追いやすいです。
実務でよくある失敗
❌ 状態を暗黙的に持つ
if文ネストだけで制御すると、
仕様追加で崩壊しやすいです。
❌ ISRごとに処理を書く
イベント追加のたびに、
ISRが肥大化します。
すると:
- 責務混在
- 保守性低下
- タイミング依存バグ
につながります。
これはかなり危険です。
割り込み×状態遷移=イベント駆動設計
この構造は、
- ベアメタル
- RTOSなし環境
- RAM制約が厳しい環境
で特に強力です。
RTOSを使わなくても、
👉 「構造化された並行設計」
ができます。
イベント駆動設計の本質
ここで重要なのは、
👉 「イベントがシステムを動かす」
という考え方です。
つまり:
イベント発生
↓
状態変化
↓
必要処理実行
という流れになります。
これはGUI、
ネットワーク、
RTOS設計などにもつながる、
かなり重要な考え方です。
さらに実務ではどう拡張する?
実務ではさらに:
- イベントキュー
- タイマイベント統合
- 優先度管理
- 非同期通信抽象化
などへ発展します。
しかし基本は同じです。
👉 割り込みは通知
👉 状態遷移が制御
この分離が本質です。
割り込みが連続する状況では、割り込み優先度設計が重要です。
割り込み優先度設計はこちらの記事で解説しています。


割り込みと状態遷移を分離する意味
ここを分離すると、
- ISRが安定
- ロジック整理
- テスト容易化
- 保守性向上
につながります。
つまり、
👉 「壊れにくい組み込み設計」
へ近づきます。
まとめ
割り込み単体では、
設計は安定しません。
重要なのは、
- 割り込み → イベント通知
- 状態遷移 → ロジック制御
という役割分離です。
この構造ができると、
- イベント駆動設計
- RTOS
- 非同期設計
なども理解しやすくなります。
次回予告
次回は、「割り込み優先度設計」について、
リアルタイム性を壊さない考え方を整理します。


この記事が参考になった方へ
割り込み処理については、
こちらのまとめ記事で全体像を整理しています。


また当サイトでは、
割り込み理解に必要なC言語文法やメモリ構造についても実務目線で解説しています。




エンジニアとして技術を学ぶことは重要ですが、
キャリアや副業についても同時に考える必要があります。
副業の現実や市場価値、今後のキャリア戦略については、
こちらの記事でまとめています。


技術に関するご相談・開発・自動化ツール作成・記事執筆などのご依頼も承っています。
小さなご相談からでもお気軽にご連絡ください。


コメント