【初投稿】赤外線リモコンで動作する機器をIoT機器にしよう！～回路編～

#はじめに
　今回、ラズパイを使って自宅の赤外線リモコンで操作するエアコンを、外部からでも操作を行えるようなシステムを構築しました。
　思い立ったのは、すっかり日も短くなった12月頃。いつものように大学の研究室から自宅に帰ると、部屋はすっかり冷え切っています。エアコンの暖房をつけても、部屋が暖まるまでには時間がかかり、それまでは寒さと闘わなくてはなりません。
　帰宅前に外からエアコンをつけられれば、まるで家族がいるかのように暖かい部屋が、僕を待ち受けてくれるのではないかと思い、今流行り(?)のラズパイを使って、外から赤外線リモコンを制御しようと思い立ちました。
#手法
　今回作成したシステムは、Raspberry PiでWebサーバを構築し、設定温度情報などのリクエストを受け取ったらリモコンと同じ信号をGPIOから出力しエアコンを制御するものとなっております。
　
　今回の記事では、リモコンの送信信号情報の読み取り・解析を行い、その情報を元に赤外線LEDを制御しエアコンを操作するところまでを紹介します。
#使用機器
・三菱電機　霧ヶ峰　MSZ-GV255-W とそのリモコン
・Raspberry Pi 3 B+
・赤外線LED (940 nm)
・抵抗 (1kΩ程度×1、50Ω程度×1)
・NchパワーMOSFET (2SK4017)
・ジャンプワイヤー (5本程度)
・赤外線受信モジュール (CHQ1838)
#赤外線信号送信機の作成
##赤外線リモコンの情報解析
　現在、日本で販売されている赤外線リモコンで動作する機器の多くは、「NECフォーマット」「AEHAフォーマット」「SONYフォーマット」のいずれかで動いています。
それぞれのフォーマットについては下記URLに詳しく載っています。
http://elm-chan.org/docs/ir_format.html
###赤外線通信について
　一般的な家電製品の多くのリモコンは赤外線で情報を送っています。赤外線は可視光よりも少し波長が長く、目には見えませんが太陽光にも含まれています。そのため太陽光は赤外線通信にとっての雑音となります。単に信号のHIGH/LOWに従って赤外線をON/OFFしても通信はできますが、太陽光等のノイズの影響を受けやすくなってしまいます。
　そこで、赤外線通信では赤外線を「高速でチカチカ」させることで、太陽光等の雑音とリモコンの信号の区別を行えるようになっています。この「高速でチカチカ」というのを詳しく言うと、'HIGH'を送る際は38 kHzで赤外線のON-OFFを繰り返し、'LOW'を送る際は赤外線を止めるというようになっています。通信工学で言うASKのような感覚です。少し違いますが。
　今回使用する赤外線受信モジュールは38 kHzの変調を前提として作られているため、内部にフィルタが入っており、出力としては純粋なHIGH/LOWが取り出されます。そのため、受信の際は38 kHzという周波数は気にする必要はありません。
　一方で送信の際は、普通の赤外線LEDで送るため、38 kHzの変調をかける必要があります。
###赤外線の受信
　まず今回使用するリモコンの情報はこの3つのフォーマットのうちのどれかを解析していきます。今回、赤外線の読み取りには赤外線受信モジュール (CHQ1838)を使用します。CHQ1838は3ピンで正面(受光面)から見て左のピンから、Vout、GND、Vccとなっており、それぞれをRaspberry PiのGPIO17番、GND、3.3 Vに接続します。赤外線受信モジュールのVoutは、赤外線を感知しているときに0を、感知していないときには1を出力します。これで受信の回路は完成です。
　次に、読み取りのプログラムを書きます。今回はC言語でWiringPiというライブラリを使って書きます。

```c:IRtest.c
#include<stdio.h>
#include<wiringPi.h>
#define GPIO 17
int main(){
        if(wiringPiSetupGpio()==-1)return 1;
        pinMode(GPIO,INPUT);
        unsigned int start=0,L=0,H=0;
        while(1){
                while(!digitalRead(GPIO));
                H=micros()-start;
                start=micros();
                delayMicroseconds(50);
                while(digitalRead(GPIO));
                L=micros()-start;
                start=micros();
                printf("%d,%d\n",H,L);
        }
        return 0;
}
```

