今回は、Iduinoスタータキットに入っていた赤外線(IR)リモコンキットを使って、キッチンタイマを作りたいと思います。まずは、赤外線リモコンと、受信モジュールの調査から。
21キーの赤外線リモコン(送信機)と、受信モジュールのセットです。受信モジュールの方は、モジュールにはなっていますが、受信セルをはんだ付けして、全端子を引き出しただけのものです。一応、LEDが載っていて、赤外線を受信すると光るようになっているみたいです。
早速、受信信号を確認してみます。受信モジュールの端子は3本、VCC、GND、OUT(S)です。先ほど述べたように、受信セルの3本足を、ほぼそのまま引き出しただけなのですが、何故か、受信セルの足の順番と異なる順番で端子が引き出されていますので、間違えないように、ArduinoのVcc、GNDと接続します。Logiscopeを使用するので、信号線はどの端子でも構いませんが、ここではデジタル端子#2(D2)に繋ぐ前提で進めます。
Arduino | 接続 | 赤外線受信モジュール |
5V | → | VCC |
GND | ⇔ | GND (-) |
D2 | ← | OUT (S) |
実験装置の外観は以下の様になります。
Arduino Microの載った小さなブレッドボードだけで間に合ってしまいました。コンパクト❤
※筆者はMicroを使いましたが、UNO等の機体でも問題ありません。
MicroにLogiscopeのプログラム(logiscope.ino)を転送し、PC上でOlive+のスクリプト(logiscope.olv)を実行します。Arduinoのつながったポートを指定してください。後は、信号を確認したいリモコンを手元に準備して、Olive+のコマンド入力ウインドウに以下のコマンドを入力、実行します。
実験サンプル: trg,2,f,150000
実行したら、すかさず、赤外線受信モジュールに向けて、リモコンのキーを押してみてください。うまくいくと、以下の様な信号が表示されるはずです。
はい。このように信号が確認できたらArduinoでの実験は、ほぼ終了です。後は、同じリモコン内の別なキーを押してみたり、別な機器を制御するリモコンを持ってきて信号を確認してみたり、色々と実験を進めるだけです。
ですが、さすがに、信号を眺めて「ふ〜ん」と、言っているだけでは何も先に進めなくて、やはり、信号の仕組みを知る必要があります。
赤外線リモコンの仕組みを検索する(一例です。この辺を頼りに検索を広げてみてください)
筆者が調べてみた範囲で、まとめてみます。
以上を図にまとめると、以下の様になります。
このルールに従って、前節のサンプル信号を読み解く:
前節のサンプル信号は、東芝REGZAレコーダのリモコンで「電源」キーを押したときの信号でした。ちなみに、REGZAのリモコンは、同じ部屋に複数台のREGZAを設置したときに混信しないように、チャンネルを4通り(CH1〜CH4)から選べるようになっていて、示した信号はCH2の物です。CH1のリモコンで、同じ「電源」キーを押したところ、データは同じ0x48で、カスタムコードが0xa23dと、CH2と異なるコードになっていました。
筆者宅にあった、ありったけのリモコンを取り込んでみました。こうしてみると意外と少なかったです、リモコンの数、もっとたくさんあるかと思っていました。
解析してみたら、「デファクト」と言われているNEC方式そのものを使っている機器は、意外と少ないです(3/8)。あと、登録なしで使えるカスタムコード0x00ff を使ったリモコンが、2つありました。これでデータ部分が一致するキーがあった場合、2つの機器は混信します(1^^;)。
ここから先は、Iduinoスタータキットに入っていた赤外線リモコン送信機を使って、Arduinoに対して指示を与えられるように、プログラムを組んでいきます(Arduinoの遠隔コントロール)。前節で調べたように、付属のリモコンはNEC方式で、カスタムコードは0x00ff でした。21個のキーがついているので、全てのコードを調べてみます。
とは言え、Logiscopeで信号を取り込んで、目視でコードを追うのはしんどくなって来ました。もう、NEC方式だとわかったので、それを前提として、赤外線データを受信するプログラムを作ってしまいましょう。
信号は、基本周期T=562.5μsで変動します。意外に速いです。うかつにloop()関数内の処理が重くなってくると、取りこぼしかねない速さではあります。
逆に、T=562.5μs「も」ありますので、loop()関数内で定期的にサンプリングして、リーダ(16T)のLOWを見つけたら、そこから処理をブロックして、ストップビットを読み込み終わるまで、受信に専念すれば、確実に取得可能でしょう。「第6回 気温と湿度の測定」で、AM2302からの送信データからビット値0/1を判定するのに使用した、pulseInLong()関数が使えるはずです。
さて、どうしたものか。やはり、実用的な遠隔操作U/Iとしての使用を念頭に入れて、前者の立場に立ち、割り込みで受信するようにプログラムしてみます。
const int pin_IRRV=2;
void setup (void)
{
// initialize
pinMode (pin_IRRV, INPUT_PULLUP);
// interrupt
attachInterrupt (digitalPinToInterrupt (pin_IRRV), int_ir_recv, FALLING);
}
信号の特徴から、割り込みは受信モジュール出力の立ち下がりエッジ(FALLING)のみ、でOKなはずです。
正しくNEC方式のデータが送信されてくるという前提では、上の方法でOKなはず。あとは、他の方式で送られてくる、関係ない信号に反応しないように、エラーチェックをしっかり実行する必要があると思われます。正確なスペックがわからないけれど、基本周期がTなので、「T/2以上ずれていたらエラー」としてしまって良いはず。
※リピートの間隔で、192T±T/2のチェックがギリギリ失敗してしまうケースがあったため、そこだけ±Tでチェックしました。
※その後も評価を続けていたところ、±Tでも失敗するリモコンがありました。さすがに、192Tと言う長い間隔に±Tのチェックはきつすぎるのかも?スペックを知らないので、どこまで緩和して良いものか、わかりませんが、とりあえず、±2Tまで緩和してあります。
前回のリーダ検出時間から、今回のリーダ検出時間までの時間もチェックします。フレームの送信間隔は、192T(108ms)なので、この値以上離れていることをチェックします。
エラーチェックまで含めた上の処理を、ISR(Interrupt Service Routine)内で実行します。
void int_ir_recv (void)
{
// ISR on pin_IRRV falling edge
ulong usec;
ulong usec2;
// time
usec=micros ();
// reset state
if (usec - usec_irr_prev>2*usec_irr_frame_peri)
{
irr_idx=0;
}
// each state
if (irr_idx==0)
{
// first falling: check frame period
if (usec - usec_irr_last<usec_irr_frame_peri - usec_irr_2T)
{
// ERROR: too short between frame
irr_idx=0;
usec_irr_prev=usec;
return;
}
}
else if (irr_idx==1)
{
// second falling: check leader length
usec2=usec - usec_irr_prev;
if (usec_irr_repeat_len - usec_irr_2T/4<usec2 &&
usec2<usec_irr_repeat_len + usec_irr_2T/4)
{
// detect repeat
// check frame period
usec2=usec - usec_irr_last - usec_irr_repeat_len;
if (usec2<usec_irr_frame_peri - usec_irr_2T ||
usec_irr_frame_peri + usec_irr_2T<usec2)
{
// ERROR: illegal repeat period, ignore
usec_irr_last=usec_irr_prev;
irr_idx=0;
usec_irr_prev=usec;
return;
}
irr_repeat=true;
usec_irr_last=usec_irr_prev;
irr_idx=0;
usec_irr_prev=usec;
return;
}
else if (usec2<usec_irr_leader_len - usec_irr_2T/4 ||
usec_irr_leader_len + usec_irr_2T/4<usec2)
{
// ERROR: illegal leader length
irr_idx=0;
usec_irr_prev=usec;
return;
}
// detect frame leader
usec_irr_last=usec_irr_prev;
irr_recv=0;
}
else if (irr_idx>34)
{
// ERROR: receive beyond 32 bits
// or receive next frame before processing last received data
usec_irr_prev=usec;
return;
}
else
{
// success: capture 0~31 bit value, lsb first as msb first(!)
usec2=usec - usec_irr_prev;
if (usec_irr_2T - usec_irr_2T/4<usec2 &&
usec2<usec_irr_2T + usec_irr_2T/4)
{
// detect "0" bit
irr_recv=(irr_recv<<1) + 0;
}
else if (2*usec_irr_2T - usec_irr_2T/4<usec2 &&
usec2<2*usec_irr_2T + usec_irr_2T/4)
{
// detect "1" bit
irr_recv=(irr_recv<<1) + 1;
}
else
{
// ERROR: illegal bit length
irr_idx=0;
usec_irr_prev=usec;
return;
}
}
irr_idx++;
usec_irr_prev=usec;
}
ちょっと重いコードになってしまいましたが、仕方ありません。コードは長いですが、何回目の立ち下がりエッジか?を示すirr_idxをカウントしていて、回数に従った場合分け(上コードの色分け個所)が深いだけで、各処理は最小限の処理に抑えたつもりです。
完成したプログラムを使って、付属リモコンの送信コードを読み取ってみます。
完成プログラム:test_ir_recv.ino
実行サンプル:
RECV: custom=00ff data=a2(5d, ~=a2)
RECV: custom=00ff data=62(9d, ~=62)
RECV: custom=00ff data=e2(1d, ~=e2)
受信モジュールに向けて、リモコンのキーを押します。うまく受信できると、上サンプルの様に、カスタムコードとデータ値をシリアルモニタに送ってきます。
付属リモコンには21個のキーがついていますので、全てのキーを押して、コードを確認します。
キー名 | カスタム | データ |
POWER | 0x00ff | 0xa2 |
FUNC/STOP | 0x00ff | 0xe2 |
VOL+ | 0x00ff | 0x62 |
VOL- | 0x00ff | 0xa8 |
PREV (|<<) | 0x00ff | 0x22 |
NEXT (>>|) | 0x00ff | 0xc2 |
PLAY/PAUSE (>||) | 0x00ff | 0x02 |
DOWN (↓) | 0x00ff | 0xe0 |
UP (↑) | 0x00ff | 0x90 |
EQ | 0x00ff | 0x98 |
ST/REPT | 0x00ff | 0xb0 |
0 | 0x00ff | 0x68 |
1 | 0x00ff | 0x30 |
2 | 0x00ff | 0x18 |
3 | 0x00ff | 0x7a |
4 | 0x00ff | 0x10 |
5 | 0x00ff | 0x38 |
6 | 0x00ff | 0x5a |
7 | 0x00ff | 0x42 |
8 | 0x00ff | 0x4a |
9 | 0x00ff | 0x52 |
カスタムコードが同じだった、HDMIマトリクスセレクタのリモコンも調べておきました。
キー名 | カスタム | データ | 備考 |
POWER | 0x00ff | 0x28 | |
AOFF | 0x00ff | 0xb0 | |
A1 | 0x00ff | 0x98 | 付属リモコンのEQキーと同じ |
A2 | 0x00ff | 0xa8 | 付属リモコンのVOL-キーと同じ |
A3 | 0x00ff | 0x0a | |
A4 | 0x00ff | 0x52 | 付属リモコンの9キーと同じ |
BOFF | 0x00ff | 0x40 | |
B1 | 0x00ff | 0x18 | 付属リモコンの2キーと同じ |
B2 | 0x00ff | 0x8a | |
B3 | 0x00ff | 0xca | |
B4 | 0x00ff | 0xa2 | 付属リモコンのPOWERキーと同じ |
キー数が少ない割には、結構重なってましたね(1^^;)っ。付属リモコンの該当するキーで、HDMIマトリクスセレクタの操作ができてしまった(混信)ことは言うまでもありません。
「NEC方式がデファクトだ」ともっぱらの噂だったのですが、実際に解析してみると、そうではない独自方式のリモコンも沢山あることが分かりました。今後、特定の機器を狙って、データを送信しようとするときは注意が必要ですね。各機器の方式に合わせて信号を出力してやる必要がありますから。
「第9回 気温・湿度のログを取る(後編、最終回)」で完成した、気温・湿度測定用のArduinoですが、それだけに使うのはもったいないな、と思っていました。と言って、気温・湿度を測定している場所にはPCが置いてあるので、わざわざArduinoを使って実行する処理もないな……と。
赤外線リモコンを使うと、Arduinoを遠隔操作することが可能なので、これを使ってキッチンタイマでも作ってみようと思いつきました。筆者宅の構造だと、キッチンから赤外線リモコンで気温・湿度測定用のArduinoに送信でき、タイマのアラーム音も十分キッチンまで届く位置関係にあるので、実用的です。
目標仕様をまとめておきます。
リモコンのキー割り当てについては、なるべく混信したキーを使わないように決めました(数字キーは避けようがなかったので、注意して使うことにします)。
必要なハードウェアは、AM2302、赤外線受信モジュール、アクティブブザー、スイッチ、7SEG LED、シフトレジスタ(74595)です。
これまでに少しづつ実験を繰り返してきた回路の寄せ集めですので、難しいことは無いはずです。スイッチと赤外線受信モジュールは割り込みを使って検出しますので、Arduino UNOで割り込み設定可能なD2とD3を割り当てています。それ以外は普通のデジタルポートで良いので、適切に割り振りました。
プログラムの方も、これまでに少しづつ実験を繰り返してきたプログラムの寄せ集めです。
キッチンタイマの処理は新規で起こす必要かありますね。
// kitchen timer count down
if (ktmr_cnt==ktmr_STOP)
{
// standby
}
else if (ktmr_cnt==ktmr_ALARM)
{
// alarm
if (alm_play==NULL)
{
// alarm finished
ktmr_cnt=ktmr_STOP;
}
}
else if (ktmr_cnt>ktmr_STOP)
{
// count down
ktmr_elaps=(msec - msec_ktmr)/(60*1000UL);
if (ktmr_cnt<=ktmr_elaps)
{
// count down finished
ktmr_cnt=ktmr_ALARM;
ktmr_elaps=0;
alm_play=alm_ptn_1;
alm_idx=0;
msec_alm=msec;
}
}
変数ktmr_cntにタイマの設定時間[分]、msec_ktmrにタイマカウントダウンをスタートした時間[ms]を保持しています。ktmr_cntは、タイマ停止中(ktmr_STOP)とカウントダウンが終わってアラーム鳴動中(ktmr_ALARM)を示すステータスも兼ねています。
タイマカウントダウン中のステート(ktmr_cnt>ktmr_STOP)では、開始時間からの経過時間(ktmr_elaps)を計算して、設定時間(ktmr_cnt)を超えたところでアラームを鳴動するステートに移るようになっています。
これらの処理はloop()関数内で実行しており、シリアルモニタからのコマンド処理をディスターブすることなく、タイマのカウントダウン処理が実行されています。この系では、いつPCから気温・湿度測定コマンドが飛んでくるかわからないので、定期的にSerial.available()を確認する必要があります。コマンド処理をディスターブしないようにすることが必要です。
7SEGへの表示は、「#2-7 7SEG LEDを表示する」で使った4桁表示の7SEGと異なり、1桁であればI/Oポートに信号を出力したら自発的に表示を続けるように構成できますので、必要に応じてシフトレジスタ(74595)にデータを出力するだけです。
// display
if (ktmr_cnt==ktmr_STOP && ktmr_cnt!=ktmr_prev)
{
// 7seg off
digitalWrite (pin_OEN, HIGH);
}
else if (ktmr_cnt==ktmr_ALARM && ktmr_cnt!=ktmr_prev)
{
// display 0
shift_in (tbl_7seg[0]);
}
else if (ktmr_cnt>ktmr_STOP && ktmr_cnt - ktmr_elaps!=ktmr_prev)
{
// display 1~19
shift_in (tbl_7seg[ktmr_cnt - ktmr_elaps]);
}
ktmr_prev=ktmr_cnt - ktmr_elaps;
使っていないときに7SEGが光っているのは目障りなので、タイマを使用しないときには、シフトレジスタの出力許可OEを不活性(負論理なのでHIGH)にして、7SEGを消灯しておきます。
完成版プログラムをリンクしておきます。
完成版プログラム:kitchen_tmr.ino
※上写真の評価では、AM2302は接続していません。
目標仕様通りになっているかどうか、U/Iを使って確かめてみてください。
しかし、配線数が半端ないですね(上の写真)……、これを万能シールド基板にはんだ付けしなくてはならない、と思うと、げんなりします。元気と時間があって、根気が続きそうなときに頑張ります。
今日の所は勘弁してやるぜぃ(1^−^;)。
ずいぶんと長い間寝かせてありましたが、ようやく、「#5-5 キッチンタイマをシールドに実装」にて、シールド基板への実装(はんだ付け)が完成しました。軽く、やらかしてますが、顛末は「#5-5 キッチンタイマをシールドに実装」をご覧ください。
測定した室温に連動して、LEDの点滅する周期を変化する機能を追加しました。プログラム(kitchen_tmr.ino)と回路図の一部を更新してあります。
個人的な見解で、過ごしやすい室温の間はゆっくり、寒すぎたり暑すぎたりすると、早く点滅するようにしてあります。また、単なる点滅ではなく、正弦波で明滅させています。
追加の回路図では、UNOのオンボードLED(D13、LED_BUILTIN)を使っているのですが、実は、上にシールドが被さっているので、隙間からしか見えないのですけどね……。PWM出力はD10を使っているので、D10から引き出して、万能シールドに浮いている、LED1などに接続しても良いです。
表でピカピカ光っていられると、返って目障りかな?と、思い、筆者はLED_BUILTINを使っています。