トップ 新規 編集 差分 一覧 ソース 検索 ヘルプ PDF RSS ログイン

Getting Started Notes - Timer0

最終更新時間:2009年09月03日 20時47分42秒

タイマ・カウンタ0の使い方

 注意(互換性)

タイマ0は、AVRのデバイスによって仕様が多少異なります。以下の2タイプがありますので、お使いのデバイスについてデータシートで確認するのを忘れないで下さい。ここではmega8を例にとって解説します。

  • プリスケーラ1/1〜1/1024の5段階設定+外部信号カウント機能があるタイプ。
  • プリスケーラ1/1〜1/1024の7段階設定タイプ。一部のmegaシリーズ。外部信号カウントは原則的に不可(Timer2で実現)、外部時計水晶オシレータ接続可能

 概要

以下のようなことができます。

  1. 8bitタイマーモード:AVR自身のクロック信号をカウントして、0-255の目盛りをもつ時計(タイマー)として使える。10MHzクロックで使えば1目盛り0.1μsec、1周25.6μsecの時計として使える。
  2. オーバーフロー割り込み:1周するごとに合図(タイマ0オーバーフロー割り込みフラグ)を出すことができる。
  3. タイマ0オーバーフロー割り込み:その合図で割り込み処理を起動し、一定時間間隔で処理を行わせることができる
  4. プリスケーラ:そのままでは速すぎて使いにくい場合のために、クロックを割り引いて(分周、最大1/1024)使うことができる。1/1024設定なら、12.8MHzで使えば1目盛り80μsec、1周20.48msecの時計として使える
  5. カウンタモード:外部のパルス信号をカウントすることができる。この場合は分周はできない(一部のAVRではタイマ0では不可)
  6. 時計水晶カウント機能:一部のAVRでは、32768Hz水晶を接続して発振させ、これをカウントすることで1/128〜8秒の周期の割り込みを容易に作れる
  7. 比較一致機能:一部のAVRではさらにタイマカウント値がある値になるときに動作を行わせる比較一致機能も持っています。これによりPWM機能などが使えます。

 タイマ0のレジスタ

レジスタ名 名称と機能
TCCR0 タイマ/カウンタ0 コントロールレジスタ
TCNT0 タイマ/カウンタ0
OCR0 タイマ/カウンタ0 比較レジスタ(一部デバイスのみ)
割り込み関連レジスタ
レジスタ名 名称と機能
TIFR タイマ割り込み要求フラグレジスタ
TIMSK タイマ/カウンタ割り込みマスクレジスタ

 タイマーモード

プリスケーラ設定

このモードのタイマはAVR自身のクロック信号で駆動されます。たとえば10MHzのクロックで動いているAVRの場合は0.1μsec毎にカウントされ、25.6μsec毎にオーバーフローしますので、一定時間毎に何かを行う用途に向いています。

しかし、もっと間隔を延ばしたい場合のために、タイマの入力を内蔵プリスケーラに通し、信号を間引くことができます。たとえばmega8の場合、プリスケーラ値は{無効、1/8、1/64、1/256、1/1024}から選べます。例えば、1024を指定すればタイマ値はシステムクロック1024回ごとに1つ増やされます。

この設定(プリスケーリング)はレジスタTCCR0に以下の値を書き込むことで実行できます。

TCCR0 = 5;    // 1024分周,1024クロック毎にカウントアップ

プリスケーラ設定(プリスケーリング)はレジスタTCCR0に以下の値を書き込むことで実行できます。大きく分けて以下の2パターンになります。お使いのAVRデバイスがどちらのタイプかデータシートで確認の上お使いください。mega8は5段階タイプです。
TCCR0 5段階タイプ 7段階タイプ 
  0 停止 停止
  1 ck/1 ck/1
  2 ck/8 ck/8
  3 ck/64 ck/32
  4 ck/256 ck/64
  5 ck/1024 ck/128
  6 立ち下がりカウント ck/256
  7 立ち上がりカウント ck/1024

