27 July 2014

赤外線リモコンの送信実験 (Raspberry Piで読み取ったデータを利用)

Raspberry Piで赤外線リモコンを学習し発信する』で読み取った、赤外線リモコンの信号データを使って、簡単な電子回路で送信実験をしてみる。

Raspberry Piで読み取ったリモコンの信号例

YAMAHAのAVアンプのリモコン信号

begin remote
  
  name  yamaha_rxs600
  flags RAW_CODES|CONST_LENGTH
  eps            30
  aeps          100
  
  gap          107686
  
      begin raw_codes
  
          name volup
             9062    4378     702     427     676    1594
              650     495     649    1556     672    1595
              760    1464     672    1599     650     450
              700    1569     651     451     699    1547
              724     400     723     401     725     398
              779     349     700    1570     778     326
              673    1574     727     397     726    1524
              677    1602     677     424     675     450
              700     425     722    1524     724     401
              752    1501     775     364     729     372
              700    1571     649    1596     679    1569
              655
 
〜 以下略 〜
   
      end raw_codes
  
end remote

赤外線リモコンのエンコード方式は、次のようなものがあり

・NEC方式
・家電協方式
・SONY方式などメーカー独自方式

今回のものはNECフォーマットなので、次のようなデータエンコードになっているはず…

20140727-nec-encode.png

具体的にはt=560マイクロ秒として、

HiLo
リーダ(スタートビット)16t = 9000us8t = 4500us
1t = 560us3t = 1690us
0t = 560ust = 560us
トレーラ(ストップビット)t = 560us

なお、1フレーム(1つのボタンの送信)は108 ミリ秒の間隔のため、60〜70ミリ秒でデータ送信が終わったとしても、その後に無通信状態を保持しなければならない。

詳しくは、ルネサスの仕様書 『その他の通信機能 : 赤外線リモコンの信号はどうなっているのですか?』に詳しく書かれている。

Raspberry Piでキャプチャされたデータで、赤で着色したところが「リーダ」と「トレーラ」、青と緑の部分が「カスタムコード」と呼ばれる機器で統一された固有ID、オレンジの部分が「データ」、その後ろは「データのXOR」となっている。

カスタムコード H = 0b01011110 (0x5e)
カスタムコード L = 0b10100001 (0xA1)
データ = 0b01011000 (0x58)

というように解読できる。

回路作成

PIC 12F1822 を利用して、次のような回路を作成

20140727-12f1822-irsender-circuit.png

当初、単純にRA2ポートを560マイクロ秒Hi、… のようなプログラムを作成したが、受信側の危機が全く反応しない。 『秋月電子 : リモコンフォーマット ※参考資料』や『赤外線リモコンの通信フォーマット』を読んでみると、単純な560マイクロ秒のHi信号ではなく、38kHzのキャリアに乗せて送信を行うらしい…

ひたすら試行錯誤しても、38kHzのキャリア信号に気づかなければ、無駄な苦労だった。

トランジスタ増幅回路周りの抵抗値の計算
 
■ コレクタ抵抗(LED側)
LED電流値 IF = 70mA (許容最大値 50〜100)
LED順方向電圧 VF = 1.5V
駆動電圧 Vcc = 5.0V
 
オームの法則より Rc = (5 - 1.5) / 0.070 = 80 オーム
 
■ ベース側スイッチング抵抗
ベース電圧 V = 5.0V or 3.3V
半導体損失 0.6V
コレクタ電流 Ic = 70mA
Hfe = 200 → 安全率3として Hfe = 67
 
オームの法則より Rb = (5 - 0.6) / (0.070/67 ) = 4211 オーム
オームの法則より Rb = (3.3 - 0.6) / (0.070/67 ) = 2584 オーム
どちらでも可能なように、2.4kの抵抗を採用する

送信信号をオシロスコープで観察してみる

20140727-ircircuit-test.jpg
測定中の様子

20140727-osc01.jpg
横軸1メモリが1ミリ秒

0,1,0,1 … のNECフォーマットの信号。0データのHiが642マイクロ秒、Loが470マイクロ秒というYAMAHAの解析値に近いタイミングにカスタマイズしている。

20140727-osc02.jpg
一部分を拡大してみる。横軸が100マイクロ秒

Hiの部分(642マイクロ秒)が、38kHzのキャリア信号で表現されている。今回は、PICで簡単に実現するため、PWMの方形波を用いている。

20140727-osc03.jpg
更に拡大してみる。横軸が5マイクロ秒

1秒/38,000Hz = 26マイクロ秒 という計算にだいたいあっている。少し短いように感じるのは、PWMのPR2値設定の時に、計算上27のものを25に丸めたため。

PICのプログラム ソースコード

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <xc.h>
 
