トップ 差分 一覧 ソース 検索 ヘルプ ログイン

Timer1

タイマ/カウンタ1の使い方(mega/AT90S両用)

 概要

タイマ0やタイマ2と異なり、タイマ1は16bitタイマ/カウンタです。そのため、より長い時間を扱うタイマとして使用することが出来ます。カウントできる値は$0000〜$FFFFです。これは2つの8bitレジスタを使うことで実現されています。また、タイマ1は比較一致動作と入力捕獲機能を持っています。

 用語説明

MAX値

  TCNT0/TCNT1/TCNT2が表現できる最大の値。8bitタイマで$FF,16bitタイマで$FFFF。

TOP値
  実際の動作でTCNT0/TCNT1/TCNT2がとりうる最大の値。PWMモードや比較一致モードではMAX値より小さく制限されることがある
BOTTOM値
  TCNT0/TCNT1/TCNT2がとりうる最小の値。通常は0だが、オーバーフロー割り込みなどで意図的に他の値を入れることで変更することができる。

 タイマ1のレジスタ

レジスタ名一覧

レジスタ名 名称
 TCCR1A タイマ/カウンタコントロールレジスタ 1A (Timer/Counter Control Register 1A)
 TCCR1B タイマ/カウンタコントロールレジスタ 1B (Timer/Counter Control Register 1B)
 TCNT1 タイマ/カウンタ1レジスタ(16bit) (Timer/Counter Register Value 1)
 OCR1A 出力比較レジスタ1A(16bit) (Output Compare Register 1A Low Byte)
 OCR1B 出力比較レジスタ1B(16bit) (Output Compare Register 1A Low Byte)
 ICR1 入力捕獲レジスタ1(16bit) (Input Capture Register)

16bitレジスタの上位・下位

レジスタ名 名称
 TCNT1L、TCNT1H タイマ/カウンタ1下位/上位レジスタ
 OCR1AL、OCR1AH 出力比較下位・上位レジスタ1A
 OCR1BL、OCR1BH 出力比較下位・上位レジスタ1B
 ICR1L 、ICR1H 入力捕獲(キャプチャ)下位・上位レジスタ1

割り込み関連

レジスタ名 名称
 TIFR タイマ/カウンタ割り込み要求フラグレジスタ (Timer Interrupt Flag Register)
 TIMSK タイマ/カウンタ割り込みマスクフラグレジスタ (Timer Interrupt Mask Register)
TIMSK
bit 名称 機能
7 OCIE2 タイマ2 比較割込許可
6 TOIE2 タイマ2 オーバーフロー割込許可
5 TICIE1 タイマ1 捕獲割り込み許可
4 OCIE1A タイマ2 比較A割込許可
3 OCIE1B タイマ2 比較B割込許可
2 TOIE0 タイマ0オーバーフロー割込許可
0 TOIE1 タイマ1オーバーフロー割込許可

タイマ関連2バイトレジスタの注意点

タイマに関係する2バイト(16bit)レジスタは、読み書きしている間に数値が変わったり、中途半端に上位だけ・下位だけ書き換えた状態があると予想外の動作をする可能性があるので、2バイトを同時に読み書きできるようなバッファが備え付けられています。

  • 読み出すときは下位バイトを先に:下位バイトを読むと同時に上位バイトがバッファされます。続いて上位バイトを読むと、下位バイトを読み出したときに同時に読み出された上位バイトの値が取り出されます。
  • 書き込むときは上位バイトを先に:上位バイトを書き込むとその時点では書き込みはなされずに、続いて下位バイトを書き込むときに上位下位同時に書き込まれます。

avrgccでは、2バイトレジスタは16bit変数のように扱えるようになっています。これを読み書きするときは上記の読み書き順序通りになされますので、気にすることはありません。しかし上位下位を別々にアクセスするときはこのバッファ動作を意識する必要があります。

また、2バイトレジスタのアクセスは2命令で行われますので、2バイトレジスタを操作する処理を含む割り込みが上位・下位の読み書きの間に割り込むと、バッファが乱されてとんでもない結果を返す可能性があります(非分断動作)。2バイトレジスタの操作時には必要に応じて割り込み禁止を行ってください。

 WGM13〜0と動作モード

WGM=Waveform Generation Mode bit(波形生成モードビット) です。megaシリーズにはtimer1用にこのビットが4つあり、それぞれWGM13,WGM12,WGM11,WGM10という名前が付けられています。このうちWGM12はAT90sシリーズではCTC1、WGM11,WGM10はPWM11,PWM10と呼ばれていたものと同じビット位置を占めます。mega8では以下の位置にあります。他でもおそらく一緒でしょうが念のためご確認ください。
bit名 位置(mega8) AT90Sでの名前
WGM13 TCCR1B-bit4 (予約bit)
WGM12 TCCR1B-bit3 CTC1
WGM11 TCCR1A-bit1 PWM11
WGM10 TCCR1A-bit0 PWM10

大きく分けて、以下のモードがあります。末尾に(M)がついているものはAT90SシリーズAVRにはなかったモードです。
モード名 TCNT1の動き OCR1x更新タイミング
タイマ・カウンタモード 0→カウントアップ→0xFFFF→0→・・ 随時
比較一致クリア(CTC)動作 0→カウントアップ→TOP値→0→・・ 随時
高速PWM動作(M) 0→カウントアップ→TOP値→0→・・ TOP時
位相基準PWM動作 0→カウントアップ→TOP値→カウントダウン→0→・・ TOP時
位相周波数基準PWM動作(M) 0→カウントアップ→TOP値→カウントダウン→0→・・ BOTTOM時