※一部のAVRでは、TCCR0の上位5bitに比較一致機能やPWM機能関連のビットが用意されています。単なるタイマとして使う場合はこれらのビットは0でよいので、ここでは割愛します。

オーバーフロー割り込みフラグの活用

TCNT0が255からオーバーフローして0に戻るときに、I/Oレジスタ TIFRにあるビットTOV0(タイマ/カウンタ0オーバーフロー割り込み要求フラグ)が1にセットされます。これを利用してみます。

/* mega8用です */
#include <avr/io.h>

int main( void )
{
    uint8_t led;
    PORTB = 0xFF; /* 初期状態:LEDは全消灯 */
    DDRB  = 0xFF; /* PORTBの全ピンを出力に */
    TCNT0 = 0;    /* タイマ0の初期値設定   */
    TCCR0 = 5;    /* プリスケーラは ck/1024*/
    led = 0;
    PORTB = ~led;
    for (;;)
    {
        if (bit_is_set(TIFR,TOV0))     /* TIFRのTOV0ビットが1なら、処理 */
        {
            led++;
            PORTB = ~led;
            TIFR  = _BV(TOV0);  /* TIFRのビットをTOV0のクリア */
        }
    }
}
  1. TCNT0の初期値を0にする
  2. レジスタTCCR0に5を代入し、プリスケールをck/1024にする。設定したとたんにTCNT0は動き始めます。
  3. 変数ledをクリア
  4. 無限ループに入る。ループ内でレジスタTIFR内のTOV0ビットを監視
  5. TOV0ビットはTCNT0が255から0になる(8bitしかないため256は0と同じになる)瞬間に立ち上がります。
  6. TOV0ビットが立ち上がるたびにledをカウントアップし、ledの値をPORTBに出力します。ポートBの各ピンにLEDがつないであればled値が2進数で読めます。
  7. TOV0のビットをクリアする。該当ビットに0を書き込むのではなく、1を書き込む点に注意。詳しくはデータシートおよびIOレジスタ、入出力ポートをお読みください。

led値の更新は、システムクロック周期×1024(プリスケーラ)×256(TCNT0一周)毎に行われることになります。クロックが4MHzなら、(1/4M)s×1024×256 = 65.536msec毎に変化し、led値(8bit)が一周するにはその256倍の16.78秒かかります。

割り込みで使う

 先の例はTOV0ビットをずっと監視しつづけて必要時に処理にジャンプするプログラムですが、AVRには割り込み機能があります。これを利用すれば、オーバーフローが起こったときに自動的に必要なルーチンを実行させることができます

#include <avr/io.h>
#include <avr/interrupt.h>

volatile uint8_t led;

ISR(TIMER0_OVF_vect)
{
    led++;
    PORTB = ~led;
}
int main( void )
{
    PORTB = 0xFF; /* 初期状態:LEDは全消灯 */
    DDRB  = 0xFF; /* PORTBの全ピンを出力に */
    led = 0;
    PORTB = ~led;
    TCNT0 = 0;    /* タイマ0の初期値設定   */
    TCCR0 = 5;    /* プリスケールは ck/1024*/
    TIMSK = _BV(TOIE0);  /* タイマ0オーバーフロー割り込みの許可*/
    sei(); /* 割り込みの許可*/
    for (;;){}
}
  1. 変数ledをクリア、初期値をPORTBに出力(LED表示)
    • ledはmain()の外でも使うので、グローバル変数になっていることに注意してください。
    • 割り込みで変更される変数にはvolatileをつけてください。
  2. TCNT0の初期値を0にする
  3. レジスタTIMSKのTOIE0(タイマ0オーバーフロー割り込み許可)ビットを1にし、この割り込みを許可
  4. レジスタTCCR0に5を代入し、プリスケールをck/1024にする。設定したとたんにTCNT0は動き始めます。
  5. sei()で全割り込み許可
  6. メインルーチンは無限ループに入る(他の処理をしてもいい)
  7. これ以降は割り込みが定期的に呼ばれる。
    1. タイマ0がオーバーフローしてTIFRのTOV0ビットが立ち上がると自動的にこれまでの処理を一時中止してISR(TIMER0_OVF_vect)部の実行がスタートされる。
    2. led値を1つ増やして、PORTBに送り表示
    3. 割り込みルーチンが終了するとき自動的に該当割り込みビットはクリアされるので今回はクリアの必要なし。

