GR-COTTONで赤外線リモコンの受信機を作ってみた


# 概要
 SWEST18の会場でがじぇっとルネサスのGR-COTTONプレゼントキャンペーンをしていたのでArduinoライクな開発環境を触ってみました。
 とりあえず、赤外線リモコンの受信機を作ってみます。GR-COTTONのページに赤外線受信のサンプルが公開されていますが、人の作ったライブラリを使うだけではつまらないので自前で開発します。
今回はNECフォーマットの赤外線信号をデコードしてコンソロールに出力するまでです。

# 使用機材
  - GR-COTTON (http://akizukidenshi.com/catalog/g/gK-09867/)
  - 赤外線受信ジュール SPS440-1 (http://akizukidenshi.com/catalog/g/gI-00614/)
    - 秋月で購入してきました。秋月で購入するとモジュールのほかに解説書が同梱されていますが、ピン配置が誤っているので注意が必要です。
    - このモジュールの出力端子は受信信号が有りの時はLow、無の時はHighを出力します。 
  - ブレッドボードとケーブル
  
# 参考にしたWebページ
 - GR-COTTON関係
    - ガジェットルネサス
        - http://gadget.renesas.com/ja/
        - がじぇっとルネサスのホームページ、Webコンパイラが公開されておりユーザ登録すればローカルにコンパイラを用意しなくても開発できます。
        - 今回はwebコンパイラを使用しました。
    - GR-COTTON特設ページ
        - http://gadget.renesas.com/ja/product/cotton_sp1.html 
        - Webコンパイラの使い方やGR-COTTONにプログラムをダウンロードする手順を解説しています。
        
 - 赤外線通信関係
    - ルネサスの赤外線プロトコルの解説 
        - https://support.renesas.com/hc/ja/articles/210901268
        - NECフォーマットの開発元によるプロトコル解説です。正規の規格書ではありませんが、平易に解説された記事だと思います
    - Arduinoで遊ぶページさんの解説
        - http://garretlab.web.fc2.com/arduino/make/infrared_sensor_nec_format/
        - こちらも良くまとまっている記事です。

# ハードウェアの構成
赤外線受信ジュール(SPS440-1)をブレッドボードに実装して、配線を画像のように接続しました。秋月が同梱している解説書では右からGND、信号出力、Vccになっていますが、実物は右からVcc、信号出力、GNDです。Vccには5Vを供給してください。
![senser.jpg](https://qiita-image-store.s3.amazonaws.com/0/81077/da770baa-2732-8c79-a85b-6f450cf4e3cd.jpeg)
GR-COTTONとは以下のように接続してください。電源は5V出力、信号線は6番ピンに接続しています。
![cotton.jpg](https://qiita-image-store.s3.amazonaws.com/0/81077/8e1e523c-9ce5-1e6e-3627-113193d4f7e0.jpeg)

# ソースコード
ソースコードは以下のようにしました。受信データのデータ幅を計測して規定値に収まっている場合のみ受信データをコンソロールに出力します。
SPS440-1は受信信号が有る時にはLow、無い時にはHighを出力します。SPS440-1の逆相出力のモジュールを使う場合はR_LOW、R_HIGHのdefine文を書き換えてください。

```c:ir_read.c
﻿/*GR-COTTON Sketch Template Version: V1.01*/
#include <Arduino.h>

#define RCV_PIN 6
#define R_LED 22
#define G_LED 23
#define B_LED 24
#define R_LOW 1
#define R_HIGH 0
#define D_T 562 //データ信号の単位幅 (562us)
#define D_T_HALF 281 //281us
#define BIT_1_LOW_W (D_T*3)
#define BIT_0_LOW_W D_T
 
void setup(){
  Serial.begin(9600);
  pinMode(RCV_PIN,INPUT);
  pinMode(R_LED,OUTPUT);
  pinMode(G_LED,OUTPUT);
  pinMode(B_LED,OUTPUT);
  digitalWrite(G_LED, HIGH);
  digitalWrite(B_LED, HIGH);
  digitalWrite(R_LED, HIGH);
}
 
#define INF_ERROR 0
#define INF_OK 1  
 
//NECフォーマットフォーマットの信号1bit受信受信する
unsigned int inf_rcv_bit(unsigned long *edge_time, unsigned char *r_bit) {
  unsigned long edge_low, low_width, time_out, current;
  unsigned int rcv_data;
  
  
  //仕様ではHIGH信号をD_T(0.56ms)受信する
  //  マージンをとってD_T+D_T_HALF以上HIGH
  //  受信したらタイムアウト判定する
  time_out = *edge_time + D_T + D_T_HALF;
  
  while(micros() < time_out){
  	if(digitalRead(RCV_PIN) == R_LOW ){
  		edge_low = micros();
  		//HIGH信号をD_T_HALF以上の期間受信したらLOW区間計測に移る
  		if((edge_low - *edge_time) > D_T_HALF){
  		  break;
  		}
  		else{
  			//HIGH信号がD_T_HALF未満の時間でLOWになったらエラー
  			return INF_ERROR;
  		}
  	}
  }
  
  //仕様上LOW区間はBIT_1_LOW_W(1.685ms)以上は継続しない
  //  マージンをとってBIT_1_LOW_W+D_T_HALF以上のLOW信号を
  //  受信したらタイムアウト判定する
  time_out = edge_low + BIT_1_LOW_W + D_T_HALF;
  while(micros() < time_out){
    if (digitalRead(RCV_PIN) == R_HIGH) {
      *edge_time = micros();
      break;
    }
  }
  
  low_width = *edge_time - edge_low;
  
  if(low_width < (D_T + D_T_HALF)){
  	*r_bit = 0x00;

  }
  else{
  	*r_bit = 0x80;
  }
  return INF_OK;
}

//シリアル信号を8bitパラレルに変換
unsigned int inf_rcv_byte(unsigned long *edge_time, unsigned char* rcv_byte) {
  unsigned long current;
  unsigned char r_byte, r_bit;
  unsigned int rcv_data, i, status;
  
  r_byte = 0;
  
  for(i=0;i<8;i++){
  	status = inf_rcv_bit(edge_time, &r_bit);
  	r_byte = (r_byte>>1) | r_bit;
  	if(status == INF_ERROR) return INF_ERROR;
  }
  *rcv_byte = r_byte;
  return INF_OK;
}

//正相と逆走のデータコードをチェックする。
unsigned int inf_rcv_byte_with_check(unsigned long *edge_time, unsigned char *rcv_byte) {
	unsigned char r_byte, r_byte_n, status;
	
	r_byte = r_byte_n = 0;
    status = INF_OK;
    
    status &= inf_rcv_byte(edge_time, &r_byte);
	status &= inf_rcv_byte(edge_time, &r_byte_n);
	
	if(status == INF_ERROR) return INF_ERROR;
	
	if(r_byte != (~r_byte_n & 0xFF)){
		//for debug
		Serial.print("Error: recived data don't match check code(");
		Serial.print(r_byte, HEX);
		Serial.print(",");
		Serial.print(r_byte_n, HEX);
		Serial.print(")\n");
		return INF_ERROR;
	}
	*rcv_byte =  r_byte;
	return INF_OK;
}

void wait_1ms(unsigned long *edge_time){
	while(micros() < (*edge_time+500));
	*edge_time += 1000; 
}


#define READER_HIGH_W 9000
#define READER_LOW_W 4500
#define READER_MARGIN 500

//信号がリーダーコードかどうかを判定する
//  リーダーコードの場合はtrueを返す
boolean check_reader(unsigned long *edge_time){
  unsigned long time_out, width, current, low_edge;
  current = *edge_time;
  //wait high
  time_out = current + READER_HIGH_W + READER_MARGIN;
  //HIGH信号の期間を計測
  while((digitalRead(RCV_PIN) == R_HIGH) && (micros() < time_out));
  low_edge = micros();
  width = low_edge - *edge_time;
  
  //タイムアウトしたかHIGH信号の期間が短いとリピートではない。
  if ((digitalRead(RCV_PIN) == R_HIGH) || width < (READER_HIGH_W - READER_MARGIN))
    return false;
  
  //wait low
  time_out = low_edge + READER_LOW_W +READER_MARGIN;
  while((digitalRead(RCV_PIN) == R_LOW) && (micros() < time_out));
  current = micros();
  width = current - low_edge;
  if ((digitalRead(RCV_PIN) == R_LOW) || width < (READER_LOW_W - READER_MARGIN)) 
    return false;
  
  *edge_time = current;
  return true;
}

unsigned int inf_rcv_data(unsigned long *start_time, unsigned char *rcv_code){
	unsigned int status;
	
	status = inf_rcv_byte(start_time, rcv_code+2);
	if(status == INF_ERROR) return INF_ERROR;
	status = inf_rcv_byte(start_time, rcv_code+1);
	if(status == INF_ERROR) return INF_ERROR;
	status = inf_rcv_byte_with_check(start_time, rcv_code);
	if(status == INF_ERROR) return INF_ERROR;
}

void print_code(unsigned char *rcv_code){
	Serial.print("Customer code = ");
	Serial.print(rcv_code[2], HEX);
	Serial.print(rcv_code[1], HEX);
	Serial.print("\ndata = ");
	Serial.print(rcv_code[0], HEX);
	Serial.print("\n");
}
 
 
void loop() {
	unsigned long wait_count;
	unsigned long edge_det_time;
	unsigned long next_time;
	unsigned char status;
	unsigned char rcv_data[3] = {0,0};
	
	if(digitalRead(RCV_PIN) == R_HIGH){
		edge_det_time = micros();
		if(check_reader(&edge_det_time)){
			if(inf_rcv_data(&edge_det_time, rcv_data) == INF_OK){
			  print_code(rcv_data);
			}
		}
	    //受信信号がLOWになるのを待つ
		while(digitalRead(RCV_PIN) == R_HIGH);
	}
	
}
```
# 実行例
Kurumi Writeでプログラムを書き込んでSerial MonitoのOpenボタンをクリックすると赤外線信号が受信準備が完了します。NECフォーマットの赤外線信号を受信すると以下のような出力が得られます。画像の例は東芝のテレビリモコンで試したものです。
![ir_result.png](https://qiita-image-store.s3.amazonaws.com/0/81077/16af990d-742e-12ba-9bae-72c01cd97725.png)

# Arduinoな開発環境を使ってみて
 個人的にがじぇっとルネサス系のボードは大量に持っているのですがArduinoな開発環境を使ってみたのは初めてでした。
使ってみた感想としてはスタートアップルーチンやIOの初期化等が用意されているため、実際に動かすまでの期間が短く済むのはいいと思いました。
ただ、マルチタスクのような複雑な処理やハードウェアを細かく操作しようとすると面倒になる気がします。(これは私がArduinoの使い方を理解できてないだけかもしれませんが)
あと、どうでも良い感想ですがArudinoのサンプルコードを読んでいると、ラダーでプログラムを書いていたころを思い出しました。

現在はRX向けのtoppersで手一杯なので積極的に触るのは難しいですが、時間ができればもう少し触って行きたいと思います。