Top Page Arduino 赤外線リモコンで割込処理
自作工程のメモとして
Feb.2017


  年初にArduinoを掲載しましたが、PICとどこまで同じ処理ができるかを確認するため、赤外線リモコンの処理を確認してみました。
簡単な操作ならWeb上で公開されているスケッチで、簡単に動かすことができましたが、「温度・湿度・気圧・時計」を表示させながら、赤外線で秒の調整をしようとスケッチを合成したところ、時々上手く反応しません。そこでWeb上のスケッチを参考に、以前使ったPICの割り込み処理を組み込んでみました。


発信器は、ホームセンターで入手した「シンプルTVリモコン」という品です。(以下、シンプルリモコン)


Edit by bluegriffon2.1.2

1.最適なメーカーコードを探す

リモコンの信号構成は、大変便利なツールがWeb上の【きむ茶工房ガレージハウス】で公開されている RimoConView を使わせていただきました。   *検索 =>「《赤外線リモコンの送信データを探る》」
リモコンのデータ長がメーカーによって長短がありますが、このスケッチで、ほとんどのものが分かります。


■ リモコン信号の読取例

上の4バイト信号は、シンプルリモコン(メーカーコード:MrMax 681 に設定)
下側の8バイト信号は、松下電工の古い空気清浄器のリモコン

■ 調べたメーカーコードの信号例

リモコン名:オーム電機 シンプルTVリモコン AV-R530N

この中から、信号の構成がシンプルで、各キーを押したときに1バイトだけが変化するメーカーコードを選択しました。
その結果、メーカーコード:MrMax 681 を採用しました。

■ リモコン信号波形の例

信号の長さが約45ms(ビット構成により変化)で、リピート動作で同信号を出すのではなく、リピート信号を出しています。

■ スタート信号辺りの拡大

家製協(AEHA)フォーマットのスタート信号長(Lowレベル:8T=2.8uS〜4.0mS 、Highレベル:4T=1.4mS〜2.0mS)に入っています。

2.動作確認のイメージ

何か動かしてみるために採用したのが、温度・湿度・気圧センサー(BME280)と、時計機能(DS3231)です。
両チップとも3.3V駆動のため、I2C接続時に双方向電圧レベル変換モジュール(PCA9306)を入れています。

■ 動作確認のイメージ

Irセンサーの信号は、D2端子に接続しています。


3.動作確認のスケッチ

動作概要
時計関係
1.時計は1秒ごとに確認して表示を行っています。
2.温度・湿度・気圧は、時計の表示と同じタイミングで、読みだして表示しています。
3.温度・湿度・気圧の表示は、初期設定で小数点以下2桁の表示になっていたので、小数点以下1桁に丸めています。
4.時計の初期設定は、別のサンプルプログラムSetTime.ino(DS1307RTC-master)で行っているため、本スケッチでは設定できません。
5.時刻調整(秒の+-)は、シンプルリモコン以外にシリアルモニタから +- 文字を入れることによっても行えます。
6.桁上げ(59秒=>00秒)には対応していないため、表示時間が30秒の位置前後で調整をする必要があります。
7.タイマーライブラリは、0.1mSが必要になったので、FlexiTimer2 を使用しています。
8.赤外線センサからの信号は、INT0(D2ピン)に入り、立下り信号で割り込み処理 void ir_int() を実行するようにしています。