　このプログラムは、出力の切り替わりを検知しその時間を記録し、再度切り替わったときの時間との差分をとることで信号の秒数(マイクロ秒)を表示するようになっています。
　試しに、プログラムを実行し接続した赤外線受信モジュールにリモコンで信号を送ってみましょう。

```
21,7129268
3498,1647
490,1227
487,1228
488,352
503,355
502,354
～～～～
490,1234
480,1233
479,370
488,1234
481,1235
480,13241
3490,1661
482,1232
482,1233
481,369
489,367
490,367
～～～
488,370
488,364
491,1230
483,1233
480,369
489,1232
483,1232
482,367
488,1233
483,1232
483,2418000
```

　私の場合はこのようなデータが得られました。1行目および最後の行は"ゴミ"です。左側の数字がHIGHの時間で右側の数字がLOWの時間です。
　データを見る限り、概ねHIGHの時間が480 μs程度、LOWの時間が350 μsまたは1200μs程度となっていることがわかります。ざっくりT=400 μsとすると、HIGHが1T、LOWが1Tまたは3Tと表せます。先ほど貼ったリンク先を見ると、これはAEHAフォーマットではないかと考えられます。つまり、"479,370"や"481,369"と出ている行は'0'を表しており、"487,1228"や"483,1233"と出ている行は'1'を表しています。
 2行目の"3498,1647"は特異的ですが、これはスタートを示すリーダです。「これから信号を送りますよ」という合図です。また途中でも"3490,1661"のようなリーダが見つかるのですが、これはリピート信号のリーダです。

　このデータを参考にリモコンの信号の解析を行います。この数列を見ながら'0'、'1'と書いてもいいですが、それだと日が暮れてしまうので、今回はAEHAフォーマットを解析するためのプログラムをもう一度組みました。

```c:IRtest2.c
#include<stdio.h>
#include<wiringPi.h>
#define GPIO 17
int main(){
        if(wiringPiSetupGpio()==-1)return 1;
        pinMode(GPIO,INPUT);
        unsigned int start=0,L=0,H=0;
        while(1){
                while(!digitalRead(GPIO));
                H=micros()-start;
                start=micros();
                delayMicroseconds(50);
                while(digitalRead(GPIO));
                L=micros()-start;
                start=micros();
                if(H>3000&&H<4000&&L>1300&&L<2000){
                        printf("start\n");
                }
                else if(H>300&&H<600&&L>1100&&L<1400){
                        printf("1");
                }
                else if(H>300&&H<600&&L>300&&L<600){
                        printf("0");
                }
                else {
                        printf("\n");
                }
        }
        return 0;
}
```

このプログラムを実行し、受信モジュールにリモコンで信号を送ってみましょう。
何回か温度などを変更しながら送ってみると、一定の長さの信号が送られていることがわかります。

```
start
110001001101001101100100100000000000000000000100000100000110000000001100000000100000000000000000000000000000000000000000000000000000000011001101
start
110001001101001101100100100000000000000000000000000100000110000000001100000000100000000000000000000000000000000000000000000000000000000011001001
start
110001001101001101100100100000000000000000000100000100001010000000001100000000100000000000000000000000000000000000000000000000000000000001001101
start
110001001101001101100100100000000000000000000000000100001010000000001100000000100000000000000000000000000000000000000000000000000000000001001001
```

1番目は暖房22度設定で、風速風向を自動としたときの信号です。
2番目はその状態で電源をOFFにしたときの信号です。
3番目は暖房21度設定で、風速風向を自動としたときの信号です。
4番目はその状態で電源をOFFにしたときの信号です。
これでも良いのですが、2進数だとやっぱり見にくいので16進数に直しましょう。
16進数にするとこのようになります。(ちなみに信号は1バイトごとにLSB FIRSTとなっているようです。)

