Arduinoを使った赤外線リモコンの作り方~送信機編
目的
前回、Arduinoと赤外線LEDを使って、リモコンの信号を読み取りました。
今回は送信機能を実装し、実際に家電を操作してみます。
さらに、スマートスピーカーとの連携を見据え、PCからのシリアル通信で信号を遅れるようにします。
リモコンの信号について
前回に続き、家電協(AEHA)方式とNEC方式について、送信方法について解説します。
基本的にLEDのON, OFFで信号を通信するのですが、LEDがONの時、38kHz、デューティサイクル1/3で変調がかかっています。
例えば、あるAEHA方式の信号をオシロスコープで見てみると、リーダーのONが8T,OFFが4Tあって、その後データ部分が続いているように見えますが…
すこし拡大してみると、高速にON,OFFを繰り返していることがわかります。
もっと拡大してみると、約38kHz, デューティサイクル1/3のパルスでON,OFFが繰り返されているのがわかります。
38kHzの逆数をとると、周期は26μs、ONが9μs, OFFが17μs程度です。Arduinoで実装するときは、高速にON, OFFを繰り返します。
簡易送信機の作成
回路と必要なもの
- Arduino (UNO等)
- 赤外線LED (広角、λ=950nm付近) … 4個
- YSL-R531FR1C-F1など
- 適当な色のLED … 1個
- タクトスイッチ … 1個
- 抵抗 … 1個
リモコンを固定して、部屋中の家電を操作しようとすると、赤外線LEDを部屋中に向ける必要があります。また、赤外線LEDだけだと、動いているかわからないので、適当な色のLEDを光らせて動作の確認が出来るようにしています。タクトスイッチは動作確認用でプルアップ回路になっています。押したら適当な信号を出すようにします。
信号はデジタル入出力のPORTDを使って出力します。赤外線LED 4個と、確認用のLEDを1個を、それぞれ2~6番ピンに繋いでいます。
(LEDをArduinoのデジタルピンに抵抗無しで直接繋いでいますが、本来ならば200Ω程度の抵抗を入れるべきです。直挿しだと電流が流れすぎて、LEDが焼き切れ、Arduinoも故障する可能性があります。とは言え、かれこれ半年ほど問題なく使えていますので、真似する場合は自己責任で。)
スケッチ
引数で与えた時間tだけ、周波数38kHz, デューティサイクル1/3でLEDを点滅させる関数を書いてみます。出力の切り替えは、digitalWriteを使うより、直接ポートを書き換えた方が早くて精度が出せます。
Arduinoはマイクロ秒単位のディレイしか出来ないので、ON 9μs, OFF 17μs辺りを狙います。ただし、命令自体が結構遅いので、間にif文などを入れると狙った通りのタイミングになりません。オシロスコープがあれば、ディレイ時間と実際の時間の関係を探った方が良さそうです。
void high(int t) { //t = 425(AEHA) or 562 (NEC) unsigned long start = micros(); while (1) { if (micros()+9 > start+t) {break;} PORTD = PORTD | B01111100; delayMicroseconds(9); PORTD = PORTD & B10000011; if (micros()+12 > start+t) {break;} delayMicroseconds(12); } }
私の環境では、delayMicroseconds(9)だけHIGHにして、delayMicroseconds(12)とif文2つ分だけLOWにすることにしたところ、HIGHが8.21μs, LOWが18.44μs, 周波数37.5kHz, デューティサイクル30.7%になりました。(なぜかHIGHが9μsより早い。)ここまで仕様に寄せなくても、動くならそれでOKです。
信号の送信は、データの方式をtrans関数の引数data1に、前回デコードした0と1の羅列を引数data2に、data2のデータ長をc2に入れて送ります。(2020/6/29 修正)
void trans(char data1[], boolean data2[], int c2) { int t; // Set t and send leader code switch (data1[0]) { case 'A' : t = 425; high(t*8); delayMicroseconds(t*4); break; case 'N' : t = 562; high(t*16); delayMicroseconds(t*8); break; } // Send data code for (int i=0; i<c2; i++) { high(t); if (data2[i] == 0) { delayMicroseconds(t); } else { delayMicroseconds(t*3); } } // Send stop bit high(t); }
ちなみに、家のSHARPのLEDシーリングライトはAEHA方式でしたが、T=425では動かず、T=390で動作しました。通信規格(AHEA, NEC)は合っているはずなのに動かない時は、信号解析編で得られたシリアル通信の正の数の平均値からTを求めると良さそうです。必要に応じてスケッチのcase文を増やして下さい。(2020/10/5 追記)
PCとの接続
スマートスピーカーと連携を見据え、PCと常時接続し、PCから受けたシリアル通信に応じて家電に信号を発信出来るようにします。この時、信号の方式選択以外にもコマンドを追加し、信号送信後の待機時間と、リピート回数を仕込んで、同じ信号をリピートして送信出来るようにしました。
シリアル通信では、上記のtrans関数を拡張し、コマンド5文字と、任意の桁数のデータを「0」と「1」で送り、最後に「e」を送ることとしました。また、タクトスイッチを押したときに送信するテスト用の信号では、test1コマンド「A 100 3」(実際にはスペースは入らない)により、AEHA方式で、100Tの間隔を開けて信号(test2)を3回繰り返し送信しています。さらに、delay(3000)で3秒開けて、別の信号(test3)をさらに3回送信しています。
尚、スケッチ中で、文字列から数字への変換は、数字のASCIIコードから0x30を引くことで実装しています。
全体のスケッチは以下の通りです。動作確認しやすいように適宜シリアル出力も行うようにしました。(2020/6/29 修正)
void high(int t) { //t = 425(AEHA) or 562 (NEC) unsigned long start = micros(); while (1) { if (micros()+9 > start+t) {break;} PORTD = PORTD | B01111100; delayMicroseconds(9); PORTD = PORTD & B10000011; if (micros()+12 > start+t) {break;} delayMicroseconds(12); } } void trans(char data1[], boolean data2[], int c2) { int delayt = (data1[1]-0x30)*100 + (data1[2]-0x30)*10 + (data1[3]-0x30); int repeat = data1[4]-0x30; int t; for (int r=0; r<repeat; r++) { // Set t and send leader code switch (data1[0]) { case 'A' : // AEHA t = 425; high(t*8); delayMicroseconds(t*4); break; case 'N' : // NEC t = 562; high(t*16); delayMicroseconds(t*8); break; } Serial.print("Mode:"); Serial.print(data1[0]); Serial.print("("); Serial.print(t); Serial.print(") Delay:"); Serial.print(delayt); Serial.print(" Repeat:"); Serial.println(data1[4]); // Send data code for (int i=0; i<c2; i++) { high(t); if (data2[i] == 0) { Serial.print(0); delayMicroseconds(t); } else { Serial.print(1); delayMicroseconds(t*3); } } // Send stop bit high(t); Serial.println(); delayMicroseconds(t*delayt); } } int c1; int c2; char buff1[5] = {'0'}; boolean buff2[512] = {'0'}; char test1[] = {'A', '1', '0', '0', '3'}; boolean test2[] = {1,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,1,0,1,0,1,1,1,1,0,1,0,1,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,1,1,1,1,1,1,0,1,0,0,0,0,1,1,1,0,1,1,1,1,0,0}; boolean test3[] = {1,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,1,0,1,0,1,1,1,1,0,1,0,1,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,0,0,1,0,0,0,0,0,1,1,0,1,1,1,1,0,1,0,0,0,0,1,1,1,0,1,1,1,1,0,0}; void setup() { Serial.begin(9600); pinMode(2, OUTPUT); pinMode(3, OUTPUT); pinMode(4, OUTPUT); pinMode(5, OUTPUT); pinMode(6, OUTPUT); pinMode(14, INPUT); c1 = 0; c2 = 0; Serial.println("Ready to trans"); } void loop() { if (digitalRead(14) == true) { if (Serial.available() > 0) { char d = Serial.read(); Serial.print(d); if (d == 'e') { // 入力に'e'が来たら送信を行いパラメータをリセットする Serial.print(" c2:"); Serial.println(c2); trans(buff1, buff2, c2); c1 = 0; c2 = 0; while (Serial.read() >= 0) {;} // 'e'以降の入力を破棄 } else if (c1 < 5) { if (d >= 0x30 && d <= 0x7A) { // 0~z以外の入力を無視 buff1[c1] = d; c1++; } } else { if (d >= 0x30 && d <= 0x7A) {// 0~z以外の入力を無視 buff2[c2] = d-0x30; c2++; } } } } else { // タクトスイッチが押された時の動作 Serial.println("TEST MODE"); trans(test1, test2, 88); delay(3000); trans(test1, test3, 88); delay(3000); } }