赤外線リモコンがどのようにしてデータを送り、受信側はどのようにデータを受け取っているのか、全くもって知らないので1から調べることにしました。
今回を番外編(電飾パート1.5)ということにして、調べた内容などを1つずつ淡々と自分用のメモとしてメモっていきます。
書いていくうちに訳が分からなくなっていきましたが数日かけて修正していきます。
図解等が無い部分も後日追加していく予定です。
最終目的地は
- 電飾コントロール用の赤外線リモコンを自作
- ヘッドライトやウインカーなどの電飾を赤外線リモコンによって別々にコントロールする
※メモの内容は”私が個人的に実現してみたい電飾赤外線コントロール”に必要な内容に絞っている為、「赤外線リモコンは絶対にこういう仕組みで動作している」という意味で解釈されないようにお願いします。
また、電子工作素人なのでメモの内容は間違っている可能性大…というか間違っていると思ってください。
赤外線リモコンのフォーマット
赤外線リモコンで使われる赤外線LED及び赤外線受信モジュールは”38kHz変調”という規格に基づいて動作しています(他の周波数も有り)
この”38kHz変調”が関係するのは送信する2進数の”「1」を送る期間”と、2進数を送る”周期”になります。
周期について
2進数はもちろん0と1で表現されるためこの2つの数字を伝えるには定められた周期を決めなければなりません。
この周期に関しては特に理由は分かりませんが”600㎲”となっており、38kHzを秒に直した”26㎲”を23回繰り返した周期となります。
「1」を送る期間
2進数の1を送る期間は要するにLEDをオンにすることで送る事ができますが、単にオンするだけではいけません。
赤外線LEDはその名の通り赤外線を発します。
この赤外線を受信モジュールで受け取ることによりデータを受け取ることが可能な訳ですが、実は人の目に見えていないだけで自然には赤外線(ノイズ)が溢れています。
この”自然にある赤外線”と”赤外線LEDが発した赤外線”を受信モジュールは見分けられないので一工夫する必要がある訳です。
そこで登場するのが38kHz変調です。
オン期間(2進数の1)を38kHzの点滅で送信することで、自然にある赤外線と区別することにしたのです。
ここまでのまとめ
このように”38kHz変調”に基づいてリモコンは設計されているので、今回利用する”赤外線リモコン受信モジュール”は38kHzで点滅する赤外線の光しか受け取らないように作られています。
ということはPICマイコンで制御する際もこの38kHz変調に合わせてプログラムを書く必要があるわけです。
プログラムを書く
今回使用するPICマイコンは12F675です。
12F675は赤外線リモコンの作例を何故か多く見かけるので選びました。(というか偶然初めて触ったPICマイコンがこれで慣れているってのもあります)
実際に組み込むPICはPIC16F15325になる予定なので最終的にプログラムの内容も変わってしまいますが、とりあえず練習ってことで。
実際に組み込むPICはPIC16F15325になる予定なので最終的にプログラムの内容も変わってしまいますが、とりあえず練習ってことで。
ちなみに受信に使う赤外線リモコン受信モジュールはSHARP製の”GP1UXC41QS”です。
秋月で1個\50とお手軽です。http://akizukidenshi.com/catalog/g/gI-06487/
ということでまずは送信機側のプログラムを書いていきます。
12F675の設定(重要な部分のみ)
- 内蔵クロック
- 動作周波数4MHz
- GP0~GP2を入力(3つのタクトスイッチに接続)
- GP4は出力(赤外線LEDを制御するトランジスタに接続)
送信側の設定はこんな感じです。
38kHz変調の関数を作る
まずは送信する2進数"0"と"1"の38kHz変調関数を作ります。
使用する赤外線リモコン受信モジュールのデータシートによるとハイレベルパルス幅の最小値が600㎲となっていたので余裕をもって780㎲の周期で作ることにしました。(このあたりはよく分かりません)
使用する赤外線リモコン受信モジュールのデータシートによるとハイレベルパルス幅の最小値が600㎲となっていたので余裕をもって780㎲の周期で作ることにしました。(このあたりはよく分かりません)
void l1() //点灯関数(38kHz変調)
{
for(b=0;b<30;b++){ //780us周期、26usを30回
GP4=1; //点灯
__delay_us(13);
GP4=0; //消灯
__delay_us(13);
}
}
void l0() //消灯関数(780us待つ)
{
GP4=0; //消灯
__delay_us(780); //780usの間
}
"0"に関しては消灯するだけでいいので780㎲の間消灯しているだけです。
"1"は38kHz(26㎲周期)の点滅を作らなければいけないのでfor文のループを使います。
点灯13㎲+消灯13㎲=26㎲
変数"b=0"に1を足していき"b=30"になると終了するループを作ることで30回の点滅(780㎲)を作る。
26㎲×30ループ=780㎲
ということでこれで38kHz変調の関数が完成かと思いきや、これでは全く上手く送受信できません。
何故かといいますと、マイコンの命令サイクルの時間を考慮していない為です。
1命令にかかる時間は4MHzの場合1㎲
1命令に1㎲ですから例えば1ループに10命令かかっていたとして、それが30回行われればそれだけで300㎲です。
780㎲を作りたいのに結果出来上がったのは1080㎲というとんでもない誤差になります。
アセンブラ言語であれば正確にこの命令サイクルが分かるようですが、C言語で正確な命令サイクルを求めることは難しいため、実際に何パターンも点灯消灯の長さを変えたりして試行錯誤する必要があります。
私が見た作例では「点灯10㎲ 消灯10㎲」や「点灯12㎲ 消灯11㎲」の場合があったり様々でした。
この点灯消灯の時間については終盤に解決します。
2進数を送信するプログラム
関数が出来上がったので次は2進数を送信するプログラムです。まず送りたい2進数を決めます。
- 1001
- 1011
- 0110
3パターン用意しました。
4bitの2進数を送る訳ですが、このままだと3つ目の「0110」は0=オフから始まるので受信側はデータの始まりを判別できません。
なので4bitの頭に「1」を挿入します。
これをスタートビットというらしいです。
- 11001
- 11011
- 10110
これで一応はOKですが、「正確に受信できているのか」「ここが終わり」を判別するためにストップビット「10101」を加えます。
- 1100110101
- 1101110101
- 1011010101
これで10bitのデータの出来上がりです。
(デバイスコードというものもあるようですが今回は省きます)
プログラムを書きます。
3つのデータを3つのスイッチ(GP0~GP2)に割り振りスイッチが押されると実行されるようになっています。(タクトスイッチはプルアップ接続しています)
while(1){
a=1;
switch(a){
case 1:
if(GP0==0){ //GP0のスイッチが0(オン)なら開始
l1(); //スタートビット
l1();l0();l0();l1(); //送信する2進数(1001)
l1();l0();l1();l0();l1(); //ストップビット(10101)
GP4 = 0;
__delay_ms(3000); //3秒停止
a=2;
break;
}
a=2;
case 2:
if(GP1==0){ //GP1のスイッチが0(オン)なら開始
l1(); //スタートビット
l1();l0();l1();l1(); //送信する2進数(1011)
l1();l0();l1();l0();l1(); //ストップビット(10101)
GP4 = 0;
__delay_ms(3000); //3秒停止
a=3;
break;
}
a=3;
case 3:
if(GP2==0){ //GP2のスイッチが0(オン)なら開始
l1(); //スタートビット
l0();l1();l1();l0(); //送信する2進数(0110)
l1();l0();l1();l0();l1(); //ストップビット(10101)
GP4 = 0;
__delay_ms(3000); //3秒停止
a=1;
break;
}
a=1;
} //case
} //while
受信側のプログラムを作る
受信側のプログラムを作っていきます。
カーモデルの電飾ですからウインカーなどでdelayを多用します。
delay中にも赤外線を受け取れるように割り込みを使って処理をしていきます。
まずはマイコンの設定です。
動作クロックは送信側と同じ4MHzです。
動作クロックは送信側と同じ4MHzです。
void main() {
//設定
CMCON = 0x07 ; //下位3bitコンパレータオフ
ANSEL = 0x00 ; //デジタルIOに設定
TRISIO = 0x04 ; //GP2を入力に設定
GPIO= 0x00 ; //IOの初期化
//割り込み設定
IOC = 0x04; //GP2割り込み検知
INTE = 1; //GP2割り込み検出の有効化
INTEDG = 0; // 割込み条件を立下がりエッジにする
INTF = 0; //GP2外部割込みフラグ、ソフトウェアで解除が必要
GIE = 1; //全体割り込みを許可
続いて受信処理(割り込み)です。
void __interrupt () isr (void){
GIE = 0; //全体割り込み不許可
if (INTF == 1) { //外部割込みが1の場合実行
while(1){
a=0;b=0;c=0;d=0;e=0; //2進数用データ
/*受信中スタートビットもしくはストップビッドが一致しなかった場合は処理を
* 中断しリセット*/
if(GP2==0) //スタートビット検出
__delay_us(100);
else break;
if(GP2==0) //確認
__delay_us(780);
else break;
if(GP2==0) //受信する2進数1ケタ目
b = 1; //1なら1を代入
__delay_us(780);
if(GP2==0) //受信する2進数2ケタ目
c = 5; //1なら5を代入
__delay_us(780);
if(GP2==0) //受信する2進数2ケタ目
d = 10; //1なら10を代入
__delay_us(780);
if(GP2==0) //受信する2進数2ケタ目
e = 20; //1なら20を代入
__delay_us(780);
if(GP2==0) //ストップビット1
__delay_us(780);
else break;
if(GP2==1)//ストップビット0
__delay_us(780);
else break;
if(GP2==0) //ストップビット1
__delay_us(780);
else break;
if(GP2==1)//ストップビット0
__delay_us(780);
else break;
if(GP2==0) //ストップビット1
a=b+c+d+e; //受信した2進数の代入値を合計
else break;
break;
/*受信処理ここまで*/
} //end of while
} //end of if
__delay_ms(30);
INTF = 0 ; // 割り込みフラグをクリア
GIE = 1; //全体割り込みを許可
} //end of 外部割り込み
スタートビットとストップビットが一致しなければ処理は中断され割り込みは終了されます。
受信するスタートビットとストップビットを除く4bitの2進数については、「0」を受信した場合は何もせず、「1」の場合にのみ対応する桁の変数に「b+1」「c+5」「d+10」「e+20」を実行。
最後に「a=b+c+d+e」を実行することで変数aに合計値を代入。
1001=1+0+0+20="21"=a
1011=1+0+10+20="31"=a
0110=0+5+10+0="15"=a
変数aの数値によって受信した2進数がどれなのかを判別します。
受信した2進数に対応したプログラムを実行
今回はテストなのでシンプルです。
赤外線を受信し変数aに「21」「31」「15」のいずれかの数値が代入されると実行されます。
それ以外の時はなにもせずただループしているだけです。
GP0,GP1,GP4にはLEDが接続されています。
while(1){
switch(a){
case 21: //2進数1001を受信した時に実行
GP0 = !GP0;
__delay_ms(30);
a=0;b=0;c=0;d=0;e=0;
break;
case 31: //2進数1011を受信した時に実行
GP1 = !GP1;
__delay_ms(30);
a=0;b=0;c=0;d=0;e=0;
break;
case 15: //2進数0110を受信した時に実行
GP4 = !GP4;
__delay_ms(30);
a=0;b=0;c=0;d=0;e=0;
break;
default: //代入値が不正の場合何もせずリセット
break;
}//end of switch
}//end of while
実際に動かしてみる
いよいよブレッドボード上でテストする段階に来ました。
で、実際にブレッドボードで組んだのがこちら。
![]() |
| 左が送信機、右が受信機 |
受信機側に1個余分なLEDが刺さっていますがこれはデバッグ用で関係ありません。
動作は単純で、赤青黄のタクトスイッチを押すと、それに対応した緑のLEDをオンオフすることができます。
手前のスイッチは手前のLED、真ん中は真ん中、奥は奥というふうに対応しています。
で、結果はもちろんちゃんと動作しませんでした。
スイッチを押しても反応が無かったり、手前のスイッチを押すと真ん中のLEDが点灯したり。
原因は分かっていて、38kHz変調の周期が命令サイクルによって狂っているからです。
void l1() //点灯関数(38kHz変調)
{
for(b=0;b<30;b++){ //780us周期、26usを30回
GP4=1; //点灯
__delay_us(13);
GP4=0; //消灯
__delay_us(13);
}
}
この点灯関数の点灯消灯の時間や、繰り返しの回数を調節する必要があります。
この作業、2日かかりました。
結果は…失敗。
というかこのままやっても成功に辿り着くまでに時間がかかりすぎます。
この作業、2日かかりました。
結果は…失敗。
というかこのままやっても成功に辿り着くまでに時間がかかりすぎます。
ということで新しくツールを導入することに。
「DSO138」という自分で組み立てて(はんだ付け含む)使えるお手軽なオシロスコープです。
早速まずは38kHz変調を調節してみました。
37.990kHzと限りなく38kHzに近い数値を出すことに成功。
次にオン期間が780㎲になるように調節しますが、これは目で見て合わせました。
オフ期間とオン期間の幅が同じになるように調節。
画像ではまだオン期間が長いようです。
このようにオシロスコープを使って調節した結果がこれです。
オシロスコープ無しでこの数値まで辿り着くのは至難の業だと思います…
オシロスコープ様様です。
上記のようにプログラムを書き換えて実際に動かしてみるとすんなりと動作するもんなんですよね~すごい。
動画です⇩
ということで以上です。
「DSO138」という自分で組み立てて(はんだ付け含む)使えるお手軽なオシロスコープです。
早速まずは38kHz変調を調節してみました。
37.990kHzと限りなく38kHzに近い数値を出すことに成功。
次にオン期間が780㎲になるように調節しますが、これは目で見て合わせました。
![]() |
| 1101 |
画像ではまだオン期間が長いようです。
このようにオシロスコープを使って調節した結果がこれです。
void l1() //点灯関数(38kHz変調)
{
for(b=0;b<24;b++){ //30回から24回に
GP4=1; //点灯
__delay_us(4); //13から4に
GP4=0; //消灯
__delay_us(4); //13から4に
}
}
オシロスコープ無しでこの数値まで辿り着くのは至難の業だと思います…
オシロスコープ様様です。
上記のようにプログラムを書き換えて実際に動かしてみるとすんなりと動作するもんなんですよね~すごい。
動画です⇩
ということで以上です。







0 件のコメント :
コメントを投稿