Megaシリーズ 波形生成種別選択(データシートより抜粋)
番号 WGM13〜0 タイマ/カウンタ動作種別 TOP値 OCR更新 TOV1設定
$0 0 0 0 0 標準動作 0xFFFF 即時 TOP (*)
$1 0 0 0 1 8ビット位相基準PWM動作 0x00FF TOP BOTTOM
$2 0 0 1 0 9ビット位相基準PWM動作 0x01FF TOP BOTTOM
$3 0 0 1 1 10ビット位相基準PWM動作 0x03FF TOP BOTTOM
$4 0 1 0 0 比較一致クリア(CTC)動作 OCR1A 即時 TOP (*)
$5 0 1 0 1 8ビット高速PWM動作 0x00FF TOP TOP
$6 0 1 1 0 9ビット高速PWM動作 0x01FF TOP TOP
$7 0 1 1 1 10ビット高速PWM動作 0x03FF TOP TOP
$8 1 0 0 0 位相/周波数基準PWM動作 ICR BOTTOM BOTTOM
$9 1 0 0 1 位相/周波数基準PWM動作 OCR1A BOTTOM BOTTOM
$A 1 0 1 0 位相基準PWM動作 ICR TOP BOTTOM
$B 1 0 1 1 位相基準PWM動作 OCR1A TOP BOTTOM
$C 1 1 0 0 比較一致クリア(CTC) ICR 即時 TOP (*)
$D 1 1 0 1 (予約)
$E 1 1 1 0 高速PWM動作 ICR1 TOP TOP
$F 1 1 1 1 高速PWM動作 OCR1A TOP TOP

例えばここで「位相基準PWM動作、TOP=OCR1A」を選びたければ、WGM13-0=1001ですので、上位2bitはTCCR1Bのbit3-4、下位2bitはTCCR1Aのbit0-1に入れればいいです。
AT90Sシリーズ 波形生成種別選択(データシートより抜粋)

番号 CTC1 PWM21:PWM20 タイマ/カウンタ動作種別 TOP値 OCR更新 TOV1設定
$0 0 00 標準動作 0xFFFF 即時 TOP (*)
$1 0 01 8ビット位相基準PWM動作 0x00FF TOP BOTTOM
$2 0 10 9ビット位相基準PWM動作 0x01FF TOP BOTTOM
$3 0 11 10ビット位相基準PWM動作 0x03FF TOP BOTTOM
$4 1 00 比較一致クリア(CTC)動作 OCR1A 即時 TOP (*)

  • OCR更新=OCR1xを更新して、実際に比較一致にそれが反映されるようになるタイミング。後述。
  • TOV1設定=オーバーフローフラグTOV1が立ち上がるタイミング。

 プリスケーラ設定

タイマ1は、内部の信号または外部の信号で動かすことができます。これはTCCR1Aのbitタイマ駆動信号毎にTCNT1の値は1ずつ増加します。この信号はシステムクロックx回に1度生成されます。xは(1, 8, 64, 256, 1024)から選べます。
例えば、1024を指定すればタイマ値はシステムクロック1024回ごとに1つ増やされます。この設定(プリスケーリング)はレジスタTCCR1Bのbit2〜0(CS12/CS11/CS10)に以下の値を書き込むことで実行できます。

CS12-0 タイマクロック
 0 停止
 1 ck
 2 ck/8
 3 ck/64
 4 ck/256
 5 ck/1024
 6 カウンタモード、立ち下がり
 7 カウンタモード、立ち上がり

  • CS12-0が 0 なら、タイマレジスタTCNT1の値は 動きません。
  • CS12-0が 1 なら、タイマレジスタTCNT1の値は 1クロックサイクル毎に1ずつ増加します。
  • CS12-0が 2 なら、タイマレジスタTCNT1の値は 8クロックサイクル毎に1ずつ増加します。3,4,5に関しても同様。
  • CS12-0が 6 なら、タイマレジスタTCNT1の値は T1ピンのダウンエッジ毎に1ずつ増加します。
  • CS12-0が 7 なら、タイマレジスタTCNT1の値は T1ピンのアップエッジ毎に1ずつ増加します。
例: 
TCCR1B = 5; /* CK/1024設定 */

 ちょっと便利なマクロ定義

TCCR1A/1Bの設定はごちゃごちゃしてめんどくさいものです。特にWGMはややこしいです。そこで簡単に設定できるようにマクロを作ってみました。以下の例題はこのmega_timer.hを前提とします。megaシリーズ用ですが、AT90Sシリーズにも使えます。

/* mega_timer.h */
// SET_TCCR1A(a,b,c,w) a=COM1A1-0,b=COM1B1-0,c=COM1C1-0,w=WGM
// SET_TCCR1B(icnc,ices,w,ps) 
//     icnc=ICNC1(0 or 1),ices=ICES1(0 or 1),w=WGM,ps=Prescaler
// SET_TCCR2(a,w,ps)   a=COM2A1-0,w=WGM,ps=Prescaler
//WGM for Timer1
#define WGM_STD       (0b0000)
#define WGM_8_PPWM    (0b0001)
#define WGM_9_PPWM    (0b0010)
#define WGM_10_PPWM   (0b0011)
#define WGM_OCR_CTC   (0b0100)
#define WGM_8_FPWM    (0b0101)
#define WGM_9_FPWM    (0b0110)
#define WGM_10_FPWM   (0b0111)
#define WGM_ICR_PFPWM (0b1000)
#define WGM_OCR_PFPWM (0b1001)
#define WGM_ICR_PPWM  (0b1010)
#define WGM_OCR_PPWM  (0b1011)
#define WGM_ICR_CTC   (0b1100)
#define WGM_ICR_FPWM  (0b1110)
#define WGM_OCR_FPWM  (0b1111)

