タイマーについてまとめてみる。タイマーにはノーマルモード、CTCモード、PWMなどあるが今回はノーマルモードに限定する。
参考になるのはLUFAの作者であるDean Camera氏のチュートリアルで
http://www.avrfreaks.net/index.php?name=PNphpBB2&file=viewtopic&t=50106
および
http://www.fourwalledcubicle.com/AVRArticles.php
にPDF化された文書もある。
_delay_ms(), _delay_us()などがあるので、遅延するだけなら不要だが、
割り込みを使ってマイコンらしい効果的な使い方ができる。
Timer0 (8bit)
AVRのタイマーには8ビットのタイマー(通常はTimer0)と16ビットのタイマー(Timer1,etc)がある。
まず8ビットタイマーTimer0を使ってみる。
tiny13にはTimer0しかない。(tiny10もTimer0しかないが、16bitで他のCPUではTimer1に相当する。)
また、ArduinoではTimer0はmillis()などで使われている。
以下ではtiny13のFUSEを変更していない状態で考える。デフォルトではクロックを内蔵9.6MHzの8分周で使うので、TCNT0というレジスタは1/1.2MHz=83us毎に増えていき、255の次にオーバーフローして0に戻るということを繰返す。
まず、この仕組みだけを使って最も単純なコードを書いてみる。
256/1200000=21.33msのタイマー(4.7kHz)にしかならないので、Lチカでは点滅しているかどうかわからない。(オシロがあれば確認できる)
割り込みベクタはgrep vect iotn13.hとかで調べると、TIM0_OVF_vectである。(世代によってTIMER0だったり、TIM0だったりするので注意)
他の動作によらずTCNT0がオーバーフローしたときに割り込みが実行される。
タイマーに関するレジスタはTCCR0A, TCCR0B, TIMSK, TIFRなどである。
TCCR0A: Timer/Counter Control Register A
bit | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
TCCR0A | COM0A1 | COM0A0 | COM0B1 | COM0B0 | – | – | WGM01 | WGM00 |
default | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
TCCR0B: Timer/Counter Control Register B
bit | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
TCCR0B | FOC0A | FOC0B | – | – | WGM02 | CS02 | CS01 | CS00 |
default | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
TIMSK: Timer/Counter Interrupt Mask Register
bit | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
TIMSK | TOIE1 | OCIE1A | OCIE1B | – | ICIE1 | OCIE0B | TOIE0 | OCIE0A |
default | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
TIFR: Timer/Counter Interrupt Flag Register
bit | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
TIFR | TOV1 | OCF1A | OCF1B | – | ICF1 | OCF0B | TOV0 | OCF0A |
default | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
設定すべきことは
(1)timerのモード(ノーマル、CTC、PWMなど)
(2)クロック分周比
(3)タイマー割り込みの許可(TIMSK0: Timer Interrupt Mask RegisterのTOIE0:Timer/Counter0 Overflow Interrupt Enable)
(4)全ての割り込みの許可(sei)
(1)についてはWGM02:0で行なう。
Waveform Generation Mode Bit
Mode | WGM02 | WGM01 | WGM00 | Timer/Counter Mode operation | TOP | update OCRx at | TOV flag set on |
0 | 0 | 0 | 0 | normal | 0xFF | immediate | MAX |
1 | 0 | 0 | 1 | PWM, phase correct | 0xFF | TOP | BOTTOM |
2 | 0 | 1 | 0 | CTC | OCR0A | immediate | MAX |
3 | 0 | 1 | 1 | Fast PWM | 0xFF | TOP | MAX |
4 | 1 | 0 | 0 | reserved | – | – | – |
5 | 1 | 0 | 1 | PWM, phase corect | OCR0A | TOP | BOTTOM |
6 | 1 | 1 | 0 | reserved | – | – | – |
7 | 1 | 1 | 1 | Fast PWM | OCR0A | TOP | TOP |
(2)についてはCS02:0で行なう。
Clock Select Bit
CS02 | CS01 | CS00 | description |
0 | 0 | 0 | no clock source (Timer/Counter stopped) |
0 | 0 | 1 | clkIO (no prescaling) |
0 | 1 | 0 | clkIO/8 |
0 | 1 | 1 | clkIO/64 |
1 | 0 | 0 | clkIO/256 |
1 | 0 | 1 | clkIO/1024 |
1 | 1 | 0 | external clock source on T0. falling edge |
1 | 1 | 1 | external clock source on T0. rising edge |
ノーマルモードでWGM02|WGM01|WGM00=000。
プリスケーラなしでCS02|CS01|CS00=001。
タイマーのオーバフロー割り込みを許可(TOIE0=1)することで、TCNT0=255の次にオーバーフローするとISR(TIM0_OVF_vect)が呼ばれる。
// for ATtiny13 #include <avr/io.h>; #include <avr/interrupt.h>; ISR(TIM0_OVF_vect) { PORTB ^= (1<<PB0); } void init_timer() { TCCR0A = 0; // normal mode TCCR0B = (1<<CS00); // prescaler = 1 TIMSK0 |= (1<<TOIE0); //TCNT0 = 0; } int main() { DDRB =(1<<PB0); init_timer(); sei(); // enable global interrupts while(1) { } }
次に肉眼でも動作がわかるようにしたい。
16bitタイマなら65535まで数えることができるが、8bitタイマでは255が限界なので分周を使う。
TCCR0B = (1<<CS00); // FCPU TCCR0B = (1<<CS01); // FCPU/8 TCCR0B = (1<<CS01)|(1<<CS00); // FCPU/64 TCCR0B = (1<<CS02); // FCPU/256 TCCR0B = (1<<CS02)|(1<<CS00); // FCPU/1024
のように変えることで、TCNT0の進み方を遅くする。
分周比1024の場合は1tick=1024/120000=85msで256カウントで0.22s(約4.6Hz)になる。
Timer1 (16bit)
16ビットタイマーについても同様だが、tiny13にはないので、以下では定番のtiny2313を使う。
世代によってレジスタ名が微妙に違うので、ヘッダファイルなりデータシートなりで調べる。
上のコードでは
TIM0_OVF_vect → TIMER0_OVF_vect, TIMSK0→TIMSK
などとする。
// for ATtiny2313 #include <avr/io.h>; #include <avr/interrupt.h>; ISR(TIMER0_OVF_vect) { PORTB ^= (1<<PB0); } void init_timer0() { TCCR0A = 0; // normal mode TCCR0B = (1<<CS02)|(1<<CS00); // prescaler 1024 TIMSK |= (1<<TOIE0); TCNT0 = 0; } int main() { DDRB = (1<<PB0); init_timer0(); sei(); while(1) { } return 0; }
Timer1の場合も0->1とする程度でほぼ同様に書ける。
ただし、TCNT1は16ビットで0-65535までを取るので、prescalerなしでも54.6ms=18Hzになる。
// for ATtiny2313 #include <avr/io.h>; #include <avr/interrupt.h>; ISR(TIMER1_OVF_vect) { PORTB ^= (1<<PB0); } void init_timer1() { TCCR1A = 0; // normal mode TCCR1B = (1<<CS10); // prescaler 1 TIMSK |= (1<<TOIE1); //TCNT1 = 0; } int main() { DDRB = (1<<PB0); init_timer1(); sei(); while(1) { } return 0; }
2つのLEDを別のタイミングで動かすような場合に_delay_ms()を使うと、かえって難しいが、
割り込みを使えば、設定しておいて、sei()で一気に動き出す。
timer0のレジスタを見ただけでもかなり微妙な名前の違いがあり、一つのコードでいろいろなCPUに使いまわそうとすると、CPUごとに場合分けして書くということになる。そういうときに
#if defined(__AVR_ATtiny13__) || defined(__AVR_ATtiny10__) TIMSK0 |= (1<<TOIE0); #else defined(__AVR_ATtiny2313__) TIMSK |= (1<<TOIE0); #endif
のように書くことである程度ポータブルにできる(が読みにくくなる)。
コメントを残す