/* PIC Configuration 1 */
__CONFIG(FOSC_INTOSC &  // INTOSC oscillator: I/O function on CLKIN pin
        WDTE_OFF &      // WDT(Watchdog Timer) disabled
        PWRTE_ON &      // PWRT(Power-up Timer) disabled
        MCLRE_OFF &     // MCLR pin function is digital input
        CP_OFF &        // Program memory code protection is disabled
        CPD_OFF &       // Data memory code protection is disabled
        BOREN_OFF &     // BOR(Brown-out Reset) disabled
        CLKOUTEN_OFF &  // CLKOUT function is disabled. I/O or oscillator function on the CLKOUT pin
        IESO_OFF &      // Internal/External Switchover mode is disabled
        FCMEN_OFF);     // Fail-Safe Clock Monitor is disabled
 
/* PIC Configuration 2 */
__CONFIG(WRT_OFF &      // Flash Memory Self-Write Protection : OFF
        VCAPEN_OFF &    // VDDCORE pin functionality is disabled
        PLLEN_OFF &     // 4x PLL disabled
        STVREN_ON &     // Stack Overflow or Underflow will not cause a Reset
        BORV_HI &       // Brown-out Reset Voltage Selection : High Voltage
        DEBUG_OFF &     // In-Circuit Debugger disabled, ICSPCLK and ICSPDAT are general purpose I/O pins
        LVP_OFF);       // Low-voltage programming : disable
 
#ifndef _XTAL_FREQ
    /* 例:4MHzの場合、4000000 をセットする */
    #define _XTAL_FREQ 4000000
#endif
 
unsigned char custom_code_1 = 0b01011110;   /* 0x5E */
unsigned char custom_code_2 = 0b10100001;   /* 0xA1 */
unsigned char cmd = 0b01011000;     /* 0x58 */
 
//待ち時間が変数設定できるarg_delay_ms()関数を定義
//(__delay_ms()は引数に変数設定が出来ない為)
void arg_delay_ms(unsigned int x) {
    while(x) {
        __delay_ms(1);
        x--;
    }
}
void arg_delay_us(unsigned int x) {
    while(x) {
        __delay_us(1);
        x--;
    }
}
 
void decode_bit(unsigned char c){
    for(int i=0; i<8; i++){
        if(c & (0x80 >> i)){
//            PORTAbits.RA2 = 1;        // ← 単なる矩形波ではダメな例
            CCP1CON = 0b00001100;
            __delay_us(560);
//            PORTAbits.RA2 = 0;
            CCP1CON = 0b00000000;
            __delay_us(1690);
        }
        else{
            CCP1CON = 0b00001100;
            __delay_us(560);
            CCP1CON = 0b00000000;
            __delay_us(560);
        }
    }
}
 
int main(int argc, char** argv) {
    // 基本機能の設定
    OSCCON = 0b01101010;        // 内部オシレーター 4MHz
    TRISA = 0b00101011;         // IOポートRA0(AN0),RA1(SCL),RA5(RX)を入力モード(RA3は入力専用)、RA2(SDA),RA4(TX)を出力モード
    APFCONbits.RXDTSEL = 1;     // シリアルポート RXをRA5ピンに割付
    APFCONbits.TXCKSEL = 1;     // シリアルポート TXをRA4ピンに割付
    ANSELA = 0b00000000;        // A/D変換をAN0,AN1,AN2,AN4を無効
    PORTA = 0;
 
    CCP1SEL = 0;            // RA2をCCP1(PWM)のP1Aで用いる
    CCP1CON = 0b00000000;   // PWM(シングル)
    T2CON   = 0b00000000;   // TMR2プリスケーラ値を1倍に設定, Timer2はこの時点ではOFF
    // PR2 = Fosc / (Fpwm * 4 * Prescale) + 1 = 4000000 / (38000 * 4 * 1) + 1 = 27
    PR2 = 0x19;     // 27→25
    CCPR1L = 0x0c;  // PR2=0x19の場合デューティ値は6bitで、デューティ比50%の設定
    CCPR1H  = 0;
    TMR2 = 0;       // Timer2カウンタを0
 
    // PWM開始
    TMR2ON = 1;
    // 0.5秒待つ
    __delay_ms(2000);
 
    // スタートビット(リーダ)
    CCP1CON = 0b00001100;
    __delay_us(9000);
    CCP1CON = 0b00000000;
    __delay_us(4500);
 
    // 送信データ本体
    decode_bit(custom_code_1);
    decode_bit(custom_code_2);
    decode_bit(cmd);
    decode_bit(cmd ^ 0xff);
 
    // ストップビット(トレーラ)
    CCP1CON = 0b00001100;
    __delay_us(560);
    CCP1CON = 0b00000000;
    __delay_us(560);
 
    /* 終了 (無限ループでこれ以降何もしない) */
    while(1){
        __delay_ms(1000);
    }
    return (EXIT_SUCCESS);
}