//WGM for Timer2
#define WGM2_STD  (0)
#define WGM2_PPWM (1)
#define WGM2_CTC  (2)
#define WGM2_FPWM (3)

//カウンタ設定機能のあるプリスケーラ用
#define PS_STOP  (0)
#define PS_CK1   (1)
#define PS_CK8   (2)
#define PS_CK64  (3)
#define PS_CK256 (4)
#define PS_CK1K  (5)
#define PS_FALLING (6)
#define PS_RISING  (7)

//一部のTimer用。どちらを使うかはデータシートで確認。
//Timer0/2のプリスケーラはデバイスによりまちまちです。
#define PS2_STOP  (0)
#define PS2_CK1   (1)
#define PS2_CK8   (2)
#define PS2_CK32  (3)
#define PS2_CK64  (4)
#define PS2_CK128 (5)
#define PS2_CK256 (6)
#define PS2_CK1K  (7)

//Timer0はプリスケーラだけなので省略
//Timer1用設定
#define SET_TCCR1A(a,b,c,w)         (((a)<<6)|((b)<<4)|((c)<<2)|((w)&3))
//a=COM1A1-0,b=COM1B1-0,c=COM1C1-0,w=WGM

#define SET_TCCR1B(icnc,ices,w,ps)  (((icnc)<<7)|((ices)<<6)|(((w)<<1)&0x18)|(ps));
//icnc=ICNC1(0 or 1),ices=ICES1(0 or 1),w=WGM,ps=Prescaler

//PWMがある8bitタイマ用。
//a=COM2A1-0,w=WGM,ps=Prescaler
#define SET_TCCR2(a,w,ps)  (((a)<<4)|((w&2)<<5)|((w&1)<<3)|(ps))

/* 例:main routine内で */
TCCR1A=SET_TCCR1A(0,0,0,WGM_OCR_PPWM);
   // COM1A,COM1B,COM1C,WGM COMX=0-3。
TCCR1B=SET_TCCR1B(0,0,WGM_OCR_PPWM,TMR1_CK256);
   // ICNC1,ICES1,WGM値,プリスケーラ値。 ICNC1,ICES1は0又は1。

標準動作(タイマ・カウンタモード)

大きく分けてPWM(Pulse Width Modulation,パルス幅変調)機能を使うモードとそれ以外のモードで大きく動作が異なります。まずはPWMを使わない「タイマ・カウンタモード」を説明します。

このモードには、単にTCNT1を0〜0xFFFFまでカウントアップさせるモードと、0〜OCR1AまたはICR1値までのカウントアップを行うCTC動作の2つがあります。

 オーバーフローを利用する

タイマ1は、タイマがオーバーフローして0x0000に戻る瞬間を検知する機構を持ちます。このとき、オーバーフロービットTOV1(TIFR上にある)が1になります。設定で許可されていればオーバーフロー割り込みが起こります。

ポーリングで使う(TIFR-TOV1を監視する方法)

/*
Timer/Counter1をタイマモード、ポーリングで使う例
タイマがオーバーフローするとTIFRのオーバーフロービットが
セットされるので、それを監視して変数 led を増加させます。
変数ledはPORTBに送られて、LED点灯で表示します。
*/
#include <avr/io.h>
uint8_t led;
uint8_t state;
int main( void )
{
   PORTB = 0xFF; /* 初期状態:LEDは全消灯 */
   DDRB  = 0xFF; /* PORTBの全ピンを出力に */
   TCNT1 = 0x0000;  /* タイマ1の初期値設定   */
   TCCR1A= 0;    /* TCCR1A タイマモード */
   TCCR1B= 1;    /* プリスケールは ck/1*/
   led   = 0;
   for (;;)
   {
       loop_until_bit_is_set(TIFR,TOV1); 
       /* TIFRのTOV1ビットが1になるまで待ち*/
       PORTB = ~led;
       led++;
       TIFR = _BV(TOV1);  /* TIFRのビットをクリアするには、
       クリアしたいビットに1を書き込みます。データシート参照。*/
  }
}

TCCR1Aレジスタは 0 、TCCR1Bレジスタは1(プリスケール=CK/1)に設定されています。TCNT1の初期値は0に設定されます。プリスケーラは1なので、システムクロック毎にTCNT1の値は1ずつ増加します。TCNT1は実際にはTCNT1LとTCNT1Hの2つのレジスタから成ります。TCNT1Lが1つずつ増加し、8bitであるTCNT1Lが255から256に成るとき,TCNT1Lは0に戻り、TCNT1Hが1つ増加します。マクロ loop_until_bit_is_set(TIFR,TOV1) により、TIFRのTOV1ビットが1になるまで待ちます。このビットはTCNT1が$FFFFから1つ増加してあふれる時に1になります。このケースではマクロ loop_until_bit_is_set(TIFR,TOV1) から抜けた後 変数ledがPORTBに出力された後、1だけ増加されます。ポーリングで使う場合は、TIFRのTOV1ビットを消去して次の待ちに備えるためTOV1ビットに1を書き込みます。1を書き込むとbitがクリアされる点に注意して下さい。詳しくはデータシートをお読みください。
ledは8bit変数なので、ledの値は0→1→2→・・・・→255→0→1→2→・・・のように変化します。led値の更新は、システムクロック周期×1024(プリスケーラ)×65536(TCNT1一周)毎に行われることになります。クロックが4MHzなら、250ns×1×65536=16.384msec毎に変化し、led値が一周するにはその256倍の4.19秒かかります。

割り込みで使う

