ソフトウェアエンジニアがarduinoの割り込みタイマーライブラリを読んでみた
arduinoのタイマーを使って割り込み処理をするにはMsTimer2とflexitimer2があってそれらを読み込めば簡単に出来るのですが、中身を知らないのもあれだなーと思い今回ソースを呼んでUno用に簡略化してみた
MsTime2: https://github.com/PaulStoffregen/MsTimer2
flexitimer: https://github.com/wimleers/flexitimer2
MsTimer2はミリセカンド単位でタイマー指定できて、flexitimerはマイクロセカンドごとに指定できるらしい
(flexitimerの方はちゃんと読んでないのでもしかしたら厳密には違うかも)
Uno用に簡略化したソース
5V/16MHz駆動でarduino UNO(atmega328P)を利用する場合の割り込みタイマー処理
volatile unsigned long count; // overflowした回数 volatile char overflowing; // overflow処理中かどうかのフラグ volatile unsigned int tcnt2; // タイマーのオーバーフローのタイミング調整用変数 unsigned long msecs; // 何秒ごとに任意の処理を実行したいか float prescaler; // 前置分周(F_CPUが1MHzから16MHzの場合は64.0が多い) // 初期化や割り込みの許可等の設定 void setTimer2(){ TIMSK2 &= ~(1<<TOIE2); TCCR2A &= ~((1<<WGM21) | (1<<WGM20)); TCCR2B &= ~(1<<WGM22); ASSR &= ~(1<<AS2); TIMSK2 &= ~(1<<OCIE2A); TCCR2B |= (1<<CS22); TCCR2B &= ~((1<<CS21) | (1<<CS20)); prescaler = 64.0; } // timerのスタート void startTimer(unsigned long ms){ msecs = ms; tcnt2 = 256 - (int)((float)F_CPU * 0.001 / prescaler); count = 0; overflowing = 0; TCNT2 = tcnt2; TIMSK2 |= (1<<TOIE2); } // timerのセットアップ void setup() { setTimer2(); startTimer(1000); // これで1000ミリセカンド単位のタイマーをスタート } // 割り込み検知 ISR(TIMER2_OVF_vect) { TCNT2 = tcnt2; overflow(); } // 割り込み処理 void overflow(){ count += 1; if (!overflowing && count >= msecs) { overflowing = 1; count = count - msecs; flash(); overflowing = 0; } } // 任意のメソッド(1000ミリセカンド毎に実行したい処理) void flash(){ static boolean output = HIGH; digitalWrite(13, output); output = !output; }
set timer
TIMSK2 &= ~(1<
- TIMSK2: タイマ/カウンタ2割り込み許可レジスタ(Timer/Counter 2 Interruput Mask Register)
- TOIE2: タイマ/カウンタ2溢れ割り込み許可(Timer/Counter 2 Overflow Interrupt Enable)
- OCIE2A: タイマ/カウンタ比較A割り込み許可(TImer/Counter2 output Compare Match A Interrupt Enable)
TCCR2A &= ~*1
- TCCR2A: タイマ/カウンタ制御レジスタA(Timer/Counter 2 Control Register A)
- WGM21,20: 波形生成種別(Waveform Generation Mode)
TCCR2B &= ~(1<*2;
- TCCR2B: タイマ/カウンタ制御レジスタB(Timer/Counter 2 Control Register B)
- WGM22: 波形生成種別 (Waveform Generation Mode bit 2)
- CS22,21,20: クロック選択(Clock Select)
ASSR &= ~(1<
- ASSR: タイマ/カウンタ2非同期状態レジスタ(TImer/Counter 2 Asynchronous Status Register)
- AS2: タイマ/カウンタ2非同期動作許可(Asynchronous Timer/Counter2)
prescaler = 64.0;
- 前置分周(次の項目で説明)
startTimer
tcnt2 = 256 - (int)((float)F_CPU * 0.001 / prescaler)
指定したミリセカンドぴったりで処理が実行されるようにするための調整値を求めている
F_CPUが16MHzだとして計算を一つ一つ説明
説明①
- F_CPU(16,000,000Hz) * 0.001 = 16,000(Hz)
- 16,000(Hz) / prescaler(64.0) = 250(count)
- atmega328Pで16MHzの場合prescalerの値は64.0
- prescalerなしだと1msecで16,000Hz(16,000カウント)だったが、prescaler処理をいれると64Hzに1回しかカウントしなくなるので、1msecで250カウントとなる
- これで8bit(256)カウント内でおさまるようになった
- しかし、8bit(256) timerはカウント250回ではなく256回でオーバーフローするため、 毎回6カウントずつずれてしまう。これの調整を以下で行う
- 256 - 250 = 6
- ようは6からカウントを開始すれば250カウント経過で256になり、ちょうど1msecでオーバーフローする
- そのためカウントの開始地点(初期カウント数)をここで求めている
- 実際にISRで割り込んだ後も毎回カウントの初期値に6をセットしている
説明②(上記と同じ説明を順番を変えて考えただけ)
- 16,000,000Hz / prescaler(64.0) = 250,000
- 通常16MHz(16,000,000Hz)なので16,000,000カウントで1秒
- arduinoにのっているtimerは8bit(256)か16bit(65536)なのでさすがにカウントが速すぎて使いにくい
- そこでCPU側で毎Hzごとにカウントアップするのではなく、何Hzかごとに1カウントアップするようにしている(この値をプリスケーラといい、チップや駆動電圧によって異なる)
- atmega328Pの場合は通常64.0
- これによって実際の16,000,000Hz(16,000,000カウント)毎ではなく、250,000カウント毎に1秒という計算になる
- 250,000 * 0.001 = 250
- 256 - 250 = 6
- しかし、8bit(256) timerはカウント250回ではなく256回でオーバーフローするため、 毎回6カウントずつずれてしまう。これの調整を以下で行う
- ようは6からカウントを開始すれば250カウントで256になり、ちょうど1msecでオーバーフローする
- そのためカウントの開始地点(初期カウント数)をここで求めている
- 実際にISRで割り込んだ後も毎回カウントの初期値に6をセットしている
TIMER2_OVF_vect
ISR(TIMER2_OVF_vect) { TCNT2 = tcnt2; overflow(); }
- ISR: interrupt service routine
- TIMER2_OVR_vect: タイマ2のオーバーフロー割り込み
- 割り込みを検知する処理
- 上記の設定の結果、1msec毎にtimer2がオーバーフローし、それを検知してこの処理が実行される
- オーバーフローする毎にカウントが0に戻るので、初期値(6)を毎回代入している
void overflow
void overflow(){ count += 1; if (!overflowing && count >= msecs) { overflowing = 1; count = count - msecs; // subtract ms to catch missed overflows. set to 0 if you don't want this. flash(); overflowing = 0; } }
- 1msecごとに実行されてcountを1ずつincrementしていく
- (このように平行した処理内で値が変わる可能性のある変数は宣言時にvolatile属性をつけておく必要がある)
- 1msec毎にカウントして、指定のmsec回数を越えたら実際に実行したい処理(このコードではflash();)を実行する
- 上記の処理中はoverflowingフラグをあげることで、処理が1ミリセカンド以上かかったとしても二重で処理が実行されるのを防ぐことができる
- しかしこの間もcountのincrementは続いているので、実行する処理が数ミリセカンドかかったとしてもその分次回の処理がずれていくということはない
- ただし指定したミリセカンド秒数を越える処理遅延が発生する場合は対策が必要(1000msecごとに行いたい処理があるのに、その処理自体が1000msec以上かかる場合など)
reference
https://avr.jp/user/DS/PDF/mega328P.pdf
&= (AND) : http://www.musashinodenpa.com/arduino/ref/index.php?f=0&pos=978
<< bit shift: http://www.musashinodenpa.com/arduino/ref/index.php?f=0&pos=784
~(NOT): http://www.musashinodenpa.com/arduino/ref/index.php?f=0&pos=767
volatile: http://www.musashinodenpa.com/arduino/ref/index.php?f=0&pos=1780