```
1  2  3  4  5  6  7  8  9  10 11 12 13 14 15 16 17 18
23:cb:26:01:00:20:08:06:30:40:00:00:00:00:00:00:00:b3 (暖房22度で電源ON)
23:cb:26:01:00:00:08:06:30:40:00:00:00:00:00:00:00:93 (暖房22度で電源OFF)
23:cb:26:01:00:20:08:05:30:40:00:00:00:00:00:00:00:b2 (暖房21度で電源ON)
23:cb:26:01:00:00:08:05:30:40:00:00:00:00:00:00:00:92 (暖房21度で電源OFF)
```
　バイトごとにコロンで区切っています。MACアドレスみたいですね。
　見てみると、18バイトのデータのようです。
　まず6バイト目に注目してみると、このバイトは電源のON、OFFの情報のようです。ONの場合は'20'でOFFの場合は'00'となっているようですね。
　また、8バイト目に注目すると、これは設定温度の情報であることがわかります。実際に8バイト目は設定温度に従って、16度では"00"、31度では"0F"となっていました。つまり、設定温度から16を引いた値が8バイト目に来るようです。
　最後のバイトはチェックサムで、すべてのバイト列の総和となっています。
　以上で6バイト目と8バイト目が電源の情報と設定温度の情報であることがわかりました。他にも設定項目はたくさんありますが、とりあえずここまでにしておきます。
　次はこのデータを送信するシステムを作ります。

##赤外線データの送信
　先ほども述べたのですが、赤外線の信号は38 kHzで変調がかかっています。今回はRaspberry PiのGPIOのPWMを使ってこの変調をかけたいと思います。
　まず回路を組むのですが、赤外線LEDをGPIOで直接ドライブしもいいのですが、GPIOから流せる電流はかなり小さく、実際にやってみたのですが、30 cm程度の距離でしか信号が送れませんでした。
　そこで、MOSFETなどのトランジスタを使って電流を増幅してあげましょう。実際にやってみたところ、赤外線LEDに100 mA程度流さないと実用的な距離からは送ることができませんでした。回路図は省略するので、適当に組んでみてください。


```c
#include<stdio.h>
#include<stdlib.h>
#include<wiringPi.h>
#define PIN 18
#define RANGE 3
#define FREQ 37.5e3
#define CLOCK (int)(19.2e6/FREQ/RANGE) //Raspberry Pi 4の場合は19.2e6を54e6にする
#define N 17
#define T 425
void send_zero(){                                                                                                                                                                                  
        pwmWrite(PIN,1);                                                                                                                                                                         
        delayMicroseconds(T);
        pwmWrite(PIN,0);                                                                                                                                                                         
        delayMicroseconds(T);
}
void send_one(){
        pwmWrite(PIN,1);
        delayMicroseconds(T);
        pwmWrite(PIN,0);
        delayMicroseconds(3*T);
}
void send_byte(unsigned char x){
        int i;
        for(i=0;i<8;i++){
                if(x%2)
                        send_one();
                else
                        send_zero();
                x>>=1;
        }
}
void send_data(unsigned char *d){
        int i;
        unsigned char check_sum=0;
        pwmWrite(PIN,1);
        delayMicroseconds(8*T);
        pwmWrite(PIN,0);
        delayMicroseconds(4*T);
        for(i=0;i<N;i++){
                send_byte(d[i]);
                check_sum+=d[i];
        }
        send_byte(check_sum);
        pwmWrite(PIN,1);
        delayMicroseconds(T);
        pwmWrite(PIN,0);
        printf("%x\n",check_sum);

}
int main(int argc,char** argv){
        if(wiringPiSetupGpio()<0)return 1;
        unsigned char temp=22;
        if(argc>1)
                temp=(unsigned char)atoi(argv[1]);
        if(temp>31||temp<16)temp=22;

        //ここにデータ列を入力　途中からすべて0なので省略
        unsigned char dat[N]={0x23,0xcb,0x26,0x01,0x00,0x20,0x08,temp-16,0x30,0x40};
        if(temp==31||temp==16)dat[9]=0x80;

        pinMode(PIN,PWM_OUTPUT);
        pwmSetMode(PWM_MODE_MS);
        pwmSetRange(RANGE);
        pwmSetClock(CLOCK);
        delay(100);

        send_data(dat);
        delay(100);
        send_data(dat);
        return 0;
}
```
※最新のRaspberry Pi4を使う場合は#define CLOCKの値を変更する必要があります。

　このプログラムでエアコンをONにすることができました。データ列の6バイト目を0x00にすることでOFFにすることもできます。
　次はこれを外部から動作させるシステムを作ります。気が向いたら続きを書こうとおもいます()。