PICによる赤外線通信7

受信プログラムの続きを行っていきます。

受信フェイズの検討

送信処理プログラムを作成したとき、送信フェイズの検討を行いました。受信プログラム側も同様にフェイズを分類することにします。

フェイズ名説明受信を期待する信号と回数
STOPデータ受信をストップ中無し
WAIT_START通信の開始(リーダ部の受信)を待っているリーダ部の信号を1回
ADDRESSADRESS部を受信中ビットデータ信号を4回
DATADATA部を受信中ビットデータ信号を8回
CHECKSUMCHECKSUM部を受信中ビットデータ信号を4回
ERROR受信エラー(不信信号を受信した)なし
受信フェーズ一覧

プログラムの最初のフェイズはSTOPですが、TMR2などの初期設定が終わり、信号を受信する準備が整い次第WAIT_STARTに移行します。

WAIT_STARTではリーダ部の信号(パルスONが8T、OFFが4Tとなる信号)を受信するまで待ち続けます。そしてリーダ部の信号を受信したらADDRESSに移行します。

ADDRESSでは送信データのADDRESS部の信号を受信中であることを表します。ここではビットデータ信号(「パルスONが1T、OFFが1T」と「パルスONが1T、OFFが3T」の2種類あります。)を4回受信したらDATAに移行します。ただし、途中でパルスON/OFFの時間が2種類以外の状態になれば、「不正信号を受信した」と判断し、ERRORに移行します。

DATAやCHECKSUMでも同様に、送信データのDATA部やCHECKSUM部の信号を受信中であることを表します。ここでもビットデータ信号を適切な回数受信したら次の処理に移行し、途中で異なる信号を受信したらERRORに移行します。

またADDRESSとDATAでは、受信した信号に応じて、アドレス値やデータ値を保持します。CHECKSUMでは、アドレス値や受信データのチェックを行います。

ERRORでは、それまで受信していたデータを全て破棄し、WAIT_STARTに戻ります。

これらのフェイズはenum値で定義して、受信フェイズとして保持することにします。

/**
 * 受信フェイズを示すenum型
 */
typedef enum
{
    IRR_PHASE_STOP,       // データ受信をストップ中
    IRR_PHASE_WAIT_START, // 通信の開始(Leader部の受信)を待っている
    IRR_PHASE_ADDRESS,    // ADDRESS部を受信中
    IRR_PHASE_DATA,       // DATA部を受信中
    IRR_PHASE_CHECKSUM,   // CHECKSUM部を受信中
    IRR_PHASE_ERROR,      // 受信エラー(不信信号を受信した)
} IRR_PHASE_t;

// 受信フェイズ
static IRR_PHASE_t IRR_phase;

ビットデータ受信処理

ビットデータの判定

ビットデータの信号は2種類あります。1つはビットデータ0に対する信号で「パルスONが1T、OFFが1T」です。これはusec単位で表すと「Onが400usec、Offが400usec」です。

以前に「PICによる赤外線通信5 送信と受信のタイミングの違い」のところで説明したように、

「Lowの時間が(N-300)usec以上、かつLow+Highの時間が(M±200)usec」の信号を受け取った場合、「パルスONがN usec、パルスON+OFFがM usec」の信号を受信したと判定する。

として判定を行います。このNに400、Mに800を入れて整理すると、

Lowの時間が100usec以上、かつLow+Highの時間が600~1000)usec」の信号を受け取った場合、ビットデータ0の信号を受信したと判定する。

となります。

ビットデータ1に対する信号は「パルスONが1T、OFFが3T」なので、同様に条件を整理すると、

Lowの時間が100usec以上、かつLow+Highの時間が1400~1800)usec」の信号を受け取った場合、ビットデータ1の信号を受信したと判定する。

となります。

ON/Offの時間が上の2つの条件に当てはまらない場合は、不正信号を受信したことになります。

ビットデータ受信処理の共用関数

ビットデータを受信するのは、ADDRESS、DATA、CHECKSUMの3つのフェイズです。

ADDRESSでは4回のビットデータを受信し、それを4bit値に変換します。同様に、DATAでは8回のビットデータを受信して8bit値に変換しますし、CHECKSUMでも4bit値に変換します。これらは処理を共用化できるので関数化します。

下にコードを示します。

// 受信したデータをIRR_bufferに受け取る際のカウンタ
static uint8_t IRR_bitcounter;

// 受信したデータを最大8bit分受け取る為のバッファ
static IRR_DATA_t IRR_buffer;

IRR_bitcounterは受信するビットデータの数をカウントするための変数です。

ADDRESS、CHECKSUMでは4回、DATAでは8回のビットデータを受信するので受信回数が異なります。そこで、IRR_bitcounterに受信する予定の回数をセットし、ビットデータを受け取るごとにカウントダウンします。0になれば、そのフェイズで必要な回数を受信したことになり、次のフェイズに進みます。