■ スケッチ例
 /*                        
// 2016/12/29 (最終更新:2017/01/04)
// タイマー関係は時分秒のみとした。
// COM側への出力はコメント処理で止めている。
// 時間調整 プラス・マイナス1秒を + - でCOMから入力する
// 注意:桁上がり桁下がりを考慮していないため、30秒前後の時に実行
// DS3231 を使用。ライブラリーは、DS1307
//
//
■赤外線リモコンフォーマットのメモ
割り込み用の設定 家製協(AEHA)フォーマット Lowアクティブ
T = 350us 〜 500us (425us TYPE)
LeaderFrame 12T = 4200us 〜 6000us
Data bit 0 : 2T = 700us 〜 1000us
                                  // 1.2mSが敷居値
Data bit 1 : 4T = 1400us 〜 2000us

  < LeaderFrame><  1 >< 0><  1 ><  1 >
--|        |----| |---| |-| |---| |---|
  |        |    | |   | | | |   | |   |
  |--------|    |-|   |-| |-|   |-|   |
  0        1    2     3   4     5     6

■データの例
手持ちの送信機が、Ntional 空気清浄器のリモコンのため、8バイトの処理にしている
(シリアル出力へのデータ例)
格安品
100000000111111110100100010110111 ( 1 FE 25 DA )
National 空気清浄器
0011010001001010100110000010111111011110111011001111000000001001 ( 2C 52 19 F4 7B 37 F 90 )
MrMAX:681
00001110011000001000000010001000 ( 70 06 01 11 )

■MsTimer2 では 1mS以下のタイミングが取れないため、FlexiTimer2 を採用した。

■赤外線リモコンは、オーム電機  シンプルTVリモコン AV-R530N
メーカーコード:MrMAX:681 をセット 1=( 70 06 01 11 ),2=( 70 06 01 12 )

■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
■■■■■■■ 外部割込みINT0 = PinD2(UNO)        ■■■■■■■
■■■■■■■ Mighty-1284pはPinD10(RX1)がINT0割り込み  ■■■■■■■
■ [消音]    tm.Second=0;時間をDS3231 へセットする    ■■■■■■■
■ [Vol UP] tm.Second++;時間をDS3231 へセットする    ■■■■■■■
■ [Vol DOWN] tm.Second--;時間をDS3231 へセットする    ■■■■■■■
■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■


*/
// ◆◆赤外線(IR)リモコンの関連
#define Last_Data_Point  4           // 読取るデータ長(且つ最終データ位置を読み取る)
#define DATA_END Last_Data_Point * 8 + 2
volatile int IN_red;        // 割り込まれる側の変数宣言にvolatile(揮発性?)を付加
volatile int t0_count;
volatile int IR_flag = 0;
volatile int irx = 0;
volatile byte IR_buff[10];  // 必ずバイト型を使うこと
volatile unsigned long time;
volatile unsigned long time2;
volatile int t2flag = 0;
/* ◆◆タイマー関連の割り込み時の処理
// FlexiTimer2の参照URL http://playground.arduino.cc/Main/FlexiTimer2
Flexitimer2がTimer 2を使用している間は、Timer 2で作成されたPWM信号は使用できなくなります。
(Arduino Unoでは、3番と11番のピンです。Arduino Megaでは、これらは9番と10番のピンです。)
*/
#include <FlexiTimer2.h>  // 0.1mSのタイミングを作るために、採用
void flash()    // FlexiTimer2 で0.1mS毎に割り込み処理
{
  time++;
  if (time2++ >= 3000) {
    t2flag = 1;
    time2 = 0;
  }
}
// ◆◆ここまで =====================

#define SerialOutFlag false  // ■■■シリアル出力する場合は true 、出力しない false
#define DebugFlag true  // ■■デバッグ用LED(Pin13)使う場合は true 、出力しない false
#define PicUp true // ■■デバッグ用(Pin8)使う場合は true 、出力しない false
#include <Wire.h>
#include <Time.h>
#include <DS1307RTC.h>

#include <LiquidCrystal_I2C.h>
LiquidCrystal_I2C lcd(0x3f, 16, 2);

const char *monthName[12] = {
  "Jan", "Feb", "Mar", "Apr", "May", "Jun",
  "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
};
// スイッチサイエンス社のBME280サンプルスケッチ
#define BME280_ADDRESS 0x76
unsigned long int hum_raw, temp_raw, pres_raw;
signed long int t_fine;

uint16_t dig_T1;
int16_t dig_T2;
int16_t dig_T3;
uint16_t dig_P1;
int16_t dig_P2;
int16_t dig_P3;
int16_t dig_P4;
int16_t dig_P5;
int16_t dig_P6;
int16_t dig_P7;
int16_t dig_P8;
int16_t dig_P9;
int8_t  dig_H1;
int16_t dig_H2;
int8_t  dig_H3;
int16_t dig_H4;
int16_t dig_H5;
int8_t  dig_H6;

tmElements_t tm;

