家のシーリングライトのリモコンをハックした話

# 1.はじめに
本記事は，自宅のシーリングライトのリモコンをハックしてマイコンから操作できるようにすることを目標としています．

今回ハックするシーリングライトはこちらになります．
[NEC-LEDシーリングライト-調光タイプ-~8畳-HLDZB0849](http://www.amazon.co.jp/NEC-LEDシーリングライト-調光タイプ-~8畳-HLDZB0849/dp/B00P0SM690/ref=pd_sim_sbs_79_1?ie=UTF8&dpID=31BjhudQUEL&dpSrc=sims&preST=_AC_UL160_SR160%2C160_&refRID=0VGHX77Q1FS7X3PCC0VV)

シーリングライトの機能として

- 多段調光（100％,約70％,約50％,約30％,約10％(5段階調光))
- 保安球
- メモリー点灯機能
- スリープタイマー（30分・60分）

があります．これらは付属の調光リモコンRE0201で操作できるため，この調光リモコンをハックしてマイコンから操作してみたいと思います．

# 2.準備物
作成に必要なものは

- マイコン　PIC24FV32KA301
- トランジスタ　S8050
- 赤外線LED
- PICKit3
- USBシリアル変換モジュール　FT232RL

くらいです．(抵抗やコンデンサは省略します．)デバッグ用にオシロスコープなどがあると便利です．特に深い理由はありませんが，マイコンはPIC24FV32KA301を使用します．手元のブレッドボード上にこれが刺さってただけです．PWMとUARTが利用できればなんでも構いません．マイコンの詳細は[こちら](http://akizukidenshi.com/catalog/g/gI-07642/)をご覧ください．秋月さんで290円で販売しています．(2016年1月31日現在)
トランジスタは低速のスイッチングに利用するため型番などは気にしなくても大丈夫です．LEDをドライブできれば十分です．(なくても大丈夫だと思います．)
赤外線LEDは手元になかったためリモコンから拝借しました．(小声)

# 3.リモコンの出力波形
どういうフォーマットで出力されているのか分からなければ取得は困難なので，まずは出力フォーマットについて調べます．日本ではNEC/家製協/SONYフォーマットが主に利用されているようです．今回データを取得するリモコンはNEC製なので，間違いなくNECフォーマットでしょう．NECフォーマットの詳細については
[赤外線リモコンの通信フォーマット - ELM by ChaN](http://elm-chan.org/docs/ir_format.html)
をご覧ください．私が言葉で説明するよりとてもわかりやすいです．
要点だけ抜粋すると

- 950nmの波長の赤外線を使用
- 0と1は赤外線の有無ではなく，赤外線がOFFの時間で0と1を表現する．
- ノイズによる影響を小さくするため38kHz，デューティ比1/3の信号を使用する．
- リーダーコードの後に32bitの情報を送信する．

となります．

# 4.データの取得
調光リモコンからデータを取得します．赤外線受光モジュールを利用して取得することが望ましいのですが，私の手元にそんなものはなかったのでリモコンから直接吸い出します．
まずは手元のリモコンをデバッグモード(笑)にします．
※デバッグモード(笑)にした場合，メーカーからのサボートが受けられなくなりますのでご注意ください．
![CWmfrYFUkAAEcEp.jpg](https://qiita-image-store.s3.amazonaws.com/0/111511/08cd1f27-b375-826f-69db-66c342dc0679.jpeg)

そして調光リモコンの赤外線LEDからの出力端子をPICのRB2に接続します．結果を表示するためにFT232RLをUART1に接続します．
下記プログラムを使用してリモコンからの出力データを吸い出します．

```C:get_data.c
void get_data(){
	//変数宣言
	char s[50];
	unsigned int count = 0;
	unsigned int i = 0;
	unsigned int size = 1500;
	unsigned char result[size];	
	unsigned char pos = 0;
		
	while(true){
		//信号があるまで待機
		while(IO_RB2_GetValue());
		
		//信号を取得
		result[count] = result[count] | (IO_RB2_GetValue()<<pos);
		pos++;
		if(pos == 8){
			pos = 0;
			count++;
		}
		//サンプリングレート2us
		__delay_us(2);
		
		//バッファフルになったら出力
		if(count >= size){
			for (i = 0; i < count; i++) {
				for (pos  = 0; pos  < 8; pos ++) {
					sprintf(s, "%d,",(result[i]>>pos) & 0b1);
					uart_tx(s);
				}
			}
			uart_tx("¥n");
			
			//初期化
			for(i=0; i < count; i++){
				result[i] = 0;
			}
			pos = 0;
			count = 0;
		}		
	}
	return;
}
```
マイコンのメモリが限られており配列で2,000程度宣言することができないため，1,500宣言し，シフト演算を利用してunsigned char型の領域を活用しています．リモコンから出力される波形は38kHz(=約26us)なので，これよりも小さいサンプリング周波数でサンプリングします．取得した波形はcsv形式で出力されるので，UARTでPCに出力し，Excelなどを用いてグラフを描画します．
取得したデータ4つ並べたものを下記に示します．赤外線LEDのカソード側から端子を出しているためHightの時はLEDがOFFになっています．
![スクリーンショット 2016-01-31 23.33.18.png](https://qiita-image-store.s3.amazonaws.com/0/111511/d8edf59b-17a2-bb49-3410-a4b82a6c28d5.png)
左の帯の太いところはリーダーコードです．そのあとに間隔の狭いところと広い所が続きますが，狭いところが0，広いところが1を表しています．データの前半16bitはカスタマーコードで製品で共通です．後半16bitはデータでデータの前半8bitを反転させたものが後半8bitとなっており，ここでエラーチェックをしています．

ちなみにサンプリングレートが低いとこんな感じになります．かろうじて読めるかどうか．．．
![CZgPlecUYAA0sDG.png-large.png](https://qiita-image-store.s3.amazonaws.com/0/111511/22e69946-0e1f-c330-2f19-2902b59863bf.png)

# 5.データの送信
データの送信にはPWMのON，OFFで4章で取得した信号を再現します．
PICは16MIPSで動作させています．38kHz，デューティ比1/3のPWMとするために以下の設定にしています．
TIMER1
- Clock Source = FOSC/2
- Prescaler = 1:1
- Period 0x1A5 (26.313us)
OC3
- Mode = Edge-Aligne PWM mode
- Source = TMR1
- DCB = start of instruction cycle
- Trigger/Sync = Trigger
- Primary Compare = 0x8A
- Secondary Compare = 0x1A5 (Duty cycle 33%)

送信には下記コードを使用しました．(初期化関数は省略しています．)

```C:send_data.c
#define customerCode	0b0100000110110110
#define lightFull		0b01100101
#define lightUp			0b01011101
#define lightDown		0b11011101
#define lightOff		0b01111101
#define T				515

void sendReaderCode(){
	OC3_Start();
	__delay_us(16 * T);
	OC3_Stop();
	__delay_us(8 * T)
}

void sendCustomerCode(){
	int n;
	//customer Code
	for(n = 15; n >= 0; n--){
		if((customerCode >> n) & 0b1){
			send1();
		}else{
			send0();
		}
	}
}

void send0(){
	OC3_Start();
	__delay_us(T);
	OC3_Stop();
	__delay_us(T);
}

void send1(){
	OC3_Start();
	__delay_us(T);
	OC3_Stop();
	__delay_us(3 * T);
}

void sendStopBit(){
	OC3_Start();
	__delay_us(T);
	OC3_Stop();
}

void sendData(unsigned char data){
	int n;
	//data
	for(n = 7; n >= 0; n--){
		if((data >> n) & 0b1){
			send1();
		}else{
			send0();
		}
	}
	
	//data inverse
	for(n = 7; n >= 0; n--){
		if((data >> n) & 0b1){
			send0();
		}else{
			send1();
		}
	}
}

void sendCmd(unsigned char data){
	sendReaderCode();
	sendCustomerCode();
	sendData(data);
	sendStopBit();
}

int main(void) {
	// initialize the device
	SYSTEM_Initialize();
	
	while(1){
			sendCmd(LightOff);
			__delay_ms(1000);
			
			sendCmd(LightFull);
			__delay_ms(1000);
			
			sendCmd(LightDown);
			__delay_ms(500);
			sendCmd(LightDown);
			__delay_ms(500);
			
			sendCmd(LightUp);			
			__delay_ms(500);
			sendCmd(LightUp);			
			__delay_ms(500);
		while(1){
			IO_RA0_SetHigh();
		}
	}
}
```
送信間隔Tは562usがフォーマットの値ですが，処理時間も考慮して515usにしています．
実行するとお部屋のシーリングライトが
電気を消す→電気をつける→2段階暗くする→2段階明るくする
の動作をします．

# 6.今回はまった所
まず1つは，ストップビットを忘れていたことです．カスタマーコードも含めデータは32bitなのですが，最後にストップ人を挿入しないと最後のbitが0なのか1なのか分からなくなり認識してくれませんでした．
もう一つは，処理速度です．今回のマイコンは16MIPSで動作させています．省電力化のため0.5MIPS程度で動作させたかったのですが，処理速度が足らず，リモコンと同じタイミングで送信できていませんでした．(シフト演算とif文の分岐をなくしても変わりませんでした．OC3_Start()か__delay_us()で処理に時間がかかってる？)

# 7.まとめ
リモコンのデータを吸い出してマイコンから操作できるようにしました．
今後は時間によって明るさを自動調節するようにしたいと思います．

# 8.参考
[赤外線リモコンの通信フォーマット - ELM by ChaN](http://elm-chan.org/docs/ir_format.html)
[FAQ No. 1007798 (NECフォーマットの赤外線リモコン・フォーマット [共通])
](http://japan.renesas.com/support/faqs/faq_results/Q1000000-Q9999999/mpumcu/com/remo_012j.jsp)[秋月電子　リモコンフォーマット参考資料](http://akizukidenshi.com/download/k4174_format.pdf)