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


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


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


Page edited 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.追記用(予備)