ダイナミック点灯や、その他センサの制御などと10ms以上の長いdelayを共存させる方法を考えていきます
前書き
ちょっと長い前書きです。本題とは関係ないので急ぎなら読み飛ばしてしまいましょう。
複数桁の7セグLEDでの点灯に必須とも言えるダイナミック点灯ですが、常に均等な時間で表示を入れ替えないといけないこともあり、delay()関数との共存が難しくなっています。
対処法は
・タイマー割り込みを使う
・1ループに一定のdelayを設定&毎ループ関数を呼び出し 関数の呼び出し回数を数え、n回目に処理を実行する
など挙げられます。私1人でコーディングしていたときは何も困ることはなかったのですが、高校の課題研究で共同作業をしたときに困ったことになりました。ペアの人はタイマー割り込みやダイナミック点灯の仕組みを知らないのです。別に教えればいいじゃん って話ではありますが、
1.Arduinoその他マイコンにほぼ触れたことない
2.参考文献がdelay(200)とかを多用している
という条件なので、WebサイトやArduinoの教本に近い形で開発をするために、どうしてもdelay()関数に近い動作をする必要がありました。
今回書いたコードなら、delay( をDelay(に置き換えるだけで問題なく動作します。
ソースコード
というわけでコードは以下の通り
void (*refresh_function[5])();
void SetFunction(void (*funcptr)()) {
static uint8_t array_num = 0;
refresh_function[array_num++] = funcptr;
}
bool Timer(unsigned long *start_time, unsigned long count_time, bool auto_countup = false) {
bool flag = false;
if (millis() - *start_time > count_time) {
flag = true;
if (auto_countup)
*start_time += count_time;
}
return flag;
}
void Delay(uint32_t count_time) {
unsigned long start_time = millis();
while (!Timer(&start_time, count_time)) {
for (uint8_t i = 0; refresh_function[i] != nullptr; i++) {
refresh_function[i]();
}
delay(1);
}
}
使い方
とりあえず使い方と使用例。詳しい解説は次のセクションで行います。
初めに、SetFunction(関数のアドレス)で常時実行し続けたい関数を設定します
あとは通常のdelayと同じようにDelay(待ちたいミリ秒数);とすればOKです(Dが大文字なので注意)
使用例
void setup() {
Serial.begin(9600);
SetFunction(resfresh);
}
void loop() {
Serial.println("\n500ms");
Delay(1000);
Serial.println("\n1000ms");
Delay(1000);
}
void (*refresh_function[5])();
void SetFunction(void (*funcptr)()) {
static uint8_t array_num = 0;
refresh_function[array_num++] = funcptr;
}
bool Timer(unsigned long *start_time, unsigned long count_time, bool auto_countup = false) {
bool flag = false;
if (millis() - *start_time > count_time) {
flag = true;
if (auto_countup)
*start_time += count_time;
}
return flag;
}
void Delay(uint32_t count_time) {
unsigned long start_time = millis();
while (!Timer(&start_time, count_time)) {
for (uint8_t i = 0; refresh_function[i] != nullptr; i++) {
refresh_function[i]();
}
delay(1);
}
}
void resfresh() {
//7セグのダイナミック点灯処理を模したプログラム
static uint8_t flag = 0;
switch (flag) {
case 0:
Serial.print("〇〇〇〇〇〇〇〇〇〇〇〇");
break;
case 1:
Serial.print("■■■■■■■■■■■■■■■■");
break;
case 2:
Serial.print("◇◇◇◇◇◇◇◇◇◇◇◇◇◇◇");
break;
case 3:
Serial.println("●●●●●●●●●●●●●●●");
break;
}
if (++flag > 4)
flag = 0;
}
動画なしの画像だけでで7セグを表現するのは難しかったので、シリアルモニタでそれっぽくしています。
解説
関数ポインタについて
関数のアドレスとは??って人もいるかもしれませんが、関数もintやcharなどと同じようにアドレスを持ちます。
関数呼び出しは”関数名()”ですが、関数のポインタは”関数名”でOKです。
Timer関数
forやwhileループを使って、指定した秒数が経過するまでfalse 経過するとtrueを返します。
流れのイメージとしては
といった形になります。
時間の取得にはmillis()関数を使っています。Arudinoの標準関数で、プログラムの実行開始からの経過時間をミリ秒で蓄積されます。約50日でオーバーフローするみたいですが、問題なく対応できているはずです。
最後にstart_timeを増やしているのは、以下のような用途に対応するためのお節介機能です。
ストップウォッチの作成に使ったコードの一部です。 やや汚いコードなのはご愛嬌ということで。
//ストップウォッチのカウントダウン部分
void StopWatch(){
start_time = millis();
while (!Count());
}
static bool Count() {
if (Timer(&start_time, 1000,true))
if (--seconds_ < 0) {
seconds_ = 59;
if (minutes_ == 0) {
seconds_ = 0;
return true;
} else
minutes_--;
}
Delay(1);
return false;
}
Delay関数
今回の目玉というか本題です。先ほど述べた関数を使用しています。最初に述べたループ回数をカウントする方法でも問題ないですが、こちらの方が精度よくできそうなのでこの形にしてみました。
やってることは非常に単純で、指定した時間が経過するまで他の関数を実行しながら待機しているだけです。
今回の例だと関数ポインタを使う必要性はほぼないですが、私の場合ファイル分割をしていたので、loop()がある.inoファイルに記述した関数を、持っていくためにポインタを使いました。この方が稼働性がよさそう というのも一つの理由です。
最後に
簡単に使えるが制約の多いdelay関数を使い勝手良くしてみました。
私もまだまだプログラミングに関しては初心者なので、もっといい方法もあるでしょうし、今回の方法が必ずしもみなさんの目的に沿えるとは思いませんが、どなたかの役に立てば幸いです。
コメント