がじぇっとるねさす

ホビーとエレクトロニクスを繋げるサイト

がじぇるね工房

作品タイトル:GR-KURUMIの機能を使う 外部割り込み編

表示名:@chobichan

GR-KURUMIの機能を使う 外部割り込み編

コンセプト・作品説明
GR-KURUMIの外部割り込み機能を使います
####

外部割込みを使ってリモコンハッキング

GR-KURUMIは端子番号2番と3番で端子状態の変化を割り込みとして受ける事ができます。

家電の赤外線リモコンは、だいたい38kHzの赤外線LEDのON/OFF信号(搬送波)を、ある長さだけ止めたり、開始したりして変調する方法で信号を伝達しています。

デジタル信号なので0と1のビット列となるのですが、各ビットは必ず搬送波の有り/無しで構成され、有りの時間に対して無しの時間が同じであれば0、有りの時間に対して無しの時間が3倍であれば1として1bitを再生します。

 

 

時間TはNECフォーマットが0.56ms、家製協フォーマットが0.4msです。マイコンの割り込みで38kHzの搬送波をサンプリングして0と1を判断する必要は無く、赤外線リモコンの受信専用のICが容易に入手できますので、これを利用します。
http://akizukidenshi.com/catalog/g/gI-04659/

とは言えこのICができるのは、搬送波の有る期間をLOW 、無い期間をHIGH で表現するだけですから、結局そのHIGH/LOWを元にbit再生処理は必要です。

####

NECフォーマット

赤外線リモコンのコード化の方式はいくつか存在します。代表的なのはNECフォーマット、家製協フォーマット、SONYフォーマットです。
※ SONYフォーマットで動く家電を持っていないので扱いません。
NECフォーマットの詳細は以下のURLを参考にしてください。
http://japan.renesas.com/support/faqs/faq_results/Q1000000-Q9999999/mpumcu/com/remo_012j.jsp

NECフォーマットはフレーム長が32bit固定長で扱い易い特徴が有りますが、事実上データは1byteだけです。
おかげで複雑な操作ができません。

天井照明など単純な操作で済む家電に使われています。

####

家製協フォーマット

家製協フォーマットはフレーム長が可変で、数byteから10数byteのデータの送信が行え、その分複雑になっています。

家電協フォーマットとなっていますが、この秋月電子通商が公開している資料が参考になります。
http://akizukidenshi.com/download/k4174_format.pdf
ChaNさんのサイトもとても参考になります。
http://elm-chan.org/docs/ir_format.html

エアコン等複雑な操作を必要とする家電に使われています。

####

リモコンコードについて

NECフォーマットにしろ、家製協フォーマットにしろ、業界標準規格ですが、家電メーカーは積極的にリモコンコードの公開は行っていないようです。と言うか全然公開していませんね。お金を出せば教えてくれるのかもしれませんが。

ネットを検索すると電子工作で赤外線リモコンのハッキングをしている方は沢山居られますが、自力で解析したり、解析した人が公開した情報を利用している様子です。
http://japan.renesasrulz.com/gr_user_forum_japanese/b/webcompilerupdate/archive/2014/10/24/gr-kurum.aspx
上記urlの「GR-KURUMで赤外線リモコンを受ける。」を参考にさせていただきました。

ただ、必ずメーカーからコードを入手しないとハッキングができないかと言えばそうではなく、マイコンで受信したデータパターンとリモコンのボタンの対応さえ取れればそれなりに家電を操作できるようになります。
学習リモコンと呼ばれるものがそれになります。パターンを覚えてそれを送信するだけなので、カスタムコードも一連のデータパターンとして扱う事ができ、がんばれば何でも対応が可能?です。

※カットアンドトライは不可避?

####

赤外線リモコン受信のための接続

受信専用ICのデータシートを見て配線を行います。
微弱な信号を扱う関係でノイズに弱い様です。コンデンサ等もきっちり指定の通りに用意しましょう。IR_RECVはGR-KURUMIの端子番号2に接続しました。オシロスコープの波形はIR_RECVをモニターしたものです。NECフォーマットとなっています。

 

 

####

赤外線リモコン受信プログラム

受信ICの出力を端子番号2に接続したので、この割り込みが発生した時間を「attachInterrupt」で登録したハンドラで都度記録し、割り込みの間隔を計算すればON時間、OFF時間が判ります。

