出力波形を精密にして指向性を下げる(動作範囲を広げる)ことに成功しました。
●動作範囲を広げる
現状でもそこそこ使えるのですが、どうせなら製品のリモコン送信機のように動作範囲を広げられないかと試してみました。
当初、指向性の低いLEDを使えばよい、と思っていたのですが、市販品を調べてもそれほど半値角の広いものはなく、やはり出力パルスの波形の質の問題という結論に達しました。
参考までに何点か調べましたので挙げておきます。
- OptoSupply OSI5LA5113A(φ5) 半値角:±7.5° (秋月電子)
- 東芝 TLN105B(φ5) 半値角:±26.5°
- スタンレー電気 AN3804X(φ3) 14mW 940nm 半値角:±23°
- OSRAM Opto Semiconductors SFH 485(φ5) 半値角:±20
●手持ちのLEDで動作範囲を再確認
連載初期に記載しましたが、今でもアイリスオーヤマのリモコンからパルス出力ポートの信号線を引き出したままにしてあります。その信号を現在の試作品のLEDドライブ回路(トランジスタバッファ)に接続し、製品リモコンから操作してみたところ、試作回路でも結構動作範囲が広いことが確認できました。したがって、LEDの指向性はあまり関係ないということです。これではっきりしました。
●波形の精密化
やはり、ライブラリのdelay関数だけでは微調整ができないため、アセンブリ言語をインラインアセンブラで併用することにしました。と言っても使用するのは”NOP”命令のみで、その数で時間を調整しているだけなので、プログラム自体は単純です。
ロジアナで波形を測定しながらdelayMicroseconds()の値とNOPの数を調整して最適値を探しました。時間はかかりましたが、だいぶ本物に近づきました。
●割込み
シリアル通信も外部割込みのアタッチも行っていないのですが、どうも部分的にキャリア波がいびつになるところがあります。そこでパルス出力の前に割込みを禁止し、パルス出力後に割り込みを許可するようにしたら、いびつさがなくなりきれいになりました。
それから、delay()関数は割込みやタイマを使用しているようで、割込み禁止にすると実時間が極端に短くなります。そのため、割込み禁止で1msを待つのに、delya(1)ではなくdelayMicroseconds(1000)としています。
●スケッチ (irRemo2_8M2.ino)
今回のソースを掲載しておきます。パルス幅のディレイ関係のみの修正です。インラインアセンブラは”__asm__”と書かれた部分です。”\n\t”はアセンブリ・ニモニックのデリミッタですので、気にする必要はありません。目障りですが。
/* アイリスオーヤマ LEDシーリングライト用赤外線リモコン評価 copyright (c) 2013 www.wsnak.com 13/10/20 新規作成 13/10/21 省電力試行 13/10/22 8MHz版 13/10/29 パルス波形改良 スイッチ入力によりコマンド切り替え メモリ点灯、消灯、全灯、常夜灯 */ /* ディジタル・ポート・アサイン スイッチポート D2-D5 IR発光(LED) D8 */ // ライブラリのリンク #include <wSwitch.h> // スイッチ用ライブラリ(w4Switch) #include <wCTimer.h> // タイマ・ライブラリ(wCtcTimer2A) #define ON HIGH #define OFF LOW #define IR_OFF digitalWrite(8, LOW); // IR LED(D7) #define IR_ON digitalWrite(8, HIGH); // IR LED(D7) // ライブラリのインスタンス生成 w4Switch sw; // スイッチオブジェクトの実体化 wCtcTimer2A tm; // タイマオブジェクトの実体化 // // 初期化 // void setup(void) { int i; tm.init(125); // タイマ初期化 sw.SwitchInit(2, 3, 4, 5); // SW用ポート定義 sw.onKeyPress(KeyPress); // スイッチが押された時に呼び出される関数の登録 pinMode(8, OUTPUT); // IR発光 (IR-LED) IR_OFF; // ペリフェラルのパワーダウン TIMER0とTIMER2を除く PRR = (1<<PRTWI) | (1<<PRTIM1) | (1<<PRSPI) | (1<<PRUSART0) | (1<<PRADC); } // // リモコン コマンド出力 // void irOut(byte sw) { int i, k; // (C) for(i = 0; i < 77; i++) { IR_ON; delayMicroseconds(5); __asm__( "nop\n\t" "nop\n\t" ); IR_OFF; delayMicroseconds(4); __asm__( "nop\n\t" "nop\n\t" "nop\n\t" "nop\n\t" "nop\n\t" "nop\n\t" ); } // delay(1); delayMicroseconds(998); // ********* リピート部分 ************ for(k = 0; k < 3; k++) { // 共通部分 // (D) for(i = 0; i < 211; i++) { IR_ON; delayMicroseconds(5); __asm__( "nop\n\t" "nop\n\t" ); IR_OFF; delayMicroseconds(4); __asm__( "nop\n\t" "nop\n\t" "nop\n\t" "nop\n\t" "nop\n\t" "nop\n\t" ); } // delay(1); delayMicroseconds(996); pulseB(); // (E) pulseA(7); pulseB(); // 共通部分終わり // コマンド依存部分 switch(sw) { case 0: // 消灯 pulseA(4); pulseB(); pulseA(19); pulseB(); pulseA(1); pulseB(); pulseB(); pulseB(); pulseB(); pulseA(1); pulseA(1); break; case 1: // メモリ点灯 pulseA(6); pulseB(); pulseA(17); pulseB(); pulseA(1); pulseB(); pulseB(); pulseA(1); pulseB(); pulseB(); pulseA(1); break; case 2: // 全点灯 pulseA(3); pulseB(); pulseA(20); pulseB(); pulseA(1); pulseB(); pulseA(2); pulseB(); pulseA(2); break; case 3: // 常夜灯 pulseA(5); pulseB(); pulseA(18); pulseB(); pulseA(1); pulseB(); pulseB(); pulseA(4); break; } delayMicroseconds(9000); // delay(9); } } // // パルス(A)出力 // void pulseA(int n) { int i, j; for(j = 0; j < n; j++) { for(i = 0; i < 19; i++) { IR_ON; delayMicroseconds(5); __asm__( "nop\n\t" "nop\n\t" ); IR_OFF; delayMicroseconds(4); __asm__( "nop\n\t" "nop\n\t" "nop\n\t" "nop\n\t" "nop\n\t" "nop\n\t" "nop\n\t" ); } delayMicroseconds(497); __asm__( "nop\n\t" "nop\n\t" ); } } // // パルス(B)出力 // void pulseB(void) { int i; for(i = 0; i < 58; i++) { IR_ON; delayMicroseconds(5); __asm__( "nop\n\t" "nop\n\t" ); IR_OFF; delayMicroseconds(4); __asm__( "nop\n\t" "nop\n\t" "nop\n\t" "nop\n\t" "nop\n\t" "nop\n\t" ); } delayMicroseconds(495); } // // メインループ // void loop(void) { if(tm.checkTimeup()) { // 8ms周期 sw.KeyProc(); // キーセンス処理 } } // // キー入力ハンドラ // スイッチが押された時に呼び出される関数 // void KeyPress(byte keyval) { noInterrupts(); // 割込み禁止 switch(keyval) { case 1: // SW1(上) メモリ点灯 irOut(1); break; case 3: // SW3(下) 消灯 irOut(0); break; case 2: // SW2(右) 全灯 irOut(2); break; case 4: // SW4(左) 常夜灯 irOut(3); break; } interrupts(); // 割込み許可 }
247、262行目で割込みの禁止、許可を行っています。
照明斜め下(垂線から30°ぐらいの位置)でLEDの向きは、ほぼ水平から垂直まで反応するようになりました。また、同時にレスポンスが非常によくなりました。
今回、赤外線LEDに東芝 TLN105Bを使い、直列の抵抗器の抵抗値を27/27=13.5Ωにして電流を多めにしてあります。電源はアルカリ乾電池2本で約3Vです。
●その他
余談ですが、WSN282の16MHz版を乾電池2本(3V)で駆動させたら、問題なく作動しました。ただし、このような使い方はプロセッサ(AVR mega328)の動作保障範囲外ですので、気温により動作が不安定になったり、ICのばらつきにより動かないという可能性もあります。あくまで、実験的に動作させるときだけにした方がよいでしょう。
ちなみに16MHz 2.9V(乾電池) のアイドル時の消費電流は 4.8mAでした。電圧が少しことなりますが、8MHz 2.7V(エネループ)で2.6mAでしたので、クロック周波数を半分に下げると消費電力も半分近くに下がるということです。
学習リモコンだと、いったん送信側でエンコードされたものを受信側でデコードする関係で波形がなまり、今回のような正確な波形は得られないと思います。
ただ、キャリア波の周波数が正確になったのが効いたのか、パルス幅や位置が正確になったのが効いたのかははっきりわかりません。多分キャリア波の周波数の方だと思いますが。
残る課題はスリープ対応です。