Arduino Unoにはタイマー機能として,Timer0,Timer1,Timer2,WDT(ウォッチドッグ)があります.
一般的に他のサイトでタイマーを使う場合,Timer2を利用することが多く,MsTimer2やFlexiTimer2といったライブラリが使われます.
ただ,Timer2を使った別のライブラリ1を利用する場合は単なるタイマーとしてTimer2を使えない問題があり,代わりにTimer1を使うことになります.
そして,Timer1のラッパーライブラリとしてよく紹介されているのがTimerOneです.
参考資料
機能
Timer1クラス-静的関数
initialize(microseconds): Timer1の初期化とマイクロ秒単位でのタイマー時間指定
attachInterrupt(func): タイマー終了時に呼び出す関数の指定
start(): タイマー開始
stop(): タイマー停止
restart(): タイマーを0から再開
resume(): タイマー再開(リセットしない)
問題点
ライブラリを導入し,以下のようなコードを試してみるとします.
#include <TimerOne.h>
bool led=false;
void toggleLED(){
led = !led;
digitalWrite(13,led);
}
void setup() {
// put your setup code here, to run once:
pinMode(13,OUTPUT);
Serial.begin(9600);
Timer1.initialize(5000000); //5秒待機
Timer1.attachInterrupt(toggleLED);
}
void loop() {
// put your main code here, to run repeatedly:
if(Serial.available()){
int in = Serial.parseInt();
if(in==0){ //begin
Timer1.start();
} else if(in==1){ //stop
Timer1.stop();
} else if(in==2){ //restart
Timer1.restart();
}
}
delay(100);
}
書き込み後,放っておくと5秒毎にArduinoボードのLEDが点滅します.
initialize()
しただけで勝手に開始しているのも予期していないです(問題点1)
次にシリアルモニタを開き,"1"と入力・決定すると,確かに点滅が止まります.
しかし "0" と入力・決定すると,すぐに点滅してしまいます.("2"も同様です)
色々試してみると,start()
,restart()
を実行するとすぐに割り込みが発生しているようです.(問題点2)
ライブラリの中身
TimerOne.hを見ると,次のような部分があります.
void start() __attribute__((always_inline)) {
TCCR1B = 0;
TCNT1 = 0; // TODO: does this cause an undesired interrupt?
resume();
}
void restart() __attribute__((always_inline)) {
start();
}
"予期せぬ割り込みが起きている?" ということらしいです.
実際,タイマー起動中にTCNT
を直接0にすると確かに割り込み発生します.
というのも,そもそも割り込み発生条件がTCNT
が0になることのようです.(参考文献も参考に)
detachInterrupt()
→TCNT=0
→attachInterrupt()
をしても,ISR(割り込みサービス)は1ループ終了後に呼び出されるため,同じloop()
内に入れても意味はありません.
あとrestart()
はstart()
と同じですね.
解決策
色々試しましたが,割り込みは回避できませんでした.
ということは,割り込み時に条件さえ設定してやればまあちゃんとできるだろうと.
#include <TimerOne.h>
bool led=false;
bool noend=false;
void toggleLED(){
if(noend){
noend = false;
return;
}
led = !led;
digitalWrite(13,led);
}
void setup() {
// put your setup code here, to run once:
pinMode(13,OUTPUT);
Serial.begin(9600);
Timer1.initialize(5000000);
Timer1.attachInterrupt(toggleLED);
Timer1.stop();
}
void loop() {
// put your main code here, to run repeatedly:
if(Serial.available()){
int in = Serial.parseInt();
switch(in){
case 0: //begin
Timer1.start();
noend = true;
break;
case 1: //stop
Timer1.stop();
break;
case 2: //resume
Timer1.resume();
break;
}
}
delay(100);
}
これで "0" 入力するまでタイマーは開始せず,また start()
しても割り込み関数の望まぬ実行は起こりません.
ちなみに,stop()
後,カウント途中からの再開はresume()
で問題なく可能です.
-
例えば赤外線送受信のできる"IR Remote"など. ↩