int b4sec;    // 前回表示した秒値を格納。

int pin = 12;
volatile int state = LOW;

void setup()
{
  pinMode(13, OUTPUT);
  digitalWrite(13, LOW);
  pinMode(8, OUTPUT);
  digitalWrite(8, LOW);

  //LCD初期設定
  lcd.init();                      // initialize the lcd
  lcd.backlight();

  Serial.begin(9600);

  // BME280初期設定
  uint8_t osrs_t = 1;             //Temperature oversampling x 1
  uint8_t osrs_p = 1;             //Pressure oversampling x 1
  uint8_t osrs_h = 1;             //Humidity oversampling x 1
  uint8_t mode = 3;               //Normal mode
  uint8_t t_sb = 5;               //Tstandby 1000ms
  uint8_t filter = 0;             //Filter off
  uint8_t spi3w_en = 0;           //3-wire SPI Disable

  uint8_t ctrl_meas_reg = (osrs_t << 5) | (osrs_p << 2) | mode;
  uint8_t config_reg    = (t_sb << 5) | (filter << 2) | spi3w_en;
  uint8_t ctrl_hum_reg  = osrs_h;
  // I2CのSETUP
  Wire.begin();
  writeReg(0xF2, ctrl_hum_reg);
  writeReg(0xF4, ctrl_meas_reg);
  writeReg(0xF5, config_reg);
  readTrim();
  //
  // 割り込みの設定(外部割込みとタイマー割り込み)
  attachInterrupt(0, ir_int, FALLING);  // 外部割込みINT0 = Pin2、立下り(FALLING)割り込み
  FlexiTimer2::set(1, 1.0 / 10000, flash); // 1 x 0.1ms = 100us タイマー割り込みセット
  FlexiTimer2::start();
  IR_flag = 0;
  IN_red = 0;
  time2 = 0;

}

void loop()
{
  char SS;
  byte SB;
  // IR 関係の記述
  if (IR_flag == 1 ) {
    lcd.setCursor(14 , 0);
    lcd2digitsH(IR_buff[Last_Data_Point - 1 ] );
    if (IR_buff[0] == 0x70 ) SB = IR_buff[Last_Data_Point - 1 ]; // 第一バイト目を照合
    IR_flag = 0;
  }

  if (time >= 3000 ) {   // 300mS以上経過したら、
    time = 0;            // IRのタイマーオーバーフローを回避
    IN_red = 0;          // 赤外線リモコンの受信状態を解除
    if (DebugFlag )   digitalWrite(13, LOW); //   デバッグ用
  }
  // ここまでIR 関係の記述

  if (RTC.read(tm)) {    // RTCから時間が読み出せれば◆
    // 時間調整 プラス・マイナス1秒を + - でCOMから入力する
    // 注意:秒が30秒前後の時に実行すること
    // (簡易設定のため、桁上がり桁下がりを考慮せず)

    // ============Serial信号を使って時刻合わせ(秒)============
    while (Serial.available() > 0) { // 受信したデータが存在する
      SS = Serial.read();  // 受信データを読み込む
      if (SS == '+') { // 受信データがプラスなら?
        tm.Second++;
        // and configure the RTC with this info
        RTC.write(tm);    // 時間をDS3231 へセットする
      }
      if (SS == '-') { // 受信データがマイナスなら?
        tm.Second--;
        // and configure the RTC with this info
        RTC.write(tm);    // 時間をDS3231 へセットする
      }
    }
    // ============================================================
    // ============赤外線リモコンによる時刻合わせ(秒)============
    if (SB == 0x1f ) {  // [消音]
      tm.Second = 0;
      RTC.write(tm);    // 時間をDS3231 へセットする
    }
    if (SB == 0x20 ) {  // [Vol UP]
      tm.Second++;
      RTC.write(tm);    // 時間をDS3231 へセットする
    }
    if (SB == 0x21 ) {  // [Vol DOWN]
      tm.Second--;
      RTC.write(tm);    // 時間をDS3231 へセットする
    }
    SB = 0x00;
    if (b4sec != tm.Second ) { // 取得した秒値が違えば、表示する
      b4sec = tm.Second;
      TempClockDisp();
    }
    // delay(1000);
  } else {    // RTCから時間が読み出せ無ければ◆
    if (RTC.chipPresent()) {
      lcd.setCursor(0, 0);
      lcd.print("run the SetTime");
    } else {
      lcd.setCursor(0, 0);
      lcd.print("DS1307 read error!");
    }
    delay(9000);
  }
  if (t2flag ){
    state = !state;
    digitalWrite(pin, state);
    t2flag = 0;
  }

}  // ■■■■■■■■■■■■ loopEND ■■■■■■■■■■■■

