LoginSignup
8
9

More than 5 years have passed since last update.

ArduinoのTimerOneで詰まった話

Posted at

Arduino Unoにはタイマー機能として,Timer0,Timer1,Timer2,WDT(ウォッチドッグ)があります.
一般的に他のサイトでタイマーを使う場合,Timer2を利用することが多く,MsTimer2FlexiTimer2といったライブラリが使われます.
ただ,Timer2を使った別のライブラリ1を利用する場合は単なるタイマーとしてTimer2を使えない問題があり,代わりにTimer1を使うことになります.
そして,Timer1のラッパーライブラリとしてよく紹介されているのがTimerOneです.

参考資料

機能

Timer1クラス-静的関数

initialize(microseconds): Timer1の初期化とマイクロ秒単位でのタイマー時間指定
attachInterrupt(func): タイマー終了時に呼び出す関数の指定
start(): タイマー開始
stop(): タイマー停止
restart(): タイマーを0から再開
resume(): タイマー再開(リセットしない)

問題点

ライブラリを導入し,以下のようなコードを試してみるとします.

例1.ino
#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を見ると,次のような部分があります.

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=0attachInterrupt()をしても,ISR(割り込みサービス)は1ループ終了後に呼び出されるため,同じloop()内に入れても意味はありません.

あとrestart()start()と同じですね.

解決策

色々試しましたが,割り込みは回避できませんでした.
ということは,割り込み時に条件さえ設定してやればまあちゃんとできるだろうと.

例2.ino
#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()で問題なく可能です.


  1. 例えば赤外線送受信のできる"IR Remote"など. 

8
9
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
9