きっかけ
リビングにあまり使ってない、PioneerのX-HM50というコンポがあり、これの電源ON/OFFをしたくなったのでやってみました。
単純に電源のON/OFFだけで、それ以外の信号は扱いません。余っていたESP8266で行いましたが、4bitのPWMが38kHzで使用できるならどんなボードでもできると思います。
特殊な点
なんと、このオーディオコンポに付属してくるリモコンは、ボタンを押すたびに送信信号が変わります。しかも通信の長さが1~5パケットと長いです。
そのため、既存のライブラリであるIRremoteESP8266ではうまく検出できず、自作することにしました。
赤外線リモコンに関する基礎知識
・38kHzで変調されている
Highのときは、38kHzのPWMでデューティー比が1/3~1/2ほどになっている。
自然光との区別をするためにこうなっているようです。
・業界である程度形式が決まっている
NEC方式だったり、Panasonic形式だったり、パケットの形式がある程度決まっているようです。
今回は使用しませんでした。
受信機(キャプチャ)側
信号の解析をしないと始まりません。赤外線リモコン受信モジュール(赤外線受光センサ)が転がっていた(多分何かの基板から取り外したやつ)のでそれを使用しました。型番・ピンアサイン不明でしたが、秋月に扱いのある、GP1UXC41QSやPL-IRM0101と同じ感じで動きました。センサの出力電圧レベルは5Vで、ESP8266は3.3Vですが、めんどくさいので変換回路無しで直接つなぎました。壊れずに済みました。
使用したコードはこんな感じです。
#include <Arduino.h>
#define TIMEOUT 500000 //500ms
#define IRIN_PIN 14 //IR sensor PIN
volatile uint32_t oldTime = 0;
volatile uint32_t long nowTime = 0;
volatile bool start = false;
void IRAM_ATTR onPinChange() {
oldTime = nowTime;
nowTime = micros();
if (start) {
Serial.print(nowTime - oldTime);
Serial.print(",");
} else {
start = true;
Serial.println();
}
}
void setup() {
Serial.begin(115200);
pinMode(IRIN_PIN, INPUT);
attachInterrupt(digitalPinToInterrupt(IRIN_PIN), onPinChange, CHANGE);
}
void loop() {
if ((TIMEOUT > micros()) && start) {
start = false;
Serial.println();
Serial.println("CycleCount Overflow");
}
if ((micros() > nowTime) && ((micros() - nowTime) > TIMEOUT) && start) {
start = false;
Serial.println();
Serial.println("Reset");
}
delay(10);
}
割り込みを使用してピンの状態が変化したら、前回変化したときからの時間差(μ秒)をシリアルで出力する感じです。そのため、1つめのデータはHighの長さ、2つめのデータはLowの長さ、3つめはHighの長さ…みたいな感じです。
ちゃんと測れていないこともあるので何回かリモコンを照射してそれっぽい値であることを確認してから、数字列をコピーしてください。まあ、今回のリモコンの場合は毎回信号が変わるのでできなかったんですけど…
送信機側
できれば受信機とは別で回路を作ることをおすすめします。オフセット時間の調整に使用します。
赤外線LEDもこれまた転がってたやつを使い、抵抗なしで直接ESP8266に繋いでます。繋いだほうがいいです。
先ほど受信機で計測した出力をcmdTimeのところに貼り付けします。
#define IR_LED 5
#define H_OFFSET -110 //depend on board
#define L_OFFSET +45 //depend on board
const uint16_t cmdTime[] = {
8546, 4179, 637, 431, 637, 1484, 637, 1484, 636, 432, 636, 432, 637, 1483, 637, 431, 636, 1485, 639, 1482, 638, 430, 636, 432, 636, 1484, 637, 1484, 637, 431, 635, 1486, 638, 430, 637, 431, 636, 432, 635, 1485, 639, 1482, 638, 1483, 637, 431, 637, 431, 639, 429, 637, 1484, 638, 1482, 638, 430, 635, 432, 636, 432, 636, 1485, 638, 1483, 636, 1485, 638, 25742, 8546, 4180, 635, 433, 635, 1485, 636, 1485, 638, 430, 637, 431, 635, 1485, 634, 435, 636, 1484, 635, 1486, 636, 432, 635, 433, 637, 1484, 635, 1485, 637, 431, 637, 1484, 636, 432, 636, 432, 636, 431, 637, 1485, 635, 1485, 636, 1485, 635, 433, 636, 432, 635, 433, 635, 1486, 635, 1485, 634, 434, 636, 432, 636, 432, 636, 1485, 635, 1485, 636, 1485, 635, 25745, 8546, 4180, 635, 433, 636, 1485, 636, 1484, 637, 431, 636, 432, 637, 1483, 636, 433, 635, 1486, 636, 1484, 635, 433, 636, 432, 636, 1485, 636, 1484, 636, 432, 635, 1487, 636, 431, 637, 431, 636, 432, 636, 1485, 636, 1484, 637, 1485, 636, 431, 637, 431, 636, 432, 636, 1484, 637, 1484, 636, 432, 636, 432, 637, 431, 636, 1485, 636, 1484, 637, 1484, 636
};
void setup() {
//Serial.begin(115200);
//Serial.println();
pinMode(IR_LED, OUTPUT);
analogWriteFreq(38000); //38kHz
analogWriteRange(15); //0~15
analogWrite(IR_LED, 0);
delay(1000);
int patternLength = sizeof(cmdTime) / sizeof(cmdTime[0]);
//Serial.println(patternLength);
bool ledStatus = false;
for (int i = 0; i < patternLength; i++) {
ledStatus = !ledStatus;
if (ledStatus) {
analogWrite(IR_LED, 7);
delayMicroseconds(cmdTime[i] H_OFFSET);
} else {
analogWrite(IR_LED, 0);
delayMicroseconds(cmdTime[i] L_OFFSET);
}
}
analogWrite(IR_LED, 0);
ledStatus = false;
}
void loop() {
}
H_OFFSETとL_OFFSETはHighの期間の長さと、Lowの期間の長さの調整(オフセット時間)です。調整がないと若干長くなったり、短くなったりしたので、その調整です。方法は簡単で、受信機にもう一度挿入して出力されたデータと比べてHighのとき、Lowのときの時間差を見てあげればH_OFFSETとL_OFFSETの大きさが算出できます。おすすめは、TAB, カンマ, 改行 相互変換でカンマを改行に変換してエクセルに貼り付けて、各値の差を取り、High時とLow時の平均をすると良いと思います。
コード自体は、電源が入った(リセット)1秒後に1度送信する感じです。
終わりに
できるようになるのに丸2日かかりました。かなり大変でした。ライブラリを試したり、オシロスコープを使ってキャプチャしてみたり、スマートリモコンを使ってみたり…結局は38kHzの変調を行っていないせいでした。