void TempClockDisp() {    // 温度・湿度・気圧・時間を表示

  if (PicUp )   digitalWrite(8, HIGH); //   デバッグ用

  // シリアル出力が必要なら
  if (SerialOutFlag ) {
    Serial.print("Ok, Time = ");
    print2digits(tm.Hour);
    Serial.write(':');
    print2digits(tm.Minute);
    Serial.write(':');
    print2digits(tm.Second);
    Serial.print(", Date (D/M/Y) = ");
    Serial.print(tm.Day);
    Serial.write('/');
    Serial.print(tm.Month);
    Serial.write('/');
    Serial.print(tmYearToCalendar(tm.Year)); // Start Year1970 + tm.Year = 2016
    Serial.println();
  }

  // I2C.LCD へも出力する。
  // 時分秒
  lcd.setCursor(8, 1);
  lcd2digits(tm.Hour);
  lcd.print(":");
  lcd2digits(tm.Minute);
  lcd.print(":");
  lcd2digits(tm.Second);

  /*
      // 年月日 表示エリヤの関係で使わない
      lcd.setCursor(0, 1);
      lcd.print(tmYearToCalendar(tm.Year));
      lcd.print("/");
      lcd2digits(tm.Month);
      lcd.print("/");
      lcd2digits(tm.Day);
      lcd.print(" ");
      */
  // delay(100);

  // 温度・湿度・気圧を表示
  double temp_act = 0.0, press_act = 0.0, hum_act = 0.0;
  signed long int temp_cal;
  unsigned long int press_cal, hum_cal;
  int aa;

  readData();

  temp_cal = calibration_T(temp_raw);
  press_cal = calibration_P(pres_raw);
  hum_cal = calibration_H(hum_raw);
  temp_act = (double)temp_cal / 100.0;
  press_act = (double)press_cal / 100.0;
  hum_act = (double)hum_cal / 1024.0;
  //初期値が小数点以下2桁のため Serial.print(XXXXX,1); ,1 で
  //小数点以下2桁目を四捨五入し、小数点以下1桁出力とする。

  if (SerialOutFlag ) {
    Serial.print("  TEMP : ");
    Serial.print(temp_act, 1);
    Serial.print(" DegC  PRESS : ");
    Serial.print(press_act, 1);
    Serial.print(" hPa  HUM : ");
    Serial.print(hum_act, 1);
    Serial.println(" %");
  }
  // I2C LCD に表示する
  //初期値が小数点以下2桁のため lcd.print(XXXXX,1); ,1 で
  //小数点以下2桁目を四捨五入し、小数点以下1桁出力とする。
  lcd.setCursor(0, 0);
  lcd.print(temp_act, 1);
  lcd.print("c ");
  lcd.setCursor(8, 0);
  lcd.print(hum_act, 1);
  lcd.print("% ");
  lcd.setCursor(0, 1);
  lcd.print(press_act, 1);
  lcd.print("h ");

  if (PicUp )   digitalWrite(8, LOW); //   デバッグ用
}

// ■◆■■これ以降、タイマーDS3231関係の記述
void print2digits(int number) {
  if (number >= 0 && number < 10) {
    Serial.write('0');
  }
  Serial.print(number);
}

// LCDへの出力を2桁にする print2digits を参照して追加。
void lcd2digits(int number) {
  if (number >= 0 && number < 10) {
    lcd.print('0');
  }
  lcd.print(number);
}
// ■◆■■ここまで、タイマーDS3231関係の記述

