私の場合、8ビットタイマーはPWMに使って、16ビットタイマーは時間の測定に使うことが多いです。
その、16ビットタイマーで時間の測定をするメモです。
時間の測定ということは、あるタイミングでカウンタをカウントしていくわけですが、16ビットタイマーでは当然16ビット分しかカウントできません。
カウントするタイミングは、最大1024分周、つまり1024クロックごとに1カウントという設定ができますから、
最大で計れる時間は、例えばクロックが16MHZの場合、
65535 * 1024 / 16,000,000 = 4.19424(秒)
4秒程度しか計れません。これではとても役に立たない。
それではどうするかというと、タイマカウンタが65535に達して、次に桁あふれした際に、割り込みを起こすことができます。
その割り込みを起こす度に、4.19424秒を、それまでの経過時間として、どこかに足し込んでいけばいいわけです。
具体的にはこんな感じです。
以下、ATMega328Pの16ビットタイマである、タイマ1を使う前提での説明です。
//CPUのクロック周波数 #define F_CPU 16000000 int32_t msec;//ミリ秒で保存します。 ISR(TIMER1_OVF_vect) //タイマ1割り込みの定義 { msec += (65536L * 1024L * 1000L) / F_CPU ; }これは、ミリ秒にするために1000を掛けていますが、これでは大きくなりすぎて、32ビット数値を桁あふれしてしまいます。 そこで、予めCPU周波数を1000で割った数字を用意し、これで割りましょう。
//CPUのクロック周波数を1000で割ったもの #define F_CPU_DIV1000 16000 int32_t msec;//ミリ秒で保存します。 ISR(TIMER1_OVF_vect) //タイマ1割り込みの定義 { msec += (65536L * 1024L) / F_CPU_DIV1000 ; }そして、あとはタイマ関係のレジスタに設定してやれば、その通り動きます。
クラスにしてみました。ATMega328P、Atmel Studio 6.0で確認済み。
Timer.h
#include <stdint.h> class Timer { public: //タイマを設定し、起動する。 void StartTimer(); //タイマを起動してからの経過時間をミリ秒で返す。 int32_t GetTime(); //タイマを終了する。 void EndTimer(); //時間を0にリセットする。 void ResetTime(); };Timer.cpp
#include "Timer.h" #include <avr/io.h> #include <avr/interrupt.h> #include <avr/common.h> #define F_CPU 16000000L #define F_CPU_DIV1000 16000L volatile int32_t current_msec; void Timer::StartTimer() { current_msec = 0; //割り込み禁止 cli(); //タイマ/カウンタ1制御レジスタAの設定 //(タイマ用ピンとして使わない) TCCR1A = 0x00; //タイマ関係のピンは標準ポート動作とする //タイマ/カウンタ1制御レジスタBの設定 TCCR1B = (1<<CS12)|(1<<CS10);//1024分周でタイマON //タイマ/カウンタ1割り込みマスク レジスタ設定 TIMSK1 = (1<<TOIE1);//タイマ1オーバーフロー割り込み許可 //割り込み許可 sei(); } int32_t Timer::GetTime() { //16ビットレジスタの読み書きの際には、テンポラリレジスタを使用する。 //このため、割り込み禁止操作が必要。 //ステータスレジスタを一時保存する変数 uint8_t sreg; //ステータスレジスタを保存 sreg = SREG; //割り込み禁止 cli(); //現在のタイマ値を取得 uint16_t t = TCNT1; //SREGを戻す。これによって割り込み禁止状態が戻る。(SREGのIビットを戻すから) SREG = sreg; int32_t tw = (int32_t)t; return current_msec + (tw * 1024) / F_CPU_DIV1000; } void Timer::ResetTime() { uint8_t sreg; sreg = SREG; cli(); //タイマを0にする TCNT1 = 0; SREG = sreg; //経過時間も0にする current_msec = 0; } void Timer::EndTimer() { TCCR1B = 0;//タイマoff } ISR(TIMER1_OVF_vect) //タイマ割り込み { current_msec += (65536L * 1024L)/ F_CPU_DIV1000; }使い方は、どこかにTimerの実体を定義しておいて、
#include "Timer.h" Timer timer; //タイマ起動時に一回だけ実行 timer.StartTimer(); ... ... //時刻を知りたいとき int32_t time_elapsed = timer.GetTime(); ... ... //時間をリセットしたいとき timer.ResetTime(); ... ... //タイマーが不要になったとき timer.EndTimer();こんな感じですかね
あ、そうそう、SREGはavr/common.hに定義されていますので、avr/interrupt.hと合わせてインクルードしておきましょう。
それと、当然ですが、この方法でもミリ秒で符号付き32ビットがいっぱいになるまでしかカウントできませんので、
0x7FFFFFFF / 1000 / 3600 / 24 = 24日程度でいっぱいになります。まあ私の用途では十分です。
0 件のコメント:
コメントを投稿