ArduinoのTimerOneで詰まった話

Arduino Unoにはタイマー機能として，Timer0，Timer1，Timer2，WDT(ウォッチドッグ)があります．
一般的に他のサイトでタイマーを使う場合，Timer2を利用することが多く，[MsTimer2](http://playground.arduino.cc/Main/MsTimer2)や[FlexiTimer2](http://playground.arduino.cc/Main/FlexiTimer2)といったライブラリが使われます．
ただ，Timer2を使った別のライブラリ[^1]を利用する場合は単なるタイマーとしてTimer2を使えない問題があり，代わりにTimer1を使うことになります．
そして，Timer1のラッパーライブラリとしてよく紹介されているのが[TimerOne](https://www.pjrc.com/teensy/td_libs_TimerOne.html)です．

## 参考資料

- [Arduinoのタイマーライブラリ:miso-engine](http://miso-engine.hatenablog.com/entry/2015/07/20/221014)
- [うしこlog](http://usicolog.nomaki.jp/engineering/avr/avrPWM.html)
- [garretlab:Arduinoで遊ぶページ](http://garretlab.web.fc2.com/arduino/inside/arduino/wiring_analog.c/analogWrite.html)

## 機能
### Timer1クラス-静的関数
__initialize(microseconds)__: Timer1の初期化とマイクロ秒単位でのタイマー時間指定
__attachInterrupt(func)__: タイマー終了時に呼び出す関数の指定
__start()__: タイマー開始
__stop()__: タイマー停止
__restart()__: タイマーを0から再開
__resume()__: タイマー再開(リセットしない)

## 問題点
ライブラリを導入し，以下のようなコードを試してみるとします．

```cpp:例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を見ると，次のような部分があります．

```cpp: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()`と同じですね．

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

```cpp:例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"など．