●緑、黄、赤と順に点滅するLED信号機を作る
LEDを3つ(緑、黄、赤)使い、信号機を作ります。緑、黄、赤がそれぞれ2秒ずつ点灯して、順に繰り返すようにします。
下図のように配線します。抵抗は250Ωとしています。
// C++ code
//
// Oz@SIT all rights reserved.
// 2022/09/10 rev. 0.1
#define LED_G 9 //緑色LEDを9番ピンに接続
#define LED_Y 10 //黄色LEDを10番ピンに接続
#define LED_R 11 //赤色LEDを11番ピンに接続
void setup()
{
pinMode(LED_G, OUTPUT); //ピンを出力に設定。デジタルピンの場合必要。
pinMode(LED_Y, OUTPUT); //ピンを出力に設定。デジタルピンの場合必要。
pinMode(LED_R, OUTPUT); //ピンを出力に設定。デジタルピンの場合必要。
}
void loop()
{
signal(1, 0, 0); //信号関数(signal)を緑LEDのみ1、残りを0として呼び出す。
delay(2000); // Wait for 2000 millisecond(s)
signal(0, 1, 0); //信号関数(signal)を黄LEDのみ1、残りを0として呼び出す。
delay(2000); // Wait for 2000 millisecond(s)
signal(0, 0, 1); //信号関数(signal)を赤LEDのみ1、残りを0として呼び出す。
delay(2000); // Wait for 2000 millisecond(s)
}
//信号関数
//戻り値:なし
//引数 intを3つ。それぞれ緑、黄、赤のLEDに対応。1とするとLEDが点灯、0とするとLEDが消灯する。
void signal(int g, int y, int r)
{
digitalWrite(LED_G, g);
digitalWrite(LED_Y, y);
digitalWrite(LED_R, r);
}
●緑、黄、赤と順に点滅するLED信号機に加え、押しボタンを設置する。押しボタンが押されたら緑のときはすぐに黄色へ移行する。
上と同様の信号機を考えます。押しボタンが押されたら次のように信号を変化します。
まず現在の信号が緑であれば、直ちに黄色にし、5秒後に赤にします。その後はまた緑に戻り、通常動作をします。
現在の信号が黄色や赤であれば、ボタンを押されたことは無視します。
このようにするためにはLEDを光らせて何秒か待っているようなプログラムでは、待っている間に押しボタンを押されても、待ち時間が終わるときにしか、押されたことを検出できません。そのためプログラムを変更して対応しないといけません。
回路は下図のようにし、押しボタンを追加します。
下記のプログラムではdelayでプログラムが待ち続けることの無いように、advancedDelayという関数を作ってそれを解決するようにしています。またplSignalという信号の関数をこのadvancedDelayと組み合わせて作成しています。(下記のadvancedDelay関数は「Arduinoのマルチタスクについて」を基に作成しています。)
plSignal(parallel(並行)動作の信号という意味です)ではplSignal関数が呼ばれた最初か、2回目以降かを切り分けるために、flagという変数を使っています。flagは外から与えることが出来るようにポインタとして実装している。ポインタとなっているので、plSignal関数を呼び出すときに変数の参照を与えれば、その変数自身を変更することが出来ます。
※なぜかTinkerCADでのArduinoではポインタがうまく使えなかったので、flagの宣言においては配列を使って対処しています。int gyr_flag[1]; によって整数型のflagを大きさ1の配列としている。こうするとgyr_flagという変数はポインタとして扱えるので、plSignal関数内のように int* flag としてポインタ変数として使うことが出来ます。
plSignal関数の最初に最初に戻り値の初期化をした後、flagのチェックを行っている。flagの中身(*flag)が0(まだ実行中ではない)であれば、信号の設定をし、開始時刻を*priviousMillisに記録します。さらに*flagを1(実行中である)に設定する。これで最初の設定は終わりで、falseを戻り値として返します。
plSignal関数が2回目以降呼ばれたときには*flagは1になっているので、関数内のelse以下が実行され、advancedDelay関数が呼ばれます。advancedDelay関数は指定したintervalを超えていたらtrue、超えていなければfalseを返すので、trueが返ってきたときに信号点灯・消灯中フラグを0に設定し、戻り値をtrueにします。戻り値がtrueになっていればplSignal関数終了ということで終了処理(ここでは信号を緑→黄→赤の順に変えていく)を行います。
advancedDelay関数ではinterval=0xffffffffのときは何もせず、0xffffffffでないときは現在時間(millis関数で取ったms単位の時間)と*priviousMillisとの差を取り、この差がintervalで指定した値を超えていれば戻り値をtrue、超えていなければfalseとします。
// C++ code
//
// push button interrupt
// Oz@SIT all rights reserved.
// 2022/09/12 rev. 0.1
/*
●緑、黄、赤と順に点滅するLED信号機がある。緑の時にボタンが押されると直ちに黄色、赤と変化するようにする。
また黄色や赤の時にボタンを押されたときは、無視する。
このようにするためにはLEDを光らせて何秒か待っているようなプログラムでは、待っている間に押しボタンを押されても、待ち時間が終わるときにしか、押されたことを検出できない。そのためプログラムを変更して対応しないといけない。
*/
#define SW 4 //スイッチを4番ピンに接続
#define LED_G 9 //緑色LEDを9番ピンに接続
#define LED_Y 10 //黄色LEDを10番ピンに接続
#define LED_R 11 //赤色LEDを11番ピンに接続
int s, ps; //スイッチの読み取り値用変数、psは1回前の値保存用
int gyr_flag[1];
unsigned long previousMs[1]; //スタート時のms時刻
int gyr = 0; //0:green, 1:yellow, 2:red
void setup()
{
pinMode(LED_G, OUTPUT); //ピンを出力に設定。デジタルピンの場合必要。
pinMode(LED_Y, OUTPUT); //ピンを出力に設定。デジタルピンの場合必要。
pinMode(LED_R, OUTPUT); //ピンを出力に設定。デジタルピンの場合必要。
pinMode(SW, INPUT); //ピンを出力に設定。デジタルピンの場合必要。
gyr_flag[0] = 0;
previousMs[0] = 0;
s = 0;
}
void loop()
{
ps = s; //前回のスイッチの状態を保存
s = digitalRead(SW); //スイッチの状態を読み込む
if(s - ps == 1){ //スイッチが押されたかを立ち上がり検出
/*
まず現在の信号が緑であれば(通常はこの状態)、直ちに黄色にし、
5秒後に赤にする。
現在の信号が黄色や赤であれば、ボタンを押されたことは無視する。
*/
if(gyr == 0){ //現在の信号が緑であれば
gyr_flag[0] = 0;
gyr = 1; //直ちに黄色にする。
}
}
switch(gyr){
case 0: //green
if(plSignal(1,0,0,5000,previousMs, gyr_flag)) gyr = 1;
break;
case 1: //yellow
if(plSignal(0,1,0,5000,previousMs, gyr_flag)) gyr = 2;
break;
case 2: //red
if(plSignal(0,0,1,5000,previousMs, gyr_flag)) gyr = 0;
break;
default:
break;
}
}
bool plSignal(int g, int y, int r, unsigned long durationMs, unsigned long* previousMillis, int* flag){ // 並列実行用信号関数
bool ret = false; //初期戻り値をfalseに設定
if(!*flag){ //今から信号の点灯・消灯を変えるなら
signal(g, y, r); //信号を設定する
*previousMillis = millis(); //Servoの開始ms時刻
*flag = 1; //信号点灯・消灯中フラグを1に設定
}else{
if(advancedDelay(durationMs, *previousMillis)){ //durationの間、信号の点灯状態を変えないように待ち、終わったら
*flag = 0; //信号点灯・消灯中フラグを0に設定
ret = true; //信号点灯・消灯が終わったのでret=trueとする
}
}
return ret; //retを戻り値として返す
}
//信号関数
//戻り値:なし
//引数 intを3つ。それぞれ緑、黄、赤のLEDに対応。1とするとLEDが点灯、0とするとLEDが消灯する。
void signal(int g, int y, int r)
{
digitalWrite(LED_G, g);
digitalWrite(LED_Y, y);
digitalWrite(LED_R, r);
}
//delayで待ちが発生しないようにするための高度化delay関数
boolean advancedDelay(unsigned long interval, unsigned long previousMillis){
bool ret = false; //初期戻り値をfalseに設定
if(interval != 0xffffffff){
unsigned long currentMillis = millis(); //現在時刻を設定
if(currentMillis - previousMillis >= interval) { //現在時刻がスタート時の時刻からintervalmsを超えたら
ret = true; //終了を示すtrueに設定
}else{
ret = false; //未終了を示すfalseに設定
}
}
return ret; //retを戻り値として返す
}
●緑、黄、赤と順に点滅するLED信号機に加え、歩行者用の緑と赤のLEDを持つ押しボタン信号機を作る
上と同様に信号機があります。ただし、この信号機は押しボタンが押されるまでずっと緑が点灯しています。押しボタンが押されたら次のように歩行者信号を緑にします。
まず現在の信号が緑であれば(通常はこの状態)、直ちに黄色にし、5秒後に赤にします。赤になると同時に歩行者信号を緑にします。歩行者信号の緑は5秒間継続し、そこから赤に移行します。赤になると同時に、メインの信号を緑に戻します。
現在の信号が黄色や赤であれば、ボタンを押されたことは無視します。
回路は下図のようにし、上側をメイン信号、下側2灯を歩行者用とします。
(以下、書きかけ)
下記のプログラムでは上で説明した並行動作の信号関数と、advancedDelay関数を基本とし、並行動作の歩行者用信号関数を加えてこの問題を解決しています。
上と同様に信号機があります。ただし、この信号機は押しボタンが押されるまでずっと緑が点灯しています。押しボタンが押されたら次のように歩行者信号を緑にします。
まず現在の信号が緑であれば(通常はこの状態)、直ちに黄色にし、5秒後に赤にします。赤になると同時に歩行者信号を緑にします。歩行者信号の緑は5秒間継続し、そこから赤に移行します。赤になると同時に、メインの信号を緑に戻します。
現在の信号が黄色や赤であれば、ボタンを押されたことは無視します。
回路は下図のようにし、上側をメイン信号、下側2灯を歩行者用とします。
// C++ code
//
/*
●緑、黄、赤と順に点滅するLED信号機に加え、歩行者用の緑と赤のLEDを持つ押しボタン信号機を作る
上と同様に信号機がある。ただし、この信号機は押しボタンが押されるまでずっと緑が点灯している。押しボタンを押されたら次のように歩行者信号を緑にする。
まず現在の信号が緑であれば(通常はこの状態)、直ちに黄色にし、5秒後に赤にする。赤になると同時に歩行者信号を緑にする。歩行者信号の緑は5秒間継続し、そこから赤に移行する。赤になると同時に、メインの信号を緑に戻す。
現在の信号が黄色や赤であれば、ボタンを押されたことは無視する。
このようにするためにはLEDを光らせて何秒か待っているようなプログラムでは、待っている間に押しボタンを押されても、待ち時間が終わるときにしか、押されたことを検出できない。そのためプログラムを変更して対応しないといけない。
*/
#define SW 4 //スイッチを4番ピンに接続
#define LED_PG 6 //歩行者用緑色LEDを6番ピンに接続
#define LED_PR 5 //歩行者用赤色LEDを5番ピンに接続
#define LED_G 9 //緑色LEDを9番ピンに接続
#define LED_Y 10 //黄色LEDを10番ピンに接続
#define LED_R 11 //赤色LEDを11番ピンに接続
int s, ps; //スイッチの読み取り値用変数、psは1回前の値保存用
//int count = 0; //スイッチが押された回数
/*int green[1];
int yellow[1];
int red[1];
int pGreen[1];
int pRed[1];*/
int gyr_flag[1];
int pgr_flag[1];
int greenB;
int yellowB;
int redB;
unsigned long previousMs[1]; //スタート時のms時刻
unsigned long previousPedMs[1]; //スタート時のms時刻 歩行者信号用
int gyr = 0; //0:green, 1:yellow, 2:red
int p_gr = 1; //0:green, 1:red for pedestrians
int mode = 0; //0:main, 1~:pedestrians
void setup()
{
pinMode(LED_G, OUTPUT); //ピンを出力に設定。デジタルピンの場合必要。
pinMode(LED_Y, OUTPUT); //ピンを出力に設定。デジタルピンの場合必要。
pinMode(LED_R, OUTPUT); //ピンを出力に設定。デジタルピンの場合必要。
pinMode(LED_PG, OUTPUT); //ピンを出力に設定。デジタルピンの場合必要。
pinMode(LED_PR, OUTPUT); //ピンを出力に設定。デジタルピンの場合必要。
pinMode(SW, INPUT); //ピンを出力に設定。デジタルピンの場合必要。
/*green[0] = 0;
yellow[0] = 0;
red[0] = 0;
pGreen[0] = 0;
pRed[0] = 0;*/
gyr_flag[0] = 0;
pgr_flag[0] = 0;
previousMs[0] = 0;
previousPedMs[0] = 0;
ped_signal(0, 1);
s = 0;
}
void loop()
{
ps = s; //前回のスイッチの状態を保存
s = digitalRead(SW); //スイッチの状態を読み込む
if(s - ps == 1){ //スイッチが押されたかを立ち上がり検出
/*
まず現在の信号が緑であれば(通常はこの状態)、直ちに黄色にし、
5秒後に赤にする。赤になると同時に歩行者信号を緑にする。
歩行者信号の緑は5秒間継続し、そこから赤に移行する。赤になると同時に、
メインの信号を緑に戻す。
現在の信号が黄色や赤であれば、ボタンを押されたことは無視する。
*/
if(gyr == 0){ //現在の信号が緑であれば
gyr_flag[0] = 0;
pgr_flag[0] = 0;
gyr = 1; //直ちに黄色にし
p_gr = 1; //歩行者用信号は赤のまま
mode = 1; //歩行者モードに設定
}
}
switch(gyr){
case 0: //green
if(plSignal(1,0,0,0xffffffff,previousMs, gyr_flag)) gyr = 1;
break;
case 1: //yellow
if(plSignal(0,1,0,5000,previousMs, gyr_flag)){
gyr = 2;
}
break;
case 2: //red
if(plSignal(0,0,1,5000,previousMs, gyr_flag)){
gyr = 0;
}
break;
default:
break;
}
if(mode >= 1){ //歩行者モードの時、以下を実行
switch(p_gr){ //緑、赤信号の切り替え
case 0: //green
if(plPedSignal(1, 0, 5000, previousPedMs, pgr_flag)){
p_gr = 1;
//mode = 0;
//ped_signal(0, 1);
}
break;
case 1: //red
if(plPedSignal(0, 1, 5000, previousPedMs, pgr_flag)){
p_gr = 0;
if(mode == 1){
mode++;
}else{
mode = 0;
}
}
break;
default:
break;
}
}
//greenB = green[0];
}
bool plSignal(int g, int y, int r, unsigned long durationMs, unsigned long* previousMillis, int* flag){ // 並列実行用信号関数
bool ret = false; //初期戻り値をfalseに設定
if(!*flag){ //今から信号の点灯・消灯を変えるなら
signal(g, y, r); //信号を設定する
*previousMillis = millis(); //Servoの開始ms時刻
*flag = 1; //信号点灯・消灯中フラグをtrueに設定
}else{
if(advancedDelay(durationMs, *previousMillis)){ //durationの間、信号の点灯状態を変えないように待ち、終わったら
*flag = 0; //信号点灯・消灯中フラグをfalseに設定
ret = true; //信号点灯・消灯が終わったのでret=trueとする
}
}
return ret; //retを戻り値として返す
}
//信号関数
//戻り値:なし
//引数 intを3つ。それぞれ緑、黄、赤のLEDに対応。1とするとLEDが点灯、0とするとLEDが消灯する。
void signal(int g, int y, int r)
{
digitalWrite(LED_G, g);
digitalWrite(LED_Y, y);
digitalWrite(LED_R, r);
}
bool plPedSignal(int g, int r, unsigned long durationMs, unsigned long* previousMillis, int* flag){ // 並列実行用信号関数
bool ret = false; //初期戻り値をfalseに設定
if(!*flag){ //今から信号の点灯・消灯を変えるなら
ped_signal(g, r); //信号を設定する
*previousMillis = millis(); //Servoの開始ms時刻
*flag = 1; //信号点灯・消灯中フラグをtrueに設定
}else{
if(advancedDelay(durationMs, *previousMillis)){ //durationの間、信号の点灯状態を変えないように待ち、終わったら
*flag = 0; //信号点灯・消灯中フラグをfalseに設定
ret = true; //信号点灯・消灯が終わったのでret=trueとする
}
}
return ret; //retを戻り値として返す
}
//歩行者用信号関数
//戻り値:なし
//引数 intを3つ。それぞれ緑、黄、赤のLEDに対応。1とするとLEDが点灯、0とするとLEDが消灯する。
void ped_signal(int g, int r)
{
digitalWrite(LED_PG, g);
digitalWrite(LED_PR, r);
}
//delayで待ちが発生しないようにするための高度化delay関数
boolean advancedDelay(unsigned long interval, unsigned long previousMillis){
bool ret = false; //初期戻り値をfalseに設定
if(interval != 0xffffffff){
unsigned long currentMillis = millis(); //現在時刻を設定
if(currentMillis - previousMillis >= interval) { //現在時刻がスタート時の時刻からintervalmsを超えたら
ret = true; //終了を示すtrueに設定
}else{
ret = false; //未終了を示すfalseに設定
}
}
return ret; //retを戻り値として返す
}