リングバッファで受信データを取りこぼさない
| 設計内容分類 | 組み込みソフトウェアにおけるC言語記述 |
|---|
実現したい仕様・課題
UART受信のように、割り込みで断続的に届くデータをメイン処理が読み出す構成では、メイン側の処理が一時的に重くなった瞬間にデータを取りこぼす危険があります。割り込み側とメイン側の処理速度の差を吸収し、データを失わずに受け渡したいというのが課題です。
設計のポイント
割り込みとメイン処理のデータ受け渡しには、リングバッファ(循環バッファ)を使用します。固定長の配列を用意し、書き込み位置(ヘッド)と読み出し位置(テール)の2つのインデックスで管理する構造で、割り込みハンドラはヘッド位置へデータを書いてヘッドを進め、メイン処理はテール位置から読んでテールを進めます。インデックスが配列の末尾に達したら先頭へ戻る(循環する)ことで、固定メモリのまま連続的なデータ流を扱えます。バッファに溜めておけるぶん、メイン側の読み出しが一時的に遅れてもデータは失われず、処理速度の瞬間的な差を吸収できます。実装のポイントとして、(1) バッファサイズを2のべき乗にすると、インデックスの循環が「& (SIZE-1)」のマスク1回で書け、剰余計算が不要になります。(2) 空の判定はヘッドとテールの一致、満杯の判定は「ヘッドの次がテール」とする方式が簡潔で、割り込み側とメイン側が互いに相手のインデックスを更新しない(書く側はヘッドのみ、読む側はテールのみ更新する)構造にすることで、排他制御を最小限にできます。(3) 共有するインデックスにはvolatileを付けます。(4) 満杯時に新データを捨てるか古いデータを上書きするかは仕様として決め、取りこぼしの発生をカウンタで記録しておくと、バッファサイズの妥当性を実測で確認できます。必要サイズは「受信レート×メイン側の最大応答遅れ」から見積もり、余裕を持たせて設計します。
【コード例】
#define RB_SIZE 256 // 2のべき乗
static volatile uint8_t rb[RB_SIZE];
static volatile uint16_t head = 0, tail = 0;
void rx_isr(void) { // 書く側:headのみ更新
uint16_t next = (head + 1) & (RB_SIZE - 1);
if (next != tail) { rb[head] = UART_DR; head = next; }
// 満杯時は捨てる(必要なら取りこぼしカウンタを加算)
}
int rb_read(uint8_t *d) { // 読む側:tailのみ更新
if (head == tail) return 0; // 空
*d = rb[tail];
tail = (tail + 1) & (RB_SIZE - 1);
return 1;
}
補足情報
設計のポイント 一覧
組み込みソフトウェアにおけるC言語記述のポイント
- リングバッファで受信データを取りこぼさない
- switch-caseによるステートマシンの実装
- 割り込みハンドラは短く書く
- constを活用する(ROM配置と書き換え防止)
- ビット演算によるレジスタ・フラグ操作
- 固定幅整数型(stdint.h)を使う
- volatile修飾子を正しく使う
- 構造体の宣言にpackedを指定する
- ソースコードのエンディアン変換
- sizeofを使用する
- 特殊文字の記述にエスケープ文字を使用する
- 変数の初期化漏れを防ぐ
- 外部バスの使用
- sprintf関数を使う
- 列挙型(enum)を使う
- 構造体を使う
- 標準関数を使う
- 条件演算子を使う
- インクリメント演算子を使う
- C言語記述の際はアプリ層とドライバ層に分ける



