まずは、受信処理プログラムの基本部分を作っていきましょう
受信処理に必要な最重要機能
前回の送信と受信のタイミングの違いでも説明したように、「パルスON」の時間と「パルスON+OFF」の時間を計測することで、送信側から送られてきた信号を判定することになります。
そのために、最も重要な機能として、
- パルスONとOFFの切り替えタイミングを知ること
- 上記の切り替えのタイミングで時間を計測すること
の2つの機能が必要です。
時間を計測する機能
順番が前後しますが、まずは時間を計測する機能です。
時間を計測するには、タイマ機能を使うのが最も簡単です。今回はTMR2を使う事にしました。
初期設定でTMR2は100usec毎に割込みが入るように設定します。(MCCを使えば設定は簡単です。)
そして計測用のカウンタ変数を用意し、TMR2割込みのタイミングでカウントしていきます。
実際のコードは下のようになりました。
/**
* 100us毎に1つカウントされるカウンタ
*
* 受信データのパルス幅を計測するために使用する。
*/
static uint8_t IRR_Counter100u = 0xFF;
void IRR_HandlerForTMR2(void)
{
// 100usカウンタが0でなければ、デクリメントする。
if (IRR_Counter100u != 0)
{
IRR_Counter100u--;
}
}
変数IRR_Counter100uがカウンタです。
IRR_HandlerForTMR2()は、TMR2の割込みが発生するたびに実行される関数です。つまり変数IRR_Counter100uは100usec毎にカウントダウンされます。
ただし0までカウントダウンしたときは、ずっと0のままにします。
このカウンタは赤外線通信のパルスONとOFFの時間を計測するために使います。そのために最大でも5000usec=50回までカウントできればよく、それ以上は51カウントするのも255カウントするのも大差ありません。そのため8bitでカウントできる最大数である255までカウントできればそれで十分なのです。
カウントアップでなく、カウントダウンする理由は、XC8が生成するアセンブルリストが原因です。変数をカウントアップするよりカウントダウンするほうが命令数を少なくすることができ、処理速度が上がるのです。このあたりについての詳細は次のページで説明しています。
このように時間計測としては特殊なカウントをするので、コメントで説明を残しておくことが大事です。
初期化するつもりで誤って0にすると誤動作します。また、変数の値をそのまま計測時間に使ったりしないように、マクロ関数を用意するのが良いでしょう。
// IRR_Counter100uは0xFFに初期化され、100usec毎にカウントダウンする。
// そして0になったら、それ以上変化しなくなる。(カウントダウンすると0xFFに戻ってしまうため)
//
// このため、(255 - IRR_Counter100u)は、IRR_Counter100uを0xFFに初期化してから
// カウントダウンした回数である。
// これを利用して、100usec単位で0~25500usecまでの時間を計測することができる。
//
// なお、0に初期化して、カウントアップし、そのままの値を使っても同様のことができるが、
// XC8はカウントアップ処理が苦手な(処理時間が長くなる)ので、この方式にしている。
/**
* IRR_Counter100uを初期化する
*/
#define IRR_InitCounter100u() \
{ \
IRR_Counter100u = 0xFF; \
}
/**
* IRR_Counter100uを初期化してからの経過時間(100usec単位)を計算する
*/
#define IRR_GetTimePer100u() (255 - IRR_Counter100u)
切り替えタイミングを知る機能
「状態変化割り込み(IOC)」機能のある入力端子を使う事で、切り替えタイミングを知ることができます。
赤外線受信モジュールの出力する信号値をIOC機能のある入力端子で読み取るようにすれば、信号値の変化したタイミングを感知することができます。
信号値がHigh→Lowに変化した時、パルスがOFF→ONに変化したタイミングになります。逆にLow→Highに変化した時、パルスがON→OFFに変化したタイミングになります。
今回使用するPIC16F1501ではIOC機能はPORTAの端子ならどれでも使う事ができますが、今回はRA5端子を使う事にしました。
RA5端子を入力端子に設定し、信号がLow→High/High→Lowの両方の変化でIOC割込みが発生するように設定します。この設定はMCCを使えば簡単に設定できます。
さらにMCCの機能でRA5端子に"IRIN"という名前を付けました。(赤外線(IR)モジュールの出力信号を入力(IN)する端子という意味です。)これで、"IRIN_GetValue()"とすればRA5端子の入力信号値を得ることができます。
以上で設定が終わったのでCのソースを記述していきまましょう。まず計測時間を保存する変数を用意します。
/**
* 赤外線通信で、On信号になっている時間
*/
static uint8_t IRR_OnTime;
/**
* 赤外線通信で、On信号1回とOff信号1回の時間
*/
static uint8_t IRR_BothTime;
パルスがONの時間と、ON+OFFの時間を計測する必要があるため、それぞれの計測値を保存するために、IRR_OnTimeとIRR_BothTimeという8bit変数を用意しています。IOC割込み時に時間を計測し、この2つの変数に保存します。
上図には赤外線パルスのON/OFFとそれに対する赤外線受信モジュールのOUT端子の信号値を示しています。最初赤外線パルスがないときはOUT端子はHighです。それがHigh→Lowに変化したときは、パルスがOFF→ONに変化したタイミングになります。逆にLow→Highに変化すればON→OFFに変化します。
時間計測するには、以下のように処理すればよいです。
- OUT端子がHigh→Lowに変わったタイミングで、カウンタとTMRをリセットする。
- OUT端子がHLow→Highに変わったタイミングでカウンタの値を参照し、IRR_OnTimeにセットする。
- さらにOUT端子がHigh→Lowに変わったタイミングでカウンタの値を参照し、IRR_BothTimeにセットする。
なお、処理1と処理3は条件が同じであり、同タイミングで実施します。ただし時間を計測する前にリセットすると正しく動作しません。そういったことを考慮すると、次のようなCソースになります。
/**
* IRINピン変化に対する割込みハンドラ
*/
void IRR_HandlerForIRINChanged(void)
{
// IRIN信号が変化したときは、カウンタを使って時間を計測する
// IRINがHIGHのとき、割り込み前LOW→割込み後HIGHということになる。
// IRINがLOWのときは、その逆。
if (IRIN_GetValue() == HIGH)
{
// LOW -> HIGHに変化したということは、赤外線パルスがOn -> Offに変わったということ。
// ということは、この瞬間の時間を計測すれば パルスがOnの間のパルス幅を取得できる。
IRR_OnTime = IRR_GetTimePer100u();
}
else
{
// HIGH -> LOWに変化したということは、赤外線パルスがOff -> Onに変わったということ。
// ということは、この瞬間の時間を計測すれば パルスがOn1回とOff1回のパルス幅を取得できる。
IRR_BothTime = IRR_GetTimePer100u();
// 同時に新たな On 状態が始まったことになるので、
// 次の時間計測のために、カウンタとTMR2を初期化する。
TMR2 = 0;
IRR_InitCounter100u();
// イベント「ON信号とOFF信号を1回づつ受信した」を発生する。
EventRise(Event_IRR_recivedOnAndOff);
}
}
IRR_HandlerForIRINChanged()はIRINのIOC割込みが発生するたびに実行される関数です。
IOC割込みはLow→Highの変化とHigh→Lowの変化のどちらでも発生します。どちらのタイプなのかIRIN_GetValue()の値で判断しています。
Low→Highに変化したタイミングではIRR_OnTimeに計測時間をセットしています。
逆にHigh→Lowの変化したタイミングではIRR_BothTimeに計測時間をセットしています。
また、次の計測のために、TMR2とカウンタをリセットしています。
これで、IRR_OnTimeとIRR_BothTimeに計測時間がセットされました。この2つの変数をチェックすれば、受信した情報の種類(リーダ部なのか、あるいは、データビットなのか)を判定することができます。
早速判定処理を行いたいところですが、この関数の中から判定処理関数をコールすることはしません。その代わり「2つの変数がセットできた」ということをイベントとし、そのイベント発生フラグを立てています。それが、EventRise(Event_IRR_recivedOnAndOff)です。
イベントの詳細は次回以降で説明する予定です。
コメント