Firmware Tertanam: Praktik Terbaik untuk Menginisialisasi Array C dengan Data File Eksternal dan Bentuk Sinyal
Pelajari cara menginisialisasi array C menggunakan file teks eksternal.
Panduan ini menjelaskan cara mengisi array dalam program C dengan data yang disimpan dalam file teks terpisah. Nilai tidak disematkan dalam kode sumber; alih-alih, mereka dimuat pada waktu kompilasi. Kami membahas array satu dimensi dan multi dimensi, menunjukkan cara mengontrol apakah array berada di RAM atau memori non-volatile, dan mengilustrasikan cara memilih antara beberapa file data untuk inisialisasi.
Contohnya menargetkan mikrokontroler ARM 32-bit dan dikompilasi dengan GCC. Semua cuplikan menggunakan C standar dan berfungsi dengan kompiler ini.
Dasar Inisialisasi Array
Saat Anda mendeklarasikan array dalam C, Anda dapat menyediakan daftar nilai—disebut initializers—di dalam kurung kurawal. Misalnya:

Jika Anda menghilangkan ukuran dalam kurung siku, kompiler menyimpulkan dari jumlah inisializer. Jika array dideklarasikan lebih besar dari daftar inisializer, elemen yang tersisa default ke nol. Menyediakan lebih banyak inisializer daripada ukuran yang dideklarasikan memicu kesalahan kompilasi.
Spasi Putih
Inisializer dipisahkan oleh koma, dan Anda dapat menambahkan spasi putih—spasi, tab, baris baru, carriage return, vertical tab, atau form feed—di antara mereka secara bebas. Dalam C, pernyataan dapat melintasi beberapa baris; ini sangat berguna untuk array besar yang dapat mengisi beberapa halaman kode sumber.

Menginisialisasi Array dari File
Preprosesor C dapat menyertakan file teks sembarang menggunakan direktif #include. Fitur ini tidak terbatas pada file header atau sumber; file teks apa pun dapat diterima. File sederhana yang berisi angka, koma, dan spasi putih dapat digunakan untuk menginisialisasi array:

File yang disertakan harus berupa teks biasa—tanpa formatting teks kaya, header, atau artefak lainnya. Contoh cepat yang dibuat dengan Windows Notepad terlihat seperti ini:

Saat dikompilasi, array muncul dalam memori seperti yang ditunjukkan dalam screenshot debugger. Dalam kasus ini, array berada di RAM, yang ditunjukkan oleh alamat tinggi dalam kolom “Location”.

Menyimpan Array di Memori Non-Volatile dan Memilih File Data
Dalam contoh di atas, array adalah variabel global tanpa penempatan eksplisit. Kompilasi mengasumsikan variabel dapat dimodifikasi saat runtime, jadi ia menempatkan variabel di RAM. Nilai awal berada di memori non-volatile (biasanya Flash) dan rutinitas boot-up menyalinnya ke RAM. Jika array benar-benar konstan, mendeklarasikannya const memberi tahu kompiler untuk menyimpannya di Flash saja, menghemat RAM yang berharga. Screenshot berikut menunjukkan efeknya: “Location” array menunjuk ke alamat Flash rendah.

Anda dapat menggunakan direktif #define dan #if untuk beralih antara penyimpanan RAM dan Flash, atau untuk memilih file data yang berbeda untuk inisialisasi. Misalnya, kode berikut memungkinkan Anda memilih lokasi penyimpanan pada waktu kompilasi:

Konstruksi #if adalah contoh klasik dari inklusi bersyarat. Ia menentukan apakah kualifikasi const diterapkan berdasarkan flag waktu kompilasi.
Demikian pula, Anda dapat secara bersyarat memilih file mana yang menyediakan data awal:

Pengujian dengan Array Besar
Untuk memvalidasi teknik, saya menggunakan bentuk gelombang noise 10.000 sampel yang disimpan dalam file CSV. Plot di bawah ini menunjukkan gelombang, sementara deklarasi menunjukkan bagaimana data dimasukkan ke dalam program:


File CSV asli tidak memiliki koma setelah setiap nilai. Menggunakan find/replace ekspresi reguler (cari \R dan ganti dengan ,\R) menambahkan koma ke semua entri 10.000 dalam satu operasi.
Array yang enak terlihat dalam debugger, dikelompokkan dalam baris 100 elemen.

Array Multi-Dimensi
Saat bekerja dengan data dua atau tiga dimensi, ingatlah bahwa C menyimpan array dalam urutan row-major. Misalnya, array uint16_t test[2][3] disusun dalam memori sebagai:
[0,0] [0,1] [0,2] [1,0] [1,1] [1,2]
Mengakses elemen berturut-turut di sepanjang subskrip terkepat lebih cepat karena data kontigu. Jika Anda perlu dua vektor 1.000 sampel, deklarasikan test[2][1000] untuk memaksimalkan kesahuran cache.
Inisializer untuk array dua dimensi biasanya dikelompokkan dengan kurung kurawal bersarang:

Namun, Anda dapat menghilangkan kurung kurawal luar; kompiler kemudian mengisi array dari kiri ke kanan. Ia mengeluarkan peringatan: missing braces around initializer. Asalkan jumlah total nilai sesuai dengan ukuran array, kode dikompilasi.

Untuk menyebarkan data di beberapa file, cukup sertakan setiap file berurutan, membungkus seluruh daftar dengan sepasang kurung kurawal:

Menginisialisasi Array dalam Union
Sebuah union memungkinkan tipe berbeda untuk berbagi lokasi memori yang sama. Misalnya, union yang berisi vektor satu dimensi dan matriks dua dimensi menempati ruang alamat yang sama:

Saat menginisialisasi, kompiler mengisi anggota pertama terlebih dahulu. Jika urutan dibalik, Anda mungkin melihat peringatan karena daftar inisializer tidak sepenuhnya diapit kurung. Kurung kurawal ganda di sekitar #include memastikan pengelompokan yang benar:
File data untuk union terlihat seperti ini:

Dalam memori, baik vector maupun matrix dimulai di lokasi yang sama, seperti yang ditunjukkan dalam debugger:

Apakah Anda tahu metode lain untuk menginisialisasi array multi-dimensi dari satu file numerik? Bagikan ide Anda di komentar.
Tips Bonus: String
Menginisialisasi string dari file eksternal memerlukan tanda kutip untuk berada di dalam file itu sendiri. #include langsung di dalam literal string akan merusak kompiler. Sebaliknya, letakkan tanda kutip di dalam file dan sertakan seperti ini:

String yang enak berada di RAM, seperti yang diilustrasikan dalam screenshot.

Contoh-contoh ini bersifat teoretis dan seharusnya berfungsi dengan kompiler C standar apa pun. Jika Anda menemukan karakteristik khusus kompiler, beri tahu kami di komentar.
Sumber disediakan.