既出すぎる内容な気もしますが、いわゆるArduino端末によく使われる
ATMega328Pではなく、ATTiny85を用いた内容、かつ
irRemoteライブラリを使わない実装としたため記事にします。
この記事は下書きのまま一年以上放置してしまいましたが
編集途中でも公開しておきます。
背景
オーム社の照明リモコンCRS-01Wは、壁スイッチでON/OFFすると
OFFから開始されるので、リモコンでONしなければ電気が点かない。
とても不便なので壁スイッチONでリモコンONの操作もさせたいと思います。
シーリングソケット
├ 通電した時にだけON信号を出す赤外線送信機
└ シーリング照明
実装
sketch
#include <avr/wdt.h> // wdt_disable()を使うため
#define OUTPUT_PIN (_BV(4) | _BV(1)) // 4:インジケータ、1:赤外線LED (PWM)
static const unsigned int irSignal[] = { // オーム社の照明リモコンCRS-01W:スイッチON信号
// ON, OFF, ON,OFF, ON,OFF, ON,OFF, ON, OFF, ON, OFF, ON, OFF, ON, OFF, ON,OFF, ON, OFF
2550,2700, 800,850, 850,850, 800,850, 850,1900, 800,1900, 850,1850, 850,1900, 800,850, 850,9000
};
/**
* 赤外線LEDを38kHzで点灯させる(1番ピン)
*/
void pwm_out(int microseconds) {
PORTB |= _BV(4); // インジケータ点灯:digitalWrite(4, HIGH)と同じ
// タイマー(TC0)を設定
OCR0A = 25; // TOP value : 38kHz --> 26.3us.
OCR0B = 6; // H period : 6.4us.
TCCR0A = B00100011; // COM0A: 00, COM0B: 10, WGM01-00: 11
TCCR0B = B00001001; // FOC:00, WGM02:1, CS02-01: 001 (system clock x1)
TCNT0 = OCR0A;
delayMicroseconds(microseconds); // 指定マイクロ秒だけ点灯
// タイマー(TC0)をリセット
TCCR0A = 0; // タイマー(TC0)リセット
TCCR0B = 0; // タイマー(TC0)リセット
PORTB &= ~_BV(4); // インジケータ消灯:digitalWrite(4, LOW)と同じ
}
void setup() {
/**
* 初期化
*/
cli(); // noInterrupts();の中身(割り込み停止)
DDRB = OUTPUT_PIN; // B010010000 pinMode(PB1, OUTPUT),pinMode(PB4, OUTPUT)
PORTB = 0; // B00000000 PB1もPB4もLOW
ACSR |= 0x80; // アナログコンパレータ禁止
ADCSRA &= 0x7f; // disable ADC
wdt_disable(); // ウォッチドッグタイマを停止
TIMSK = 0; // disable Timer0/1 overflow interrupt.
sei(); // noInterrupts();の中身(割り込み停止)
/**
* ループ
*/
int len = sizeof(irSignal) / sizeof(irSignal[0]); // 送信する信号の数
int ir_mcs = 0; // マイクロ秒。信号の送信に要する時間
for (int i = 0; i < len; i++) {
ir_mcs += irSignal[i]; // 合計:32,650マイクロ秒=32.65ミリ秒(9000マイクロ秒の赤外線OFFはダミー)
}
int interval_ms = 50; // ミリ秒。次の信号を送信するまでの間隔
int loop_ms = 1000; // ミリ秒。信号を送信し続ける時間
int loop_cnt = loop_ms / interval_ms; // ループの回数を計算
// 次の信号を送信するまでの待ち時間(ミリ秒)
// =インターバル(ミリ秒)-(1回あたりの赤外線送信時間(マイクロ秒)÷1000)
double wait_ms = interval_ms - (ir_mcs / 1000); // 50 - 32.65 = 17.35
for (; loop_cnt > 0; loop_cnt--) {
// このループ全体で一つの赤外線信号
for (int i = 0; i < len; i += 2) { // 1回のループでONとOFFを処理する
pwm_out(irSignal[i]); // 赤外線ON(38kHzで指定のマイクロ秒だけ点灯)
delayMicroseconds(irSignal[i+1]); // 赤外線OFF
}
// インターバル。delayが使えないのでdelayMicrosecondsを必要ms分ループ
for (int i = 0; i < wait_ms; i++) { // (小数点切り捨て=17ミリ秒待つ処理)
delayMicroseconds(1000); // 1ミリ秒待つ
}
}
}
// Arduinoのタイマーは使わないため、loop()では何も処理しない
void loop() {}
回路図
- USB電源
- 基板
- R1 / R2
- ATTiny85
- LED
解説
赤外線信号の仕組み
今回学んだことを羅列します。
- 赤外線信号は 点灯・消灯を繰り返す時間の長さ で情報を伝えます(モールス信号みたいなもの)
- 一般的に赤外線通信に使う赤外線の波長は 900nm らしいです(LEDを購入するときに念のため確認しましょう)
- 受信側は太陽光など他の光と区別するために 38kHzという周波数の光だけを受信する ようにできています
- つまり発信側も38kHzで発光させる必用があります
- 38kHzとは 26.3μ秒で一周期 で、この間にON/OFFを1回します(ONは6μ秒、残り時間はOFFで良いみたいです→省電力)
- デューティー比=1/3:一周期でONになっている時間は1/3の時間とする(つまり8.7μ秒がON、上記の6μ秒と矛盾してるけどw)
- 送信機の条件は 38kHzで発光できること 、 点灯・消灯時間をμ秒で制御できること 、になります
ちなみに今回の信号は irSignal
で定義したとおりです。単位は μ秒
です。
(最後のOFF継続時間9000μ秒は、信号終了が判断可能な時間を十分に確保しようと、私が勝手に追加したものです)
static const unsigned int irSignal[] = { // オーム社の照明リモコンCRS-01W:スイッチON信号
// ON, OFF, ON,OFF, ON,OFF, ON,OFF, ON, OFF, ON, OFF, ON, OFF, ON, OFF, ON,OFF, ON, OFF
2550,2700, 800,850, 850,850, 800,850, 850,1900, 800,1900, 850,1850, 850,1900, 800,850, 850,9000
};
PWM
ATTiny85のPWM(パルス幅変調)は0番ピン・1番ピンです。
→データシート
今回は1番ピンを使って38kHzの赤外線信号を点灯させます。
なぜ0番ではなく1番ピンを使うのかは後述します。
(irRemoteでも勝手に1番ピンが使われるのはPWMに対応しているからという理由です、他のピンでは代替えできません)
irRemoteライブラリを使わない理由
いま思えば最後のスイッチOFF時間を十分に取れていなかっただけかも知れませんが
送信した信号がうまく解析してもらえなかったのが理由です。
ON/OFFを繰り返し10回行いますが、3回分しか認識しなかったり15回分だと判断してしまったり。。
それ以外にも delay()
が使えないとか(使わない方が良い?)
ライブラリに手を加えないと動かない、とか、いくつか問題がありました。