引き続き、入力装置のスケッチを 模索します。
入力装置で、求められる機能は次の通り。
➀ アップボタンで、から 変数(時間間隔)の値が一定数(100)づつ増加する。
➁ ダウンボタンで、変数の値が一定数(100)づつ減少する。
操作により、点滅間隔が変化すれば、大成功。なお、数値単位はミリ秒(1000分の1秒)なので、
初期値500(0.5秒)に、100の増減(0.1秒の増減)です。
③ ➀の変数は、LCDに表示される。
④ 変数の変更後、LCDをスピード表示に戻すため、➁の表示をクリアできる。
さて、いきなり、スピードメータに組み込んで、動作を検証する・・・と、失敗の検証過程が複雑になります。ここでの目的は、スケッチの開発だけ。そこで、単純な装置・・・たとえば「Lチカ回路」・・・をつくり、点滅間隔を入力装置で変えていく。そんなスケッチを開発します。
具体的には、アルデュイーノで、まず、点滅時間を変数にした「LED」点滅スケッチを書き、なお、その時間変数を 変更できればよい。ということです。
【LED点滅回路】イラスト
【スケッチ】
//LED点滅+0.1秒づつ間隔増減調整装置
#include <LiquidCrystal.h>
#define ledPin 2//ledPinを2番ピンに
#define upButton 4//upButtonを4番ピンに
#define downButton 5//downButtonを5番ピンに
#define clearButton 6//clearButtonを6番ピンに
#define RS 7//RSピンを7番に設定
#define RW 8//RWピンを8番に
#define E 9//Eピンを9番に
#define D4 10//D4ピンを10番
#define D5 11//D5ピンを11番
#define D6 12//D6ピンを12番
#define D7 13//D7ピンを13番
#define delayTime 100//チャタリング防止時間
LiquidCrystal lcd(RS,RW,E,D4,D5,D6,D7);//LCD設定
int Threshold;//閾値の変数
unsigned long referenceTime=0;//基準時刻の変数
void setup(){
pinMode(ledPin,OUTPUT);//2番 ledPin 出力
pinMode(upButton,INPUT_PULLUP);//4番 UPボタン 内部プルアップ
pinMode(downButton,INPUT_PULLUP);//5番 Donwボタン 内部プルアップ
pinMode(clearButton,INPUT_PULLUP);//6番 クリアボタン 内部プルアップ
lcd.begin(16,2);//LCD開始16桁2行
lcd.setCursor(1,0);//位置1桁上段に
lcd.print("Start");//「スタート」表示
delay(3000);//表示停止
lcd.clear();//表示クリア
Threshold=500;//点灯時間間隔(閾値)の初期値
lcd.setCursor(1,0);//位置1桁上段に
lcd.print("500");//初期値(0.5秒間隔)を表示
playLcd();//自作関数playLcdを実行
}
void loop(){
unsigned long currentTime=millis();//現在時変数に現時刻を取る
if(currentTime-referenceTime>=Threshold){//現時刻と基準時の差が閾値(間隔)以上なら
digitalWrite(ledPin,!digitalRead(ledPin));//ledピンを作動。ピン電位とはアベコベに
playLcd();//自作関数の実行
referenceTime=currentTime;//基準時間を「使った現時刻」に更新
}
if(!digitalRead(upButton)){//UPボタン押せば
delay(delayTime);//チャタリング防止
Threshold+=100;//閾値を100(0.1秒)づつ増
if(Threshold>900){//900を超えれば
Threshold=900;
}//Threshold変数は900
}
if(!digitalRead(downButton)){//Downボタン押せば
delay(delayTime);//チャタリンング防止
Threshold-=100;//100づつ減
if(Threshold<100){//100を下回るなら
Threshold=100;
}//Thresholdは100
}
if (!digitalRead(clearButton)){//クリアボタンで
delay(delayTime);//チャタリング防止
lcd.clear();//表示クリア
}
}
//自作関数playLcdとは
void playLcd(){
switch(Threshold){
case 100:// Thresholdが100のとき
lcd.clear();//クリア
lcd.setCursor(1,0);//位置1桁上段に
lcd.print("100");//100を表示
break;
case 200:// Thresholdが200のとき
lcd.clear();
lcd.setCursor(1,0);//位置1桁上段に
lcd.print("200");//200
break;
case 300:// Thresholdが300のとき
lcd.clear();
lcd.setCursor(1,0);//位置1桁上段に
lcd.print("300");//300
break;
case 400:// Thresholdが400のとき
lcd.clear();//
lcd.setCursor(1,0);//位置1桁上段に
lcd.print("400");//400表示
break;
case 500:// Thresholdが500のとき
lcd.clear();
lcd.setCursor(1,0);//位置1桁上段に
lcd.print("500");//500
break;
case 600:// Thresholdが600のとき
lcd.clear();
lcd.setCursor(1,0);//位置1桁上段に
lcd.print("600");//600
break;
case 700:// Thresholdが700のとき実行される
l cd.clear();
lcd.setCursor(1,0);//位置1桁上段に
lcd.print("700");//700
break;
case 800:// Thresholdが800とき
lcd.clear();
lcd.setCursor(1,0);//位置1桁上段に
lcd.print("800");//800
break;
case 900:// Thresholdが900のとき
lcd.clear();
lcd.setCursor(1,0);//位置1桁上段に
lcd.print("900");//900
break;
}
}
スケッチ解説
delay()関数を安易に使うな!。
delay()関数は「超・曲モノ」です。使っちゃ「ダメ関数」と言って、云い過ぎではありません。
思い出してみてください。アルデュイーノの入門で、誰しも最初に書くスケッチは「Lチカ」ですね。そのLEDの点滅間隔を操作するのが、このdelay()関数でした。だから、delay()関数は、「基本の基じゃないか」と思いたいところです。事実、単純で使い勝手がいい関数ですから。
もちろん、マイコンの仕事が、単純に1つだけ、例えば「LEDを1だけを点滅させる」ならば、問題は起きません。
ところが、LEDを光らせ、サーボを動かし、なおかつ、その状況をLCDに表示せよ・・・なんて、アルデュイーノに「複数の仕事」を要求すると・・,LEDは点滅しても、その先のスケッチに書かれてる「サーボの挙動」「LCDの表示」が、全く動かない・・・なんて現象がおきます。回路もスケッチも「正常」なのに。・・・です。
まあ、初心者として、素直に「他人の書いたスケッチ」を”そっくりコピペ”しているうちは、問題にならないところでもあります。初心者を抜けて、自分で書き始めて、はじめて"delayの罠"に嵌る(笑)わけです。
原因は、delay()関数が実行されると、そこでマイコン全体が「一時停止」してしまうことにあります。LEDだけを停めるはずが、そこでマイコン全体が一時停止するため、結果として、その後のスケッチに書かれた、サーボにもLCDにも、「停止命令」を受けて、挙動がおかしくなるのです。
millis()関数を使う 間隔制御の構文・・・背後にある「変数のスコープ」
それじゃ、delay()を使わないで、どうやって「間隔」を取るのか!それがmillis()「現在時刻の取得」関数です。
➀まず、2つの時刻の変数、「現時刻」と「基準時刻」を設定します。
変数には,一般的にunsinged long という一番大きな箱を使います。0から4,294,967,295(2の32乗 - 1)まで、変数が入ります。4294967295(マイクロ秒)÷1000(秒)÷60(分)÷60(時)÷24(日)=49日(連続稼働可能?)。そこまで連続運転することもないですが。
変数「基準時刻」は グローバル変数として ヘッダー部分に書きます。loop関数の周回しても、変数が0に更新される=解放(解消)されないように するためです(あるいは、スタティクス変数にしてもOK)。なお、初期値として=0を置きます。
unsigned long referenceTime=0;//基準時刻の変数 初期値0
変数「現時刻」は、loop関数内に置きます。=millis();で、これに現在時刻(ループ開始時)を放り込みます。こちらの変数は、ループ周回ごとに「更新」されなきゃ困るので、loop関数内に置きます。
unsigned long currentTime=millis();//現在時変数に現時刻を取る
➁「変数・現時刻」-「変数・基準時刻(最初は0)」という計算式で 経過時間が計算できます。これを使えば、「もし経過時間がたてば、~せよ」と「if文」を書けます。これで、時間ごとに処理が実行されます。
③「➁のif文」の中で、基準時刻を ➀の時刻に 置き換えます。
if(currentTime-referenceTime>=Threshold){//現時刻と基準時の差が閾値(間隔)以上ならdigitalWrite(ledPin,!digitalRead(ledPin));//ledピンを作動。ピン電位とはアベコベに
referenceTime=currentTime;//基準時間を「使った現時刻」に更新
}
図解すると
最初、基準時刻はゼロなので ループごとに新しく習得する「現在時」から、基準時(最初はゼロ)を引いた時間(=即ち「経過時間」です)が、閾値未満なら、if文は実行されません。LEDの動作や、基準時間更新がなく(つまり、ゼロのまま)、図の黒線のループをクルクル回ります。ループごとに、現在時刻からゼロを引いた時間=経過時間が刻まれます。
その経過時間が閾値以上ならば、if文の条件を満たし、「LEDの動作」と「基準時刻」の更新が実行されます。図で赤字に転線するのです。ここで、「基準時刻に現在時を入れろ」と命令されてます。
この「現在時刻」とは・・・?。「現在時刻」の変数は、loopk関数内に設定されているので、ループごとに消え、ループのアタマで再設定されています。ですから、if文の条件を満たした時点(経過時間を満たした時点)の最新「時刻」です。これを基準時刻にしろ、と命令されてます。
結果、条件を満たした点を、基準にして、新しい経過時間をカウントすることになります。今度は、ループごとの最新時刻から、新しい基準時刻(=直近で条件を満たした時刻)を引いた時間、即ち、新しい経過時間で、条件が判定されます。
前述の通り、「現在時刻」の変数は、loop関数内にあるので、ループごとに、変数が消され、ループの最初に最新時刻が、変数に代入されます。一方「基準時刻」の変数は、ヘッダーに設定された「グローバル変数」です。ループ関数をループしても、数値は不変。基準時刻が変わるのは、条件文を満たし、時間更新の命令が実行された場合のみです。つまり、基準時間の更新で、経過時間も更新されることになります。
この新しい経過時間が、閾値を満たさないなら、そのままです(赤字のループ)。満たせば、条件文が実行され、なおかつ、基準時刻が再更新されます(青字に転線)。次からは、青地のループが始まります。
これにより、delay()関数でマイコンを停止させることなく、経過で時間ごと(一定時間ごと)に、条件文が実行する ことがきます。なるほど・・・、でしょ。この根底で、「変数のスコープ=有効範囲」が巧みに利用されていますね。
ですから、ここでは、変数を設定する「場所」が、とっても大切なんです。その場所を間違えても、コンパイルエラーにはなりませんが、それでも、プログラムが動いてくれません。
【余談】
マイコンの話にだけではないのですが、日本人は「なんちゃら検定」が大好き。もちろん、アルデュイーノ検定なんてありませんが、電子技術の世界でも、「電検」?なるものあるようです。YOUTUBE動画には「Ⅽ++言語講座」が多数あります。これらを見る限り、明らかに「なんちゃら検定」を意識しているようです。
もちろん、これらの動画は「ウソ」を言っているわけではありません。ただ、講座を正確に暗記し「なんちゃら検定」に合格したとことで、プログラム(スケッチ)が書けるかは、別問題のような気がします。
前出の「変数のスコープ」も、「c++講座」にしっかり解説があります。けれども、ビデオ講座で「変数のスコープとは、変数の有効範囲のことでる」と、これを杓子行儀に覚えたとしても、意味が薄く、それが、スケッチでどう使われ、どう使い倒すのか、そこまでたどれて「理解」じゃないですかね。
「杓子行儀な知識」の積み重ねより、「millis()関数の使い方って、変数のスコープをうまく使っているな」と感じるほうが、アタマに残るような 気がします。







































































































































































































































































































































































































































































































































