Arduinoで定期的に処理するコードをいつか書いてみた。
ググるとタイマー割り込みを使う系が多いのだが、割り込み系は他とバッティングしたり、用意されているライブラリや関数を使うと、1つしか時間が設定できないようだったので、loop()内でポーリングして処理させるようなものとしてみた。
ハードウェアの動作概要は次の通り。
- LED1は0.5秒単位に点滅させる
- LED2は1.2秒単位に点滅させる
基本的な考え方
一応、以下の処理を基本として、C言語的な書き方、Metroというライブラリを使用した書き方、独自にクラスを用意した書き方をしてみる。
- millis()で現在の時間を取り出し、前処理した時間から指定時間経過したら、何らかの処理をする。
C言語ライクな書き方
特にクラスを使用せず、C言語的な構造で書いてみたもの。
// LED1と2を指定された間隔で点滅させる
// 時間:millis()はunsigned long型。1周は4,294,967,295ミリ秒≒1193時間≒49日
const int led_pin1 = 5;
const int led_pin2 = 6;
struct LedData {
unsigned long interval_;
unsigned long prev_;
int pin_;
bool isOn_;
};
LedData led1 = { 500, 0, led_pin1, false};
LedData led2 = { 1200, 0, led_pin2, false };
// LED1は0.5秒単位に点滅、LED2は1.2秒単位に点滅とする
void setup() {
led1.prev_ = led2.prev_ = millis();
pinMode(led1.pin_, OUTPUT);
pinMode(led2.pin_, OUTPUT);
}
// LEDのON/OFFを定時間隔で行う共通関数
void ledOnOff(unsigned long now, struct LedData& led)
{
if (now - led.prev_ > led.interval_) {
led.prev_ += led.interval_;
if (!led.isOn_) {
analogWrite(led.pin_, 255);
led.isOn_ = true;
}
else {
analogWrite(led.pin_, 253);
led.isOn_ = false;
}
}
}
void loop() {
unsigned long now = millis();
ledOnOff(now, led1); // 1秒周期
ledOnOff(now, led2); // 2.4秒周期
}
一応、構造化プログラミング的に記述し、ledOnOff()関数内で、時間の判断と処理をしている。
当初は、LedData内のメンバを、グローバル変数として直書きしていたが、いくらC言語風とはいえちょっとなと思って、上の様に修正した。
ちなみに、修正前は以下のような感じ。なんとなく、よく見るArduinoのコード風になっている。たぶん、何も考えずに、勢いで書くとこんな感じになってしまうという見本。
// LED1と2を指定された間隔で点滅させる
// 時間:millis()はunsigned long型。1周は4,294,967,295ミリ秒≒1193時間≒49日
unsigned long led1prevTime = 0;
unsigned long led2prevTime = 0;
bool led1on = false;
bool led2on = false;
const int led_pin1 = 5;
const int led_pin2 = 6;
// LED1は0.5秒単位に点滅、LED2は1.2秒単位に点滅とする
void setup() {
pinMode(led_pin1, OUTPUT);
pinMode(led_pin2, OUTPUT);
led1prevTime = led2prevTime = millis();
}
// LEDのON/OFFを定時間隔で行う共通関数
void ledOnOff(unsigned long now, unsigned long& prevTime, int pin, unsigned long interval, bool& status)
{
if (now - prevTime > interval) {
prevTime += interval;
if (!status) {
analogWrite(pin, 255);
status = true;
}
else {
analogWrite(pin, 253);
status = false;
}
}
}
void loop() {
unsigned long now = millis();
ledOnOff(now, led1prevTime, led_pin1, 500, led1on); // 1秒周期
ledOnOff(now, led2prevTime, led_pin2, 1200, led2on); // 2.4秒周期
}
短いコードであればいいけど、長いコードになると理解しづらいものになったり、コードの再利用ができないという大いなる欠点が出てくる。
Metroライブラリを使用した場合
Metroはここを参照。
C言語風のLedDataをMetroに代替した感じになる。
// LED1と2を指定された間隔で点滅させる
// 時間:millis()はunsigned long型。1周は4,294,967,295ミリ秒≒1193時間≒49日
#include <metro.h>
Metro interval1(500);
Metro interval2(1200);
bool led1on = false;
bool led2on = false;
const int led_pin1 = 9;
const int led_pin2 = 10;
// LED1は0.5秒単位に点滅、LED2は1.2秒単位に点滅とする
void setup() {
pinMode(led_pin1, OUTPUT);
pinMode(led_pin2, OUTPUT);
}
// LEDをON/OFFするメインの処理
void ledOnOff(Metro& elapsed, int pin, bool& status)
{
if (elapsed.check()) {
if (!status) {
analogWrite(pin, 255);
status = true;
}
else {
analogWrite(pin, 128);
status = false;
}
}
}
void loop() {
ledOnOff(interval1, led_pin1, led1on); // 1秒周期
ledOnOff(interval2, led_pin2, led2on); // 2.4秒周期
}
Metroというクラスを使用したが、隠蔽?できているのは時間の間隔判定部分だけで、実際の処理は外側で記述している。そのため、LEDのON/OFF処理用変数がグローバル宣言されていて、再利用などがしづらくなっている。
Metroをメンバとして保持し、C言語風のLedData構造体をクラス化したような感じにしていけばいいのかもしれないが。
独自クラスを用意
Intervalというクラスとその派生のLedOnOffというクラスで実装したもの。
Intervalは、指定時間の間隔を判断し、処理をさせるクラスになるのだが、実際の処理はactionという仮想関数に委譲している。
LedOnOffはaction部分に実際のLEDのON/OFFを実装している。
独自のクラスを一つのファイルの中に書いているので、コードは長い。ただしIntervalを派生させれば、独自の時間間隔処理をカプセル化できるので汎用性は高いと思う。
一応下の実装では、beginなども用意している。これはsetupで行う初期処理もクラス内に押し込むことで、初期処理等の変更にも柔軟な対応ができるようになっている。
// LED1と2を指定された間隔で点滅させる
// 時間:millis()はunsigned long型。1周は4,294,967,295ミリ秒≒1193時間≒49日
#pragma region 基底クラス
// 定時処理を行う
class Interval {
private:
unsigned long prevTime_;
unsigned long intervalTime_;
// 実際の処理
virtual void action() = 0;
public:
Interval(unsigned long time);
void begin(unsigned long time);
// 前回の実行時間から指定時間経過したらactionを実行する
void checkAndAction(unsigned long time);
};
Interval::Interval(unsigned long time) :
prevTime_(0),
intervalTime_(time)
{
}
void Interval::begin(unsigned long time)
{
prevTime_ = time;
}
void Interval::checkAndAction(unsigned long time) {
if (time - prevTime_ > intervalTime_) {
action();
prevTime_ += intervalTime_;
}
}
#pragma endregion
class LedOnOff : public Interval {
private:
int pin_;
bool isOn_;
virtual void action();
public:
LedOnOff(unsigned long time, int pin);
void begin(unsigned long time);
};
LedOnOff::LedOnOff(unsigned long time, int pin) :
Interval(time),
pin_(pin),
isOn_(false)
{
}
void LedOnOff::begin(unsigned long time)
{
Interval::begin(time);
pinMode(pin_, OUTPUT);
analogWrite(pin_, 255);
}
// LEDをON/OFFするメインの処理
void LedOnOff::action() {
if (!isOn_) {
analogWrite(pin_, 255);
isOn_ = true;
}
else {
analogWrite(pin_, 230);
isOn_ = false;
}
}
const int led_pin1 = 9;
const int led_pin2 = 10;
LedOnOff pin1(500, led_pin1);
LedOnOff pin2(1200, led_pin2);
// LED1は0.5秒単位に点滅、LED2は1.2秒単位に点滅とする
void setup() {
unsigned long time = millis();
pin1.begin(time);
pin2.begin(time);
}
void loop() {
unsigned long time = millis();
pin1.checkAndAction(time);
pin2.checkAndAction(time);
}
それぞれの実装のコードサイズ
実装方法 | コードサイズ | メモリサイズ |
C言語風 | 1196 | 19 |
Metoro | 1208 | 27 |
独自クラス | 1484 | 41 |
ちなみに、C言語風に作ったのが一番サイズが小さい。
コメント