Arduino 赤外線リモコンで割込処理
自作工程のメモとして
Feb.2017
年初にArduinoを掲載しましたが、PICとどこまで同じ処理ができるかを確認するため、赤外線リモコンの処理を確認してみました。
簡単な操作ならWeb上で公開されているスケッチで、簡単に動かすことができましたが、「温度・湿度・気圧・時計」を表示させながら、赤外線で秒の調整をしようとスケッチを合成したところ、時々上手く反応しません。そこでWeb上のスケッチを参考に、以前使ったPICの割り込み処理を組み込んでみました。
発信器は、ホームセンターで入手した「シンプルTVリモコン」という品です。(以下、シンプルリモコン)
Edit by bluegriffon2.1.2
リモコンの信号構成は、大変便利なツールが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)に入っています。
何か動かしてみるために採用したのが、温度・湿度・気圧センサー(BME280)と、時計機能(DS3231)です。
両チップとも3.3V駆動のため、I2C接続時に双方向電圧レベル変換モジュール(PCA9306)を入れています。
■ 動作確認のイメージ
Irセンサーの信号は、D2端子に接続しています。
動作概要
時計関係
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);
}