送信プログラム作成の続きです。
送信フェイズの検討
前回までのコードで状況に応じてIRT_NextSignal
にセットする値を変えることで、赤外線通信ができるようになっています。「状況に応じて」を整理するために、送信処理をいくつかのフェイズに分割して考えます。
フェイズ名 | 説明 | IRT_NextSignal にセットする値 |
STOP | 送信していない状態。初期値 | クリア |
START | データ送信開始。 | クリア |
LEADER_ON | リーダー部のOnの部分を送信する。 | On 8T |
LEADER_OFF | リーダー部のOffの部分を送信する。 | Off 4T |
ADDRESS | アドレス部の4bitデータを送信する | アドレスに応じて変化 |
DATA | データ部の8bitデータを送信する | データに応じて変化 |
CHECKSUM | チェックサム部の4bitデータを送信する | チェックサム値に応じて変化 |
ENDER | 終了部のOnを送信する | On 1T |
これらのフェイズはenum値で定義して、送信フェイズとして保持することにします。
/**
* 送信フェイズを示すenum型
*/
typedef enum
{
IRT_PHASE_STOP, // 送信停止
IRT_PHASE_START, // 送信開始
IRT_PHASE_LEADER_ON, // LeaderのOn 8Tを 送信
IRT_PHASE_LEADER_OFF, // LeaderのOff 4Tを 送信
IRT_PHASE_ADDRESS, // Address部を送信
IRT_PHASE_DATA, // Data部を送信
IRT_PHASE_CHECKSUM, // CheckSum部を送信
IRT_PHASE_ENDER, // 終了部を送信
} IRT_PHASE_t;
// 送信フェイズ
static IRT_PHASE_t IRT_phase;
そして、IRT_Phaseをチェックして「状況に応じてIRT_NextSignal
にセット」することができます。下のようなコードにすればよいでしょう。
switch (IRT_phase)
{
case IRT_PHASE_STOP: // 停止中
// 大前提として、IRT_NextSignalがクリアされている時に、
// 「送信状況に応じて」IRT_NextSignalをセットするので、ここでクリアする必要はない。
break;
case IRT_PHASE_START: // 送信開始
IRT_phase = IRT_PHASE_LEADER_ON;
break;
case IRT_PHASE_LEADER_ON: // Leader部のOnを送信
// Leader部の On 8T を送信開始
SetNextSignalOn(8);
// Leader部のOffを送信に遷移する。
IRT_phase = IRT_PHASE_LEADER_OFF;
break;
// 以降は省略
各フェイズのうち、ADDRESS、DATA、CHECKSUMの3フェイズはデータに応じてIRT_NextSignal
にセットする値が変化します。ADDRESSフェイズの場合、以下のようになります。
ADDRESSのサブフェイズ | 説明 | IRT_NextSignalにセットする値 |
BIT0_ON | 0bit目のOnを送信する | On 1T |
BIT0_OFF | 0bit目のOffを送信する | Off 1T または Off 3T |
BIT1_ON | 1bit目のOnを送信する | On 1T |
BIT1_OFF | 1bit目のOffを送信する | Off 1T または Off 3T |
BIT2_ON | 2bit目のOnを送信する | On 1T |
BIT2_OFF | 2bit目のOffを送信する | Off 1T または Off 3T |
BIT3_ON | 3bit目のOnを送信する | On 1T |
BIT3_OFF | 3bit目のOffを送信する | Off 1T または Off 3T |
DATAフェイズ、CHECKSUMフェイズも同様のサブフェイズがあります。これらサブフェイズも同様にenum定義するのは、効率が悪いでしょう。前述のswitch文が巨大になってしまいます。
また、3つのフェイズは全て複数のビットデータを送信するフェイズです。「最初にOnを1T、bitの値に応じてOffを1Tまたは3T」の送信を繰り返すので、処理手順を共通化したほうが良いはずです。
ビットデータ送信処理
そこで、ビットデータを送信するための関数を作成しました。
// IRT_SendBuffer()で使用する作業バッファ
static uint8_t IRT_buffer;
// IRT_SendBuffer()で使用するカウンタ
static uint8_t IRT_bitcounter;
/**
* IRT_SendBuffer()を使うための設定処理
*
* @param data:uint8_t 送信したいデータ
* @param bit:uint8_t dataの送信したいビット数。1から8の値を指定する。
*
* IRT_InitBuffer(xxxx,4) とすれば、xxxxの下位4bit分を送信する。
*/
#define IRT_InitBuffer(data, bit) \
{ \
IRT_buffer = (data); \
IRT_bitcounter = (bit)*2; \
}
/**
* Address部やData部などの送信中に IRT_SendRun() からコールされる。
*
* IRT_InitBuffer()でセットしたデータを0bit目から順に送信していく。
*
*/
static void IRT_SendBuffer(void)
{
// 1bitのデータを送信するには、 Onを1Tの間送信した後、Offを1または3Tの送信する必要がある。
// つまり N bitのデータを送信するには On/Offの切り替えがN*2回発生する。
// そこで、IRT_InitBuffer()で送信データとビット数をセットするときに、
// IRT_bitcounterにはビット数の2倍の値をセットしておく。
// そして、IRT_bitcounterをカウントダウンしていき、
// 奇数ならOnを送信し、偶数ならOffを送信する。
IRT_bitcounter--;
if (IRT_bitcounter & 0x01)
{
// 1Tの間Onにする。
SetNextSignalOn(1);
}
else
{
// IRT_bufferの0bit目が1なら3Tの間Offにし、0なら1Tの間Offにする。
if (IRT_buffer & 0x01)
{
SetNextSignalOff(3);
}
else
{
SetNextSignalOff(1);
}
// 次のbitのデータ送信のために、IRT_bufferをシフトする。
IRT_buffer >>= 1;
}
}
/**
* IRT_SendBuffer()による送信処理が忙しい(何かのデータを送信中)
* か否かを判定する。
* 「送信処理が終わっている」なら IRT_bitcounterが 0 になることを利用してチェックしている。
*
*/
#define IRT_SendBufferIsBusy() (IRT_bitcounter != 0)
ビットデータに対してIRT_NextSignalをセットするのは、IRT_SendBuffer()です。この関数の中で、変数IRT_bufferとIRT_bitcounterの値から現在の送信状態を判断し、IRT_NextSignalに値をセットしていきます。
変数IRT_bufferは送信データを一時的に保持します。この変数は送信処理が進むと右シフトされていき、やがて0になってしまいます。送信処理のための作業バッファとして使います。
変数IRT_bitcounterは現在の送信状態を保持します。この変数は送信処理が進むとカウントダウンし、やがて0になってしまいます。
ビットデータの送信では、1bit分のデータを送信するときは最初に「Onを1T」を送信し、次に「Offを1T or 3T」送信します。これらを送信したいビット数繰り返すので、IRT_NextSignalをセットする回数は「ビット数×2」になります。そこでビット数の2倍をIRT_bitcounterをセットしておきます。そしてIRT_SendBuffer()の中でカウントダウンすれば、残りの回数や今の状況を判断し、IRT_NextSignalにセットする値を決定することができます。
IRT_SendBuffer()を使う前に、2つの変数をセットしておかなくてはなりません。そのためにマクロ関数IRT_InitBuffer()を使います。例えば、「アドレス値(4bit)として1を送信する」というケースでは、IRT_InitBuffer(1,4)とします。そうすると、IRT_buffer = 1, IRT_bitcounter = 8 がセットされます。
IRT_SendBuffer()では最初にIRT_bitcounterをカウントダウンします。その結果が奇数のときは、「Onを1T」のタイミングだと判断しています。偶数のときは「Offを1T or 3T」のタイミングだと判断しています。1Tと3Tのどちらなのかは、送信するビットデータによって変わるので、IRT_bufferの0bit目(IRT_buffer & 0x01)で判定しています。そして、次回の判定のために、IRT_bufferを右シフトしています。
IRT_InitBuffer()でセットした情報がすべて送信し終わっていれば、IRT_bitcounterが0になります。マクロ関数IRT_SendBufferIsBusy()はその判定行うためのに用意しています。
赤外線送信処理のメイン部分
赤外線送信処理のメイン部分IRT_SendRun()のコードを以下に示します。この関数は、状況に応じて、IRT_NextSignalをセットすることが目的です。
// 送信アドレス
static IRT_DATA_t IRT_address;
// 送信データ
static IRT_DATA_t IRT_data;
/**
* 赤外線通信の送信処理を行う。
*/
void IRT_SendRun(void)
{
// 実際に赤外線送信の制御(信号のOn/Offや送信ターン数の管理)する処理は、
// IRT_HandlerForTMR0() で行っている。
// その IRT_HandlerForTMR0()では、 IRT_NextSignal に従って赤外線送信を行なう。
//
// この関数では 処理状況(どのようなデータを送信しているのか)にあわせて、
// IRT_NextSignal をセットする処理を行っている。
// NextSignalがクリアされていないときは、スキップする。
if (!IsClearNextSignal())
{
return;
}
// IRT_SendBuffer()を使って送信する処理中なら、その続きを行う。
if (IRT_SendBufferIsBusy())
{
IRT_SendBuffer();
// IRT_SendBuffer()の中で NextSignal がセットされるので、以降の処理はスキップ。
return;
}
switch (IRT_phase)
{
case IRT_PHASE_STOP: // 停止中
break;
case IRT_PHASE_START: // 送信開始
// IRT_counterがカウントダウンして0になることで、送信のための
// さまざまな処理が実施される。( IRT_HandlerForTMR0() の処理を参照。)
// しかしこの時点ではIRT_counterの値が不定であり、
// 大きな値になっていると無駄な待ち時間が発生する。
// 処理がすぐに始まるように(カウントダウンですぐに0になるように)、
// ここで1をセットしておく。
// (なお、0をセットするとカウントダウンで255になってしまうので、適切ではない。)
IRT_counter = 1;
// Leader部のOnを送信に遷移する。
IRT_phase = IRT_PHASE_LEADER_ON;
break;
case IRT_PHASE_LEADER_ON: // Leader部のOnを送信
// Leader部の On 8T を送信開始
SetNextSignalOn(8);
// Leader部のOffを送信に遷移する。
IRT_phase = IRT_PHASE_LEADER_OFF;
break;
case IRT_PHASE_LEADER_OFF: // Leader部のOffを送信
// Leader部の Off 4T を送信開始
SetNextSignalOff(4);
// Address部を送信に遷移する。
IRT_phase = IRT_PHASE_ADDRESS;
break;
case IRT_PHASE_ADDRESS: // Address部を送信
// Address部を送信する。
IRT_InitBuffer(IRT_address.A8, 4);
// Data部を送信に遷移する。
IRT_phase = IRT_PHASE_DATA;
break;
case IRT_PHASE_DATA: // Data部を送信
// Data部を送信する。
IRT_InitBuffer(IRT_data.A8, 8);
// CheckSum部を送信に遷移する。
IRT_phase = IRT_PHASE_CHECKSUM;
break;
case IRT_PHASE_CHECKSUM: // CheckSum部を送信
// CheckSum部を送信する
// CheckSum部は Address(4bit) + dataの下位4bit + dataの上位4bitの合計の下位4bit
// 厳密に書けば、IRT_address.L4 + IRT_data.L4 + IRT_data.H4 になるが、
// 最終的に3つの値の合計値の上位4bitは無視されるので、下のコードでも正しく動作する。
// そして下のコードの方が、アセンブルリストが短くなる。
IRT_InitBuffer((IRT_address.A8 + IRT_data.A8 + IRT_data.H4), 4);
// 終了部の送信に遷移する。
IRT_phase = IRT_PHASE_ENDER;
break;
case IRT_PHASE_ENDER: // 終了部を送信
// On 1Tを送信開始
SetNextSignalOn(1);
// 全ての送信が終わったので、停止状態にする。
IRT_phase = IRT_PHASE_STOP;
break;
default:
break;
}
}
/**
* アドレスをセットする
*
* @param address:uint8_t 送信アドレス。ただし、下位4bitまで有効
*/
void IRT_SetAddress(uint8_t address)
{
IRT_address.A8 = address;
}
/**
* データを送信する
*
* @param data:uint8_t 送信データ
*/
void IRT_SendData(uint8_t data)
{
IRT_data.A8 = data;
IRT_phase = IRT_PHASE_START;
}
IRT_SendRun()はmain()関数の処理ループから何度もコールされることが前提となっています。そのため、アドレスやデータなどの送信情報を関数の引数として渡すのではなく、あらかじめ、IRT_addressとIRT_dataにセットしておきます。
まず、IRT_NextSignalがクリアでなければ次の値をセットする必要がないので、即座に関数を抜けます。
そして、IRT_SendBuffer()を使ってIRT_NextSignalをセットする処理の途中なら、そちらでIRT_SendBuffer()をコールし、関数を抜けます。
どちらでもない場合は、フェイズに応じてIRT_NextSignalをセットするか、あるいは、IRT_SendBuffer()を使うための設定を行います。
IRT_SetAddress()はアドレスをセットする関数です。IRT_addressをセットしているだけです。
同様にIRT_SendData()は送信データをセットする関数ですが、IRT_dataをセットすると同時に、IRT_PhaseをSTARTにしています。こうすることで、送信フェイズが変化し始め、IRT_SendRun()コール時にIRT_NextSignalがセットされるようになります。
IRT_SendRun()をコールするmain()処理側は下のようなコードになるイメージです。
void main(void)
{
// 各種初期設定(コードは省略するが、以下のようなことを行う。)
// PICの初期設定
// TMR0の初期設定(割込みに対してIRT_HandlerForTMR0()がコールされるように設定)
// 赤外線通信処理部を初期化する。(IRT_Phaseなどの変数を初期値にする)
IRT_Initialize();
// アドレスをセットする。
IRT_SetAddress(ADDRESS);
while (1)
{
// 赤外線通信の処理を行なう。
IRT_SendRun();
if (!IRT_IsBusy())
{
// リモコンのレバーやスイッチから送信データを決定する。
uint8_t sendData = GetSendData();
IRT_SendData(sendData);
}
}
コードは省略しますが、IRT_IsBusy()は送信処理が行われいてるか否かを判定する関数です。前のデータが送信されている途中で次のデータの送信処理が始まると、全体の動きがおかしくなってしまいます。送信がすべて終わってから次のデータ送信を開始する必要があります。
ただし、上のコードですと、休むことなく赤外線通信を行ってしまいます。TMR0か別のタイマを使って、例えば「250msecに1回の頻度で送信処理を行う。」という処理を追加して、ある程度間隔を空けて送信したほうが良いでしょう。
NECフォーマットへの応用
今回のコードは独自フォーマット用に作っていますが、NECフォーマットを送信するように変更することは難しくないでしょう。変更点は下記の通りです。
- 1Tの長さが違うので、TMR0割込みの間隔を変える。
- リーダー部のOnとOffのターン数を変える。
- アドレス(4bit)ではなく、カスタマーコード(16bit)を送信する。フェイズを1つ追加し、カスタマーコード上位ビットフェイズと下位ビットフェイズにすれば、8bitを2つ送信するだけなので、IRT_SendBuffer()をそのまま使うことができる。
- チェックサム(4bit)の代わりにデータの反転ビット(8bit)を送信する。
コメント