はじめに
初心者さんイベントが来たなら、そのままISRで全部処理した方が早くないですか?



初心者の頃はみんなそう考える。でもそれを始めると、後でシステム全体が崩れ始めるかもね
これまでの記事では、
- 割り込みの基本
- 割り込みでやってはいけないこと
について整理してきました。
今回はさらに一歩踏み込みます。
テーマは、
👉 「割り込みは処理を書く場所ではない」
という考え方です。
これは単なるテクニックではありません。
組み込み設計そのものに関わる、
かなり重要な設計思想です。
割り込みの基本についてはこちらの記事で解説しています。


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


割り込み記事シリーズ一覧です。
- 割り込みとは何か?ポーリングとの違いから理解する【組み込み入門】
- 割り込み処理でやってはいけないこと5選|組み込み設計の落とし穴
- 割り込みは「処理を書く場所」ではない|組み込み設計の本質(この記事)
- 割り込み×状態遷移設計|イベント駆動型組み込みの基本パターン
- 割り込み優先度設計の考え方|リアルタイム性を壊さないために
- 割り込みとRTOSなし構成の違いとは?設計思想の本質を整理する
なぜ人はISRに処理を書いてしまうのか?
理由はシンプルです。
- イベント発生場所に近い
- すぐ動く
- コードがまとまって見える
- その場で完結できる
からです。
例えば:
void UART_IRQHandler(void) {
receive_data();
parse_packet();
update_state();
}一見、
かなり綺麗に見えます。
実際、
小規模サンプルでは普通に動いてしまいます。
しかしシステム規模が大きくなると、
この構造が問題を生み始めます。
何が問題なのか?
このコードは、
👉 「責務を混ぜている」
状態です。
本来、
- イベント通知
- データ解析
- 状態更新
- アプリケーション処理
は別レイヤーの責務です。
しかしISRへ全部詰め込むと、
- 割り込み依存コード増加
- 保守性低下
- タイミング依存化
- テスト困難
につながります。
ISRに重い処理を書く危険性については、こちらの記事でも詳しく解説しています。


ISRの本来の役割
ISR(割り込み処理)の役割は、
本来たった1つです。
👉 「イベントが発生したことを通知する」
ことです。
つまりISRは、
👉 「処理を完結させる場所」
ではありません。
理想的なISR
理想形はこうです。
volatile uint8_t uart_event = 0;
void UART_IRQHandler(void) {
uart_event = 1;
}ISRでは、
イベント通知だけ行います。
そして実際の処理は、
メインループ側で実行します。
実処理はメイン側へ逃がす
while(1) {
if(uart_event) {
uart_event = 0;
receive_data();
parse_packet();
update_state();
}
}こうすることで、
- ISR短縮
- リアルタイム性向上
- デバッグ容易化
- 保守性向上
など、多くのメリットがあります。
なぜ「通知だけ」が重要なのか?
理由は主に4つあります。
① ISR実行時間を最小化できる
ISR実行中は、
CPUの通常処理が中断されています。
そのためISRが長いと、
- 他割り込み遅延
- タイマ精度悪化
- UART取りこぼし
- リアルタイム性崩壊
などが起きます。
ISRを短くすることは、
組み込み設計の基本です。
② 割り込みネストに強くなる
ISR中にさらにISRが発生すると、
割り込みネストが起きます。
Timer ISR 実行中
↓
UART ISR 発生
↓
さらに別ISR
ネストが深くなるほど、
- スタック消費増加
- RAM不足
- HardFault
などの危険が増えます。
ISRを短くすることで、
こうしたリスクも減らせます。
スタック領域については、
こちらの記事で詳しく解説しています。


割り込みが連続する状況では、割り込み優先度設計も重要です。
割り込み優先度設計はこちらの記事で解説しています。


③ テストしやすくなる
これは実務ではかなり重要です。
ISRへロジックを書き始めると、
- 単体テスト困難
- 再現条件依存
- ハード依存増加
などが発生します。
例えば:
void UART_IRQHandler(void) {
parse_packet();
}この場合、
「割り込み発生」が前提になります。
つまり、
👉 普通の関数テストがしづらい
ということです。
一方、
ロジックをメイン処理側へ逃がせば、
👉 通常関数としてテスト可能
になります。
これは保守性へかなり効きます。
④ 保守性が大きく変わる
ISRに処理を書き始めると、
仕様変更のたびにISRが肥大化します。
すると、
- 他割り込みと干渉
- デバッグ困難
- 再現しない不具合
などが発生しやすくなります。
特に危険なのは、
👉 「タイミング依存バグ」
です。
実行順や割り込みタイミングでしか再現しないため、
解析が非常に難しくなります。
割り込みは「入口」にすぎない
設計視点で見ると、
理想構造は以下です。
[割り込み]
↓
[イベント通知]
↓
[状態管理]
↓
[処理]
割り込みは、
一番外側の「入口」にすぎません。
ロジックの中心にしてはいけません。
イベント駆動設計との関係
組み込み設計では、
👉 「イベントを起点に状態を変化させる」
設計が非常によく使われます。
例えば:


この考え方を、
「イベント駆動設計」と呼びます。
つまりISRは、
👉 イベント発生を知らせるだけ
なのです。
状態遷移設計については、
こちらの記事で詳しく解説しています。


この「イベント通知→処理実行」という考え方は、RTOS設計にもつながります。
RTOS設計についてはこちらの記事で解説しています。


並行処理としての割り込み
割り込みは本質的に、
👉 「並行処理」
です。
なぜなら、
- いつ実行されるか分からない
- メイン処理を中断する
- 他ISRと競合する
- スタックを共有する
からです。
ここを理解すると、
👉 ISRへロジックを書く危険性
が見えてきます。
実務で意識していること
私が実務でかなり意識しているのは、
👉 「ISRは3行以内」
という考え方です。
例えば:
- フラグON
- FIFO退避
- タイムスタンプ取得
程度に留めます。
それ以上は、
メイン処理へ逃がします。
これだけでも、
トラブルはかなり減ります。
ISRで最低限の処理が必要なケース
もちろん実務では、
👉 ISR内で最低限の処理が必要
なケースもあります。
例えば:
- UART FIFOが小さい
- 数µsレベルで厳しい
- レジスタ値を即保存する必要がある
などです。
例えばUART受信
void UART_IRQHandler(void) {
uint8_t data = UART_DR;
buffer[write_idx++] = data;
}UARTでは、
受信レジスタを早く読み出さないと、
👉 次データで上書きされる
可能性があります。
そのため、
- レジスタ読み出し
- FIFO退避
程度はISRで行うことがあります。
ただし重要なのは「完結させない」こと
ここが重要です。
ISR内では、
- 解析しない
- 判定しない
- 長いループを書かない
- 状態変更を最小限にする
のが基本です。
つまり、
👉 「最低限だけやって、後は逃がす」
という考え方です。
設計思想の話
割り込みをどう扱うかには、
- 並行処理理解
- 責務分離
- 保守性意識
- リアルタイム設計
がそのまま表れます。
つまりここは、
👉 「動くコードを書く人」と
👉 「壊れない設計をする人」
の差が出る場所です。
まとめ
割り込みは、
- 処理を書く場所ではない
- イベント通知する場所
- システムの入口
です。
ISRへロジックを書き始めると、
- 保守性低下
- タイミング依存化
- デバッグ困難
などにつながります。
そのため実際の組み込みでは、
👉 ISRはイベント通知だけ行う
という設計が非常によく使われます。
次回予告
次回は、「割り込み×状態遷移設計」について、
イベント駆動型組み込み設計の基本パターンを整理します。


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


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




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


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

コメント