NECフォーマットと家製協フォーマットの区別は、リーダー部の長さでできそうです。
NECフォーマットのリーダー部はON期間、OFF期間合わせて13.5ms、家製協フォーマットのリーダー部は4.8msと大きく異なっています。

以降、ONからONまでの時間を元にbit列に直し、8bit毎にデータとして保存します。
図は実行結果です。
NECフォーマットはオーデリック(オーヤマ照明)のLED照明です。
AEHA(家製協)フォーマットはシャープのテレビのAQUOSです。

 

 

サンプルのコードを掲載しておきます。

 

/*

Infra-red IR-remote capture program version 1.0
by Kaz Kinoshita, Insutitute of Daiichi Technologies
date of 2014-10-23

*/
/* ------------------------------------------------------------------------ */
/* 赤外線リモコンの受信をしてみる                  */
/*                                             designed by hamayan          */
/*                                             Copyright(C) hamayan         */
/*                                             since 2004 - 2016            */
/* ------------------------------------------------------------------------ */
#include  

#if !defined( _MULTITASK_H_ )
#define  dly_tsk(tim)  delay(tim)
#define  rot_rdq()
#define  loc_cpu()     interrupts()
#define  unl_cpu()     noInterrupts()
#endif /*_MULTITASK_H_ */

char *itoh( char *dst, unsigned int value );
void irRemocon_init( void );
void irRemocon_interrupt( void );
int  irRemocon_availableNEC( void );
int  irRemocon_availableAEHA( void );
int  irRemocon_rxNEC( byte dst[], int size );
int  irRemocon_rxAEHA( byte dst[], int size );
void IR_nec( void );
void IR_aeha( void );

#define  IR_RECV     2 // Ir RECEIVE
#define  IR_Pin      IR_RECV
#define  IR_CONT     3 // Ir LED ON/OFF
#define  IR_RECV_INT  0 // Ir RECEIVE interrupt number
#define  Max_IR_Bits 35
#define  Max_IR_Bits_AEHA 256
#define  HEADER_Low_min 8000UL
#define  Data_Low_min 200UL

unsigned long IR_Low_Time;    // holds IR bursting time
unsigned long IR_High_Time;   // holds IR high idling time
unsigned short IR_Data[Max_IR_Bits_AEHA] = {0,};  // holds the bit length of each data bit by micro-sec.
//unsigned long IR_Bits;   // 32 bits of IR data
int IR_State;
int IR_Active;       // when 1, the capturing IR data steam is valid
char IR_formatType;

void setup()
{
  Serial.begin( 9600 );
  Serial.println("IR remote control test.");
  attachInterrupt(IR_RECV_INT, irRemocon_interrupt, CHANGE);
  // 1 sets Pin 2 for interrupt with low/high-active edges

  while( 1 )
  {
    if( IR_Active == 1 )
    {
      if(IR_formatType == 'N') IR_nec();
      else if(IR_formatType == 'A') IR_aeha();
    }
  delay( 10UL );
  }
}

void loop()
{
}

char *itoh( char *dst, unsigned int value )
{
  sprintf( dst, "%02x", value );

  return dst;
}

/*************************************************************************/
/* IR_REMOCON初期化                                                      */
/*************************************************************************/
void irRemocon_init( void )
{
  IR_Low_Time = 0;    // holds IR bursting time
  IR_High_Time = 0;   // holds IR high idling time
  IR_Active = 0;       // when 1, the capturing IR data steam is valid
  IR_formatType = 0;
  IR_State = 0;
}

/*************************************************************************/
/* IR_REMOCON割り込み                                                    */
/*************************************************************************/
void irRemocon_interrupt( void )
{
  if(digitalRead(IR_Pin) == LOW)
  {
    if(IR_Active == 1)  //LOWからLOWまでの時間を計測して配列に保存する
    {
      if(IR_State < Max_IR_Bits_AEHA)
      {
        IR_Data[IR_State++] = (unsigned short)(micros() - IR_Low_Time);
      }
      else
      {
        irRemocon_init();
      }
    }
    IR_Low_Time = micros();
    return;
  }
  else
  {
    //IR_High_Time = micros();
    if(micros() - IR_Low_Time < Data_Low_min)  //LOWの期間が200μs以下をフィルターしている
    {
      IR_Active = 0;
      return;
    }

    unsigned long lTime = micros() - IR_Low_Time;
    if (lTime > HEADER_Low_min)  //LOWの期間が8000μsを超える場合はNECフォーマットだとしている
    {
      IR_Active = 1;
      IR_State = 0;
      IR_High_Time = millis();  //先頭フレームの後半の時間となる
      IR_formatType = 'N';
    }
    else if (lTime > 2500UL)  //LOWの期間が2500μsを超える場合は家製協(AEHA)フォーマットだとしている
    {
      IR_Active = 1;
      IR_State = 0;
      IR_High_Time = millis();  //先頭フレームの後半の時間となる
      IR_formatType = 'A';
    }
    else if (lTime > 2000UL)  //LOWの期間が2000μsを超える場合はSONYフォーマットだとしている
    {
      IR_Active = 1;
      IR_State = 0;
      IR_High_Time = millis();  //先頭フレームの後半の時間となる
      IR_formatType = 'S';
    }
  }
}

