組み込みファームウェア:外部ファイルデータと信号波形でC言語配列を初期化するベストプラクティス
外部テキストファイルを使用してC言語配列を初期化する方法を学ぶ
このガイドでは、別のテキストファイルに格納されたデータでCプログラムの配列を初期化する方法を説明します。値はソースコードに埋め込まれず、コンパイル時に読み込まれます。一次元および多次元配列をカバーし、配列をRAMまたは不揮発性メモリのどちらに配置するかを制御する方法、および初期化のために複数のデータファイルから選択する方法を示します。
例は32ビットARMマイクロコントローラを対象とし、GCCでコンパイルされます。すべてのスニペットは標準Cを使用し、このコンパイラで動作します。
配列初期化の基本
C言語で配列を宣言する際、中括弧内に値のリスト(初期化子と呼ばれます)を指定できます。例:

角括弧内のサイズを省略すると、コンパイラは初期化子の数からサイズを推測します。宣言された配列サイズより初期化子リストが少ない場合、残りの要素はデフォルトでゼロになります。宣言サイズより多くの初期化子を指定するとコンパイルエラーになります。
空白文字
初期化子はカンマで区切られ、スペース、タブ、改行、キャリッジリターン、垂直タブ、フォームフィードなどの空白文字を自由に挿入できます。C言語ではステートメントが複数行にまたがることが可能で、ソースコードの数ページにわたる大規模配列に特に有用です。

ファイルからの配列初期化
Cプリプロセッサは#includeディレクティブを使用して任意のテキストファイルをインクルードできます。この機能はヘッダーやソースファイルに限定されず、プレーンテキストファイルなら何でも使用可能です。数値、カンマ、空白のみを含むシンプルなファイルを配列初期化に利用できます:

インクルードされるファイルはプレーンテキストでなければなりません。リッチテキスト形式、ヘッダー、その他の付加物は不可です。Windowsメモ帳で作成した簡単な例は次のようになります:

コンパイルすると、デバッガのスクリーンショットのように配列がメモリ上に配置されます。この場合、「Location」列の高アドレスから配列がRAMに配置されていることがわかります。

配列を不揮発性メモリに格納し、データファイルを選択する
上記の例では、配列は明示的な配置指定のないグローバル変数です。コンパイラは実行時に変更される可能性があると判断し、変数をRAMに配置します。初期値は不揮発性メモリ(通常はFlash)に置かれ、起動ルーチンがそれらをRAMにコピーします。配列が真に定数であれば、constを宣言することでコンパイラにFlashのみに配置させ、貴重なRAMを節約できます。次のスクリーンショットはその効果を示し、配列の「Location」が低いFlashアドレスを指しています。

#defineと#ifディレクティブを使用して、RAMとFlashのストレージを切り替えたり、初期化用の異なるデータファイルを選択したりできます。例えば、次のコードではコンパイル時にストレージ場所を選択できます:

#if構造は条件付きインクルードの古典的な例です。コンパイル時フラグに基づいてconst修飾子を適用するかどうかを決定します。
同様に、初期データを供給するファイルを条件付きで選択することもできます:

大規模配列でのテスト
この手法を検証するため、CSVファイルに格納された10,000サンプルのノイズ波形を使用しました。以下のプロットは波形を示し、宣言はデータがプログラムにどのように組み込まれるかを示しています:


元のCSVファイルには各値の後にカンマがありませんでした。正規表現の検索/置換(\Rを検索し,\Rで置換)を使用して、10,000エントリすべてに一度の操作でカンマを追加しました。
結果の配列はデバッガで確認でき、100要素ずつの行にきれいにグループ化されています。

多次元配列
2次元または3次元データを扱う際、C言語は配列を行優先順で格納することを覚えておいてください。例えば、uint16_t test[2][3]配列はメモリ上で次のように配置されます:
[0,0] [0,1] [0,2] [1,0] [1,1] [1,2]
右端の添字に沿って連続する要素にアクセスする方が高速です。データが連続しているためです。1,000サンプルの2つのベクトルが必要な場合、キャッシュ効率を最大化するためにtest[2][1000]と宣言します。
2次元配列の初期化子は通常、ネストした中括弧でグループ化されます:

しかし、外側の中括弧を省略できます。コンパイラは左から右へ配列を埋めます。missing braces around initializerという警告が出ますが、値の総数が配列サイズと一致すればコンパイルされます。

データを複数のファイルに分散させるには、各ファイルを順番にインクルードし、リスト全体を一組の中括弧で囲みます:

共用体での配列初期化
unionを使用すると、異なる型が同じメモリを共有できます。例えば、一次元ベクトルと二次元マトリックスを含む共用体は同じアドレス空間を占有します:

初期化時、コンパイラは最初のメンバから順に埋めます。順序が逆の場合、初期化子リストが完全に括弧で囲まれていないため警告が出ることがあります。#includeを二重中括弧で囲むことで正しいグループ化を確保できます:
共用体用のデータファイルは次のようになります:

メモリ上では、デバッガのスクリーンショットのようにvectorとmatrixの両方が同じ場所から始まります:

単一の数値ファイルから多次元配列を初期化する別の方法をご存知ですか?コメントでアイデアを共有してください。
ボーナスヒント:文字列
外部ファイルから文字列を初期化するには、引用符をファイル内に含める必要があります。文字列リテラル内で直接#includeするとコンパイラがエラーになります。代わりに、ファイルに引用符を置いて次のようにインクルードします:

結果の文字列はスクリーンショットのようにRAMに配置されます。

これらの例は理論的なものであり、標準的なCコンパイラであれば動作するはずです。コンパイラ固有の癖に遭遇した場合はコメントでお知らせください。
ソース提供。
埋め込み
- Nexus Integraと主要IoT・ビッグデータプラットフォームの比較
- STMicroelectronics、STM32Cube.AI発表 – 組み込みニューラルネットワークでエッジデバイスを強化
- DATA MODUL、EasyTouch搭載超高輝度21.5インチ/31.5インチオープンフレームモニター発表 - 屋内外対応
- Microchip、153.6kSPSデータレートの24bit/16bitデルタシグマADCを発表
- Contrinex 2020:クラウド対応センサ & Bluetoothライトカーテンで安全性向上
- クラウド革命:IT、セキュリティ、キャリアをどう変えるか
- IBMとTech DataでIoTを現実に - パート2
- IoTを現実に:Tech DataとIBMの洞察 - パート1
- C++配列を徹底解説:宣言、初期化、ポインタ活用テクニック
- C++動的配列の確保をマスター:コード例付き実践ガイド
- Java BufferedReader でファイル読み込み:実践ガイドと例