switch-caseによるステートマシンの実装
| 設計内容分類 | 組み込みソフトウェアにおけるC言語記述 |
|---|
実現したい仕様・課題
通信手順や起動シーケンス、モード遷移のような「段階を踏んで進む処理」を、フラグとif文の継ぎ足しで実装すると、状態の組み合わせが管理しきれなくなり、想定外の遷移や デッドロック的な状態残りが発生します。シーケンス制御を見通しよく、拡張しやすい形で実装したいというのが課題です。
設計のポイント
段階的に進む処理は、ステートマシン(状態機械)として設計し、enumによる状態定義とswitch-case文で実装するのが定石です。まず処理の段階をenumで列挙し(例:待機→接続→送信→応答待ち→完了)、状態変数を1つ持ちます。処理関数は「現在の状態に応じた処理を行い、遷移条件が成立したら状態変数を次の状態に書き換える」構造とし、メインループから周期的に呼び出します。各caseの処理を「待ち合わせでブロックしない」よう書くのが重要なポイントで、応答待ちであれば「届いていれば次へ、届いていなければ何もせず戻る」と記述することで、1つの関数で複数の機器・複数のシーケンスを並行して進められ、システム全体の応答性も保たれます。defaultには初期状態へ戻す処理を書き、想定外の状態値からの復帰経路を確保します。タイムアウトは状態と併せて開始時刻(またはカウンタ)を記録し、経過判定で エラー状態へ遷移させます。状態が増えてswitchが肥大化してきた場合は、状態ごとの処理関数を関数ポインタの配列にまとめる方式へ発展させることもできます。この構造はFPGA設計におけるステートマシンと同じ考え方であり、状態遷移図を先に描いてからコード化する、遷移図とcaseを1対1に対応させる、という進め方がそのまま通用します。シーケンスの仕様変更にも、該当状態のcase修正だけで対応でき、影響範囲が局所化されます。
【コード例】
typedef enum { ST_IDLE, ST_CONNECT, ST_SEND, ST_WAIT_ACK, ST_DONE } STATE;
static STATE st = ST_IDLE;
void comm_task(void) { // メインループから周期呼び出し
switch (st) {
case ST_IDLE: if (start_req) st = ST_CONNECT; break;
case ST_CONNECT: if (connected()) st = ST_SEND; break;
case ST_SEND: send_frame(); st = ST_WAIT_ACK; break;
case ST_WAIT_ACK: if (ack_recv()) st = ST_DONE;
else if (timeout()) st = ST_IDLE; break;
case ST_DONE: st = ST_IDLE; break;
default: st = ST_IDLE; break; // 不正状態から復帰
}
}
補足情報
設計のポイント 一覧
組み込みソフトウェアにおけるC言語記述のポイント
- リングバッファで受信データを取りこぼさない
- switch-caseによるステートマシンの実装
- 割り込みハンドラは短く書く
- constを活用する(ROM配置と書き換え防止)
- ビット演算によるレジスタ・フラグ操作
- 固定幅整数型(stdint.h)を使う
- volatile修飾子を正しく使う
- 構造体の宣言にpackedを指定する
- ソースコードのエンディアン変換
- sizeofを使用する
- 特殊文字の記述にエスケープ文字を使用する
- 変数の初期化漏れを防ぐ
- 外部バスの使用
- sprintf関数を使う
- 列挙型(enum)を使う
- 構造体を使う
- 標準関数を使う
- 条件演算子を使う
- インクリメント演算子を使う
- C言語記述の際はアプリ層とドライバ層に分ける