割り込みでの使用の方がポーリングよりもよく使われます。この方法ではTOV1ビットをずっと監視しつづける必要はありません。オーバーフローが起こったとき、コントローラは今まで実行していたプログラムから適切な割り込みベクタアドレスに飛びます。通常そこにはジャンプ命令が書かれており、そこから割り込みルーチンへ飛びます。割り込みルーチンが終わると、元のプログラムの、割り込み発生時実行していた場所からプログラム実行を再開します。

/*
Test program for Timer/Counter 1 in the Interrupt Mode
Every time the Timer starts an interrupt routine the led variable
is written on the PORTB and increased one time.
Leitner Harald
07/00
*/
#include <avr/io.h>
#include <avr/interrupt.h>

uint8_t led;
ISR(TIMER1_OVF_vect )
{
   PORTB = ~led; /* write value of led on PORTB */
   led++;
}
int main( void )
{
   PORTB = 0xFF; /* 初期状態:LEDは全消灯 */
   DDRB  = 0xFF; /* PORTBの全ピンを出力に */
   TIMSK = _BV(TOIE1);  /* タイマ1オーバーフロー割り込みの許可
   TCCR1A= 0;    /* TCCR1A タイマモード */
   TCCR1B= 1;    /* プリスケールは ck/1*/
   led = 0;
   TCNT1 = 0x0000;  /* タイマ1の初期値設定   */
   sei(); /* 割り込みの許可。個別の割り込み許可と全体の割り込み許可双方が必要 */
   for (;;){}
}

割り込みルーチンはキーワード ISRによって導入されます。オーバーフローが発生するとすぐにこの割り込みルーチンが実行されます。割り込みを有効にするには、メインプログラムで割り込みを有効とするビットをセットし、さらにsei();を実行しなければなりません。ここではTIMSKのTOIE1ビットをセットし、さらにsei();でSREG(ステータスレジスタ)のi-bit(全般割り込み有効)をセットすることになります

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

上記の例では、タイマ割り込みの間隔は (1/システムクロック)×プリスケール値×256となるので、例えば4MHzクロックの場合はプリスケール値により16.384msec、131.072msec、1048.576msec、4194.304msecが可能になります。ここでたとえば500msec間隔で割り込みを駆動したい場合はどうしたらいいでしょうか?

割り込みがかかったときTCNT1は0ですが、このとき例えばCK/64の状態で、割り込み時TCNT1を(65536-31250)にセットしてやれば次の割り込みは31250カウント後掛かることになります。この場合1048.576×31250/65536=500msecでの割り込みが実現できます。ただし割り込みがかかってから割り込みルーチン内でTCNT1に値をセットするまでの時間の分の誤差が生じますのでご注意下さい。

#include <avr/io.h>
#include <avr/interrupt.h>
#define TCNT1_BOTTOM  (65536-31250);
uint8_t led;
ISR(TIMER0_OVF_vect )
{
   TCNT1 = TCNT1_BOTTOM;
   PORTB = ~led;
   led++;
}
int main( void )
{
   PORTB = 0xFF; /* 初期状態:LEDは全消灯 */
   DDRB  = 0xFF; /* PORTBの全ピンを出力に */
   TCNT1 = TCNT0_BOTTOM;  /* タイマ0の初期値設定   */
   TCCR1A= 0;       /* TCCR1A タイマモード */
   TCCR1B= 3;       /* プリスケールは ck/64*/
   TIMSK = _BV(TOIE0);  /* タイマ0オーバーフロー割り込みの許可
   led = 0;
   sei(); /* 割り込みの許可。個別の割り込み許可と全体の割り込み許可双方が必要 */
   for (;;){}
}

カウンタ動作

WGM値はタイマ動作と同じ0000ですが、TCCR1Bで設定するプリスケーラ設定が異なります。TCCR1BのSC12,SC11,SC10の3bitに書かれる値が6又は7だと、タイマ1は内部の信号ではなく外部(T1ピン)の信号を数えるカウンタ動作を行います。TCCR1BのSC12-SC10に6を書き込むとpinT1の立ち下がりをカウントします。7 を書き込むと立ち上がりをカウントします。それ以外はタイマ動作と同じです。オーバーフローが起こる時に割り込みルーチンを呼び出すことができます。

T1ピンがどのピンであるかについてはデータシートを参照ください。先頭付近にあるピン配置図に書かれています。

T1ピンは入力モードに設定しなければなりません。たとえばmega8ではT1ピンはPD5にあたるので、DDRDのbit5を0にしてやらなければなりません。

#include <avr/io.h>
#include <avr/interrupt.h>
#define TCNT1_BOTTOM  (65536-50000);
uint8_t led;
ISR(TIMER0_OVF_vect )
{
   // 50000カウント単位の計数値をLED列で2進表示する
   TCNT1 = TCNT1_BOTTOM;
   PORTB = ~led;
   led++;
}
int main( void )
{
   PORTB = 0xFF; /* 初期状態:LEDは全消灯 */
   DDRB  = 0xFF; /* PORTBの全ピンを出力に */
   TCNT1 = TCNT0_BOTTOM;  /* タイマ0の初期値設定   */
   TCCR1A= 0;       /* TCCR1A タイマ/カウンタモード */
   TCCR1B= 6;       /* T1入力、立ち上がりを検知 */
   TIMSK = _BV(TOIE0);  /* タイマ0オーバーフロー割り込みの許可
   led = 0;
   sei(); /* 割り込みの許可。個別の割り込み許可と全体の割り込み許可双方が必要 */
   for (;;){}
}

 比較一致機能(非PWMモード)

概要

タイマ/カウンタ1は、1〜3個の出力比較一致機能(Output Compare,OC)モードを持っています。OCR1xレジスタ(x=A,B,C。以下同様)という16bitレジスタが用意されており、このレジスタの値がTCNT1の値と一致したときに何らかのアクションを起こすことができます。実行できる動作は以下のようなものがあります。

  1. 機能ピン OC1xピン出力値を変化させる
  2. OCF1xフラグビットを立てる
  3. 上記フラグビットが立つとき割り込みを発生させる
  4. WGMビットの設定により、TCNT1値をゼロリセットさせる=OCR1Aを周期とするタイマを作る

