配列の初期化の仕方が分からないという人が意外と多いように思うので、配列の初期化の方法を お話ししていこうと思います。
1つ目は、配列を宣言時にデータも一緒に設定するもの。
2つ目は、プログラムの実行時に配列のデータを使用する前に、その時に応じて設定するもの。
3つ目は、外部からの入力データをもとに配列に設定するもの。
組み込みマイコンを扱っていく場合は、変数の初期化はものすごく大事なので、決しておろそかにしないでくださいね。初期化していないだけで、意図しない動作をすることが普通にあるためです。
それでは、順を追って解説していきますね。
1. 配列を宣言時にデータも一緒に設定する
この方法が、一番簡単です。しかし、配列のデータ数が多くなると、記述するのが大変なのはわかりますよね。
こんな感じです。
short example1[5] = {10, 40, 123, 11, -3}; short example2[10] = {20, -122, 50, 30}; short example3[] = {8, 500, 2000, -10000};
配列example1のデータすべてをshort型で表せられる数値で初期化しています。
このような状態で配列が初期化されます。C言語では配列のINDEXは0から始まります。
example1[0] = 10; example1[1] = 40; example1[2] = 123; example1[3] = 11; example1[4] = -3;
次に、example2のデータですが、配列数を10で定義していますが、初期値は4つしか設定していません。この場合は、以降のデータは0で初期化されます。
example2[0] = 20; example2[1] = -122; example2[2] = 50; example2[3] = 30; example2[4] = 0; example2[5] = 0; example2[6] = 0; example2[7] = 0; example2[8] = 0; example2[9] = 0;
最後のexample3は配列の大きさを宣言していません。ですが、初期値を4つ入れているので、この配列の大きさは4ということになります。
逆に、初期値の記述がない場合は、配列の大きさが不定となりますので、使用する際は、プログラム実行時に配列の大きさ分の領域確保、使用後の領域の解放の処理が必要になります。
その際の方法はここでは説明しないことにします。また、組み込みマイコンの世界では配列の大きさを不定で宣言することはまずありません。
example3のデータはこのようになります。
example3[0] = 8; example3[1] = 500; example3[2] = 2000; example3[3] = -10000;
例はshort型で記述しましたが、他の型でも初期化のやり方は同じです。
2. プログラムの実行時に配列のデータを使用する前に、その時に応じて設定する
先の方法は、始めから値を決めて初期化する方法でした。ここで、解説するのはプログラム実行時の状況に応じて、設定していく方法を解説していきます。
どういうことかと言うと、配列の宣言時には初期化をせず、実際に配列のデータを使う直前に初期化をするということです。
プログラムの流れ上、あるケースのときはこのデータ1で、またあるときはこのデータ2でというような場合、宣言時に配列を初期化できないですよね。
この場合は、プログラム実行時に初期化するしかありません。
こんな感じです。
#include <stdio.h> void main(void) { short data1[5] = {1, 2, 3, 4, 5}; short data2[5] = {6, 7, 8, 9, 10}; short data3[5] = {}; short use_data[5]; short select_data; short i; printf("初期データの選択 0 or 1: "); scanf("%d", &select_data); switch (select_data) { case 0: for (i = 0; i < 5; i++) { use_data[i] = data1[i]; } break; case 1: for (i = 0; i < 5; i++) { use_data[i] = data2[i]; } break; default: for (i = 0; i < 5; i++) { use_data[i] = data3[i]; } } /* use_dataを表示 */ for (i = 0; i < 5; i++) { printf("use_data[%d] = %d\n", i, use_data[i]); } }
意味のある処理ではないですが、select_dataの値によって、use_dataに入れる値を変えています。その結果を出力させるプログラムです。ちなみにdata3はすべて0の配列です。
組み込みマイコンの世界ではprintfやscanf文は使えませんので、ハードウェアからの入力データがselect_dataへ設定されて、use_dataが確定し、そのデータを用いて、特定のランプを光らしたり、音を鳴らしたりする指令をハードウェアへ出力するという具合に使用します。
上記の例のような処理では、同じことを何回も書いても大して変わりませんが、データ設定の後、さらに複雑な処理が続く場合には、処理の入り口で場合分けしたほうが、読みやすさ、メンテナンスのしやすさから考えても断然いいです。
配列の初期化という風に思えないかもしれませんが、use_dataを参照する前にデータを設定しているということで、use_dataにとっては初期化と言えます。
3. 外部からの入力データをもとに配列に設定する
今まで解説したものは、すべてプログラム内で用意されたデータで初期化したものでした。
ここでは、プログラムで外部から入力を受けて、そのデータを用いて、配列を初期化するという方法を解説していきたいと思います。
外部からの入力と聞いて、どのようなものを思い浮かべますか?
・キーボードからのデータ入力
・ファイルデータを読み込んでの入力
・レジストリのデータを読み込んでの入力
・通信データからの入力
というようなところでしょうか?
組み込みマイコンでは、EEPROMと言われるデータ保存用電子機器や通信データからの読み込みで、配列を初期化することがあります。逆に、キーボードからの入力やファイル入力はできません。
ファイルをWindowsのアプリで読み込んで、通信でマイコンへ送って設定するということはありますが、直接ファイルデータを読み込む手段はありません。
キーボードからの入力はprintf,scanf文で読み込んだデータを順番に配列に設定していく方法です。
あまりやらないと思いますが、やるとしたら、こんな感じです。
#include <stdio.h> void main(void) { unsigned char use_data[100] = {}; short input_data; short i, j; i = 0; while (i < 100) { printf("i番目のデータ入力: ", &i); scanf("%d", &input_data); if ((input_data > 0) && (input_data < 256)) { use_data[i] = input_data; } else { break; } i++; } /* use_dataを表示 */ for (j = 0; j < i; j++) { printf("use_data[%d] = %d\n", j, use_data[j]); } }
ファイルからの読み込みでは、こんな感じになります。
#include <stdio.h> #include <stdlib.h> void main(void) { short use_data[100] = {}; short input_data; short i, j; FILE *file; i = 0; file = fopen("data_file.txt", "r"); if (file == NULL) { printf("ファイルが読み込めませんでした。\n"); } else { while (i < 100) { if (fscanf(file, "%d", &input_data) != EOF) { use_data[i] = input_data; } else { break; } i++; } fclose(file); /* use_dataを表示 */ for (j = 0; j < i; j++) { printf("use_data[%d] = %d\n", j, use_data[j]); } } }
レジストリの扱いはやらないほうがいいので、例文は示しません。
組み込みマイコンでの例文は書きにくいので、流れだけ示します。
PCのアプリケーションでファイルデータを読み込み | | RS232CやRS422等の通信データでやりとり ↓ マイコンにデータを取り込み(受信割り込みでデータを取得) /* 割り込みハンドラで呼ばれる関数 */ void tick_recieve_xmt(void) { rcv_data[pt] = 取得レジスタの値 pt++; if (pt >= max_pt) { pt = 0; } 受信割り込みフラグクリア処理 } ※rcv_data:受信データ格納用バッファ pt:受信バッファの格納するindex番号 max_pt:rcv_dataのバッファサイズ | ↓ メインルーチンで取得データを処理し、配列に設定する。 /* メインルーチンのどこかで呼ばれる関数 */ void set_rcieve_data(void) { short end_pt; /* 受信バッファに格納された場所のindexをセットする。 */ /* ptは割り込みで変化するデータなので、そのときの状態を別変数に格納する */ end_pt = pt; /* 前回読み込んだrcv_dataのindexの続きから、今回受信したところまでの データをdataにコピーする。 */ while(end_pt <> rp) { data[wp] = rcv_data[rp]; wp++; rp++; if (rp >= max_pt) { rp = 0; } } ※data:メインルーチンで使用する配列 wp:dataにセットするindex rp:rcv_dataの読み出しindex
こんな感じですが、理解できるでしょうか?
割り込み処理で動く部分と常時動いているメインルーチンでデータのやりとりを行う必要があります。
割り込み処理の中で、たくさんの処理をさせてしまうと、ほかの処理が回らなくなってしまうので、割り込み処理ではなるべく負荷が小さくなるようにコーディングしなくてはなりません。
この部分が組み込みマイコンのプログラミングをする上で、気をつけておかなくてはいけないポイントです。
また、割り込みで設定しているデータをメインルーチンであらゆるところで参照するのもいけません。それは、メインルーチンの実行中に割り込み処理が働くからです。そのタイミングによっては、問題が発生する可能性があります。
したがって、一旦、メインルーチン内で使用するデータにコピーしているのです。そうすることで、割り込み処理でデータが変更されても、メインルーチンでは同じデータで処理するため、問題が発生しなくなるのです。
ちょっと話が逸れましたが、組み込みマイコンでの通信処理を用いて行う初期化の解説は以上となります。
4. まとめ
配列の初期化の方法を3つ紹介し、それぞれ解説しましたが、いかがでしたでしょうか?
組み込みマイコンプログラムでは、マイコン起動時に配列だけでなく、あらゆる変数の初期化を行いますので、2節で解説した内容が近いです。3節で解説したものもないことはないのですが、まず、マイコンが起動しないと出来ない処理ですので、マイコン起動後にデータを設定し、それからいろんな処理を実行していくという流れになります。
組み込みマイコンプログラムでは、1節で解説したような初期化をすることは少ないと思います。ただ、すべて0で初期化はあるので、そのときには活用出来ると思います。
ゆくゆくは3節でのやり方ができるようにならないと、組み込みマイコンプログラムをやっていくのは厳しいと思います。
しかし、1節や2節のやり方をマスターすれば、自然に3節のやり方ができるようになります。したがって、始めは1節や2節のやり方をマスターしましょう!