割り込みルーチンの書き方については。[Getting Started Notes - 割り込み]も参照。

タイマ割り込み間隔を調整する

上記の例では、タイマ割り込みの間隔は (1/システムクロック)×プリスケール値×256となるので、例えば4MHzクロックの場合はシステムクロック時間は0.25μsecで、プリスケール値により64μsec、512μsec、4.096msec、16.384msec、65.536msecが可能になります。ここで約50msec間隔で割り込みを駆動したい場合はどうしたらいいでしょうか?1/1024だとちょっと間隔が長すぎるようです。

 割り込みがかかったときTCNT0は0ですが、このとき例えばTCNT0を61にセットしてやれば次の割り込みは256カウント後ではなく、195カウント後になります。0.25(μsec)×1024×(256-61)=49.92msecでの割り込みが実現できます。ただし割り込みがかかってから割り込みルーチン内でTCNT0に値をセットするまでの時間の分の誤差が生じますのでご注意下さい。

#include <avr/io.h>
#include <avr/interrupt.h>
#define TCNT0_BOTTOM  (256-195);

uint8_t led;
ISR(TIMER0_OVF_vect)
{
    TCNT0 = TCNT0_BOTTOM;
    PORTB = ~led;
    led++;
}
int main( void )
{
    PORTB = 0xFF; /* 初期状態:LEDは全消灯 */
    DDRB  = 0xFF; /* PORTBの全ピンを出力に */
    TCNT0 = TCNT0_BOTTOM;  /* タイマ0の初期値設定   */
    TCCR0 = 5;             /* プリスケールは ck/1024*/
    TIMSK = _BV(TOIE0);    /* タイマ0オーバーフロー割り込みの許可
    led = 0;
    sei(); /* 割り込みの許可*/
    for (;;){}
}

8bitタイマで256カウントより長い時間間隔を扱う

タイマオーバーフローの回数を数える変数を設定すれば、この変数とTCNT0あわせて16bitタイマのように使えます。たとえば、4MHz-Clockでおよそ1秒ごとにLEDのカウントアップをしたければ、以下のようにできます。

#include <avr/io.h>
#include <avr/interrupt.h>

volatile uint8_t led;
volatile uint8_t TCNT0H;
ISR (TIMER0_OVF_vect)
{
// 4MHzでは1カウント=256/4M=64usec.15625カウントで1秒
    TCNT0H++;
    if (TCNT0H==61)  //15616カウント、999.424msec
    {
        TCNT0H=0;
        PORTB = ~led;
        led++;
    }
}
int main( void )
{
    PORTB = 0xFF; /* 初期状態:LEDは全消灯 */
    DDRB  = 0xFF; /* PORTBの全ピンを出力に */
    TCNT0 = TCNT0_BOTTOM;  /* タイマ0の初期値設定   */
    TCCR0 = 5;             /* プリスケールは ck/1024*/
    TIMSK = _BV(TOIE0);    /* タイマ0オーバーフロー割り込みの許可
    led = 0;
    
    sei(); /* 割り込みの許可*/
    for (;;){}
}

もう少し細かく間隔を設定したければ、カウントアップを1ではなくたとえば256にしてやればいいです。個々の間隔は若干ばらつきますが、その誤差が累積しにくくなくなります。

ISR (TIMER0_OVF_vect)
{
// 4MHzでは1カウント=256/4M=64usec.15625カウントで1秒
    TCNT0_word+=256;
    if (TCNT0_word>=15625)
    {
        TCNT0_word -= 15625;
        PORTB = ~led;
        led++;
    }
}