////////////////////////////////////////////////////////////////
// ■■■これ以降、BME280 温度・湿度・気圧関係の記述
void readTrim()   // BME280の処理
{
  uint8_t data[32], i = 0;                   // Fix 2014/04/06
  Wire.beginTransmission(BME280_ADDRESS);
  Wire.write(0x88);
  Wire.endTransmission();
  Wire.requestFrom(BME280_ADDRESS, 24);      // Fix 2014/04/06
  while (Wire.available()) {
    data[i] = Wire.read();
    i++;
  }

  Wire.beginTransmission(BME280_ADDRESS);    // Add 2014/04/06
  Wire.write(0xA1);                          // Add 2014/04/06
  Wire.endTransmission();                    // Add 2014/04/06
  Wire.requestFrom(BME280_ADDRESS, 1);       // Add 2014/04/06
  data[i] = Wire.read();                     // Add 2014/04/06
  i++;                                       // Add 2014/04/06

  Wire.beginTransmission(BME280_ADDRESS);
  Wire.write(0xE1);
  Wire.endTransmission();
  Wire.requestFrom(BME280_ADDRESS, 7);       // Fix 2014/04/06
  while (Wire.available()) {
    data[i] = Wire.read();
    i++;
  }
  dig_T1 = (data[1] << 8) | data[0];
  dig_T2 = (data[3] << 8) | data[2];
  dig_T3 = (data[5] << 8) | data[4];
  dig_P1 = (data[7] << 8) | data[6];
  dig_P2 = (data[9] << 8) | data[8];
  dig_P3 = (data[11] << 8) | data[10];
  dig_P4 = (data[13] << 8) | data[12];
  dig_P5 = (data[15] << 8) | data[14];
  dig_P6 = (data[17] << 8) | data[16];
  dig_P7 = (data[19] << 8) | data[18];
  dig_P8 = (data[21] << 8) | data[20];
  dig_P9 = (data[23] << 8) | data[22];
  dig_H1 = data[24];
  dig_H2 = (data[26] << 8) | data[25];
  dig_H3 = data[27];
  dig_H4 = (data[28] << 4) | (0x0F & data[29]);
  dig_H5 = (data[30] << 4) | ((data[29] >> 4) & 0x0F); // Fix 2014/04/06
  dig_H6 = data[31];                                   // Fix 2014/04/06
}
void writeReg(uint8_t reg_address, uint8_t data)
{
  Wire.beginTransmission(BME280_ADDRESS);
  Wire.write(reg_address);
  Wire.write(data);
  Wire.endTransmission();
}
void readData()
{
  int i = 0;
  uint32_t data[8];
  Wire.beginTransmission(BME280_ADDRESS);
  Wire.write(0xF7);
  Wire.endTransmission();
  Wire.requestFrom(BME280_ADDRESS, 8);
  while (Wire.available()) {
    data[i] = Wire.read();
    i++;
  }
  pres_raw = (data[0] << 12) | (data[1] << 4) | (data[2] >> 4);
  temp_raw = (data[3] << 12) | (data[4] << 4) | (data[5] >> 4);
  hum_raw  = (data[6] << 8) | data[7];
}

signed long int calibration_T(signed long int adc_T)
{
  signed long int var1, var2, T;
  var1 = ((((adc_T >> 3) - ((signed long int)dig_T1 << 1))) * ((signed long int)dig_T2)) >> 11;
  var2 = (((((adc_T >> 4) - ((signed long int)dig_T1)) * ((adc_T >> 4) - ((signed long int)dig_T1))) >> 12) *
 ((signed long int)dig_T3)) >> 14;

  t_fine = var1 + var2;
  T = (t_fine * 5 + 128) >> 8;
  return T;
}