/*************************************************************************/
/* NECタイプのデータ数確認                                               */
/*************************************************************************/
int irRemocon_availableNEC( void )
{
  unsigned long hTime = millis() - IR_High_Time;
  //if (IR_Active == 1 && (IR_Data[IR_State] >= 10000 || IR_State >= 200 || hTime > 200UL))
  if (IR_Active == 1 && (IR_Data[IR_State] >= 10000 || hTime > 100UL))
  {
    return ((IR_State - 2) / 8) + 1;
  }

  return 0;
}

/*************************************************************************/
/* AEHAタイプのデータ数確認                                              */
/*************************************************************************/
int irRemocon_availableAEHA( void )
{
  unsigned long hTime = millis() - IR_High_Time;
  //if (IR_Active == 1 && (IR_Data[IR_State] >= 10000 || IR_State >= 200 || hTime > 200UL))
  if (IR_Active == 1 && (IR_Data[IR_State] >= 10000 || hTime > 200UL))
  {
  return ((IR_State - 2) / 8) + 1;
  }

  return 0;
}

/*************************************************************************/
/* NECタイプのデータ受信                                                 */
/*************************************************************************/
int irRemocon_rxNEC( byte dst[], int size )
{
  int i;
  volatile int limit = IR_State;
  int index = 0;

  for (i = 1; i <= limit; i++)
  {
    dst[index] >>= 1;
    if ( IR_Data[i] > 1500 ) dst[index] |= 0x80;
    if( !(i % 8) ) index++;
    if(index >= size) break;
  }

  IR_Active = 0;    // Close IR-bit decoding session and wait for the next stream
  IR_State = 0;

  return index;
}

/*************************************************************************/
/* AEHAタイプのデータ受信                                                */
/*************************************************************************/
int irRemocon_rxAEHA( byte dst[], int size )
{
  int i;
  volatile int limit = IR_State;
  int index = 0;

  for (i = 1; i <= limit; i++)
  {
    dst[index] >>= 1;
    if ( IR_Data[i] > 1200 ) dst[index] |= 0x80;
    if( !(i % 8) ) index++;
    if(index >= size) break;
  }

  IR_Active = 0;    // Close IR-bit decoding session and wait for the next stream
  IR_State = 0;

  return index;
}

/****************************************************************************/
/* IR RECEIVE for NEC format                                                */
/****************************************************************************/
void IR_nec( void )
{
  int sz;

  if( (sz = irRemocon_availableNEC() ) >= 4 )
  {
    byte *iRData = new byte[sz];
    sz = irRemocon_rxNEC( iRData, sz );

    String str = "";
    char asc[8];
    for(int i = 0; i < sz; i++)
    {
      str += itoh(asc,(unsigned int)iRData[i]);
      str += ",";
    }
    delete[] iRData;

    if(str.length() > 0)
    {
      Serial.print("type NEC:sz=");
      Serial.print(sz,DEC);
      Serial.print(" code:");
      //      Serial.write((const unsigned char*)&str[0],str.length());
      Serial.println(str);
    }
  }
}

/****************************************************************************/
/* IR RECEIVE for AEHA format                                               */
/****************************************************************************/
void IR_aeha( void )
{
  int sz;

  if( (sz = irRemocon_availableAEHA()) >= 6 )
  {
    byte *iRData = new byte[sz];
    sz = irRemocon_rxAEHA( iRData, sz );

    char buf[8];
    String str = "";
    for(int i = 0; i < sz; i++)
    {
      str += itoh(buf,(unsigned int)iRData[i]);
      str += ",";
    }
    delete[] iRData;

    if(str.length() > 0)
    {
      Serial.print("type AEHA: sz=");
      Serial.print(sz,DEC);
      Serial.print(" code:");
      //      Serial.write((const unsigned char*)&str[0],str.length());
      Serial.println(str);
    }
  }
}


 

