2011/11/23 水曜日

【電子工作】赤外線リモコン式調光器【ソフトウェア編】

Filed under: ソフトウェア関連,ハードウェア関連,日記,電気電子・情報関連 — ハイジんブルー @ 20:38:45

【電子工作】赤外線リモコン式調光器 【目次】

【電子工作】 赤外線リモコン式調光器 【信号解析】
【電子工作】 赤外線リモコン式調光器 【試作編】
【電子工作】 赤外線リモコン式調光器 【ハードウェア編】
【電子工作】 赤外線リモコン式調光器 【ソフトウェア編】←今ココ
【電子工作】 赤外線リモコン式調光器 【実装編】
【電子工作】 赤外線リモコン式調光器 【まとめ】

最後にマイコンのソフトウェア部分。mikroCを使って書きました。

正直なところプログラミングは苦手です…

よって、あまり賢いコードになってないと思いますので参考程度に…

リモコン信号解析部

【試作編】でも書きましたが、リモコン信号の解析部分は以下のページのソースコードを用いました。

TVリモコン信号の受信機 – PICとMikroC
http://kuri6005.sakura.ne.jp/pic/index.php?
TVリモコン信号の受信機

ただ、上記ページのソースコードでは『カスタムコード』部分の信号は無視するようになっているので、『カスタムコード』の判定も行うように改良しました。

if (custom_a == 0x86 && custom_b == 0x3b)

を追加しただけです…

圧電サウンダの発音部

圧電サウンダようにRA1ポートから矩形波を発生させます。PICのPWM機能を使うと簡単に発音できるようですが、今回はPWM機能が使える端子を赤外線リモコンの受信に使っているため、ON/OFFのタイミングをDelayを使って制御しました。

//ブザー1kHz
void buzzer_1000() {

unsigned short int i;
for(i = 0; i < 128; i++){

PORTA.F1 = 1;
Delay_us(450);
PORTA.F1 = 0;
Delay_us(450);

}

}


//ブザー2kHz
void buzzer_2000() {

unsigned short int i;
for(i = 0; i < 255; i++){

PORTA.F1 = 1;
Delay_us(225);
PORTA.F1 = 0;
Delay_us(225);

}

}


//ブザー4kHz
void buzzer_4000() {

unsigned short int i;
for(i = 0; i < 255; i++){

PORTA.F1 = 1;
Delay_us(100);
PORTA.F1 = 0;
Delay_us(100);

}

}

Delayの値を変えることで1kHz、2kHz、4kHzの音を出せるようにしました。この方法では音が出ている間はPICが他の処理を行えなくなります。よって、リモコンを素早く連打しても入力を受け付けてくれません。iの最大値を変えて音の長さを変えれますが、今回は操作確認音に使う4kHzを音の長さと操作性のバランスが良いように値を設定しました。

ちなみに、圧電サウンダは周波数によって聞こえる音の大きさ違います。このソースのまま実行すると音の大きさが周波数によって大きく変わってしまうので、同じ音量にするにはデューティー比を調節する必要があります。今回は音量がさほど重要でもないので特別なことしませんでした。

音を鳴らしたい箇所にbuzzer_1000()と記述すれば音が鳴ります。

例えば、今回起動音として

buzzer_1000();
buzzer_2000();

終了音として

buzzer_2000();
buzzer_1000();

を用いました。はい、お気づきの人はマニアですね。98を意識しています(笑)

調光制御部

蛍光灯の点灯はPORTBのF1~6の値を1/0で変化させることで制御します。1ならON、0ならOFFになる。PORTBの値は上位ビットからF7/F6/F5/F4/F3/F2/F1/F0の順に並んで8ビットになっている。よって、点灯数を増減させるにはシフト演算を用いました。

シフト演算とはビットを左右にずらすこと。例えば00001100というビット列を左シフトすると00011000と左に1個ズレる。00001100を右シフトすると00000110と右に1個ズレる。

ズレることで空きができるビットには0が入るため、点灯数を減らす方は非常に簡単。

void main_lamp_dim(){

PORTB = (PORTB >> 1)&254;

}

全点灯「111111」から右シフトすると「011111」になり点灯数が5、もう一度右シフトで「001111」で点灯数が4となる。右シフトを繰り返せば減光が可能になる。上の関数では&254を付けてビットマスク操作をしてますが、これは最下位ビット(PORTB.F0)が別な用途で使われているからです。

逆に点灯数を増やす場合は一工夫必要となる。

void main_lamp_bright(){

PORTB = (PORTB << 1)&254;
PORTB.F1 = 1;

}

点灯数2の「000011」から左シフトしても「000110」になる。よって、最下位ビットには1を代入する必要がある。

