PICを使った小型赤外線学習リモコン

PIC12F1822を使ったFRISKサイズの小型赤外線学習リモコンを作成しました。
信号送信用に６ボタン、固定信号の送信モード／学習モード／学習した信号の送信モードの切り替え用に１ボタンです。
学習した信号はEEPROMに保存し、保存可能な信号数はボタン数と同じ６信号までとしています。
リモコン信号フォーマットは、NECフォーマット、家電協フォーマットに対応（リピートコードには非対応）しました。
![1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/628816/bec5d9de-d0de-15f5-d996-f3c2e1a00590.png)
[動作の様子](https://youtu.be/5eJ887QZyuM)

#材料
<table>
  <tr>
    <td>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</td>
    <td>製&nbsp;品</td>
    <td>個&nbsp;数</td>
    <td>参&nbsp;考&nbsp;価&nbsp;格&nbsp;&nbsp;</td>
  </tr>
  <tr>
    <td>PICマイコン</td>
    <td>PIC12F1822</td>
    <td>1</td>
    <td><a href="https://akizukidenshi.com/catalog/g/gI-04557/">&yen;110</a></td>
  </tr>
  <tr>
    <td>基板</td>
    <td>ユニバーサル基板<br>7Cmx3Cmm<br>(一部カット)</td>
    <td>1</td>
    <td><a href="https://akizukidenshi.com/catalog/g/gP-12978/">&yen;35</a></td>
  </tr>
  <tr>
    <td>赤外線LED</td>
    <td>OSI5FU3A11C</td>
    <td>1</td>
    <td><a href="https://akizukidenshi.com/catalog/g/gI-04313/">&yen;200/10個</a></td>
  </tr>
  <tr>
    <td>押しボタン</td>
    <td>タクトスイッチ<br>TVDP01-G73BB<br>(お好みで)</td>
    <td>6</td>
    <td><a href="https://akizukidenshi.com/catalog/g/gP-09825/">&yen;20</a></td>
  </tr>
  <tr>
    <td>リセット用<br>ボタン</td>
    <td>小さめタクトスイッチTVDT18-050CW</td>
    <td>1</td>
    <td><a href="http://akizukidenshi.com/catalog/g/gP-08080/">&yen;120/10個</a></td>
  </tr>
  <tr>
    <td>ボタン電池</td>
    <td>リチウム電池CR2032</td>
    <td>1</td>
    <td><a href="http://akizukidenshi.com/catalog/g/gB-05694/">&yen;200/5個</a></td>
  </tr>
  <tr>
    <td>電池ホルダー</td>
    <td>基板取付用<br>CH005-2032LF</td>
    <td>1</td>
    <td><a href="http://akizukidenshi.com/catalog/g/gP-08967/">&yen;50</a></td>
  </tr>
  <tr>
    <td>トランジスタ</td>
    <td>2SC2120-Y</td>
    <td>1</td>
    <td><a href="http://akizukidenshi.com/catalog/g/gI-13829/">&yen;110/20個</a></td>
  </tr>
  <tr>
    <td>ダイオード</td>
    <td>1N4148</td>
    <td>9</td>
    <td><a href="http://akizukidenshi.com/catalog/g/gI-00941/">&yen;100/50本</a></td>
  </tr>
  <tr>
    <td>赤外線受信<br>モジュール</td>
    <td>GP1UXC41QS</td>
    <td>1</td>
    <td><a href="http://akizukidenshi.com/catalog/g/gI-06487/">&yen;50</a></td>
  </tr>
  <tr>
    <td>0.1μ<br>セラミック<br>コンデンサー</td>
    <td>RPEF11H<br>104Z2P1A01B</td>
    <td>1</td>
    <td><a href="http://akizukidenshi.com/catalog/g/gP-00090/">&yen;100/10個</a></td>
  </tr>
  <tr>
    <td>10Ω抵抗</td>
    <td>CFS50J10RB</td>
    <td>1</td>
    <td><a href="http://akizukidenshi.com/catalog/g/gR-07795/">&yen;100/100本</a></td>
  </tr>  <tr>
    <td>10KΩ抵抗</td>
    <td>RD25S 10K</td>
    <td>2</td>
    <td><a href="http://akizukidenshi.com/catalog/g/gR-25103/">&yen;100/100本</a></td>
  </tr>
  <tr>
    <td>ケース</td>
    <td>フリスク</td>
    <td>1</td>
    <td>￥200</td>
  </tr>
</table>
#回路図

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/628816/4059e4e0-0086-8879-36c7-f29a42312f68.png)

