一定時間で何かをする、といえばタイマーの割込みを使うことでしょうが、タイマーを使いたくないないし使えないということもあると思います。
そういうときループをぶんまわしながら、時刻などを比較して一定時間ごとに処理をするという方法をとったりすることがあります。今回はそれを簡単に書けるスニペットを考えたという話です。
他のライブラリでどうにかする
こういうことをするライブラリを調べてみると、Metro というのがあります。これは以下のようにして使うライブラリのようです。
Metro interval250 = Metro(250);
void loop() {
if (interval250.check()) {
// ここで 250msごとの処理
}
}
もちろん悪くはないのですが、変数宣言と実際の処理が分かれており、余計な変数を宣言する必要があってなんか嫌です。(変数名をいちいち考えたくないという意味です)
loop() でいきなり使える定期実行
そこで以下のようなスニペットを考えました。interval class が本体です。
template <uint16_t time>
class interval {
uint32_t next_run = 0;
template <class T>
void _run(T func) {
uint32_t now = millis();
if (next_run < now) {
func();
next_run = now + time;
}
}
interval() {}
public:
template <class T>
static void run(T func) {
static interval<time> instance;
instance._run(func);
}
};
void setup() {
Serial.begin(9600);
}
void loop() {
interval<250>::run([]{
Serial.println("250ms 1");
});
interval<250>::run([]{
Serial.println("250ms 2");
});
interval<1000>::run([]{
Serial.println("1000ms");
});
}
loop() 内でいきなり適当な書きかたをするだけですみます。
interval クラス
事前準備 (グローバル変数やstatic変数を自力で宣言したり) をせず、いきなり静的な関数ないしメソッドを読んで使うことができるようにするため、以下のような感じになっています。
まず、interval クラスは time をテンプレート引数にとっているので、time ごとに別のクラスが作られます。
各 time を持つ interval クラスには、さらに引数 func の型をテンプレート引数にとる run() という static メソッドを持っています。
run() は内部で static 変数として interval
func には lambda 式を書くことを想定しています。lambda 式の型は書く度に違うものになるので、同じ time を持つ処理も複数書けます。
つまり、テンプレート引数を使うことで、グローバルな状態を複数作っています。自分で余計な変数を宣言せずに「状態」を持つためにはこのような小細工が必要なようです。
これにより、各 time と引数 func ごとに interval