※機能ピン(OC1x)がどのピンに割り当てられているかはデバイスによって異なります。必ずお使いのデバイスのデータシートを参照ください。

機能ピン OC1xの設定

TCCR1Aの設定により出力についていろいろな機能が実現できます。PWM動作でない場合は以下の通りです。
bit 定義名称
Bit 2 COM1C0
Bit 3 COM1C1
Bit 4 COM1B0
Bit 5 COM1B1
Bit 6 COM1A0
Bit 7 COM1A1
※OC1Cがあるデバイスはmega128など一部です。

COM1x1 COM1x0 説明
 0  0  ピンOC1xをタイマから切り離す(普通の入出力ピンとして使う)
 0  1  比較一致するたびにピンをトグル(Hi→Lo、Lo→Hi)する
 1  0  比較一致するたびにピンをクリア(Lo)
 1  1  比較一致するたびにピンをセット(Hi)

比較一致出力を使った例です。割り込みが要らない点にご注目ください。

//mega8でOC1A(PB1)から矩形波を出力する
#include <avr/io.h>
int main( void )
{
   DDRB = 0x02; //PB1(OC1A)を出力モードに
   OCR1A=0x4000;    
   //TCNT1==0x4000の時比較一致が起こる。周期は65536カウント毎
   TCCR1A= (1<<COM1A0);  // 比較一致時トグル
   TCCR1B= 5;            // CK/1024
   for (;;);
}

COM1A1:COM1A0=01で、TCNT1==OCR1Aとなるたびに、ピンOC1AがHiになったりLoになったり切り替わります。結果、OC1A(PB1)ピンからはタイマーの周期の2倍の周期の矩形波が出てきます。

比較一致フラグビット OCF1A/OCF1B

TCNT1==OCR1Aとなったとき、TIFRレジスタ上のOCF1Aが1となります。OCF1Bについても同様です。OCF1Cも同様ですが、このビットはTIFRに入りきれないためETIFRに置かれています。このビットは、1を書き込むことで消去できます。

// これにより10000カウントだけwaitします。
#include <avr/io.h>
int main(void)
{
   TCCR1A=0;
   TCCR1B=5; //1024分周
   OCR1A=10000;
   TCNT1=0;
   TIFR = (1<<OCF1A);  //ビットクリア
   loop_until_bit_is_set(TIFR,OCF1A);
   TIFR = (1<<OCF1A);  //ビットクリア
   //TIFR|=(1<<OCF1A)でないことに注意! 
   for(;;);
}

I/O入出力ポート 参照

比較一致割り込み

比較一致フラグビットが立つときに割り込みを掛けることができます。

// 65536カウント周期、1000カウント幅のパルスをPB7から出力します。
#include <avr/io.h>
#include <avr/interrupt.h>

#define p |=  _BV(b);
#define p &= ~_BV(b);

 ISR(TIMER1_OVF_vect)
{
        PORTB |=  _BV(7);
}
 ISR(TIMER1_COMPA_vect)
{
        PORTB &= ~_BV(7);
}
int main(void)
{
   TCCR1A=0;
   TCCR1B=5; //1024分周
   TIMSK=(1<<OCIE1A)|(1<<TOIE1);  //オーバーフロー割り込み・比較一致1A割り込み
   OCR1A=1000;
   sei();
   for(;;);
}

比較一致割り込みを使って任意の周期の割り込みを作る

比較一致割り込みを使って任意の周期を作れます。

/*
500msec毎にPB0に接続されたLEDを点滅させます。クロックは4MHz。
original by anonymous @ 2cn.net
*/
#include <avr/io.h>
#include <avr/interrupt.h>

#define LED_ON   PORTB|= 0x01;  /* LED点灯 */
#define LED_OFF  PORTB&=~0x01;  /* LED消灯 */
#define LED_TGL  PORTB^= 0x01;  /* LEDトグル */
#define TOP_VALUE (31250)
/* 4MHz,プリスケーラCK64にて、31250カウント=500msec */

ISR(TIMER1_COMPA_vect )/* Compare interrupt routine */
{
   LED_TGL;
   OCR1A += TOP_VALUE;
}
int main( void )
{
   DDRB = 0xFF; /* PORTBをすべて出力モードに*/
   LED_OFF;
   TIMSK  = _BV(OCIE1A);     /* 比較一致A割り込み有効 */
   TCNT1  = 0x0000;          /* TCNT1初期化 */
   OCR1A  = TOP_VALUE;       /* 比較一致値A */
   TCCR1A = 0;               /* タイマモード、比較一致で出力は設定せず */
   TCCR1B = 3;               /* プリスケーラCK/64 */
   
   sei();
   for (;;){}
}

最初TCNT1=0で始まり、OCR1A=TOP_VALUEの時点で最初の割り込みが掛かります。ここでOCR1A=OCR1A+TOP_VALUEとして、さらにTOP_VALUE時間(500msec)後に割り込みをかけるようにします。

CTC動作

タイマ・カウンタモードにおいて、TCCR1AまたはICR1との比較一致時、TCNT1をゼロリセットすることができます。このモードはWGMを設定することで実現できます。
番号 WGM13〜0 タイマ/カウンタ動作種別 動作
$4 0 1 0 0 比較一致クリア(CTC)動作 0→カウントアップ→OCR1A→0を繰り返す
$C 1 1 0 0 比較一致クリア(CTC) 0→カウントアップ→ICR1→0を繰り返す
AT90Sの場合は以下の1種類のみです。

