ジョイスティックを使う(実装編)(割込みなし)[A/D変換][タイマ]では,メイン関数内で常にAD変換結果を取得し,かつ表示し続けていました.ジョイスティックなどのようなユーザインタフェースは,ユーザつまり人間の動作はマイコンと比べて大変遅いため,頻繁にAD変換する必要がないことがよくあります.そこで,マイコンには他の処理をメインで行わせ,AD変換と表示は一定時間ごとに行わせるようにプログラミングします.そのために,Timer2を使って一定時間ごとに割り込みを発生させるようにします.この割り込みはドットマトリクスディスプレイを表示するために用いているTimer0と比べて長い時間間隔ごとに割り込むことにします.具体的には,Timer0では125[us]ごとに割り込ませていたのに対し,Timer2では短くとも16[ms]で割り込ませることにします.また,Timer2ではユーザに割込み間隔を3段階で決めてもらえるようにします.
-
Timer2
まずTimer2のブロック図を下に示します.
(出典: PIC16F193X/LF193X データシート)
Fosc/4の周波数のクロックを分周しカウントします.この分周は1,4,16,64の4種類です.このように,カウントする前に分周するものをプリスケーラということがあります.プリスケーラを制御するレジスタがTxCKPSです.ここでxにはタイマの番号2,4,6の何れかとなります.なお,PIC16F1936にはTimer2,Timer4,Timer6の3個の同じタイマが備わっているるため,xには2,4,6のいずれかが入ることになります.そして,カウントした値はTMRxに納められます.このxも先ほどと同じように,2,4,6のいずれかが入ります.
Timer2にはTimer0のようにオーバフローしたときに立つフラグが無い代わりに,TMRxと比較するPRxレジスタが備わっています.このレジスタと比較し同じになったとき(コンペアマッチという)立つTMRxIFフラグがあります.このようなタイマのことをコンペアマッチタイマと言います.
コンペアマッチしたのち,TMRxはクリアされます.また,今ペアマッチした回数を数えて(言い換えれば分周して),TMRxIFフラグが立つ仕組みになっています.このように,カウント後に行う分周のことをポストスケーラということがあります.ポストスケーラを制御するレジスタがTxOUTPSです.
次に割り込みについて説明します.先ほど説明したように,割込みが発生するとTMRxIFが立ちます.このフラグを立てるには事前に割込みを行える状態にしなければなりません.下にTimer2に関係する割込み可能フラグを示します.
(出典: PIC16F193X/LF193X データシート)
Iimer2の割込みを制御するTXIEレジスタを1にすると割込みが可能となり,TXIFレジスタが立つと割り込みが発生します.このように,上にある周辺機能に関する割込みが1つでも発生すると,図の右側にある「To Interrupt Logic」がHighになります.これの接続先を下に示します.
(出典: PIC16F193X/LF193X データシート)
左側にある「From Peripheral Interrupt Logic」が接続先です.図のようにPEIEがHighになっており,かつGIEがHighになっていないとCPUに対して割込みが発生しないようになっています.このように,Timer0のときとは割込みを行うための初期設定が異なっていることに注意をしてください.
最後に,タイマの動作をさせるレジスタについて説明します.Timer2を動作させるには,TMR2ONレジスタをHighにします.なお,もし割り込みを行わせるのであれば,TMR2IEもHigh(PEIEとGIEもお忘れなく)にしてください. -
割込み間隔の定義
ジョイスティックの状態をチェックする間隔を列挙型を使って3段階で定義します.後に,他のインターバルタイマでも使用するため,IntervalTimerForJoystic.hではなく,下記の定義をIntervalTimer.hに書いてください.
/** * 割込みを行う頻度.単位は[ms] */ typedef enum { /** 頻繁 */ FREQUENTLY = 16, /** ときどき*/ SOMETIMES = 96, /** めったにしない */ RARELY = 256 } Frequency;
ここで,各定義には定数を割り当ててあります.この値は割込みを行う間隔を[ms]で表しています.すべて16の倍数となっているのは,Timer2で16[ms]単位で計時しているためです.
-
Interrupt.c
Interrupt.cには,これまでにTimer0の割込み発生したときの処理が書かれていますので,それに加えてTimer2の割込み処理についても書き加えましょう.-
スタティックな変数の宣言
スタティックな変数として,すでに_timer0Callbackが宣言されていますので,それに加えて_timer2Callbackを下のように宣言します.
/* Timer0の割込み発生時に呼び出す関数ポインタ */ static void (*_timer0Callback)(void) = NULL; /* Timer2の割込み発生時に呼び出す関数ポインタ */ static void (*_timer2Callback)(void) = NULL; /*!!追記!!*/
-
Interrupt_setTimer2Callback関数
この関数では,Timer2の割込みが発生したときに呼び出すコールバック関数を設定します.下図のようにスタティック変数_timer2Callbackに仮引数callback変数を設定します.
-
Interrupt_interrupt関数の改変
Timer0による割込みが起こったときの処理がこの関数にしてあります.そこで,下図のようにTimer2に関する処理を追記します.大筋,Timer0のときと同様に記述します.
-
-
IntervalTimerForJoystick.c
このファイルには,ジョイスティックの位置の検出,つまりAD変換を一定間隔で行うためのタイマに関するプログラムを書きます.-
スタティックな変数の定義
一定時間経ったときに呼び出す関数ポインタ_callbackFunctionを下のように宣言します.この変数は,後ほど説明するIntervalTimerForJoystick_setCallback関数でJoystickに関する関数から設定できるようにします.また,一定時間が経ったとき,この関数ポインタを使って呼び出します.
/* 一定時間経ったときに呼び出す関数ポインタ */ static void (*_callbackFunction)(void) = NULL;
-
IntervalTimerForJoystick_initialize関数
IntervalTimerForJoystick関数は,Timer2に関して初期化するための関数です.下の図は処理内容を表しています.まず,Timer2のポストスケーラを64分周に設定するためにT2CKPSを設定します.Fosc/4=1[MHz]ですので,それを64分周した15.625[kHz]となります.
次に,16[ms]ごとにコンペアマッチするようにPR2を設定します.先ほど述べたように,周波数は15.625[kHz]ですので,1周期は0.064[ms]です.16[ms]数えるには,16÷0.064=250回カウントすればよいということが分かります.このため,PR2には250-1=249とすれば16[ms]ごとにコンペアマッチさせられます.なぜ1引くかというと,TMR2は0から数え始まるため,250回数えたときにはTMR2は249になるのです. -
IntervalTimerForJoystick_setFrequency関数
この関数では,ジョイスティックの更新頻度を設定します.設定はFrequencyにある3種類の何れかを指定します.Frequencyに定義した定数は,すべて16の倍数となってます.また,Timer2のカウンタは16[ms]でコンペアマッチするようになっているので,ポストスケーラを制御するT2OUTSレジスタに,「f÷16-1」を行えばよいです.16で割るということは,4ビット右にシフトすることと同じですので,まとめますと,「(f>>4)-1」をT2OUTSに設定してください.
-
IntervalTimerForJoystick_setCallback関数
この関数は,一定時間が経過したときに呼び出されるコールバック関数をIntervalTimerForJoystickの外部から呼び出せるようにするために設定する関数です. -
IntervalTimerForJoystick_start関数とIntervalTimerForJoystick_stop関数
IntervalTimerForJoystick_start関数とIntervalTimerForJoystick_stop関数では,ジョイスティックのためにインターバルタイマの開始と停止を行います.Timer2を開始もしくは停止するにはTMR2ON,割込みを可能もしくは不可能にするにはTMR2IEレジスタを変更します.下にそれぞれの関数の処理を記したアクティビティ図を示します. -
_callbackedFunction関数
この関数は,タイマ割込みが発生したときに呼び出されるよう,Interrupt_setTimer2Callback関数を通じて設定されるコールバック関数です.この関数内では,ジョイスティックのコールバック関数を呼び出すとともに,Timer2による割込みフラグTMR2IFをクリアします.
-
-
Joystick.c
このファイルには,割込みなしバージョンのプログラムでも使用していました.今回はそれを改造することにします.改造点は,AD変換するタイミングをTimer2の割込み時に行うことです.-
スタティックな変数の宣言
割込みなしのときには,AD変換結果を_verticalと_horizontalのスタティック変数で記憶していました.しかし,割込みありの場合には割込み時に呼び出される_meature関数(後ほど説明します)内のローカル変数として取り扱い,同関数内でAD変換結果を返すようにします.従って,_verticalと_horizontalは削除します.
新たに追加するものとして,変換後に呼び出すコールバック関数をスタティックな変数として宣言します.この変数には,AD変換結果2個を返すようにするため,unsigned char *型の引数2個を備えた関数ポインタにします.こうすることで,参照返しにより,変換結果を渡すことができます.
下に_vertical変数と_horizontal変数を削除した様子と,コールバック関数を記憶しておく_callbackFunction関数ポインタ変数の宣言した様子を示します./* 水平方向の値 */ //static uint8_t _horizontal; /*!! 削除 !!*/ /* 垂直方向の値 */ //static uint8_t _vertical; /*!! 削除 !!*/ /* コールバック関数 */ static void (*_callbackFunction)(uint8_t, uint8_t) = NULL;
-
Joystick_getHorizontalValue関数,Joystick_getVerticalValue関数の消去
_vertical変数と_horizontal変数を削除しましたので,これをJoystick.cの外部へ提供するのに必要なJoystick_getHorizontalValue関数とJoystick_getVerticalValue関数を削除してください. -
Joystick_setCallbackFunction関数
一定時間経過し,AD変換によりジョイスティックの位置を求めたときに呼び出すコールバック関数を設定します.具体的には,スタティックな変数_callbackFunctionに仮引数callbackを設定します.
-
Joystick_setFrequency関数
割込みをさせる間隔を設定します.仮引数fをIntervalTimerForJoystickの関数に渡します. -
_measure関数の改変
もともと,外部参照可能な「Joystick_measure関数」がありましたが,今回はこの関数をスタティック関数「_measure」に変えます.AD変換をするタイミングは,IntervalTimerForJoystickによる割込みのときであるよう,後ほど説明するJoystick_initialize関数で設定することから,外部から変換を指示する必要がないため,スタティック関数に変えます.
この関数で行うことは,AD変換を行うことと,その結果を伝えるためのコールバック関数を呼び出すことです.下の図はその様子を示しています.ローカル変数hとvをuint8_t型で宣言し,それらにAD変換の結果を入れます.そして,コールバック関数にhとvを引数として渡すことで,それらの値を渡します. -
Joystick_initialize関数の改変
ジョイスティックの初期化をこの関数では行います.まず,AD変換器の初期化を行います.ここまでは前のものと同じですがそれ以降は新しい処理です.IntervalTimerForJoystickを初期化し,更新頻度を初期値として最低のRARELYにします.そののち,一定時間経過したときに呼び出してもらう関数として_measureを設定し,タイマの動作を開始します.
-
-
Main.c
Main.cにはmain関数とともに,ジョイスティックが変化したときに呼び出される関数_changeJoystickを作成します.この関数をJoystickのコールバック関数とすることで,ジョイスティックが変化したときに呼び出され,ドットマトリクスディスプレイ上に結果を表示することができます.-
_changeJoystick関数
この関数にはuint8_t型のvとh2個の仮引数が備わっています.これらの仮引数にAD変換結果が入っています.ドットマトリクスディスプレイに表示するとき,十の位に垂直方向,一の位に水平方向の結果を0~7までの値で表示するため,vとhを右に5ビットシフトします.この結果,本来は0~255間での値を取るところ,0~7に変換することが出来ます. -
main関数
今回のメイン関数に記述すべきことのうち,2点の大切なことがあります.ひとつ目は,PEIEレジスタへの設定です.Timer2からの割込みを行うには,前述のとおりPEIEを使わなければなりません.
もうひとつは,無限ループです.これを忘れてしまうと,マイコンはすべての処理を停止し,スリープ状態になってしまいます.今回のメイン処理では,割込み以外に行うことがないからといって,無限ループをなくさないで下さい.
-