####

リモコン送信のための接続

送信専用のICみたいな物の存在は不明なので、送信に関してはマイコンのポートで直接出力します。
IR_CONTはGR-KURUMIの端子番号3に接続されています。

複数の赤外線LEDを同時にON/OFFさせる為にトランジスタを使用していますが、「多重化された端子編」で調べた様にGR-KURUMIの端子はアナログ兼用ポート以外は思った以上に電流を流せますので、1つのLEDであれば直接ポートでLEDを駆動しても問題は無いと思います。

図の回路はIR_CONTがLOWの時、LEDがONします。

 

####

赤外線リモコン送信プログラム

受信プログラムでLED照明やテレビのリモコンコードを取得したので、今度はそのコードを送信してみます。

IR_CONTが接続されている端子番号3はanalogWriteでPWM出力が可能です。このPWM出力を使って38kHzの搬送波を生成します。
搬送波を時間で出力したり、停止する事でbitを変調します。

 

pinMode(IR_CONT, OUTPUT);
analogWriteFrequency((uint32_t)38 * 1000);  //set IR carrier

 

この38kHzは、38kHz近辺で構いません。39kHzとか40kHzでも受信側は許容します。多分。

1の変調は以下の方法とします。

 

 #define T_VALUE  555UL
  #define PPM_ON   170
  #define PPM_OFF  255

  analogWrite(IR_CONT,PPM_ON);
  delayMicroseconds(T_VALUE * 1);  //1T
  analogWrite(IR_CONT,PPM_OFF);
  delayMicroseconds(T_VALUE * 3);  //3T

 

0の変調は以下の方法とします。

 

   analogWrite(IR_CONT,PPM_ON);
   delayMicroseconds(T_VALUE * 1);  //1T
   analogWrite(IR_CONT,PPM_OFF);
   delayMicroseconds(T_VALUE * 1);  //1T

 

受信プログラムでサンプリングしたコードは以下になります。

 

const unsigned char TVOnOff[] = {0xaa,0x5a,0x8f,0x12,0x16,0xd1};
const unsigned char light01On[]  = {0x84,0x51,0x9a,0x65};
const unsigned char light01Off[] = {0x84,0x51,0x08,0xf7};

 

テレビ(AQUOS)の電源入り切りは同じコードで行います。
LED照明の電源入り切りは異なっていました。
このコードを先の変調方法で1bitずつ送出し、最後にSTOP bitを付加します。



サンプルのコードを掲載しておきます。

 

/*

Infra-red IR-remote capture program version 1.0
by Kaz Kinoshita, Insutitute of Daiichi Technologies
date of 2014-10-23

*/
/* ------------------------------------------------------------------------ */
/* 赤外線リモコンの送信をしてみる                                           */
/*                                             designed by hamayan          */
/*                                             Copyright(C) hamayan         */
/*                                             since 2004 - 2016            */
/* ------------------------------------------------------------------------ */
#include 

#if !defined( _MULTITASK_H_ )
#define  dly_tsk(tim)  delay(tim)
#define  rot_rdq()
#define  loc_cpu()     interrupts()
#define  unl_cpu()     noInterrupts()
#endif /*_MULTITASK_H_ */

void irRemocon_txNEC(const byte data[], unsigned int sz);
void irRemocon_txAEHA(const byte data[], unsigned int sz);

#define  IR_CONT     3 // Ir LED ON/OFF

const unsigned char TVOnOff[] = {0xaa,0x5a,0x8f,0x12,0x16,0xd1};
const unsigned char light01On[]  = {0x84,0x51,0x9a,0x65};
const unsigned char light01Off[] = {0x84,0x51,0x08,0xf7};

void setup()
{
  Serial.begin( 9600 );
  Serial.println("IR remote control test.");
  pinMode(IR_CONT, OUTPUT);
  analogWriteFrequency((uint32_t)38 * 1000);  //set IR carrier

  while( 1 )
  {
    Serial.println("TV on.");
    irRemocon_txAEHA( TVOnOff, sizeof(TVOnOff) );
    delay( 1000UL );
    Serial.println("Light off.");
    irRemocon_txNEC( light01Off, sizeof(light01Off) );
    delay( 5 * 1000UL );
    Serial.println("TV off.");
    irRemocon_txAEHA( TVOnOff, sizeof(TVOnOff) );
    delay( 1000UL );
    Serial.println("Light on.");
    irRemocon_txNEC( light01On, sizeof(light01On) );
    delay( 5 * 1000UL );
  }
}