番号 CTC1 PWM21-0 タイマ/カウンタ動作種別 動作
$4 1 00 比較一致クリア(CTC)動作 0→カウントアップ→OCR1A→0を繰り返す

// これにより10000カウント周期でPB7につないだLEDを点滅させます。タイマループを作成します
#include <avr/io.h>
#include <avr/interrupt.h>

#define tbi(p,b)  p^=(1<<b)

ISR(TIMER1_OVF_vect )
{
   tbi(PORTB,7); //5000カウント毎に点灯・消灯を繰り返す
}
int main(void)
{
   TIMSK=(1<<OCIE1A);  //オーバーフロー割り込み
   TCCR1A=SET_TCCR1A(0,0,0,WGM_OCR_CTC);
   TCCR1B=SET_TCCR1A(0,0,WGM_OCR_CTC,5);
       //1024分周,CTC動作、WGM13-0=0100
   TCNT1=0;
   OCR1A=5000;
   sei();
   for(;;);
}

比較一致を使う場合の注意(非PWMモード)

タイマー走行中にOCR1A/OCR1BやTCNT1をプログラムで書き換える場合、起こるはずの比較一致が起こらなくなることでとんでもない動作になることがあります。各レジスタ変更後、TCNT1が1つ進むまでは正常な比較一致動作は期待できないと考えた方が良さそうです。詳細はデータシートを参照してください。

タイマカウンタモードでは、OCR1xがすぐに反映されるので、上記のようなタイミングで変更すると1周期比較一致が無効になる。

PWMモードでは、赤線のタイミングでOCR1xを変更しても、実際の比較値は青線のように周期の境目で変化するので問題が起こりにくい。

  • 解決策1:この問題を回避できる高速PWMモードを使う
  • 解決策2:OCR1A設定時にTCNT1を飛び越していないかどうかチェックする
OCR1A=0x1000;
if (OCR1A<TCNT1) TCNT1=OCR1A-1; //強制的に比較一致を起こさせる
  • 解決策3:OCR1Aに設定する値を一時他の変数に保存しておいて、OverFlow割り込み時にOCR1Aを更新する
メインルーチン内で、OCR1A_value=0x1000;
OverFlow割り込み内で OCR1A=OCR1A_value;
//このときTCNT1は0付近なので、0付近の設定さえ避ければOK

//LEDの明るさを調整する
#include <avr/io.h>
#include <avr/interrupt.h>

#define LED_ON     PORTB=0xFE;  /* LED点灯 */
#define LED_OFF    PORTB=0xFF;  /* LED消灯 */
#define LED_ONTIME 0x100;

uint16_t delay = 120;  /* 点滅周期、初期値120 */

ISR(TIMER1_COMPA_vect )/* Compare interrupt routine */
{
   OCR1B = delay<<8;
   LED_ON;
}
ISR(TIMER1_COMPB_vect )/* Compare interrupt routine */
{
    LED_OFF;
}
ISR(INT0_vect ) /* PD2 */
{
    if (delay < 30) delay = 15; else delay = delay - 15;
}
ISR(INT1_vect )  /* PD3 */
{
   if (delay > 240) delay = 255; else delay = delay + 15;
}
int main( void )
{
   DDRB = 0xFF; // PORTBをすべて出力モードに
   DDRB = 0x00; // PORTDをすべて入力モードに
   LED_OFF;
   TIMSK = _BV(OCIE1A)|_BV(OCIE1B);  // 比較一致A・B割り込み有効 
   GIMSK = _BV(INT0)|_BV(INT1);      // 外部割り込みINT0,INT1有効 
   MCUCR = (3<<ISC00)|(3<<ISC10);    // INT0,INT1とも立ち上がりエッジで有効 
   TCNT1 = 0x0000;   // TCNT1初期化 
   OCR1A = 50000; // 比較一致値A 
   OCR1B = delay<<8; // 比較一致値B 
   TCCR1A = 0;       // タイマモード、比較一致で出力は設定せず 
   TCCR1B = 1;       // プリスケーラCK/1、タイマモード。周期は10MHzで5msec
   
   sei();
   for (;;){}
}

WGM=0100で、OCR1AによるCTC設定がなされましたので、OC1A割り込みでTCNT1は0にリセットされます。TCNT1は0→1→2→・・・・OCR1A値→0→1→・・・を繰り返します。ここではOCR1Aは50000で固定しています。プリスケーラCK/1で10MHzクロックで動かすとちょうど5msec間隔になります。

delayをスイッチ割り込みで変更し、OCR1Bにはdelay*256を代入します。OCR1BがOCR1Aを越えた場合、OC1B割り込みがかからなくなりますが、この場合はLEDは点灯しっぱなしになるだけなので放置してあります。これでは困るような事情がある場合にはOCR1B値がOCR1A値を超えないようチェックを入れてください。

OC1A割り込みではLED点灯、OC1B割り込みではLED消灯を行いますので、結果、LEDはduty比(OCR1B/OCR1A)で点灯します。delay値を変更しそれをOCR1Bに出力することで点灯duty比が変更できます。

ここで注意すべきなのは、OCR1Bの変更タイミングです。スイッチによりdelay値を変更したとき、すぐにOCR1Bに周期設定値を入れるとまずいです。たとえば、TCNT1が$8000に達しているときにdelayが135であったのを120に変更したとします。対応するOCR1Bは$8700から$7800に変更となりますが、これをすぐに反映させてしまうと、TCNT1がOCR1Bを越えているのにもかかわらず比較一致が起こらず(一致する瞬間がないため)、TCNTは$FFFFまで走ってしまいます。そこで、OCR1Bはすぐには変更せず、現在設定中のOC1B割り込みが起こった後、OC1A又はタイマオーバーフロー割り込み内で反映させれば問題が起こりません。