分周比を小さくするのも手ですが、MPU負荷は増えます。

ISR (TIMER0_OVF_vect)
{
// TCCR0=3(1/8)にして、0.5usec/countとする。1秒=7813サイクル
    TCNT0H_word++;
    if (TCNT0H_word>=7813)
    {
        TCNT0_word =0;
        PORTB = ~led;
        led++;
    }
}

 カウンタモード

概要

このモードでは 外部からpin T0に与えられた信号をカウンタで数えることができます。デバイスによっては、32768Hz副発振子の信号をカウントさせることができるものもあります。

カウント信号入力ピンT0はデバイスによって異なります。データシートで確認してください。その上で、該当ピンを入力ポートとしてDDRレジスタなどの設定を行ってください。ここでは例としてATmega8をあげています。ATmega8ではT0はPD4に割り当てられています。

カウンタモードの設定はデバイスによって異なります。プリスケーラが7段階のタイプたとえばATmega8ではTCCR0レジスタの下位3bit(CS02,CS01,CS00)の設定で行われます。
CS02-CS00 動作
 6  T0ピン入力がLow→Highをカウント
 7  T0ピン入力がHigh→Lowをカウント

プリスケーラが7段階のタイプ、たとえばATmega128などでは、SFIORなど他のレジスタの設定で外部または時計水晶のカウントを行えます。[タイマ0非同期カウンタ]参照。

割り込み要求フラグを利用する方法

省略。プリスケーラ以外タイマモードと同様です。

割り込みによる方法

#include <avr/io.h>
#include <avr/interrupt.h>

volatile uint8_t led;

