AVR timer(1)

タイマーについてまとめてみる。タイマーにはノーマルモード、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

のように書くことである程度ポータブルにできる(が読みにくくなる)。

広告
カテゴリー: AVR

コメントを残す

以下に詳細を記入するか、アイコンをクリックしてログインしてください。

WordPress.com ロゴ

WordPress.com アカウントを使ってコメントしています。 ログアウト /  変更 )

Facebook の写真

Facebook アカウントを使ってコメントしています。 ログアウト /  変更 )

%s と連携中

カテゴリー
2012年3月
 1234
567891011
12131415161718
19202122232425
262728293031  
%d人のブロガーが「いいね」をつけました。