PWMモードだと、ハードウェア的にこの問題が解決されています。OCR1xを変更してもその値はTOP値に達して現在の周期が終わるまでは反映されない仕掛けがあります。
比較一致を使う場合の注意(megaシリーズ)

比較一致動作について、AT90SとATMegaとで少し動作が異なります。

AT90S2313など:TCNT1がOCR1Aに一致した時比較一致が起こる

プリスケーラ1/8、OCR1A=5でCTC動作をすれば(0〜4)x8=40クロック周期になる

0000000011111111・・・・・344444444500000001111111・・・・(4が8回,5が1回、0が7回)
ATmega8など:TCNT1がOCR1A+1に一致した時比較一致が起こる

プリスケーラ1/8、OCR1A=5でCTC動作をすれば(0〜5)×8=48クロック周期になる

0000000011111111・・・・・344444444555555550000000011・・・(4が8回,5が8回、0が8回)

高速PWM動作(megaシリーズのみ)

番号 WGM13〜0 タイマ/カウンタ動作種別 TOP値 OCR更新 TOV1設定
$5 0 1 0 1 8ビット高速PWM動作 0x00FF TOP TOP
$6 0 1 1 0 9ビット高速PWM動作 0x01FF TOP TOP
$7 0 1 1 1 10ビット高速PWM動作 0x03FF TOP TOP
$E 1 1 1 0 高速PWM動作 ICR1 TOP TOP
$F 1 1 1 1 高速PWM動作 OCR1A TOP TOP

TCNT1値は0からカウントアップして、設定に応じたTOP値まで達したら再び0からカウントを再開する動作を繰り返します。CTC動作との違いは以下の通り。

  • TOP値に0x00FF,0x01FF,0x03FFの固定値が選べる
  • TOP値にOCR1Aを選んだ場合、OCR1Aに新しいTOP値を書き込んでもすぐには反映されず、旧TOP値による比較一致が起こってから反映される。先のCTC動作例でのソフトウェア対応に相当することをハードウェアでやってくれる。OCR1B以降の比較一致動作についても同様。
  • TOP値にICR1を選んだ場合は、上記のような動作は行われずCTC動作と同様になる→ICR1設定は固定値で使う場合にお勧め。ICR使用時でも比較一致に使うOCR1xについては上記のバッファ動作が行われる。

高速PWMモードでは、通常TCNT1に0〜TOP値のカウントを繰り返させ、OCR1xレジスタとTCNT1の比較一致で比較出力ピンから希望するDuty比の矩形波を出力させます。出力される信号の設定はタイマカウンタモードでも使ったCOM1x1,COM1x0ビットで設定します。
COM1x1 COM1x0 説明
 0  0  ピンOC1xをタイマから切り離す(普通の入出力ピンとして使う)
 0  1  TOP=OC1A動作の時だけ、比較一致するたびにOC1Aピンをトグル
 1  0  比較一致でLow、TOPでHigh。duty比=(OCR1x/TOP)
 1  1  比較一致でHigh、TOPでLow。duty比=1-(OCR1x/TOP)
TOP値にOCR1Aを使っていて、OC1Aピンが遊んでいる場合のみ、OC1Bなど他の比較一致でOC1Aピンをトグル動作させることができます。

たとえば8bit高速PWM動作中にOCR1A=0x0040を入れると、

  • COM1A1:COM1A0=10の場合、duty=25%の矩形波が得られます。
  • COM1A1:COM1A0=11の場合はduty=75%の矩形波が得られます。

先ほどCTC動作で紹介したプログラムがどう変化するか見てみましょう。出力ポートはPB2(OC1B)に限定されますが、プログラムはだいぶ簡単になります。比較一致割り込みが不要になります。

#include <avr/io.h>
#include <avr/interrupt.h>
#include "mega_timer.h"
//先に出した定義ファイル

uint8_t delay = 120;  /* 点滅周期、初期値120 */

ISR(INT0_vect )  /* PD2 */
{
   if (delay < 30) delay = 15; else delay = delay - 15;
   OCR1B=(uint16_t)delay<<8; 
   //OCR1Bダブルバッファのためいきなり値を変更しても良い
}
ISR(INT1_vect )  /* PD3 */
{
   if (delay > 240) delay = 255; else delay = delay + 15;
   OCR1B=(uint16_t)delay<<8;
}
int main( void )
{
   DDRB = 0xFF; /* PORTBをすべて出力モードに*/
   DDRB = 0x00; /* PORTDをすべて入力モードに*/
   /* Switches PB3,PB4 for Interrupt 0 and 1 */
   GIMSK = _BV(INT0)|_BV(INT1);     // 外部割り込みINT0,INT1有効 
   MCUCR = _BV(ISC00)|_BV(ISC10);   // INT0,INT1とも立ち上がりエッジで有効 
   TCNT1 = 0x0000;     // TCNT1初期化 
   OCR1A = 50000;      // 比較一致値A 
   OCR1B = delay<<8;   // 比較一致値B 
   TCCR1A = SET_TCCR1A(0,3,0,WGM_OCR_FPWM); 
       //TOP=OCR1Aな高速PWM、OC1B反転出力(LEDシンクドライブのため)
   TCCR1B = SET_TCCR1B(0,0,WGM_OCR_FPWM,PS_CK1);     
       // CK/1、WGM設定
   for (;;);
}

位相基準PWM動作