ISR (TIMER0_OVF_vect)
{
    led++;
    PORTB = ~led;
}
int main( void )
{
    PORTB = 0xFF; /* 初期状態:LEDは全消灯 */
    DDRB  = 0xFF; /* ポートBの全ピンを出力に */
    DDRD  = 0;  /* PD4(T0)を含むポートDの全ピンを入力に */
    PORTD = 0xFF;  /* プルアップ指定 */
    led = 0;
    PORTB = ~led;
    TCNT0 = 0;    /* タイマ0の初期値設定   */
    TCCR0= 7;    /* タイマ0を立ち下がりエッジでのカウンタモードに */
    TIMSK = _BV(TOIE0);  /* タイマ0オーバーフロー割り込みの許可
    sei();
    for (;;);
}

タイマモードの時とほとんど同じです。異なるのはTCCR0の設定と、カウントするのが外部信号ということだけです。

 比較一致機能

概要

一部のAVRには、タイマ0での比較一致機能があります。これはタイマ値(TCNT0)がOCR0レジスタの値と一致したときに何か動作を起こさせるものです。これにより実現できるものとしては以下のものがあります。

  • 比較一致動作:
    • 任意の一定時間間隔を作り出す。TCNT0=0の時にOCR0=100にしてこの動作を有効にすると100カウント後にフラグを立てたり割り込みを起こしたりできる
  • CTC動作:
    • TCNT0==OCR0になった後TCNT0をゼロリセットする機能。プリスケーラで行うより細かい任意の周期のタイマーを作れる。
  • PWM機能:
    • 任意のHigh/Low比を持つ矩形波発生ができる。モーター駆動や簡易DACに使える
OCR0又はOCR0xがあるAVR(他にもあると思います)
mega8を除くmegaシリーズの大部分+一部のtinyシリーズと考えていいと思います。
at94k,can128,mega103,mega128,mega16,mega161,mega162,mega165,mega169,
mega32,mega323,mega325,mega3250,mega64,mega645,mega6450,
mega8515,mega8535,tiny13,tiny2313,tiny45

比較一致動作

TCNT0がOCR0と同じ値になり、そこから+1する瞬間に比較一致が起きます。TCNT0==OCR0になった瞬間ではないことに注意してください。

OCR0=50、プリスケーラが1/8の時の動作
TCNT0 ・・ 49 49 50 50 50 50 50 50 50 50 51 51 51 ・・
OCF0  0 0 0 0 0 0 0 0 0 0 0 1 1 1 ・・

OCF0はTIFRレジスタ内にあります。OCF0ビットはよくある割り込み要求フラグとして働き、これをクリアするにはこのビットに1を書き込む必要があります。以下を参照。

例として、4MHz-mega16で1回だけ約50msecだけLEDを点灯するプログラムをあげます。

#include <avr/io.h>

int main(void)
{
    PORTB=0x00;
    DDRB=0x01;  //PORTB0 出力・Highに。Highで点灯するようLEDを接続
    TCNT0=0;
    OCR0=194;   // TCNT0=195の瞬間に比較一致成立。195*1024/4MHz≒50msec.
    TIFR=_BV(OCF0); //OCF0クリア
    PORTB=0x01; // LED点灯(High) 
    TCCR0=5;    // プリスケーラ 1/1024 タイマが動き出す
    loop_until_bit_is_set(TIFR,OCF0); //OCF0が1になるまでループ
    PORTB=0x00; // LED消灯(Low) 
    while(1);
}

OCF0ビットは割り込み要求フラグで、TIMSKレジスタのOCIE0ビットを1にして割り込みを許可すれば、比較一致で割り込みを起こせます。62.5msec周期で、LEDを10msec点灯するものを割り込みで書いてみます。(この動作は後述するPWMを使えば簡単に実現します)

#include <avr/io.h>
#include <avr/interrupt.h>

ISR(TIMER0_OVF_vect)
{
    PORTB=0x01; // LED点灯(High)
}
ISR(TIM0_COMP_vect)
{
    PORTB=0x00; // LED消灯(Low) 
}
int main(void)
{
    PORTB=0x00;
    DDRB=0x01;  //PORTB0 出力/Lowに。Highで点灯するようLEDを接続
    TCNT0=0;
    OCR0=38;   // TCNT0=39の瞬間に比較一致成立。39*1024/4MHz≒10msec.
    TIFR=_BV(OCF0)|_BV(TOV0); //OCF0/TOV0クリア
    TIMSK=_BV(TOIE0)|_BV(OCIE0); //オーバーフロー・比較一致割り込み許可
    TCCR0=5;    // プリスケーラ 1/1024 タイマが動き出す
    sei();      // 全割り込み許可
    while(1);
}

CTC動作

 ここからはWGMという聞き慣れない言葉が出てきます。WGM(Wave Generation Mode)とは、タイマの特別な動作を規定するためのビットです。mega16ではTimer0に対してはTCCR0レジスタにWGM01,WGM00の2bitが用意されています。

 WGM01=1,WGM00=0 にすると、タイマ0はCTC動作を行います。これは、比較一致を起こすとTCNT0が0にリセットされるというものです。これを利用すると任意の周期のタイマーが簡単に作れます。たとえば[タイマ割り込み間隔を調整する]の例題をこの機能で実現してみます。

#include <avr/io.h>
#include <avr/interrupt.h>

uint8_t led;
ISR(TIM0_COMP_vect)
{
    PORTB = ~led;
    led++;
}
int main( void )
{
    PORTB = 0xFF;   // 初期状態:LEDは全消灯
    DDRB  = 0xFF;   // PORTBの全ピンを出力に
    led = 0;
    TCNT0 = 0;      // タイマ0の初期値設定
    OCR0  = 194;    // TCNT0==195になったらゼロリセット
    TIMSK = _BV(OCIE0);    // タイマ0オーバーフロー割り込みの許可
    TCCR0 = _BV(WGM01)|5;  // CTC動作、プリスケールは ck/1024
    
    sei();          // 割り込みの許可
    while(1);

}

位相基準PWM動作

WGM01=0,WGM00=1だと、タイマ0は位相基準PWMという動作を行います。これはTCNT0が0から0xFFを往復するような動きをし、その上でTCNT0がOCR0値を横切るたびにある出力ピンをHighにしたりLowにしたりするものです。結果として、この機能はデューティ比(OCR0/0x100)の矩形波、もしくはその反転の矩形波を出せます。

ただし、PWM機能の制限として、出力ピンはOC0ピン(mega16ではPB3)に限定されます。

矩形波の極性はTCCR0レジスタのCOM01,COM00ビットで行います。
COM01 COM00 動作 DUTY比
 0  0 PWM出力しない  
 0  1 PWM出力しない  
 1  0 TCNT0上昇時比較一致でLow、
下降時比較一致でHigh
 (OCR0/256)
 1  1 上記の反転  1-(OCR0/256)

先ほど比較一致動作で行った周期65.5msec、点灯10msecをこの機能を使って実現します。周期は131msec、点灯20msecになります。点灯比率は同じです。

#include <avr/io.h>
int main(void)
{
    DDRB=_BV(PB3);  //PB3を出力に
    OCR0=39;  // PB3はTCNT0が下降中38でHighに、上昇中40でLowになる
    TCCR0=_BV(WGM00)|_BV(COM01)|5;  //PWMモードセット、プリスケーラ1024
    while(1);
}

えらい短くなってしまいました・・・・

高速PWM動作

WGM01=1,WGM00=1だと、タイマ0は高速PWMという動作を行います。これはTCNT0は通常のタイマモードと同様が0から255までカウント後0に戻る動きをし、その上でTCNT0が0になるとき、およびTCNT0がOCR0値を横切るたびにある出力ピンをHighにしたりLowにしたりするものです。結果として、この機能はデューティ比(OCR0/0x100)の矩形波、もしくはその反転の矩形波を出せます。比較一致PWMより矩形波の周期は短く(1/2)なります。

ただし、PWM機能の制限として、出力ピンはOC0ピン(mega16ではPB3)に限定されます。

矩形波の極性はTCCR0レジスタのCOM01,COM00ビットで行います。
COM01 COM00 動作 DUTY比
 0  0 PWM出力しない  
 0  1 PWM出力しない  
 1  0 比較一致でLow、TOPでHigh  (OCR0/256)
 1  1 上記の反転  1-(OCR0/256)

先ほど比較一致動作で行った周期65.5msec、点灯10msecをこの機能を使って実現します。周期が65.536msec(4MHz時)になる点だけが異なります。

#include <avr/io.h>
int main(void)
{
    DDRB=_BV(PB3);  //PB3を出力に
    OCR0=39;  // PB3はTCNT0が下降中38でHighに、上昇中40でLowになる
    TCCR0=_BV(WGM01)|_BV(WGM00)|_BV(COM01)|5;  //PWMモードセット、プリスケーラ1024
    while(1);
}

 非同期カウンタ(一部のAVR、Timer2仕様)

一部のAVRは、Timer0に同期カウンタの代わりに非同期カウンタモードが用意されています。これの特徴は以下の通りです。

  • システムクロックが無くても計測できるので、スリープ中カウントが可能
  • システムクロックと関係なく変更されるので、レジスタ値読み書きがちょっと面倒
  • 時計用32768Hz水晶を接続し、これをカウントさせることができる。プリスケーラを1/128にすればきっかり1秒に1回オーバーフロー割り込みを起こさせることができる。
  • まだ使ったことないので、省略

 その他

タイマ0/タイマ1プリスケーラ同期

特殊I/O機能レジスタ SFIORのビットPSR10(bit0)に1が書かれると、タイマ0とタイマ1のプリスケーラがリセットされます。タイマ0とタイマ1を同期して使用したい場合に便利です。詳細はデータシート参照。

一部デバイスのTimer0はTimer2仕様です

mega64/128など一部のmegaシリーズのTimer0は、Timer2と同じ仕様になっているようです。新たに加わった機能、プリスケーラの違い、カウンタモード設定の違いなどにご注意ください。