void loop()
{
}

char *itoh( char *dst, unsigned int value )
{
  sprintf( dst, "%02x", value );

  return dst;
}

/*************************************************************************/
/* NECタイプのデータ送信                                                 */
/*************************************************************************/
void irRemocon_txNEC(const byte data[], unsigned int sz)
{
//#define T_VALUE  562UL
#define T_VALUE  555UL
#define PPM_ON   170
#define PPM_OFF  255

loc_cpu();
analogWrite(IR_CONT,0);
//frame ppm on
analogWrite(IR_CONT,PPM_ON);
delayMicroseconds(T_VALUE * 16);  //16T
//frame ppm off
analogWrite(IR_CONT,PPM_OFF);
delayMicroseconds(T_VALUE * 8);  //8T

for(int i = 0; i < sz; i++)
{
byte temp = data[i];
for(int j = 0; j < 8; j++)
{
if(temp & 0x01)
{
//frame ppm on
analogWrite(IR_CONT,PPM_ON);
delayMicroseconds(T_VALUE * 1);  //1T
//frame ppm off
analogWrite(IR_CONT,PPM_OFF);
delayMicroseconds(T_VALUE * 3);  //3T
}
else
{
//frame ppm on
analogWrite(IR_CONT,PPM_ON);
delayMicroseconds(T_VALUE * 1);  //1T
//frame ppm off
analogWrite(IR_CONT,PPM_OFF);
delayMicroseconds(T_VALUE * 1);  //1T
}
temp >>= 1;
}
}

//stop bit
//frame ppm on
analogWrite(IR_CONT,PPM_ON);
delayMicroseconds(T_VALUE * 1);  //1T
//frame ppm off
analogWrite(IR_CONT,PPM_OFF);
unl_cpu();
}

/*************************************************************************/
/* AEHAタイプのデータ送信                                                */
/*************************************************************************/
void irRemocon_txAEHA(const byte data[], unsigned int sz)
{
#undef T_VALUE
#define T_VALUE  425UL
//#define PPM_ON   170  //170
//#define PPM_OFF  255

loc_cpu();
analogWrite(IR_CONT,0);
//frame ppm on
analogWrite(IR_CONT,PPM_ON);
delayMicroseconds(T_VALUE * 8);  //8T
//frame ppm off
analogWrite(IR_CONT,PPM_OFF);
delayMicroseconds(T_VALUE * 4);  //4T

for(int i = 0; i < sz; i++)
{
byte temp = data[i];
for(int j = 0; j < 8; j++)
{
if(temp & 0x01)
{
//frame ppm on
analogWrite(IR_CONT,PPM_ON);
delayMicroseconds(T_VALUE * 1);  //1T
//frame ppm off
analogWrite(IR_CONT,PPM_OFF);
delayMicroseconds(T_VALUE * 3);  //3T
}
else
{
//frame ppm on
analogWrite(IR_CONT,PPM_ON);
delayMicroseconds(T_VALUE * 1);  //1T
//frame ppm off
analogWrite(IR_CONT,PPM_OFF);
delayMicroseconds(T_VALUE * 1);  //1T
}
temp >>= 1;
}
}

//stop bit
//frame ppm on
analogWrite(IR_CONT,PPM_ON);
delayMicroseconds(T_VALUE * 1);  //1T
//frame ppm off
analogWrite(IR_CONT,PPM_OFF);
unl_cpu();
dly_tsk(10);
}


 

####

IoT実現のための赤外線リモコンハッキング

自分でIoTを実現したい時、赤外線リモコンのハッキングはお奨めです。
直接機器の電源を操作する事が無いので、安全性を高く保つ事を期待できます。

※電気製品のAC入力を自動的だったり遠隔からの操作で直接入り切りするような装置は法律上アウトの可能性があります。

なんにせよ、留守の間に火事になっていたら目も当てられませんからね。
十分気を付けてください。

@chobichan

極一般のハードウエアエンジニア。たまに雑誌記事を書いています。
twitterアカウント:https://twitter.com/chobichan

フォローする

share

ページトップへ