組み込みソフトウェア開発の技術を習得するのに、実際にモノを動かしながらプログラミングできる電子工作は有用と思います。簡単な課題から現実のシステムを模した難しい課題まで、様々なレベルに対応できますし、要件定義、設計、実装、テストと様々な開発フェーズのソフトウェアエンジニアリングを体験することができます。
なので、ここでは信号機の制御を題材にした電子工作を通じてソフトウェア開発の雰囲気を体験したいと思います。電子工作に主眼を置くのではなく、ステートマシンを活用した組み込みソフトウェア設計などのソフトウェア開発がテーマになります。
実装
ソフトウェアシステムをプログラムとして記述することを実装と言います。今回は信号機③設計で作成したステートマシン図から以下のようなプログラムを作成します。
/* 1: car */
const int LED1_R = 2; /* red */
const int LED1_Y = 3; /* yellow */
const int LED1_G = 4; /* green */
/* 2: person */
const int LED2_R = 5; /* red */
const int LED2_G = 6; /* green */
const int LED2_W = 7; /* white */
const int BTN2 = 8;
static unsigned int timer0 = 0; /* now */
static unsigned int timer1 = 0; /* 1: car */
static unsigned int timer2 = 0; /* 2: person */
static int btn2 = 0;
static int counter2 = 0; /* blink */
/* E1: stop car, E2: stop person */
typedef enum Event { CLR,
E1,
E2 } Event;
/* S1 1: G(waiting btn), 2: G, 3: Y, 4: R, 5: R(switching buf) */
/* S2 1: R(waiting btn), 2: R, 3: R(switching buf), 4: G, 5: G(blink) */
typedef enum State { Undef,
S11,
S12,
S13,
S14,
S15,
S21,
S22,
S23,
S24,
S25 } State;
static Event ev1 = CLR;
static Event ev2 = CLR;
static State st1 = Undef;
static State st2 = Undef;
void HandleEvent1() {
switch (st1) {
case S11:
if (btn2 == LOW) {
timer1 = millis();
st1 = S12;
}
break;
case S12:
if (timer0 - timer1 > 10000) {
timer1 = millis();
digitalWrite(LED1_G, LOW);
digitalWrite(LED1_Y, HIGH);
st1 = S13;
}
break;
case S13:
if (timer0 - timer1 > 5000) {
ev1 = E1;
digitalWrite(LED1_Y, LOW);
digitalWrite(LED1_R, HIGH);
st1 = S14;
}
break;
case S14:
if (ev2 == E2) {
ev2 = CLR;
timer1 = millis();
st1 = S15;
}
break;
case S15:
if (timer0 - timer1 > 1000) {
digitalWrite(LED1_R, LOW);
digitalWrite(LED1_G, HIGH);
st1 = S11;
}
break;
default:
break;
}
}
void HandleEvent2() {
switch (st2) {
case S21:
if (btn2 == LOW) {
digitalWrite(LED2_W, HIGH);
st2 = S22;
}
break;
case S22:
if (ev1 == E1) {
ev1 = CLR;
timer2 = millis();
st2 = S23;
}
break;
case S23:
if (timer0 - timer2 > 1000) {
timer2 = millis();
digitalWrite(LED2_W, LOW);
digitalWrite(LED2_R, LOW);
digitalWrite(LED2_G, HIGH);
st2 = S24;
}
break;
case S24:
if (timer0 - timer2 > 5000) {
counter2 = 0;
timer2 = millis();
st2 = S25;
}
break;
case S25: /* (0) 1 (2) 3 (4) 5 (6) 7 (8) 9 (10) */
if (counter2 > 10) {
ev2 = E2;
digitalWrite(LED2_R, HIGH);
digitalWrite(LED2_G, LOW);
st2 = S21;
} else if (timer0 - timer2 > 500) {
if (counter2 % 2 == 0) {
digitalWrite(LED2_G, LOW);
} else {
digitalWrite(LED2_G, HIGH);
}
counter2 += 1;
timer2 = millis();
}
break;
default:
break;
}
}
void setup() {
pinMode(LED1_R, OUTPUT);
pinMode(LED1_Y, OUTPUT);
pinMode(LED1_G, OUTPUT);
pinMode(LED2_R, OUTPUT);
pinMode(LED2_G, OUTPUT);
pinMode(LED2_W, OUTPUT);
pinMode(BTN2, INPUT_PULLUP);
timer0 = 0;
timer1 = 0;
timer2 = 0;
btn2 = HIGH;
counter2 = 0;
st1 = S11;
st2 = S21;
digitalWrite(LED1_R, LOW);
digitalWrite(LED1_Y, LOW);
digitalWrite(LED1_G, HIGH);
digitalWrite(LED2_R, HIGH);
digitalWrite(LED2_G, LOW);
digitalWrite(LED2_W, LOW);
}
void loop() {
btn2 = digitalRead(BTN2);
timer0 = millis();
HandleEvent1();
HandleEvent2();
}
状態に応じてイベント、アクション、状態遷移を記述するシンプルな構造です。タイマーイベントの扱いやイベントのハンドリング方法は、規模が大きいわけではないので、凝った作りにはしていません。命名規則やモジュールの結合度などソフトウェアエンジニアリング的にどうかとは思いますが、ご容赦ください。

おわりに
実装に学校のテストのような誰もが同じになる正解はありません。今回はswitch/case文を用いた実装を行いましたが、二次元配列や一次元配列、Stateパターンを用いる実装方法もあります。
今回、詳しく説明していませんが、ステートマシン図からのプログラムの作成は、作成というより変換作業に近いものになると思います。慣れれば誰でも実装できるようになります。詳細は、別の機会に触れたいと思います。