IRR_bufferは、受信したビットデータを4bit値や8bit値に変換するための途中の値を保持するための変数です。

なお、IRR_DATA_t型は、送信処理のときのIRT_NextSignalの説明で出てきたIRT_DATA_t型とほぼ同じようなunion型なので詳細な説明は割愛します。

/**
 * Adress部などビットデータに対する受信を行い、IRR_bufferに受け取る。
 *
 * 1ビット分のデータを受信する毎に、以下を繰り返す。
 *   IRR_bufferを右シフトする。
 *   IRR_bufferの最上位ビットに受信データ(0/1)を格納する。
 *   IRR_bitcounterをカウントダウンする。
 * そして、IRR_bitcounterが0になったらtrueを返す。
 */
static __bit IRR_ReceiveBuffer(void)
{
    // OnとOffの長さをチェックして、受信データを決定する。
    // - Onが100usec以上 かつ On+Offが600~1000usecなら 0を受信
    // - Onが100usec以上 かつ On+Offが1400~1800usecなら 1を受信
    // - 上記以外なら、不正データを受信

    if (IRR_OnTime < IRR_PULSE_WIDTH(100))
    {
        // On時間を満たさなかった場合は、不正データの受信としてERRORにする。
        IRR_phase = IRR_PHASE_ERROR;
        return false;
    }

    if (IRR_PULSE_WIDTH(600) <= IRR_BothTime && IRR_BothTime <= IRR_PULSE_WIDTH(1000))
    {
        // 0 を受信したので、IRR_bufferを右シフトして、最上位bitを0にする。
        IRR_buffer.A8 >>= 1;
        // 右シフトで自動的に0になるので、IRR_buffer.B7 = 0; は不要。

        // カウンターを減らし、0になったらtrueを返す。
        return (--IRR_bitcounter == 0);
    }
    else if (IRR_PULSE_WIDTH(1400) <= IRR_BothTime && IRR_BothTime <= IRR_PULSE_WIDTH(1800))
    {
        // 1 を受信したので、IRR_bufferを右シフトして、最上位bitを1にする。
        IRR_buffer.A8 >>= 1;
        IRR_buffer.B7 = 1;

        // カウンターを減らし、0になったらtrueを返す。
        return (--IRR_bitcounter == 0);
    }
    else
    {
        // Off時間を満たさなかった場合は、不正データの受信としてERRORにする。
        IRR_phase = IRR_PHASE_ERROR;
        return false;
    }
}

受信した信号の判定と、上記2つの変数の管理を関数IRR_ReceiveBuffer()の中で行います。

ONおよびON+OFFの時間を判定し、ビットデータとして一致しない場合はフェイズをERRORにしています。

一致する場合は、対応するデータをIRR_bufferにセットし、IRR_bitcounterをカウントダウンします。

IRR_bufferは「右シフト→最上位ビットに0 or 1をセット」を繰り返していきます。フェイズがADDRESSのときはこれを4回繰り返すので、IRR_bufferの上位4bitにアドレス値が入ります。フェーズがDATAのときは8回繰り返すので、IRR_buffer全体で8bitのデータ値が入ります。

前述したように、IRR_bitcounterが0になったら「次のフェイズ」に移動する必要がありますが、それはこの関数の中では行いません。この関数をコールした側(後述のIRR_ReceiveRun()の中)で行います。ただ、0になったことを判定しやすくするためにtrueを返します。IRR_bitcounterが0でないときはfalseを返します。

赤外線受信処理のメイン部分

赤外線受信処理のメイン部分IRR_ReceiveRun()のコードを以下に示します。

/**
 * 赤外線受信処理のメイン処理
 *
 * OnとOffを受信し、それぞれの時間を計測できた後でコールされる。
 */
