S/Wによる、IrDA encoding/decoding

PIC16F88でSWのみでIrDAのエンコード/デコードを実装してみました。

IrDA規格

まず、規格について確認しておきます。調べてみると、IrPHYと呼ばれるハードウェア規格と、SIR/MIR/FIRというプロトコルの2種類に大別されるのですが、ボーレートの指定が両方にあるので紛らわしいです。
今回、ターゲットとするのは、IrDA DATA1.2 SIRとなります。通信距離0.3m, 115kbps以下の規格です。速度は2400bpsとしました。
他の規格の話はここでは割愛します。

SIRの場合、RS232Cのデータフレームをベースにしており、図のようなシグナリングとなっています。
基本的には、0の場合にパルスを送信します。このパルスは115.2kbps以下場合、3/16 bit clock, Minimum 1.63usとなっているので、特定のパルス幅で処理できます。
実際に送信される赤外線を観察すると、立上り/立下がりに若干時間が掛るので、この例では2.0usとしてあります。

bit clockは1/2400=416.7us, Start bit=1, Stop bit=0なので、フレームは10 bitになります。

ハードウェア構成

通常、IrDAエンコーダーと、トランシーバーで構成します。
ここでは、エンコーダーはソフトウェア実装、トランシーバーは赤外線LEDと赤外線フォトトランジスタとしてみました。

ソフトウェア構成

概要

クロックは、内蔵の8MHzオシレータを使用しました。

クロックやハードウェアバッファの関係で、並列処理は面倒なので、フルデュプレックスでの実装は行っていません。
クロックが十分速ければ、bit clockの間に、1bitの送受信を両方処理する実装にすれば可能と思われますが、20MHzを超える様なクロックのPICを使用するくらいなら、UARTがIrDAをサポートしている16 bit/32 bit のPICを用いてしまったほうが良いでしょう。

1.63usのパルスはwait loopで実現しており、IRDA_PULSE_CYCLESで定義しています。計算上だと、4 cycleですが、計測の結果loop処理のオーバーヘッドで1 loopで2.0usとなりました。

bit clockはTimer2によるインターバルタイマーで実現しています。タイマーがexpireした際に自動的にリセットされるので便利です。時間はIRDA_BIT_COUNTで定義しており、この値がPR2レジスタにセットされます。
タイマー割込は使用しておらず、TMR2IFがセットされるまでwaitする実装としています。

送信

ソースコード例
inline void putIRDABit(char b) {
    if (!(b&1)) {			// pulse for 0, no pulse for 1
        IR_TX=0;
        _delay(IRDA_PULSE_CYCLES);	// 1.63 uSec or 1/4 bit clock under IrDA 1.0, actually 2 uSec
        IR_TX=1;
    }
    for(TMR2IF=0; !TMR2IF; );		// wait for TMR2 expire
}

void putch(unsigned char b) {
    int i;
 
    TMR2 = 3;			// reset TMR2
    TX_LED=0;
    putIRDABit(0);		// start bit
    for(i=0;i<8;i++){
        putIRDABit((b>>i));
    }
    putIRDABit(1);		// stop bit
    TX_LED=1;
}

送信波形

MPLAB XC8では、putchで1文字出力を実装すれば、文字列出力には標準関数が利用できます。putchはstart/stop bitの出力、出力文字をbitシフトさせながらの出力を行います。bit出力はputIrDABit関数で行っており、データが1なら2usのパルスを送信し、416us待つ。という動作を行います。

loop処理のオーバーヘッドを避け、一定delayでbitが処理されるよう、loop処理をせずに関数呼び出しを並べたりする亊で変化があるか試しましたが、1us程度変化するだけで、通信に影響はありませんでした。ボーレートが速く、文字化けの原因となる場合は気にする必要があるかもしれません。
ただし、関数をならべても一定にならず、loopの最中に時間がのびたりするので、コンパイラが余計な処理を入れているものと考えられます。タイミングがシビアな場合は、ここはアセンブラで記述した方が良さそうです。

(左図が送信波形です。入力データ側はbit区切りが判り易いよう加工してあります。)

受信

ソースコード例
inline int getIRDABit(void){
    RBIF=0;
    for(TMR2IF=0; !TMR2IF; );
    return(!RBIF);
}

char getche(void){
    char b=0;
    int i;
    
    i=PORTB;                 // clear mismatch condition    
    for(RBIF=0; !RBIF; );    // detect start bit  

    RX_LED=0;
    TMR2=30;                 // reset TMR2 with margin for standby
    for(TMR2IF=0; !TMR2IF;); // wait end of start bit
    for(i=0; i<8; i++)
        b+=(getIRDABit()<<i);
    RX_LED=1;
    return(b);
}

受信波形

送信と同様に、標準関数を利用できるよう、1文字入力関数としてgetcheを作成します。
start bitを検出したのち、それぞれのdata bitについて417us待ち、その間にパルスの受信があった場合は0, なかった場合は1をセットします。
パルスの受信には、当初RBIF(ポート変化検知フラグ)を用いていましたが、後述の理由からCMIF(コンパレータ割込み検知フラグ)を用いるよう変更しました。なお、フラグ変化のみ利用しており、割込みは使っていません。

赤外線フォトトランジスタの使いかた

初め、図のような回路を用いていたのですが、専用のトランシーバーを使う場合に比べ、受信感度が悪い問題が発生しました。具体的には通信可能距離が1,2cm程度になってしまいます。フォトトランジスタの出力を調べたところ、10cm程度の距離では200mV程度の出力しかなく、これではトランジスタが駆動されない為でした。バイアスを掛けてパルス入力するか、FETで受けるなどの対策が必要でしょう。

受信波形(バイアス回路使用時)

というわけで、実際に試してみました。入力インピータンスはそれほど高くなくても動作したので、トランジスタで用が足ります。2SC1815に右の図のようにバイアスを掛けて使用しました。ノイズで動作しないギリギリのところにVRを調整します。0.6Vより僅かに小さい値になるはずです。
10cm程度まで動作するようにはなりますが、まだ改善の余地があるようです。フォトトランジスタが結構ノイズを拾うので、これを低減する事が出来ればもう少し距離が伸ばせると思いますが、伝達距離が必要なければ、十分ともいえます。
市販のドライバICでは、フィルタがついていて、蛍光灯の光が差し込むと誤動作する程の感度になっているので、ノイズ対策が必要そうです。もっとも、ユニバーサル基板や長いワイヤで接続しているせいもあるでしょうが。

今回は、極力回路を間単にしたいため、コンパレーター入力とし、内部リッファレンス電圧を、Vcc/24=3.0/24=0.125Vとする亊で10cm程度まで動作するようになりました。16F88はFVRが無いためVccを3Vとする必要があります。この場合、PORT状態変化割り込みの代わりに、コンパレーター割り込み検知bitを利用します。

プログラムソース

プログラムは こちら