RA1-RA3の3ピンで7個のボタン(リモコンボタン6個、モードの切り替えボタン1個)を識別します。
発光確認、及び各種操作の確認用に、LED1個を配置しました。
赤外線受光モジュールの電源はsleep時の消費電力を少なくするため、RA4ピンからとるようにしました。

![2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/628816/6d61cd7f-a8a8-51ce-f3ed-3d90581e5059.png)


#赤外線フォーマット
多くの家電で使用されているリモコンの赤外線は、38KHzパルス変調し送信しています。
赤外線リモコンの信号については、こちらに詳しく書かれています。
・<a href="https://www.asahi-net.or.jp/~gt3n-tnk/IR_TX1.html">赤外線リモコンのフォーマット</a>
・<a href="https://www.elm-chan.org/docs/ir_format.html">赤外線リモコンの通信フォーマット</a>
大変参考になりました。ありがとうございました。

#プログラム
作成したリモコンでは、多くで使用されているNECフォーマットと家電協フォーマットに対応し、ボタンは６個としました。
本記事では、それら機能の一部を簡潔にまとめた次のプログラム例を掲載します。

+ リモコン信号の送信例
+ リモコン信号の学習例
+ 学習した信号の送信例

先の回路図から、Button2～Button7を除いたもので動作します。
各自で作成する場合の参考になれば幸いです。
ただ、私の環境ではこれで動作しましたが、厳密な条件など、全ての環境での動作を保証するものではありませんので、ご理解の上参考にしてください。
開発環境は、MPLAB X IDEでコンパイラは付属のXC8で作成しています。
MPLAB X IDE V5.35
xc8-cc(V2.10)

##リモコン信号送信プログラム

+ 内部オシロレータを使って4MHzで動作しています
+ NECフォーマット信号送信の例です。
+ Button1のボタンを押すと、あらかじめ設定しているリモコンデータを送信します。<br>リモコンデータは、IR_FixData[4]で定義（１信号分）していますので、各自の環境に合わせて変更してください。
+ 38KHz変調はCCPのPWMモードを利用しています。<br>
レジスタの初期化で動作モードを設定しておき、必要の都度、TMR2ON=1/0で、送信/停止を行います。時間は__delay_us(xxx)で取得します。

<table style="width:1000px;background: #ffffff;">
  <tr>
    <td></td>
    <td colspan="2">リーダー<br>ON&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;OFF</td>
    <td>データ<br>ON</td>
    <td colspan="2">データビット<br>ON&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;OFF</td>
    <td>フレーム<br>調整幅</td>
  </tr>
  <tr>
    <td width="500px">時間</td>
    <td>9000</td>
    <td>4500</td>
    <td>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;560</td>
    <td>&nbsp;&nbsp;&nbsp;&nbsp;560</td>
    <td>1690</td>
    <td>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;36000</td>
  </tr>
</table>
NECフォーマットの場合、単位[μS]

+ ボタンが押されていない間は、節電のため、SLEEPします。<br>
レジスタの初期化でピンの状態変化割込み設定をしておき、SLEEP前に、IOCIE=1で割込みを有効にする。