megaシリーズでは以下のような設定になります。
番号 WGM13〜0 タイマ/カウンタ動作種別 TOP値 OCR更新 TOV1設定
$1 0 0 0 1 8ビット位相基準PWM動作 0x00FF TOP BOTTOM
$2 0 0 1 0 9ビット位相基準PWM動作 0x01FF TOP BOTTOM
$3 0 0 1 1 10ビット位相基準PWM動作 0x03FF TOP BOTTOM
$A 1 0 1 0 位相基準PWM動作 ICR TOP BOTTOM
$B 1 0 1 1 位相基準PWM動作 OCR1A TOP BOTTOM

AT90Sシリーズでは、TOP値にICR,OCR1Aを使うことはできません。
番号 CTC1 PWM21-0 タイマ/カウンタ動作種別 TOP値 OCR更新 TOV1設定
$1 0 01 8ビット位相基準PWM動作 0x00FF TOP BOTTOM
$2 0 10 9ビット位相基準PWM動作 0x01FF TOP BOTTOM
$3 0 11 10ビット位相基準PWM動作 0x03FF TOP BOTTOM

COM1x1 COM1x0 説明
 0  0  ピンOC1xをタイマから切り離す(普通の入出力ピンとして使う)
 0  1  TOP=OCR1A動作の時だけ、比較一致するたびにOC1Aピンをトグル
 1  0  カウントアップ比較一致でLow、カウントダウン比較一致でHigh。duty比=(OCR1x/TOP)
 1  1  カウントアップ比較一致でHigh、カウントダウン比較一致でLow。duty比=1-(OCR1x/TOP)

TOP値にOCR1Aを使っていて、OC1Aピンが遊んでいる場合のみ、OC1Bなど他の比較一致でOC1Aピンをトグル動作させることができます。(megaのみ)

このモードでは、TCNT1は0→カウントアップ→TOP値→カウントダウン→0という動作を繰り返します。その他の動作は高速PWMとほぼ同じです。この動作のメリットは、OCR1xを変更したとき、位相がずれにくい所にあるようです(モーター駆動に有利とか)。

OCR1x変更は高速PWMと同様、即座には反映されずにTOP値の時に反映されます。

高速PWMとプログラムはほとんど変わりません。WGM設定が異なるだけです。ただ、プリスケーラやTOP値が同じだと、位相基準PWMの周期は高速PWMの2倍になります。先の例だと高速PWM5msecに対し位相基準PWMは10msec周期になるので、TOP値を調整しないとちらつきが気になるかもしれません。

    TCCR1A = SET_TCCR1A(0,3,0,WGM_OCR_PPWM);
       //TOP=OCR1Aな高速PWM、反転出力(LEDシンクドライブのため)
   TCCR1B = SET_TCCR1B(0,0,WGM_OCR_PPWM,PS_CK1);     
       // CK/1

位相周波数基準PWM(megaのみ)

番号 WGM13〜0 タイマ/カウンタ動作種別 TOP値 OCR更新 TOV1設定
$8 1 0 0 0 位相/周波数基準PWM動作 ICR BOTTOM BOTTOM
$9 1 0 0 1 位相/周波数基準PWM動作 OCR1A BOTTOM BOTTOM

位相基準PWMとほぼ同じですが、OCR1xの更新タイミングがBOTTOM値時点である点だけが異なるようです。先の例もWGM設定部分が異なるだけです。

   TCCR1A = SET_TCCR1A(0,3,0,WGM_OCR_PFPWM);
       //TOP=OCR1Aな位相周波数基準PWM、反転出力(LEDシンクドライブのため)
   TCCR1B = SET_TCCR1B(0,0,WGM_OCR_PFPWM,PS_CK1);     
       // CK/1

捕獲入力(インプットキャプチャ)

外部信号が入った瞬間のタイマの値を記憶することができます。信号を確認してからタイマレジスタを読みに行く場合起こるタイムラグの問題が解消できます。入力捕獲割り込みをセットしておけば、値が入るとすぐに割り込みが起動され、その値を利用することができます。

pin ICP(PD6) 信号の立ち上がり又は立ち下がりを検知し、その瞬間のタイマ/カウンタ1の値が16bitのインプットキャプチャレジスタ ICR1(ICR1L,ICR1H)に保存されます。このときTIFRレジスタ内のICF1(捕獲割り込み要求フラグ)がセットされ、TIMSKのTICIE1ビットで許可されていればTIMER_CAPT_vect 割り込みルーチンを呼び出すことができます。

入力捕獲割り込みの例
/*
タイマ1の入力捕獲割り込みを使用し、捕獲信号が来るたびにタイマ1の値を捉え、
その上位バイト をPORTBに出力してLEDで表示します。
07/00 Leitner Harald , modified by anonymous @ 2ch.net
*/
#include <avr/io.h>
#include <avr/interrupt.h>

ISR(TIMER1_CAPT_vect )
{
   PORTB = ~(ICR1>>8); /*ICR1に保存された値の上位8bitをPORTBに出力 */
}

int main( void )
{
   DDRB = 0xFF; /* PORTBをすべて出力モードに*/
   DDRB = 0x00; /* PORTDをすべて入力モードに*/
   PORTB= 0xFF; /* LED全消灯 */
   TIMSK = _BV(TICIE1); //入力キャプチャ時割り込みを許可
   TCNT1 = 0x0000;
   TCCR1A = 0;        /* タイマモード、ICES=0(立ち下がりエッジでキャプチャ) */
   TCCR1B = 5;        /* プリスケーラCK/1024 */
   sei();
   for (;;){}
}

このプログラムはICPピンの信号立ち下がりが起こった瞬間のタイマ/カウンタ1の値をICRに保存します。TIMER_CAPT_vect で指定された割り込みが利用できるので、その割り込み内でICRの値を取り出し利用します。ここでは上位8bitをPORTBに送り、LED表示させています。