void IRR_ReceiveRun(void)
{
    switch (IRR_phase)
    {
    case IRR_PHASE_WAIT_START:
        // Leader部の受信を待っていたので、OnとOffの長さをチェックする。
        // On時間が2900usec以上、On+Off時間が4800usec±200usecなら
        // OK(Leader部を受信できた)と判定する。
        if (IRR_OnTime >= IRR_PULSE_WIDTH(2900)
            && IRR_PULSE_WIDTH(4600) <= IRR_BothTime && IRR_BothTime <= IRR_PULSE_WIDTH(5000))
        {
            // 次のフェイズ(Address部の受信)に進む。
            IRR_phase = IRR_PHASE_ADDRESS;
            // Address部はIRR_ReceiveBuffer()を使って処理を行うので、bitcounterをセットする。
            // Address部は4bitなので、4をセット
            IRR_bitcounter = 4;
        }
        // On/Off時間が満たさなかった場合は、Leader部を受け取っていないので、
        // Leader部が来るのを待ち続ける。
        // 状態は変化しない。
        break;
    case IRR_PHASE_ADDRESS:
        if (IRR_ReceiveBuffer())
        {
            // 4bit分のデータをIRR_bufferに格納できたので、IRR_addressに格納する。
            // ただし、データは IRR_bufferの上位4bitに格納されているので、シフトする必要がある。
            IRR_address.A8 = IRR_buffer.H4;
            // 次のフェイズ(Data部の受信)に進む。
            IRR_phase = IRR_PHASE_DATA;
            // Data部は8bitなので、8をセット
            IRR_bitcounter = 8;
        }
        break;
    case IRR_PHASE_DATA:
        if (IRR_ReceiveBuffer())
        {
            // 8bit分のデータを格納できたので、dataにセットする。
            IRR_data = IRR_buffer;
            // 次のフェイズ(CheckSum部の受信)に進む。
            IRR_phase = IRR_PHASE_CHECKSUM;
            // CheckSum部は4bitなので、4をセット
            IRR_bitcounter = 4;
        }
        break;
    case IRR_PHASE_CHECKSUM:
        if (IRR_ReceiveBuffer())
        {
            // 4bit分のデータを格納できたので、チェックサムを照合する。
            // addressとdataからチェックサムを算出
            // 本来なら、IRR_data.H4 + IRR_data.L4 + IRR_address.L4とするべきだが、
            // checksumの上位4bitは使用しないので、下の式で問題ない。
            // IRR_data.L4を使うと上位4bitを マスクする処理が入ってしまい冗長になる。
            // IRR_address.L4も同様。
            uint8_t checksum = (IRR_data.H4 + IRR_data.A8 + IRR_address.A8) & 0x0F;

            // checksumが、IRR_bufferの上位4bitと一致すれば、照合OK
            if (IRR_buffer.H4 == checksum)
            {
                // 正常にデータ受信したので、STOPにする。
                IRR_phase = IRR_PHASE_STOP;

                // データ受信が完了したイベントを発生
                EventRise(Event_IRR_DataIsReceived)
            }
            else
            {
                // 異常データを受信したのでERRORにする。
                IRR_phase = IRR_PHASE_ERROR;
            }
        }
        break;
    default:
        // その他の条件(STOP/ERROR)では何もしない。
        break;
    }

    // 結果がどうあれ、計測した時間をクリアする。
    IRR_OnTime = 0;
    IRR_BothTime = 0;

    if (IRR_phase == IRR_PHASE_ERROR)
    {
        // ERRORになった場合は、受信処理を再開する。
        IRR_ReceiveStart();
    }
}

フェイズに応じて受信する信号や処理内容が変化するため、最初にswitch(IRR_phase)で処理を分岐しています。

フェイズがWAIT_STARTのときは、リーダ部の信号であるか判定し、一致すればフェイズをADDRESSに移行します。このとき、IRR_bitcounterもセットします。(ADDRESSでは4回のビットデータを受信するので4をセット。)

一致しない場合は、何もしません。

フェイズがADDRESSときは、前述したIRR_ReceiveBuffer()を使って処理します。

そしてIRR_ReceiveBuffer()がtrueを返したなら、IRR_bufferの上位4bitをアドレス値としてIRR_addressに保存します。そして次のフェイズ(DATA)に移行します。また次のフェイズに適切な値をIRR_bitcounterをセットします。

同様に、フェイズがDATAときもIRR_ReceiveBuffer()を使って処理、trueを返した場合は、IRR_bufferの値をIRR_dataに保存して、次のフェイズに移行します。

フェイズがCHECKSUMの時も同様ですが、CHECKSUMでは受信データの照合チェックを行います。そのチェックに失敗したら、(通信処理全体として)不正な信号受信と見なしフェイズをERRORにします。

チェックに成功したらフェイズをSTOPにし、データ受信が完了したイベントを発生します。

swicth文の後、次の判定のためにIRR_OnTimeとIRR_BothTimeを0クリアします。またフェイズがERRORになっている場合は、ここで受信処理を再開します。

IRR_addressやIRR_dataは下記のように宣言されています。

// 赤外線受信のアドレス
static IRR_DATA_t IRR_address;

// 受信したData部のデータ
static IRR_DATA_t IRR_data;

赤外線受信開始処理

受信処理を開始する時や、エラーから復帰する時には、以下のIRR_ReceiveStart()をコールします。

/**
 * 赤外線受信を開始する
 *
 * STOPからの再受信やERRORから復帰するときに、この関数を実行する。
 *
 * すでに信号受信処理を進めている時にコールすると、それまでの受信データが破棄される。
 */
void IRR_ReceiveStart(void)
{
    // ビットデータ受信処理で使用するメモリ
    IRR_buffer.A8 = 0;
    IRR_bitcounter = 0;

    // 受信データ
    IRR_address.A8 = 0;
    IRR_data.A8 = 0;

    // フェイズをSTARTにする
    IRR_phase = IRR_PHASE_WAIT_START;
}

使用する変数や、受信結果を格納するための変数を0クリアし、フェイズをWAIT_STARTにしているだけです。

コメント