今回はArduinoに関してのプログラムの話題も少し紹介します。
プログラムに関してのサイトはほかにも多くあり、基本的な動作に関してはもう皆さんもご存知だと思います。そこで今回は少し挑戦的な内容を紹介してみたいと思います。
プログラムを作成するにあたり、必ずといっていいほどあたるのが“if文”であると思います。
決められた動作を行うだけでも、内部処理の処理を変更するときにはif文が存在し、分岐処理を含まないのは無いと考えてもいいくらいですかね。
しかし、上司が意地悪で「必ずif文は使わないと複雑な処理はかけないのか?」ということをいわれたら、どう返しますか?(まずされることはないと思うが・・・)
今回は危機を解決するべくif文の形に注目してみました。
if文
文法としては
if(条件){
処理・・・
}
if()の中の条件は真と偽を確認している箇所になります。
しかし、中に1を入れれば確実に実行されますし、0を入れれば処理はされません。そうすると()の中では真偽を判定するのみで判定処理が括弧の中のみしか有効でないのか少し疑問があがります。
検証として少しこんな形を作ってみましょう!
判定処理結果は真と偽のブール型であるので方に注目すると
boolean branch;//変数宣言
branch = (条件);//判定処理結果格納
といった形でもいいように感じます。実際に行うとなんとコンパイルは通ります。
実は()内だけでしか計算できないわけではないのです。
ということは、逆に()内で行った条件計算で間違って“=”を入力してしまうと代入されてしまうことになります。(ここよくあるミスなので注意!!)
話は戻って、では分岐はどのようにすれば可能なのでしょうか?
実は、メモリ空間に対する移動を行うと可能になります。
変数のメモリ空間の処理を確実にするためには“配列”ですね。
それに、配列は要素数を指定すれば好きな変数にアクセスできます。
これなら十分活用できそうです。
しかし、変数と処理は別物です。ここでちょっとしたトリックを使います。
実はメモリ空間上は変数だけでなく、実行する処理についても展開されています。
ここで処理=関数とすればピンとくると思いますが、関数もプログラムカウンタを指定することができれば、関数の処理を実行することは可能です。
つまり”関数ポインタ”を使うということです。
整理すると配列の要素に対応する関数のアドレスを用意することができれば、可能であるという事ですね。
論より証拠!!
ということでサンプルプログラムを用意しました。
Arduinoで動作します。
———————–以下プログラム———————–
//イベント数・モード数
#define EVENTNUM 3
//コントロール数
#define CONT 2
//カウンタの最大値・最小値
#define NUMMAX 10
#define NUMMIN 0
//イベント名列挙型
enum{
chk = 0,
initcnt,
contproc
};
//処理関数群のプロトタイプ宣言
void fn_count();
void fn_direct();
void fn_max();
void fn_min();
void fn_UP();
void fn_DOWN();
//関数ポインタによるファンクションマトリックス宣言
void (*cotrol[EVENTNUM][CONT])() = {{fn_count,fn_direct},
{fn_max, fn_min},
{fn_UP, fn_DOWN}};
//グローバル変数
int counter; //カウント用変数
boolean UPDOWNFLG; //アップダウンフラグ
void setup() {
//変数の初期化
counter = 0;
UPDOWNFLG = false;
Serial.begin(9600);
}
void loop() {
boolean branch;
//カウンタ値チェック結果を格納
branch = (NUMMAX <= counter || NUMMIN >= counter);
//ファンクションマトリックスにて関数の選択
(*cotrol[chk][(int)branch])();
//カウンター結果を表示
Serial.print(“counter :”);
Serial.println(counter);
delay(500);
}
//カウント処理
void fn_count(){
Serial.println(“fn_count”);
//ファンクションマトリックスにて関数の選択
(void)(*cotrol[contproc][(int)UPDOWNFLG])();
}
//カウント方向の変更処理
void fn_direct(){
boolean branch;
//カウンタが大きいかチェック結果を格納
Serial.println(“fn_direct”);
branch = NUMMAX > counter;
//ファンクションマトリックスにて関数の選択
(void)(*cotrol[initcnt][(int)branch])();
}
//最大値を超えた場合
void fn_max(){
Serial.println(“fn_max”);
//カウンタを一つ前にする
counter = NUMMAX – 1;
//アップダウンフラグをダウンにセット
UPDOWNFLG = true;
}
//最小値を下回った場合
void fn_min(){
Serial.println(“fn_min”);
//カウンタを一つ前にする
counter = NUMMIN + 1;
//アップダウンフラグをアップにセット
UPDOWNFLG = false;
}
//カウントアップ
void fn_UP(){
Serial.println(“fn_UP”);
counter++;
}
//カウントダウン
void fn_DOWN(){
Serial.println(“fn_DOWN”);
counter–;
}
———————–以上プログラム———————–
動作させてみていかがでしたか?確かにifはないですね。
シリアルにはわかりやすいように呼ばれた関数名が表示されます。
処理が切り替わる個所を表示してみます。10までカウントアップしてカウントダウンに切り替わっていることを確認できます。
これの正式な名称は知りませんが状態遷移マトリックス(STM)と呼んでいます。
これでオートマトンを作ることができます。
では、ちょっと解説してみます。
fn_~関数群はこの関数ポインタに所属している関数であり、呼出しはすべて (*cotrol[~][~])()からです。その関数ポインタ配列が宣言されている部分をみると配列が定義されています。これはイベントに関しては3つの要素で、コントロールに関しては2つ(真偽)であることを示したものです。
イベント | 偽 | 真 |
---|---|---|
1.カウンタ値が境界値になったか? | fn_count() | fn_direct() |
2.カウント値が最大値未満か? | fn_max() | fn_min() |
3.アップダウンフラグがON? | fn_UP() | fn_DOWN() |
イベントに関しては列挙型で示していています。(chk=0、initcnt=1、contproc=2)
流れとしては始めは1.のイベントでカウントかカウント方向かの処理を選択します。
カウントの場合は3.のアップダウンフラグの状態より処理を選択し、カウント方向の場合は2.の最大値未満の値かどうかで処理を選択します。
使い方によってはループ処理も可能(再帰になるので注意してください)
組み込み開発ではこの方法を使用されることがあり、主に状態遷移機械の役割を持たせています。
真偽の部分を複数条件での分岐(switch文、elseif文)も始めの配列宣言内の関数の並べ方で処理できます。
最後まで読んでいただきありがとうございました。興味のある方はいろいろ試してみてくださいね。
それではまた。