ちなみに、全点灯状態のPORTBの中身を見ると「0111111x」(xは別用途使用のため不確定)となってますが、この状態から点灯数増処理を行うと「1111111x」となり、全点灯状態が維持される。この状態で減光のために右シフトすると「0111111x」となる。つまり、減光ボタンを押したハズなのに全点灯状態が維持されてしまう。これは、PORTBの最上位ビットが存在するために右シフトのゼロ代入がすぐに反映されないためである。

よって、プログラム内でこれを防ぐため赤外線リモコンからの割り込みを待っている間の無限ループに

PORTB.F7 = 0;

を行うようにしました。ビットマスク操作すればできそうですが、どうもうまくできなかったし、PORTB.F7は使用していないのでこのようにしました。動けば良いんです!

【実装編】

ソースコード

/**
赤外線リモコン式蛍光灯調光器
Device Flags: _BODEN_OFF _BOREN_OFF _CP_OFF _PWRTE_ON _WDT_OFF
_LVP_OFF _MCLRE_OFF _INTRC_OSC_NOCLKOUT

PICとMikroCさん(http://kuri6005.sakura.ne.jp/pic/)のソースを
PIC16F88  内蔵4MHzに変更
RB0:赤外線リモコン受信モジュール PL-TRM2161-C438 二個で100円
LED: RB1, RB2, RB3, RB4, RB5, RB6 デバグ時の動作確認用
電源:AC100V (DC側4~5V)
コンパイラ:MikroC Pro 5.0.1
*/

unsigned short int flag = 0;
unsigned short int custom_a = 0; //カスタムコードその1
unsigned short int custom_b = 0; //カスタムコードその2
unsigned short int data_a = 0; //赤外線データ部
unsigned short int data_b = 0; //赤外線データ部(反転値)
unsigned short int main_power = 0; //全消灯確認
unsigned short int signal_ok = 0; //赤外線信号解析成功で確認音
unsigned short int debug = 0; //デバグ時に1

//ブザー1kHz
void buzzer_1000() {

unsigned short int i;
for(i = 0; i < 128; i++){

PORTA.F1 = 1;
Delay_us(450);
PORTA.F1 = 0;
Delay_us(450);

}

}
//ブザー2kHz
void buzzer_2000() {

unsigned short int i;
for(i = 0; i < 255; i++){

PORTA.F1 = 1;
Delay_us(225);
PORTA.F1 = 0;
Delay_us(225);

}

}

//ブザー4kHz

void buzzer_4000() {

unsigned short int i;
for(i = 0; i < 255; i++){

PORTA.F1 = 1;
Delay_us(100);
PORTA.F1 = 0;
Delay_us(100);

}

}

void main_button(){

if(main_power == 1){

PORTB = 0b00000000;
PORTA.F0 = 0;
PORTA.F3 = 0;
main_power = 0;
buzzer_2000();
buzzer_1000();

}else{

main_power = 1;
PORTB = 0b00000110;
buzzer_1000();
buzzer_2000();

}

}

void sub_lamp_a(){

if(PORTA.F0 == 0){

PORTA.F0 = 1;
main_power = 1;

}else{

PORTA.F0 =0;

}
signal_ok =1;

}

void sub_lamp_b(){

if(PORTA.F3 == 0){

PORTA.F3 = 1;
main_power = 1;

}else{

PORTA.F3 =0;

}

signal_ok = 1;

}

void main_lamp_dim(){

PORTB = (PORTB >> 1)&254;
signal_ok = 1;

}

void main_lamp_bright(){

PORTB = (PORTB << 1)&254;
PORTB.F1 = 1;
main_power = 1;
signal_ok = 1;

}

void debug_mode(){

if(debug == 1){

PORTA.F2 = 0;
debug = 0;
buzzer_1000();
buzzer_2000();
buzzer_1000();

}else{

PORTA.F2 = 1;
debug = 1;
buzzer_1000();
buzzer_2000();
buzzer_1000();
buzzer_2000();
buzzer_1000();
buzzer_2000();

}

}

