皆さん、楽しい赤外線ライフを満喫していますか? 電子工作で赤外線リモコンを作るとき、特に Raspberry Pi や MicroPython では数十μ秒のタイミング制御がうまくいかなかったり、言語やライブラリのお作法に悩まされたり、色々な問題に阻まれることがあります。かといって何でも C や Arduino で書くのは面倒だし。やっぱりイマドキの高級言語で書きたいですよね? ということで、言語やライブラリを問わずPCや各種マイコンに幅広く対応する送信モジュールを開発しました。
まずは今回作ったリモコン送信モジュール。ATTiny85を使い Software Serial で受け取ったコマンドを赤外線で送出します。
動機
ESP32 + MicroPython で赤外線リモコンを作ろうと思ったら On/Off のタイミングを思ったように制御できなくて悲しい気持ちになったから (結局このときは ESP32 + Arduino で開発した)。よろしい、ならば抽象化だ。
やりたいこと
- 言語やフレームワーク・ランタイムを問わない赤外線送信の抽象化
- ハードウエアを問わず接続できるモジュール化
- NEC フォーマットとその派生フォーマットへの対応
ハードウエア編
用意したもの
- 本体
- ATtiny85
- 赤外線LED x2 ……Linkman 503IRC2V-2AD (データシート)
- 抵抗……トランジスタのベース側に3.3kΩ、赤外線LEDの電流制限抵抗に10Ω
- 増幅用トランジスタ……2SC1815、今回は相当品のKSC1815YTA
- その他、ユニバーサル基板やリード線、ブレッドボードなど
- 開発環境
- Arduino (ATtiny85の書き込み・リモコンのコード解析に使用)
- 赤外線リモコンの受光素子と周辺部品……ローム RPM6938 (データシート)
- 接続する機器
- スイッチサイエンス FT231XS USB-シリアル変換ボード
- ESP-WROOM-32 スイッチサイエンス ESPr® Developer 32
- Raspberry Pi
- BBC micro:bit
仕様と回路図
- 電源: 3.3V
- 通信速度: 9600bps
- ストップビット: 2
- パリティ: なし
回路図のATtiny85から出ているRXDは接続先のTXDに、TXDはRXDに接続します。
リモコンのコード解析
過去の拙著をそのまま使いました。今回もrawData配列はそのまま使えます。
ソフトウエア編
Arduinoのスケッチ
東芝のシーリングライト LEDH96040-LC と、そのリモコン FRC-194T(W) は、一見NECフォーマットにも見えるのですが機能によって信号が32bit長だったり64bit長だったりします。またHi/Loの持続時間も通常のNECフォーマットより100〜1000μ秒ほど長くなっています。NECフォーマット専用のプログラムでは対応できないため「Hi/Loの持続時間」をコンマ区切りで渡すことにしました。終端は"."(ピリオド)とし、これを受け取ったら送信します。
今回はNECフォーマットの派生のようですが、搬送波38kHz・1/3デューティであれば他のフォーマットにも対応できるでしょう。
ATtiny85で赤外線リモコン用の波形を作る話 | 東京お気楽カメラを参考に以下のスケッチを作成し、ATtiny85に書き込みます。
#include <avr/wdt.h>
#include <SoftwareSerial.h>
#define COMMAND_BUFFER_SIZE 150
#define OUTPUT_PIN _BV(1)
/**
* ATTiny85 @ 8MHz を使い、Software Serial で受信したタイミングに
* 沿って赤外線LEDを制御する。
*
*/
SoftwareSerial serial(3, 4); // RX, TX
unsigned int commandBuffer[COMMAND_BUFFER_SIZE];
char c;
int index = 0;
int signalSize = 0;
unsigned int t;
boolean end = false;
void setup() {
serial.begin(9600);
// IR LED
// http://okiraku-camera.tokyo/blog/?p=7480 から引用
noInterrupts();
DDRB = OUTPUT_PIN; // inputは0
PORTB = 0; // 他は0
ACSR |= 0x80; // アナログコンパレータ禁止
ADCSRA &= 0x7f; // disable ADC
wdt_disable();
TIMSK = 0; // disable Timer0/1 overflow interrupt.
interrupts();
}
void mark() {
// 38kHz, 1/3 duty
// http://okiraku-camera.tokyo/blog/?p=7480 から引用、8MHz
// に対応するよう改変
OCR0A = OCR1C = 25; // TOP value : 38kHz --> 26.3us.
OCR0B = OCR1B = 6; // H period : 6.4us.
TCCR0A = B00100011; // COM0A: 00, COM0B: 10, WGM01-00: 11
TCCR0B = B00001000; // FOC:00, WGM02:1, CS02-01: 000 (stop clock)
GTCCR = B01100000; // OC1B cleared on compare match. Set when TCNT1 = $00.
TCCR1 = B10000100; // CTC=1. clear TCNT1 on OCR1C==TCNT1. CS13-10 = 0100B (/8)
TCNT1 = 0;
TCCR0B |= B010; // enable clock (/8).
TCNT0 = OCR0A ;
}
void space() {
TCCR0A = 0;
TCCR0B = 0;
TCCR1 = 0;
GTCCR = 0;
}
void loop() {
if(serial.available()) {
c = serial.read();
switch (c) {
case '0': t = t * 10; break;
case '1': t = t * 10 + 1; break;
case '2': t = t * 10 + 2; break;
case '3': t = t * 10 + 3; break;
case '4': t = t * 10 + 4; break;
case '5': t = t * 10 + 5; break;
case '6': t = t * 10 + 6; break;
case '7': t = t * 10 + 7; break;
case '8': t = t * 10 + 8; break;
case '9': t = t * 10 + 9; break;
case ',':
commandBuffer[index] = t;
index++;
t = 0;
break;
case '.':
commandBuffer[index] = t;
signalSize = index+2;
t = 0;
end = true;
break;
}
if(COMMAND_BUFFER_SIZE <= index) {
index = 0; // バッファがあふれたらいったん全て捨てる
}
}
if(end) {
for(index = 0; index < signalSize; index++) {
if(0 == index % 2) {
mark();
} else {
space();
}
delayMicroseconds(commandBuffer[index]);
}
space();
end = false;
index = 0;
signalSize = 0;
}
}
ATtiny85へのスケッチの書き込み
High-Low Tech – Programming an ATtiny w/ Arduino 1.6 (or 1.0) を参考にスケッチを書き込みます。注意点は以下の通り。
- ArduinoからATmega328を外さない
- Programmerは"Arduino as ISP"を使う
- 内部クロックを使用し、周波数は8MHz
- まずブートローダを書き込め。話はそれからだ
- "ISP" といいつつ "In-System Programming"がうまくいかない。面倒でもATtiny85をプログラマに挿したり基板に戻したりする
使用例
東芝のシーリングライト用リモコン FRC-194T(W) の「全灯」ボタン チャネル2のコードを色々な方法で送ってみます。
USB/Serial変換アダプタでMacにつなぎ、echoコマンドで送信
echo '10200,5050, 650,1900, 650,1900, 650,1900, 650,600, 650,600, 700,1900, 650,1850, 700,1850, 700,600, 650,600, 650,1900, 650,1900, 650,600, 700,550, 700,600, 700,600, 650,1850, 700,1900, 650,1850, 700,600, 650,1900, 650,600, 650,650, 650,600, 650,650, 650,600, 650,600, 700,1850, 650,650, 650,1900, 650,1850, 650,1900, 650.' | sudo tee /dev/cu.usbserial-SOMEDEVICE
ESP32のMicroPythonで送信
UART0はREPLおよびWebREPLで使っているので、別のピンを使うことにします。ESP-32とMicroPythonでCO2センサを読んでみた+α - Qiita を参考に TXD, RXD はそれぞれ4番・5番としました。
import machine
uart = machine.UART(2, baudrate=9600, rx=5, tx=4)
uart.write("10200,5050, 650,1900, 650,1900, 650,1900, 650,600, 650,600, 700,1900, 650,1850, 700,1850, 700,600, 650,600, 650,1900, 650,1900, 650,600, 700,550, 700,600, 700,600, 650,1850, 700,1900, 650,1850, 700,600, 650,1900, 650,600, 650,650, 650,600, 650,650, 650,600, 650,600, 700,1850, 650,650, 650,1900, 650,1850, 650,1900, 650.")
このコードで無事にシーリングライトを制御できました。ひとまず目的は達成です。
micro:bitから送信
本体のAボタンを押すとシーリングライトが点灯する仕組みにしました。
ちなみに JavaScript だとこんな感じになるらしいです。
input.onButtonPressed(Button.A, function () {
serial.writeString("10200,5050, 650,1900, 650,1900, 650,1900, 650,600, 650,600, 700,1900, 650,1850, 700,1850, 700,600, 650,600, 650,1900, 650,1900, 650,600, 700,550, 700,600, 700,600, 650,1850, 700,1900, 650,1850, 700,600, 650,1900, 650,600, 650,650, 650,600, 650,650, 650,600, 650,600, 700,1850, 650,650, 650,1900, 650,1850, 650,1900, 650.")
})
serial.redirect(
SerialPin.P0,
SerialPin.P1,
BaudRate.BaudRate9600
)
basic.forever(function () {
})
余談ですが micro:bit は超絶簡単でいいですね。初めて使いましたが一気に好きになりました。
Raspberry Piのシェルから送信
事前にUARTを有効にしておく必要があります。
sudo raspi-config nonint do_serial 0 # 0で有効化
sudo shutdown -r now # 再起動
コマンド自体はMacのときと同様です。
echo '10200,5050, 650,1900, 650,1900, 650,1900, 650,600, 650,600, 700,1900, 650,1850, 700,1850, 700,600, 650,600, 650,1900, 650,1900, 650,600, 700,550, 700,600, 700,600, 650,1850, 700,1900, 650,1850, 700,600, 650,1900, 650,600, 650,650, 650,600, 650,650, 650,600, 650,600, 700,1850, 650,650, 650,1900, 650,1850, 650,1900, 650.' > /dev/serial0
Raspberry PiのPythonで送信
pyserialモジュールを導入。
pip install pyserial
続いて本体のコード。
import serial
s = serial.Serial('/dev/serial0', 9600)
s.write(b"10200,5050, 650,1900, 650,1900, 650,1900, 650,600, 650,600, 700,1900, 650,1850, 700,1850, 700,600, 650,600, 650,1900, 650,1900, 650,600, 700,550, 700,600, 700,600, 650,1850, 700,1900, 650,1850, 700,600, 650,1900, 650,600, 650,650, 650,600, 650,650, 650,600, 650,600, 700,1850, 650,650, 650,1900, 650,1850, 650,1900, 650.")
s.close()
Arduinoから送信
IRremoteという強力なライブラリがあります。ただボードやコントローラによっては対応していない場合もあり、そういった場合に有効かと思います。
起動直後に「全灯」のコマンドを送信してみます。
void setup() {
char signal[] = "10200,5050, 650,1900, 650,1900, 650,1900, 650,600, 650,600, 700,1900, 650,1850, 700,1850, 700,600, 650,600, 650,1900, 650,1900, 650,600, 700,550, 700,600, 700,600, 650,1850, 700,1900, 650,1850, 700,600, 650,1900, 650,600, 650,650, 650,600, 650,650, 650,600, 650,600, 700,1850, 650,650, 650,1900, 650,1850, 650,1900, 650.";
Serial.begin(9600);
Serial.print(signal);
}
void loop() {
}
まとめとか今後とか
- シェルから
echo
で家電を制御できるって楽しい - Windows環境でも
COPY
コマンドでCOMnに書き込めば同様のことができるかも。BATで家電を制御できると楽しいですね - シリアルの1対1接続と違ってI2Cだと1対nで接続できるので、そのうち I2C slave版も作るかも。ただシリアルの利便性も捨て難い……
謝辞
この工作は調光君2号の開発の過程で生まれました。すぐに何かの役に立つ品物でもないのに、家事を疎かにし実験や開発に没頭していても「仕事で帰りが遅いしストレス発散は大事だよね」と大目に見てくれる妻に感謝します。