```main.c
#include <xc.h>
#define _XTAL_FREQ 4000000
// CONFIG1
#pragma config FOSC = INTOSC    // Oscillator Selection (INTOSC oscillator: I/O function on CLKIN pin)
#pragma config WDTE = OFF       // Watchdog Timer Enable (WDT disabled)
#pragma config PWRTE = ON       // Power-up Timer Enable (PWRT enabled)
#pragma config MCLRE = OFF      // MCLR Pin Function Select (MCLR/VPP pin function is digital input)
#pragma config CP = OFF         // Flash Program Memory Code Protection (Program memory code protection is disabled)
#pragma config CPD = OFF        // Data Memory Code Protection (Data memory code protection is disabled)
#pragma config BOREN = ON       // Brown-out Reset Enable (Brown-out Reset enabled)
#pragma config CLKOUTEN = OFF   // Clock Out Enable (CLKOUT function is disabled. I/O or oscillator function on the CLKOUT pin)
#pragma config IESO = ON        // Internal/External Switchover (Internal/External Switchover mode is enabled)
#pragma config FCMEN = ON       // Fail-Safe Clock Monitor Enable (Fail-Safe Clock Monitor is enabled)
// CONFIG2
#pragma config WRT = OFF        // Flash Memory Self-Write Protection (Write protection off)
#pragma config PLLEN = ON       // PLL Enable (4x PLL enabled)
#pragma config STVREN = ON      // Stack Overflow/Underflow Reset Enable (Stack Overflow or Underflow will cause a Reset)
#pragma config BORV = LO        // Brown-out Reset Voltage Selection (Brown-out Reset Voltage (Vbor), low trip point selected.)
#pragma config LVP = OFF        // Low-Voltage Programming Enable (High-voltage on MCLR/VPP must be used for programming)

const unsigned char IR_FixData[4] = { 0x21, 0x8A, 0x59, 0xA6 };

void Regster_Int(void) {
    OSCCON = 0b01101010;   //内部オシロレータ　4MHz HF
    TRISA	=   0b00000010; /* RA1はIN。その他はOUT            */
    ADCON0  =   0b00000000; //ADC使用しない
    ANSELA	=	0b00000000;	//アナログモードは使用しない
    OPTION_REG= 0b00000000; //bit7:全体プルアップ有効　0:有効
    WPUA    =	0b00000010; //個別プルアップ有効を設定　0:無効、1:有効 
// ピンの割込み設定
    INTCON	= 	0b00000000;	//bit7:全ての割込みを無効にする。状態変化割込みは検出する、他の割込みが入らないように全体はoffとする
    IOCAN	=	0b00000010; //各ピンの立ち下がりエッジの状態変化割り込みを有効にする。
//PWMキャリアの設定
    PR2     = 0x19;         // 38KHz ( クロック周波数 / (PWM周波数 × 4 × TMR2プリスケール値) ) - 1
    T2CON   = 0x08;         // ポストスケーラ 1:2。Timer2=OFF。プリスケーラ 1。
    CCP1CON = 0b00111100;   // デューティサイクル下位2ビット:0b11、PWMモード
    CCPR1L  = 0x08;         //デューティサイクル35%
    APFCON = 0x01;          //CCP1SEL(bit0):CCPの機能をRA5に割当
}

void main(void) {
    unsigned char   codebyte;
    Regster_Int();              // レジスター初期設定

    for (;;) {
      if (RA1==0){
         __delay_ms(50);         // 安定化待ち
         //リーダーコード送信
         TMR2ON = 1;
         __delay_us(9000);
         TMR2ON = 0;
         __delay_us(4500);
         // カスタム/データコード送信
         for (int i=0;i<4;i++)
         {
             codebyte = IR_FixData[i];
             for(int j=7;j>=0;j--)
             {
                 TMR2ON = 1;
                 __delay_us(560);
                 TMR2ON = 0;
                 //PWM OFFの時間でOn/Off区別
                 if (codebyte & (0b00000001 << j)) {
                     __delay_us(1690);       //Bit on
                 }
                 else {
                     __delay_us(560);        //Bit Off
                 }
             }
         }
         //ストップビット送信
         TMR2ON = 1;
         __delay_us(560);
         TMR2ON = 0;
         __delay_ms(36);         
      }
      //ボタンの押下があるまでスリープ  
      IOCAF   =   0b00000000;   //各ピンの状態変化割り込みがあったか状態表示フラグ
      IOCIE = 1;                //NTCON.IOCIE ピンの状態変化割り込みを有効にする
      SLEEP();
      NOP();
      IOCIE = 0;
      IOCAF   =   0b00000000;   
    }
}
```