unsigned long int calibration_P(signed long int adc_P)
{
  signed long int var1, var2;
  unsigned long int P;
  var1 = (((signed long int)t_fine) >> 1) - (signed long int)64000;
  var2 = (((var1 >> 2) * (var1 >> 2)) >> 11) * ((signed long int)dig_P6);
  var2 = var2 + ((var1 * ((signed long int)dig_P5)) << 1);
  var2 = (var2 >> 2) + (((signed long int)dig_P4) << 16);
  var1 = (((dig_P3 * (((var1 >> 2) * (var1 >> 2)) >> 13)) >> 3) + ((((signed long int)dig_P2) * var1) >> 1)) >> 18;
  var1 = ((((32768 + var1)) * ((signed long int)dig_P1)) >> 15);
  if (var1 == 0)
  {
    return 0;
  }
  P = (((unsigned long int)(((signed long int)1048576) - adc_P) - (var2 >> 12))) * 3125;
  if (P < 0x80000000)
  {
    P = (P << 1) / ((unsigned long int) var1);
  }
  else
  {
    P = (P / (unsigned long int)var1) * 2;
  }
  var1 = (((signed long int)dig_P9) * ((signed long int)(((P >> 3) * (P >> 3)) >> 13))) >> 12;
  var2 = (((signed long int)(P >> 2)) * ((signed long int)dig_P8)) >> 13;
  P = (unsigned long int)((signed long int)P + ((var1 + var2 + dig_P7) >> 4));
  return P;
}

unsigned long int calibration_H(signed long int adc_H)
{
  signed long int v_x1;

  v_x1 = (t_fine - ((signed long int)76800));
  v_x1 = (((((adc_H << 14) - (((signed long int)dig_H4) << 20) - (((signed long int)dig_H5) * v_x1)) +
          ((signed long int)16384)) >> 15) * (((((((v_x1 * ((signed long int)dig_H6)) >> 10) *
            (((v_x1 * ((signed long int)dig_H3)) >> 11) + ((signed long int) 32768))) >> 10) +
              (( signed long int)2097152)) *
                ((signed long int) dig_H2) + 8192) >> 14));
  v_x1 = (v_x1 - (((((v_x1 >> 15) * (v_x1 >> 15)) >> 7) * ((signed long int)dig_H1)) >> 4));
  v_x1 = (v_x1 < 0 ? 0 : v_x1);
  v_x1 = (v_x1 > 419430400 ? 419430400 : v_x1);
  return (unsigned long int)(v_x1 >> 12);
} // ■■■ここまで、温度・湿度・気圧関係の記述
// ///////////////////////////////////////////////////////////////

// 赤外線割り込み発生時の処理 PIC用(MikroC)の記述を変更して対応
void ir_int()
{
  /*  // すでにIR信号を検出している(IR_flag = 1 )ときは、
      // loop()内で処理が完了するまでスキップする。            */
  if (IR_flag == 0) {
    if (DebugFlag )   digitalWrite(13, HIGH); //   デバッグ用
    switch (IN_red) {
      case 0:        // LeaderFrame のスタート
        t0_count = 0;
        time = 0;
        IN_red++;
        break;
      case 1:        // LeaderFrame のエンド
        t0_count = time;
        time = 0;
        if ((t0_count >= 42) && (t0_count <= 60)) { //実測4.9mS
          IN_red++;
        } else {                                     //範囲を外れた破棄
          IN_red = 0;
        }
        break;
      default:
        t0_count = time;
        time = 0;
        if (t0_count > 20) {               // 2.0mSより多ければ無効
          IN_red = 0;
        } else {
          irx = (IN_red - 2) / 8;
          IR_buff[irx] = IR_buff[irx] >> 1;
          if (t0_count <= 12) {           // 1.2mSが敷居値
            bitClear(IR_buff[irx], 7);    // '0' set  t0_count : 2T = 700us 〜 1000us
          } else {
            bitSet(IR_buff[irx], 7);    // '1' set  t0_count : 4T = 1400us 〜 2000us
          }
          IN_red++;
        }
        if (IN_red >= DATA_END ) {    // 最終ビットを受け取った 8バイトデータの場合=66
          IR_flag = 1;
          IN_red = 0;
        }
        break;
    }
  }
}
// LCDへの出力をHEX2桁にする print2digits を参照して追加。
void lcd2digitsH(int number) {
  if (number >= 0 && number <= 15) {
    lcd.print('0');
  }
  lcd.print(number, HEX);
}
// Serial出力をHEX2桁にする print2digits を参照して追加。
void print2digitsH(int number) {
  if (number >= 0 && number <= 15) {
    Serial.write('0');
  }
  Serial.print(number, HEX);
}


99.追記用(予備)