void interrupt() { //割込み関数

unsigned short int i, b;

if(INTCON.INTF) { //割込み種がRB0/INT割込みの場合

INTCON.INTE = 0; //RB0/INT割込みの禁止
//リーダ部の確認 時間の計測にはタイマー0を割込みなしで使用
TMR0 = 0; //timer0リセット、タイマーは常に動作
while(PORTB.F0 == 0); //0Vの状態をカウント、5Vになるまでループ
//カウンタの比較
if(TMR0 < 120) { // < 141 (=9.0ms * 4MHz/4 /64) 誤差を見越して120

flag = 1; //120より少なかったらヘッダではないので受信を終了し、
return; //メインルーチンに戻り、受信待ち状態にします

} //120以上であれば次に進みます。

//リーダー部の残りの部分を判定
TMR0 = 0; //timer0リセット
while(PORTB.F0 == 1); //5Vの状態をカウント
if(TMR0 < 27) { // < 35 (=2.250ms * 4MHz/4 /64)

flag = 1;
return;
}else if(TMR0 < 60) { // < 71 (=4.5ms * 4MHz/4 /64)
//リピートリーダを受信した時(現バージョンでは無視する)
Delay_ms(96);
flag = 1;
return;

}
//リーダー部の確認終了

//custom codeの取得
custom_a = 0;
for (i = 0; i < 8; i++) { //繰返し0-7になるまで

TMR0 = 0; //timer0リセット
while(PORTB.F0 == 0); //0Vの期間をカウント
while(PORTB.F0 == 1); //5Vの期間をカウント
if(TMR0 < 27) // < 18 (=1.125ms * 4MHz/4 /64) 誤差を見越して27(35より小さい値)
b = 0;
else // < 35 (=2.250ms * 4MHz/4 /64)
b = 1;
custom_a |= (b << i); //custom_aと(b << i)の論理和をcustom_aに代入する
//bを左にiビットずらす

}
custom_b = 0;
for (i = 0; i < 8; i++) {

TMR0 = 0; //timer0リセット
while(PORTB.F0 == 0);
while(PORTB.F0 == 1);
if(TMR0 < 27) // < 18 (=1.125ms * 4MHz/4 /64)
b = 0;
else // < 35 (=2.250ms * 4MHz/4 /64)
b = 1;
custom_b |= (b << i);

}
//custom codeの取得終了

//data codeの取得
data_a = 0;
for (i = 0; i < 8; i++) {

TMR0 = 0; //timer0リセット
while(PORTB.F0 == 0);
while(PORTB.F0 == 1);
if(TMR0 < 27) // < 18 (=1.125ms * 4MHz/4 /64)
b = 0;
else // < 35 (=2.250ms * 4MHz/4 /64)
b = 1;
data_a |= (b << i);

}

data_b = 0;
for (i = 0; i < 8; i++) {

TMR0 = 0; //timer0リセット
while(PORTB.F0 == 0);
while(PORTB.F0 == 1);
if(TMR0 < 27) // < 18 (=1.125ms * 4MHz/4 /64)
b = 0;
else // < 35 (=2.250ms * 4MHz/4 /64)
b = 1;
data_b |= (b << i);

}
//while(PORTB.F0 == 0); //ストップビット受信

if (custom_a == 0x86 && custom_b == 0x3b) {

//data_aとdata_bの各ビットを反転しその結果が同じか比較
if (data_a == ~data_b) { //data誤りのチェックOKの場合

//オリンパスリモコン用判別コード
switch (data_a) {

case 0x01: //メインボタンが押されている場合
main_button();
break;

case 0x02: //Wボタンが押されている場合
sub_lamp_a();
break;

case 0x03: //Tボタンが押されている場合
sub_lamp_b();
break;

case 0x04: //マイナスボタンが押されている場合
main_lamp_dim();
break;

case 0x05: //プラスボタンが押されている場合
main_lamp_bright();
break;

case 0x7e:
debug_mode();
break;

default:
PORTB.F1 = 0;
PORTB.F2 = 0;
PORTB.F3 = 0;
PORTB.F4 = 0;
PORTB.F5 = 0;
PORTB.F6 = 0;
PORTA.F0 = 0;
PORTA.F3 = 0;
break;

}

}

}

}
Delay_ms(40);
flag = 1;

}

void main() {

//使用変数の定義
unsigned int i;
PORTA = 0b00000000; //PORTAの中身をきれいにする
PORTB = 0b00000000; //PORTBの中身をきれいにする
CMCON = 0b00000111; //PORTAをデジタル入出力使用に設定
ANSEL = 0b00010000;
OSCCON = 0b01100000; //内臓クロック4MHzに設定
TRISA = 0b00010000; //RA0を1:入力、他は0:出力に設定
TRISB = 0b00000001; //RB0を1:入力、他は0:出力に設定

//初期点灯動作
PORTB = 0b00000110;
main_power = 1;
//起動確認音
buzzer_1000();
buzzer_2000();

signal_ok = 0;

//timer0プリスケーラ64回に設定
OPTION_REG = 0b10000101;

INTCON.INTE = 1; //RB0/INT割込みの許可
INTCON.GIE = 1; //全体割込み許可

do {

if(flag == 1) {

flag = 0;
PORTB.F7 = 0; //全点灯時の左シフト対策
INTCON.INTE = 1; //RB0/INT割込みの許可

}
if(main_power ==1 && ((PORTB)&126) == 0b00000000 && ((PORTA)&9) == 0b00000000){

main_power = 0;

}
if(signal_ok == 1){

buzzer_4000();
signal_ok = 0;

}

}while(1);

}

【実装編】

コメントはまだありません »

No comments yet.

RSS feed for comments on this post. TrackBack URL

Leave a comment

HTML convert time: 0.641 sec. Powered by WordPress