DAIKINのリモコンのフォーマット
ArduinoでDAIKINのエアコンをコントロールしたい。
IRremoteのライブラリ ではなぜかうまくいかない・・・なぜだ・・・
結論から言うと、家電協のフォーマットに従っていた。ただし、IRremoteはうまく扱えなかった。
フォトトランジスタ(図ではダイオード)につける抵抗は50kΩにしました。抵抗が小さい方がしっかり反応します。
ON,OFFが繰り返している様子が見て取れる。
ON時の波形をさらに細かく見ると次のようになる。
ON時は37kHzで振動している。
PWMの割合に関しては50%ではないのが少し気になる(後述50%でも問題なくエアコンは反応した)。
(注意)
フォトトランジスタは光が入ると電流を流すようになるので、出力電圧はLOWに落ちる。
つまり、赤外線LEDはフォトトランジスタで計測された結果のHIGH,LOWを反転させる必要がある。
赤外線リモコンのフォーマットについて
にある通り
- NEC
- 家電協
- SONY
の3つがある。
それぞれの特徴としては、
NEC
38kHz搬送
T=0.562ms
家電協
33~40kHz搬送
T=0.35ms~0.5ms
SONY
40kHz搬送
T=0.6ms
センサーについて
右がフォトトランジスタ、左がよくつかわれる赤外線リモコン用のセンサーだ。
ピンアサイン
2つの違い
実は違いがある!
赤外線リモコン用のセンサーは37kHzの光のみを選択的に計測する。
日常にあふれる光から特定周波数のみを見ることで外乱に強いセンサになる。
一方で出力が37kHzで振動していては、取り扱いにくいため平滑化が行われる。
振動が取り除かれている。
赤外線リモコン用のセンサの方が取り扱いやすいが、赤外線LEDを使ってリモコンを作ろうとしたときに37kHzで振動させることを忘れてしまう点には注意したい。
読み取る
Arduinoでパルスの間隔を計測する。pin2にリモコン用センサの出力をつなぐ。
pin2の外部割込みを使用し、立下り(FALLING)の時間間隔をus単位で計測する。
最後に割り込みされてから、一定以上時間が経った時に送信終了とみなし、結果をUSBで転送する。
#define box_length 300
volatile unsigned long time_box[box_length];
volatile int pos = 0;
volatile unsigned long before_time;
volatile boolean flag = false;
void setup() {
pinMode(2,INPUT);
attachInterrupt(0,time_save,FALLING);
Serial.begin(115200);
}
void time_save(){
unsigned long now_time = micros();
time_box[pos] = now_time - before_time;
before_time = now_time;
pos++;
if(pos>box_length){
pos = 0;
flag = true;
}
}
void loop() {
if(flag == true){
Serial.println("overflow");
}
unsigned long now_time = micros();
if( now_time - before_time > 500000 and pos > 0){
for(int i = 0 ; i < pos ; i++){
Serial.print(time_box[i]);
Serial.print(",");
}
Serial.println(" ");
pos = 0;
}
delay(10);
}
結果
8256416,864,864,868,864,868,25728,5192,1732,868,864,868,1728,868,864,864,868,1732,864,1732,1732,864,1732,1732,1732,1732,1732,864,868,1728,868,864,868,864,868,864,864,868,864,868,1728,868,1732,864,868,864,1732,1732,864,868,864,868,864,864,868,864,868,864,868,864,868,864,868,864,1732,1732,1732,864,1732,864,1732,1732,35064,5192,1732,864,868,864,1732,868,864,864,868,1732,864,1732,1732,868,1728,1732,1732,1732,1732,864,868,1732,864,864,868,760,972,864,868,864,868,788,940,1732,868,864,868,864,1728,868,868,864,868,864,864,868,864,868,868,792,936,868,864,864,868,864,868,864,1732,864,1736,864,1732,864,35064,5196,1728,864,868,868,1728,868,864,868,864,1732,868,1728,1732,868,1732,1732,1728,1732,1732,796,936,1732,864,868,864,868,864,868,864,864,868,864,868,864,864,868,864,868,864,868,1732,864,868,1732,864,864,1732,864,868,864,1736,1728,864,1736,864,868,764,964,864,868,868,864,868,864,864,868,864,868,864,1732,868,1728,1732,1732,1732,1732,864,868,864,868,864,868,864,868,864,868,864,864,868,1728,1736,864,868,860,868,868,864,868,864,864,868,1732,1732,864,868,864,864,868,864,868,864,868,864,868,864,868,864,864,868,864,1732,868,864,868,864,864,1732,1732,868,864,864,868,864,868,864,1732,868,864,864,868,864,868,868,864,1732,864,1732,1732,1732,864,1732,1732,
400~450の倍数の間に大体入っている。
の家電協のフォーマットだろう。
T=430usとして、
0ならT,T
1ならT,3T
みたいな時間間隔なので、このデータを0,1に変換してみよう。
自動的に計測結果からデータを読み出す
Python3でデータに変換する。
方針
外部割り込みはFALLINGのみである。
- 信号の長さが2Tなら0,4Tなら1として考えられる。
- 12Tなら先頭(Leader)であると考えられる。
- 一番最後のビットは0,1判別不可
- [Leader,data,Delay,data,Delay,data]のような構成をしている。先程の最後のビットと同じでDelayの前のビットも0,1の判別ができない。よって、Delay判定をした際に1をデータ前に追加
コード
p = "864,864,868,864,868,25728,5192,1732,868,864,868,1728,868,864,864,868,1732,864,1732,1732,864,1732,1732,1732,1732,1732,864,868,1728,868,864,868,864,868,864,864,868,864,868,1728,868,1732,864,868,864,1732,1732,864,868,864,868,864,864,868,864,868,864,868,864,868,864,868,864,1732,1732,1732,864,1732,864,1732,1732,35064,5192,1732,864,868,864,1732,868,864,864,868,1732,864,1732,1732,868,1728,1732,1732,1732,1732,864,868,1732,864,864,868,760,972,864,868,864,868,788,940,1732,868,864,868,864,1728,868,868,864,868,864,864,868,864,868,868,792,936,868,864,864,868,864,868,864,1732,864,1736,864,1732,864,35064,5196,1728,864,868,868,1728,868,864,868,864,1732,868,1728,1732,868,1732,1732,1728,1732,1732,796,936,1732,864,868,864,868,864,868,864,864,868,864,868,864,864,868,864,868,864,868,1732,864,868,1732,864,864,1732,864,868,864,1736,1728,864,1736,864,868,764,964,864,868,868,864,868,864,864,868,864,868,864,1732,868,1728,1732,1732,1732,1732,864,868,864,868,864,868,864,868,864,868,864,864,868,1728,1736,864,868,860,868,868,864,868,864,864,868,1732,1732,864,868,864,864,868,864,868,864,868,864,868,864,868,864,864,868,864,1732,868,864,868,864,864,1732,1732,868,864,864,868,864,868,864,1732,868,864,864,868,864,868,868,864,1732,864,1732,1732,1732,864,1732,1732"
#print(p)
l = p.split(',')
l_int = []
for m in l:
m = int(m)
l_int.append(m)
print(l_int )
code = ""
low = 350
high = 500
for time in l_int:
if(low*2 < time < high*2):
code += '1';
elif(low*4 < time < high*4):
code +='0'
elif(low*12 < time < high*12):
code +='L'
else:
code += '1' #Delayの前のデータは1,0判別不能なため
code += 'D'
print(time)
code += "1" #末尾のデータは構造上判別不能なのでとりあえず1にしておこう
print(code)
結果
'111111DL01110111101001000001101111111111010111001111111111111111000101001DL01110111101001000001101111111111101111011111111111111111110101011DL011101111010010000011011111111111111111101101101110010111111111111111010000011111111111110011111111110011111111111111111011111001111111011111111010001001'
とりあえず、同じようにLEDを発光させる
Timer1:37kHz発生
Timer2:450us割り込み
として、
1-> T,3T
0->T,T
D->30T (Delay)
L->8T,4T (Leader)
になるようクラスを作る。
方針
オブジェクト指向でやってみる。
赤外線データというオブジェクト"Code"を作る
- 送信データ
- 送信位置
- フェーズ情報
こんな感じに区分けする。タイマー割り込みが入るたびに"phase"はインクリメントされる。
- nextメソッド、タイマー割り込みが入ったときに呼び出される想定
1-> T,3T
0->T,T
D->30T (Delay)
L->8T,4T (Leader)
に従って、"phase"が既定値を超えると送信位置を動かす。
setup,loop内
/* OC1A,OC1B 37kHz出力
PWMモード:Fast PWM
分周比:1
OCR1A:コンペアマッチLOW
OCR1B:コンペアマッチLOW
*/
かなりレジスタを叩いて作った。
正直読み解けなくなってしまった。
搬送波38kHzを作るためのタイマーと、送信時間単位Tを作るタイマーの2つ必要になるが、
一つにまとめようとして難しくなった記憶がある。
コード
#ifndef CODE
#define CODE
#include <Arduino.h>
class Code {
private:
int phase = 0; //各文字の現在のフェーズ
int pos = 0; //文字位置
String code_string;
public:
Code(String n);
void next();
int state();
};
#endif
#include "Code.h"
Code::Code(String n) {
this->code_string = n;
}
int Code::state() {
char type = code_string[pos];
if (type == '1') { //1
if ( phase == 0) {
return 1;
} else {
return 0;
}
} else if (type == '0') { //0
if ( phase == 0) {
return 1;
} else {
return 0;
}
} else if (type == 'H') { //Header
if ( phase % 2 == 0 ) {
return 1;
} else {
return 0;
}
} else if (type == 'L') { //Leader
if ( phase <= 7 ) {
return 1;
} else {
return 0;
}
}else{
return 0;
}
}
void Code::next() {
phase = phase + 1;
char type = code_string[pos];
if (type == '1') { //1
if ( phase >= 2) {
pos ++;
phase = 0;
}
} else if (type == '0') { //0
if ( phase >= 4) {
pos ++;
phase = 0;
}
} else if (type == 'H') { //Header
if ( phase >= 12) {
pos ++;
phase = 0;
}
} else if (type == 'L') { //Leader
if ( phase >= 12) {
pos ++;
phase = 0;
}
} else if (type == 'D') { //Delay
if ( phase >= 50) {
pos ++;
phase = 0;
}
} else { //END
if ( phase >= 100) {
pos = 0;
phase = 0;
}
}
}
#include "Code.h"
volatile boolean flag = true;
volatile Code code("111111DL01110111101001000001101111111111010111001111111111111111000101001DL01110111101001000001101111111111101111011111111111111111110101011DL011101111010010000011011111111111111111101101101110010111111111111111010000011111111111110011111111110011111111111111111011111001111111011111111010001001E");
//volatile Code code("00");
void setup() {
pinMode(10, OUTPUT); //OC1B
pinMode(9, OUTPUT); //OC1A
/*タイマ1初期化*/
TCCR1A = 0;
TCCR1B = 0;
TCCR2A = 0;
TCCR2B = 0;
/* OC1A,OC1B 37kHz出力
PWMモード:Fast PWM
分周比:1
OCR1A:コンペアマッチLOW
OCR1B:コンペアマッチLOW
*/
TCCR1A |= (1 << WGM11) | (0 << WGM10)
| (1 << COM1A1) | (1 << COM1A0);
TCCR1B |= (1 << WGM13) | (1 << WGM12) | (1 << CS10);
ICR1 = 431;
OCR1A = 215;
//450us 割り込み
TCCR2A |= (1 << WGM21) | (1 << WGM20);
TCCR2B |= (1 << WGM22) | (1 << CS22) | (0 << CS21) | (0 << CS20) ;
OCR2A = 113;
//OCR2A = 255;
TIMSK2 |= (1 << OCIE2A);
//TIMSK2 &= ~(1 << OCIE2A);
}
ISR (TIMER2_COMPA_vect) {
flag = code.state();
//digitalWrite(10,flag);
if (flag) {
TCCR1A |= (1 << COM1A1) | (1 << COM1A0);
} else {
TCCR1A &= ~((1 << COM1A1) | (1 << COM1A0));
}
code.next();
}
void loop() {
}
で無事エアコンが反応した。
累計作業時間20hぐらい。
だいぶ時間をかけてしまった。
同じことをやりたい方はロジックアナライザは持っていた方が絶対うまくいく
1000円ぐらいなのでぜひ買おう
追記:IRIS OHYAMA 照明リモコン解析
アイリスオーヤマ LED シーリングライト 6畳 10段階調光タイプ リモコン付き 常夜灯 明るさメモリ機能 おやすみタイマー リビング 寝室 和室 台所 天井照明 CL6D-5.0
を購入したので、こちらでも行ってみる。
38kHzが搬送波であることがわかった。
白ー>常夜灯
1回目3445268,3028,6492,2040,2012,1020,1024,1020,1024,1020,1024,2012,1020,1024,1020,1024,1020,1024,2012,1024,2012,1020,1024,1020,1024,1020,1024,1020,1020,1024,1020,1024,1020,1020,1024,1020,2012,1024,2012,2012,1024,2012,2016
2回目
7629524,3008,6536,2016,2012,1024,1020,1020,1024,1020,1024,2012,1020,1024,1020,1020,1024,1020,2012,1024,2012,1020,1024,1020,1020,1024,1020,1020,1024,1020,1024,1020,1020,1024,1020,1024,2012,1020,2016,2012,1024,2012,2012,
常夜灯ー>切る
1回目
23184812,3004,6512,2016,2012,1020,1020,1024,1020,1020,1024,2012,1020,1020,1024,1020,1024,1020,2012,1020,2016,1020,1020,1024,1020,1020,1020,1024,1020,1020,1024,1020,1020,1024,1020,1020,2012,1024,2012,2012,1020,2012,2012,
2回め
9243732,3028,6488,2040,2016,1020,1020,1024,1020,1020,1024,2012,1020,1024,1020,1024,1020,1024,2012,1024,2012,1020,1024,1020,1024,1020,1024,1020,1024,1020,1020,1024,1020,1024,1020,1024,2012,1020,2016,2012,1024,2012,2012,
切るー>白
1回目
47751868,3004,6488,2040,2012,1020,1020,1020,1024,1020,1020,2012,1024,1020,1020,1024,1020,1024,2008,1020,2012,1024,1020,1020,1020,1024,1020,1020,1020,1024,1020,1020,1024,1020,1020,1020,2012,1024,2012,2012,1020,2012,2012,
2回目
8974264,3028,6488,2016,2032,1024,1020,1024,996,1020,1024,2036,1024,1024,1016,996,1024,1020,2040,1020,2012,1000,1016,1024,1024,1044,1020,1020,1000,1020,1020,1024,1044,1020,1020,1024,1988,1020,2036,2012,1024,1988,2012,
測定結果からわかること
T=1020として
x,3T,6.5T,2T,2T,[T,2T]
の信号列でないかと考えられる。
- 前述のリモコンフォーマット全てに一致しない
- 3つの動作が全て同じ信号である可能性が高い
p = "9243732,3028,6488,2040,2016,1020,1020,1024,1020,1020,1024,2012,1020,1024,1020,1024,1020,1024,2012,1024,2012,1020,1024,1020,1024,1020,1024,1020,1024,1020,1020,1024,1020,1024,1020,1024,2012,1020,2016,2012,1024,2012,2012"
#print(p)
l = p.split(',')
l_int = []
for m in l:
m = int(m)
l_int.append(m)
print(l_int )
code = ""
low = 900
high = 1100
for time in l_int:
if(low < time < high):
code += '0';
elif(low*2 < time < high*2):
code +='1'
elif(low*3 < time < high*3):
code +='L'
elif(low*6.5 < time < high*6.5):
code +='M'
else:
code += '1' #Delayの前のデータは1,0判別不能なため
code += 'D'
print(time)
code += "1" #末尾のデータは構造上判別不能なのでとりあえず1にしておこう
print(code)
結果
ライトON操作
計測1回目
1DLM11000000100000010100000000000000010110111
計測2回目
1DLM11000000100000010100000000000000010110111
常夜灯操作
1DLM11000000100000010100000000000000010110111
3回とも一致する。
つまり、どの操作も同じ信号を使っている。今回のプログラムで正常に解析できたと言える。
送信プログラム
ESP32でやってみる。
- 省エネルギー:Deep Sleepを使う
でDeep Sleepを使う。複数ピンを外部起動スイッチにするには
マスクで作る
ここで、BUTTON_PIN_BITMASKの作り方は
pin4なら、0x10=2^5になる。0x08ではない。
引っかかったポイント
何もしていない状態で何度もDeepSleepから抜けてしまう
TimerEnd(timer)をtimerを起動しない状態で実行したからだと思われる。
赤外線LED駆動
単純に抵抗方式では光が弱く、反応が不安定
トランジスタで電流を増やそうとしても不安定。
他の照明について
どこの製品かは不明
波形
搬送波が捉えられていない
搬送波は38kHzだった。
解析
この波形からわかること
- Long,shortの2つの情報で構成されている
- Long,shortの2つの時間的長さは同じ
- ON区間の差で2情報に差をつけている。
Long
short
Long,short2つで信号の時間的長さは同じなため、前述のプログラムでは変換できない。
さらに1:2みたいなきれいな比率から外れている。
解析プログラム
ON区間の時間を計測する方法がある。
一旦、手作業でも変換できるのでそのまま行く
LLSLSSSLSLSL
となる。
データを直接覚える方式
わざわざ解析するより、送信IRのONOFF時間を覚えてまるまるコピーして送信するほうが正攻法・省エネルギー・効率的かもしれない。