#リモコン信号学習プログラム
+ 内部オシロレータを使って4MHzで動作しています
+ NECフォーマット信号受信の例です。
+ 赤外線受信モジュールで受信したON/OFFは、反転した値がVoutより出力されます。
+ Button1を押すと、LED点灯と同時に赤外線受信モジュールに通電し、リモコン信号の受信待ち状態となります。
+ フォーマットの識別は、リーダーコードのONの時間で判断します（9ms±1msをNECフォーマット。OFFの時間は無視しています）。
+ データビットは、OFFの時間で判断します（1ms以上：1、1ms未満：0。ONの時間は無視しています）。
+ ストップビットの長さは無視していますが、その後、1ビット分の時間内に次の信号を受信した場合は、エラーとしました。
+ 正常に受信した場合は、EEPROM内の0-3番地にデータを保存します。

```main.c
#include <xc.h>
#define _XTAL_FREQ 4000000
// CONFIG1
#pragma config FOSC = INTOSC    // Oscillator Selection (INTOSC oscillator: I/O function on CLKIN pin)
#pragma config WDTE = OFF       // Watchdog Timer Enable (WDT disabled)
#pragma config PWRTE = ON       // Power-up Timer Enable (PWRT enabled)
#pragma config MCLRE = OFF      // MCLR Pin Function Select (MCLR/VPP pin function is digital input)
#pragma config CP = OFF         // Flash Program Memory Code Protection (Program memory code protection is disabled)
#pragma config CPD = OFF        // Data Memory Code Protection (Data memory code protection is disabled)
#pragma config BOREN = ON       // Brown-out Reset Enable (Brown-out Reset enabled)
#pragma config CLKOUTEN = OFF   // Clock Out Enable (CLKOUT function is disabled. I/O or oscillator function on the CLKOUT pin)
#pragma config IESO = ON        // Internal/External Switchover (Internal/External Switchover mode is enabled)
#pragma config FCMEN = ON       // Fail-Safe Clock Monitor Enable (Fail-Safe Clock Monitor is enabled)
// CONFIG2
#pragma config WRT = OFF        // Flash Memory Self-Write Protection (Write protection off)
#pragma config PLLEN = ON       // PLL Enable (4x PLL enabled)
#pragma config STVREN = ON      // Stack Overflow/Underflow Reset Enable (Stack Overflow or Underflow will cause a Reset)
#pragma config BORV = LO        // Brown-out Reset Voltage Selection (Brown-out Reset Voltage (Vbor), low trip point selected.)
#pragma config LVP = OFF        // Low-Voltage Programming Enable (High-voltage on MCLR/VPP must be used for programming)

void Regster_Int(void) {
    OSCCON  =   0b01101010; //内部オシロレータ　4MHz HF
    TRISA	=   0b00000011; // RA0,1はIN。その他はOUT
    ADCON0  =   0b00000000; //ADC使用しない
    ANSELA	=	0b00000000;	//アナログモードは使用しない
    OPTION_REG= 0b00000000; //bit7:全体プルアップ有効　0:有効
    WPUA    =	0b00000010; //個別プルアップ有効を設定　0:無効、1:有効 
    // Timer1情報設定
    T1CON   = 0x00; // プリスケール値1:1 、Timer1を停止
    TMR1IE  = 0;    // Timer1 オーバーフロー割り込みを無効
}

void main(void) {
    unsigned char   rcv_data[4];
    Regster_Int();                  // レジスター初期設定
    
    RA4=0;
    for (;;) {
      if (RA1==0){
          __delay_ms(50);           // 安定化待ち
          RA4=1;                    //受信LEDに電源供給＆LED点灯
          __delay_ms(1000);
          // リーダーコードの開始待ち
          while (RA0);
          //リーダーコードの長さ測定
          TMR1L=0;TMR1H=0;TMR1ON=1;     //Timer1開始
          while (!RA0);
          // HIになるまでの時間（ONの長さ）でフォーマット決定
          if ((TMR1H >= 0x1F) && (TMR1H <= 0x27)) {
              // ONが8ms-10.0msをNECフォーマットと判断
              // 一旦LOWになるのを待つ
              while (RA0);
              // データコード32ビット分繰り返す
              for (int i = 0; i < 4; i++) {
                  rcv_data[i] = 0 ;
                  for ( int j=7;j>=0;j--) {
                      while (!(RA0));
                      TMR1L=0;TMR1H=0;TMR1ON=1;     //Timer1開始
                      while (RA0);
                      // LOWになるまでの時間（OFFの長さ）で0/1決定（1ms以上：1、1ms未満：0）
                      if (TMR1H >= 0x04) {
                          rcv_data[i] = rcv_data[i] | (0b00000001 << j);
                      }
                  }
              }
          } else {
              //不明のフォーマットを受信の合図
              RA4=0;__delay_ms(100);RA4=1;__delay_ms(100);RA4=0;
              //もう一度、最初から
              continue;
          }
          // ストップビットはスキップ
          while (!(RA0));
          TMR1L=0;TMR1H=0;TMR1ON=1;     //Timer1開始
          while (RA0) {
          // 次信号までのタイミングで1フレーム送信の終了を確認
            if (TMR1H >= 0x08) {    //2.25ms-0.56ms=1.69ms 2ms位無ければ
                // 正常 EEPROM 0-3番地にデータ書き込み
                for (int eppaddr=0; eppaddr < 4; eppaddr++) {           
                    EEPROM_WRITE(eppaddr, rcv_data[eppaddr]);
                }
                RA4=0;
                return;
            }
          }
          //時間内に次の信号を受信（データコードの続き？）した場合、エラー
          RA4=0;__delay_ms(100);RA4=1;__delay_ms(100);RA4=0;__delay_ms(100);RA4=1;__delay_ms(100);RA4=0;
          //もう一度、最初から
          continue;
      }
    }
}
```

