多くのマイコンと同様,RX62Nにもタイマが内蔵しています.この章では,RX62Nにどのようなタイマがあるのか説明したのち,そのうちのひとつであるコンペアマッチタイマの使い方について説明します.
-
タイマの種類
RX62Nには,計時することのできる周辺機能として,コンペアマッチタイマ(CMT),マルチファンクションタイマパルスユニット(MTU2),8ビットタイマ(TMR)があります.すべてこれらは周辺機能クロックをカウントする機能があるため,時間を測ることができます.ただしそれぞれ次のような特徴があります.
CMT MTU2 TMR 測れる時間 短い 長い 長い 外部クロックを使えるか 使えない 使える 使える カウンタサイズ[bits] 16 16 8(16) PWM波形の出力 直接行えない 行える 行える 三相交流波形の出力 直接行えない 行える 直接行えない
使い分け方を簡単にいうと,CMTは簡単に短時間の計時をするとき,MTU2はPWMや三相交流などの複雑な波形を出力するとき,TMRは長時間時間を計時するときに使います.なお,CMTなど以外に,時間に関係する周辺機能としてリアルタイムクロック(RTC),ウォッチドックタイマ(WDT)がありますが,これらは時間を測るために用いるわけではないのでこの章では説明を省略します. -
コンペアマッチタイマ
簡単に時間を計るとき,コンペアマッチタイマは大変便利です.それは,コンペアマッチタイマには必要最低限のレジスタから成り立ってるからです.まずはコンペアマッチタイマを制御するとき使うレジスタについて説明します.
名称 略称(レジスタ名) 役割 コンペアマッチタイマカウンタ CMCNT クロック信号のオン・オフを数えるレジスタです.
16ビットで構成されているため,0~65535まで数えることができます.コンペアマッチタイマコンスタントレジスタ CMCOR CMCNTと比較するときに用いるレジスタです.
CMCNTと同じく16ビットで構成されています.
CMCNTとCMCORが同じになったとき,コンペアマッチしたといい,
コンペアマッチタイマによる割込みを行うことができます.コンペアマッチタイマコントロールレジスタ CMCR コンペアマッチタイマの動作周波数を決定し,
加えて割込みの許可・禁止を設定するレジスタです.コンペアマッチタイマスタートレジスタ CMSTR コンペアマッチタイマの動作開始と停止を行うレジスタです.
RX62Nにあるコンペアマッチタイマは2個のユニットから成り立っています.各ユニットには2個のチャネルがあります.1個のチャネルで1個の計時を行えるようになっていますので,4個の計時を行うことができます.各ユニットにはCMSTRが1個あり,各チャネルにはCMCNT,CMCOR,CMCRが1個ずつあります.つまり,CMSTRは同一ユニット内にある2個のチャネルの開始および停止を行えるようになっています. コンペアマッチタイマの動作を次に説明します.コンペアマッチタイマは,周辺機能クロック(PΦ)を分周してクロック源とします.また,CMCORにはあらかじめ値を格納しておきます.さて,カウントした値はCMCNTに格納されていき,いずれCMCORと同じ値になります.そのとき,コンペアマッチし,インタラプトレジスタ(IR)の該当箇所のフラグが立ちます.例を挙げて説明します.PΦを24[MHz]のとき640[ms]待つとします.この場合,分周比を512,CMCORを29999にすればよいです.理屈はこうです.PΦを512分周すると24[MHz]÷512=46.875[kHz]です.従って,1周期はその逆数である21.33[μs]です.求めたのは640[ms]=640000[μs]ですので640000÷21.33=30000となります.ただし,カウンタは0から数えているため,30000-1=29999となります.よって,PΦを24[MHz]のとき分周比512にし,CMCORに29999を設定すれば640[ms]待つことができます. -
ウェイトタイマの作成
では,プログラムを作成していきましょう.今回サンプルとして,LED7を1秒おきに点灯と消灯を繰り返す動作をするプログラムを作成します.こちらにastah*のファイルを置いておきますのでダウンロードし,スケルトンコードを生成してください.その後,LEDを点灯させるプログラムを流用して作成した後,生成されたスケルトンコードのmisc/timer内にあるファイルをプロジェクトに追加してください.
スケルトンコードをただ追加してコンパイルをするとエラーが発生します.このエラーを修正するには,該当するヘッダファイルの冒頭でextern"C"を追加してください.たとえばIntervalTimerクラスでは,IntervalTimer.hppの冒頭にvoid Excep_CMT1_CMI1クラスを次のように追記してください.
#ifndef MISC_TIMER_INTERVAL_TIMER_H #define MISC_TIMER_INTERVAL_TIMER_H #include <stdio.h> #include <iodefine.h> #include "misc/timer/GivenTimeTimer.hpp" #include "misc/timer/ITimeOverListener.hpp" extern "C"{ void Excep_CMT1_CMI1(); } namespace misc { namespace timer { /* 以下省略 */
-
GivenTimeTimer.cpp
WaitTimerのスーパクラスとしてGivenTimeTimerがあり,これを実装するファイルがGivenTimeTimer.cppです.コンペアマッチタイマは,周辺機能クロック(PΦ)を分周してクロックとしており,このクラスでは最適な分周比とCMCORを求めることができる_getOptimizedCmcorAndDivideメンバ関数を実装します.WaitTimerにより一定時間待つとき,この関数を呼び出して分周比とCMCORを得ます.
次になぜこの処理が必要なのか説明します.CMCORとCMCNTはともに16ビットのレジスタですので0から65535までの数字を数えることができますが,それ以上はオーバフローしてしまい数えることができません.例えば,コンペアマッチタイマに入力されるクロックが1[MHz]つまり1周期1[μs]のとき,65536[μs]しか数えられないということです.この場合,クロックを分周してより長く数えるようにします.なお,RX62Nでは8,32,128および512分周から選ぶようになっています.ではなんでも512分周していればいいのかといえばそうではありません.何故なら,512分周では細かく時間を計ることができないからです.従って,適切な分周比を考える必要があるのです.
さて,この関数で行う処理について説明します.すべての分周比には数えられる時間の上限がありますので,その値を越える時間を指定されたら1つは大きい分周比を用いてみようとします.それでもダメならさらに大きな分周比をしていくのです.ただそれでも限界があり,例えば周辺機能クロックが48[MHz]で最大の分周比である512分周とした場合でも,最大時間は65536×(512÷48)= 699040[μs]を越える時間を計ることはできません.この場合にはfalseを返します.なお,この場合にはWaitTimerクラスの関数で1[ms]をx回繰り返すことでx[ms]を作り出します.
最後にプログラムを作成しようと思いますが,行うことがだいぶ泥臭いところでもありますにで,下に示しますのでコピペして下さい.先ほど説明したことプログラミングしていることを確認して下さい.#include <stdio.h> #include <iodefine.h> #include "GivenTimeTimer.hpp" #include "Frequency.hpp" namespace misc { namespace timer { int GivenTimeTimer::_MIN_DIVIDE_8 = 1; int GivenTimeTimer::_MAX_DIVIDE_8 = 10922; int GivenTimeTimer::_MIN_DIVIDE_32 = 10923; int GivenTimeTimer::_MAX_DIVIDE_32 = 43690; int GivenTimeTimer::_MIN_DIVIDE_128 = 43691; int GivenTimeTimer::_MAX_DIVIDE_128 = 174760; int GivenTimeTimer::_MIN_DIVIDE_512 = 174761; int GivenTimeTimer::_MAX_DIVIDE_512 = 699040; bool GivenTimeTimer::_getOptimalCmcorAndDivide(int time, unsigned short *cmcor, unsigned short *divide) const { if(_MIN_DIVIDE_8<time && time<=_MAX_DIVIDE_8) { (*cmcor) = (unsigned short)(( ( time * Frequency::getInstance()->getPeripheralClock() ) >> 3 ) - 1); (*divide) = 0; } else if(_MIN_DIVIDE_32<time && time <=_MAX_DIVIDE_32) { (*cmcor) = (unsigned short)(( ( time * Frequency::getInstance()->getPeripheralClock() ) >> 5 ) - 1); (*divide) = 1; } else if(_MIN_DIVIDE_128<time && time<=_MAX_DIVIDE_128) { (*cmcor) = (unsigned short)(( ( time * Frequency::getInstance()->getPeripheralClock() ) >> 7 ) - 1); (*divide) = 2; } else if(_MIN_DIVIDE_512<time && time<=_MAX_DIVIDE_512) { (*cmcor) = (unsigned short)(( ( time * Frequency::getInstance()->getPeripheralClock() ) >> 9 ) - 1); (*divide) = 3; } else{ return false; } return true; } } // namespace timer } // namespace misc
-
WaitTimer.cpp
このファイルにはウェイトタイマの動作を記述します.-
コンストラクタなど
WaitTimerはシングルトンですので,コンストラクタ,コピーコンストラクタ,代入演算子,デストラクタおよびgetInstanceメンバ関数を作成してください. -
_initializeメンバ関数
下図は_initialize関数のアクティビティ図です.行うことは,スタンバイモードの解除,コンペアマッチにIRのフラグを立てること,そしてコンペアマッチによる割り込みを行わないことです.-
スタンバイモードの解除
マイコンが消費する電力を少しでも減らすため,周辺機能を使わないときには動作を停止させられるようにRX62Nマイコンは設計されています.コンペアマッチタイマはマイコンの動作開始時にスタンバイモードとなっており,これを解除しないと使用できません.スタンバイモードを解除するとき使用するレジスタがSYSTEM構造体に定義されているMSTPレジスタです.多くの周辺機能が存在するRX62Nには多くのMSTPレジスタがやはり数多く存在するため,操作するのが煩雑です.そこで,iodefine.hにはマクロMSTPが用意されています.このマクロの引数として,例えばCMT0(コンペアマッチタイマのチャネル0)を設定し,0を与えると解除,1を与えると設定となります.つまり,下のようにするとコンペアマッチタイマのチャネル0のスタンバイモードを解除することができます.
MSTP(CMT0)=0;
-
コンペアマッチ時のフラグ
コンペアマッチしたとき,IR(インタラプトレジスタ)にあるフラグを立てるか決めるレジスタがCMCR内にあるCMIEです.少し紛らわしいことなのですが,CMIEは割り込みを発生させないときにでもCMIEを1(許可)にします.CMIEという名前からは割り込みを発生させないときには0(禁止)にしておくように感じますが,CMIEはコンペアマッチタイマの使用許可/禁止を設定するためのものと捉えたほうがよいです.コンペアマッチしたときにコンペアマッチフラグが存在し,それが立つSuperHとは異なり,RX62Nでは,IRにあるフラグが立つ仕組みになっているのです.もし,IRにあるフラグが立ったとき割り込みを発生させるには,外部割込みのときにも説明したIERレジスタに割込みを許可するように設定してください.
今回,コンペアマッチタイマチャネル0においてコンペアマッチしたとき,IRレジスタにフラグを立てるには下のようなプログラムとなります.
CMT0.CMCR.BIT.CMIE = 1;
-
割込み禁止
前節でも説明しましたとおり,割り込みを禁止するにはIERレジスタのうち,CMT0に対応する箇所を0にすればよいです.IERレジスタを操作するとき便利なのがマクロIENです.マクロIENにCMT0とCMI0を引数として渡し,0を設定することで禁止することができます.下にプログラム例を示します.
IEN(CMT0,CMI0)=0;
-
-
_waitメンバ関数
すこしややこしいのですが,WaitTimerクラスにはpublicであるwaitメンバ関数とprivateである_waitメンバ関数があり,ここでは後者を説明します.この関数では,下図のようにコンペアマッチタイマを使って一定時間待つ処理を行います.注意点としては,CMTチャネル0のタイマを開始させるレジスタCMT.CMSTR0.BIT.STR0についてです.コンペアマッチタイマにはチャネルが4個ありますが,CMSTRレジスタはユニット数である2個しかありません.他のCMCRのように,CMT0.CMCRとならないことに注意をしてください. -
waitメンバ関数
今度はpublicであるwaitメンバ関数で行う処理について説明します.大まかに処理を説明すると,最適なCMCORと分周比が得られるか調べ,得られたらそれらを設定して_wait関数を1回呼び出します.もし得られなかったら,_wait関数1回呼び出すごと1024[us]待つよう設定し,待ちたい時間÷1024[回],_wait関数を呼び出すようにしています. -
waitAsCountValueメンバ関数
この関数もwait関数と同様にウェイトさせることができますが,時間の指定方法が異なります.まず,この関数では分周比8,CMCORを最大値にした状態で_wait関数をoverflow_num回呼び出します.その後,CMCORをcntに変えて_wait関数を1回呼び出します.つまり,wait関数のように待ちたい時間を直接指定するのではなく,コンペアマッチタイマのCMCORを使って時間を指定しているのです.
void WaitTimer::waitAsCountValue(unsigned short cnt, int overflow_num){ /* CMCORに最大値を入れる */ CMT0.CMCOR = (unsigned short)((2<<16)-1); /* 分周比を8とする */ CMT0.CMCR.BIT.CKS = 0; /* オーバーフローした回数だけ待つ */ for(int i=0; i<overflow_num; i++) { _wait(); } /* CMCORに与えられたcntを入れる */ CMT0.CMCOR = cnt; /* cntだけ待つ */ _wait(); }
-
-
Main.cpp
メイン関数では,LED7を1[s]ごとに点灯と消灯を交互に繰り返す処理を行うようにします.既に作ってあるLEDの点灯および消灯のプログラムのうち,for文によるウェイトをWaitTimerのwaitを使うように変えてください.下のようなプログラムでよいでしょう.
#include "WaitTimer.hpp" /* 中略 */ /* timer名前空間のクラスを容易に使えるようにする */ using namespace misc::timer; void main(void){ /* LED7のインスタンスを得る */ Led7 *led = Led7::getInstance(); while(1) {/* 無限ループ */ /* 点灯させる */ led->turnOn(); /* 1[s]ウェイト */ WaitTimer::getInstance()->wait(1000000); /* 消灯させる */ led->turnOff(); /* 1[s]ウェイト */ WaitTimer::getInstance()->wait(1000000); } }
-