##学習した信号送信プログラム

+ プログラムは、基本的にリモコン信号送信プログラムと同じ。違いは送信データの取得方法。
+ リモコン信号送信プログラムでは、IR_FixData[4]のデータを送信していたところを、EEPROM内の0-3番地から読み出したデータを送信するところ。

```main.c
#include <xc.h>
#define _XTAL_FREQ 4000000
// CONFIG1
#pragma config FOSC = INTOSC    // Oscillator Selection (INTOSC oscillator: I/O function on CLKIN pin)
#pragma config WDTE = OFF       // Watchdog Timer Enable (WDT disabled)
#pragma config PWRTE = ON       // Power-up Timer Enable (PWRT enabled)
#pragma config MCLRE = OFF      // MCLR Pin Function Select (MCLR/VPP pin function is digital input)
#pragma config CP = OFF         // Flash Program Memory Code Protection (Program memory code protection is disabled)
#pragma config CPD = OFF        // Data Memory Code Protection (Data memory code protection is disabled)
#pragma config BOREN = ON       // Brown-out Reset Enable (Brown-out Reset enabled)
#pragma config CLKOUTEN = OFF   // Clock Out Enable (CLKOUT function is disabled. I/O or oscillator function on the CLKOUT pin)
#pragma config IESO = ON        // Internal/External Switchover (Internal/External Switchover mode is enabled)
#pragma config FCMEN = ON       // Fail-Safe Clock Monitor Enable (Fail-Safe Clock Monitor is enabled)
// CONFIG2
#pragma config WRT = OFF        // Flash Memory Self-Write Protection (Write protection off)
#pragma config PLLEN = ON       // PLL Enable (4x PLL enabled)
#pragma config STVREN = ON      // Stack Overflow/Underflow Reset Enable (Stack Overflow or Underflow will cause a Reset)
#pragma config BORV = LO        // Brown-out Reset Voltage Selection (Brown-out Reset Voltage (Vbor), low trip point selected.)
#pragma config LVP = OFF        // Low-Voltage Programming Enable (High-voltage on MCLR/VPP must be used for programming)

void Regster_Int(void) {
    OSCCON = 0b01101010;   //内部オシロレータ　4MHz HF
    TRISA	=   0b00000010; /* RA1はIN。その他はOUT            */
    ADCON0  =   0b00000000; //ADC使用しない
    ANSELA	=	0b00000000;	//アナログモードは使用しない
    OPTION_REG= 0b00000000; //bit7:全体プルアップ有効　0:有効
    WPUA    =	0b00000010; //個別プルアップ有効を設定　0:無効、1:有効 
// ピンの割込み設定
    INTCON	= 	0b00000000;	//bit7:全ての割込みを無効にする。状態変化割込みは検出する、他の割込みが入らないように全体はoffとする
    IOCAN	=	0b00000010; //各ピンの立ち下がりエッジの状態変化割り込みを有効にする。
//PWMキャリアの設定
    PR2     = 0x19;         // 38KHz ( クロック周波数 / (PWM周波数 × 4 × TMR2プリスケール値) ) - 1
    T2CON   = 0x08;         // ポストスケーラ 1:2。Timer2=OFF。プリスケーラ 1。
    CCP1CON = 0b00111100;   // デューティサイクル下位2ビット:0b11、PWMモード
    CCPR1L  = 0x08;         //デューティサイクル35%
    APFCON = 0x01;          //CCP1SEL(bit0):CCPの機能をRA5に割当
}

void main(void) {
    unsigned char   IR_EepData[4];
    unsigned char   codebyte;
    Regster_Int();              // レジスター初期設定
    
// テストデータ
//    EEPROM_WRITE(0, 0x21);
//    EEPROM_WRITE(1, 0x8A);
//    EEPROM_WRITE(2, 0x59);
//    EEPROM_WRITE(3, 0xA6);

    __delay_ms(1000);    
    //学習済のデータをEEPROMから読み出し
    for (int eppaddr=0; eppaddr < 4; eppaddr++) {
            IR_EepData[eppaddr] = EEPROM_READ(eppaddr);
    }
    for (;;) {
      if (RA1==0){
         __delay_ms(50);         // 安定化待ち
         //リーダーコード送信
         TMR2ON = 1;
         __delay_us(9000);
         TMR2ON = 0;
         __delay_us(4500);
         // カスタム/データコード送信
         for (int i=0;i<4;i++)
         {
             codebyte = IR_EepData[i];
             for(int j=7;j>=0;j--)
             {
                 TMR2ON = 1;
                 __delay_us(560);
                 TMR2ON = 0;
                 //PWM OFFの時間でOn/Off区別
                 if (codebyte & (0b00000001 << j)) {
                     __delay_us(1690);       //Bit on
                 }
                 else {
                     __delay_us(560);        //Bit Off
                 }
             }
         }
         //ストップビット送信
         TMR2ON = 1;
         __delay_us(560);
         TMR2ON = 0;
         __delay_ms(36);         
      }
      //ボタンの押下があるまでスリープ  
      IOCAF   =   0b00000000;   //各ピンの状態変化割り込みがあったか状態表示フラグ
      IOCIE = 1;                //NTCON.IOCIE ピンの状態変化割り込みを有効にする
      SLEEP();
      NOP();
      IOCIE = 0;
      IOCAF   =   0b00000000;   
    }
}

```
#終わりに
プログラム次第では、メモリ制限まで数十信号を保存可能ですが、信号送信時に信号の選択方法など手順が複雑になるので、ボタンと同じ6個にしました。
自分でプログラムを書き込むのなら、自分でデータも変更できるので、「学習機能モード」／「学習した信号の送信モード」は不要では・・・と思ったりも^_^;。