VNゲージ ユニトラムの走行を赤外線リモコンで制御する装置をPICマイコンで作成しました。軌道のレイアウトは、74.5cm X 30cm と本棚に収まる大きさにしたため、奥行きが狭くなり閉ループの軌道にすることができません。このため端から端までを往復する複線の半楕円軌道です。
市販のTV用赤外線リモコンからのコマンドを PIC16F1503 で解析し、自動往復運転をさせたり、リモコンの音量キー【大小】とチャンネルキー【+-】でトラムの速度を手動コントロールし、駅に停車させることなどができます。また、レイアウト上に設置されている建物に照明用白色LEDを取り付けました。これらのON/OFFもリモコン操作することができます。制御装置自体は銀行ビルの中にあります、銀行の窓の内側に赤外線受光センサーが位置するように配置し、レイアウト全体がすっきりするよう心がけました。
TV用赤外線リモコンは、JVCの簡単リモコンRM-225を使用し、メーカーコードは、ビクター/JVCにしています。トラムへのコマンドは、右図のキーに設定しました。
赤外線リモコン受信モジュール PL-IRM2161-XD1 で受信した信号をPICマイコン PIC16F1503 で処理し、トラムの走行コマンドであれば、モータードライバー TA7291P に PWM 信号を出力します。 ビルの照明点灯コマンドなら、LED駆動用の 2SC1815 をONにし、LEDに約10mAの電流を流します。
軌道の端には、トラムがオーバーランしないように、絶縁ジョイナーを使い無給電区間が設けられています。 無給電区間に停止しているトラムが、軌道レイアウトの中央方向には走行できるよう、ダイオードによりギャップをバイパスしています。
制御部は構成部品数が出来る限り少なくなるように設計しました。電源は、直流12v 1A のパワーパックを使用しています。軌道プレートには、独自の加工は加えていません。純正オプションの給電フィーダ4個、絶縁ジョイナー4個を取り付けて目的の機能を達成しています。
回路図をクリックすると別ウインドで大きな回路図を表示します。
/*************************************************************** * 赤外線リモコンを受信しトラムを制御 20150110 * ビクター/JVC のリモコンコードを利用 * 1 VDD 14 VSS * 2 RA5 IrIN 13 RA0 * 3 RA4 VR 12 RA1 * 4 MCLR 11 RA2 PWM3 * 5 RC5 PWM1 10 RC0 LGT3 * 6 RC4 LGT2 9 RC1 PWM4 * 7 RC3 PWM2 8 RC2 LGT1 * * * Notes: 4MHz 内部クロック * Language: MPLABX XC8 PIC16F1503 * Copyright (c) 2015 iwamoto All Rights Reserved ****************************************************************/ #include <xc.h> #define _XTAL_FREQ 4000000 #define L_ON_Min 5000 // 5msec リーダーの最小継続時間 #define DataTH 1000 // 1msec データの「1」「0」境界時間 #define D_Off_Max 2000 // 2msec データ「0」の最大継続時間 #define IrIN RA5 // 赤外線センサー入力 #define autoRunSec 10 // 自動走行の片側走行時間 #define duty_1 50 // spd_1のDutycycle #define duty_2 100 // spd_2のDutycycle #define duty_3 200 // spd_3のDutycycle #define LGT1 LATC0 // 照明1の出力端子 #define LGT2 LATC4 // 照明2の出力端子 #define LGT3 LATC2 // 照明3の出力端子 #define innerN1 PWM3CON // 内回りドライバICのN1端子 #define innerN2 PWM4CON // 内回りドライバICのN2端子 #define outerN1 PWM1CON // 外回りドライバICのN1端子 #define outerN2 PWM2CON // 外回りドライバICのN2端子 #define innerD1 PWM3DCH // 内回りトラム速度(Duty) #define innerD2 PWM4DCH // 内回りトラム速度(Duty) #define outerD1 PWM1DCH // 外回りトラム速度(Duty) #define outerD2 PWM2DCH // 外回りトラム速度(Duty) #define inner 0 // トラム外回り #define outer 1 // トラム内回り #define both 2 // 両トラム #define stop 0 // トラム速度 停止 #define spd_1 1 // トラム速度 1 #define spd_2 2 // トラム速度 2 #define spd_3 3 // トラム速度 3 #define left 0 // トラム走行方向 #define right 1 // トラム走行方向 #define none 0 // 自動走行ステイタス 自動走行でない #define startauto 1 // 自動走行ステイタス 自動走行開始 #define accel 2 // 自動走行ステイタス 加速 #define cruise 3 // 自動走行ステイタス 走行 #define turnback 4 // 自動走行ステイタス 方向反転 // コンフィグ ******************************************************** #pragma config FOSC = INTOSC, WDTE = OFF, PWRTE = ON, MCLRE = ON #pragma config CP = OFF, BOREN = ON, CLKOUTEN = OFF #pragma config WRT = OFF, STVREN = ON, BORV = LO, LPBOR = OFF, LVP = OFF // ----- 関数プロトタイピング ----- char IrCmdRcv(void); int IrByteRcv(void); void run(unsigned char spd, char dir, char tram); int delay_sec(char sec); char invert(char); void main(void){ int delay[2]; // 添字(inner or outer) char speed[2]; char runDir[2]; char autoRun[2]; char timeRun[2]; unsigned char spdVal[] = {stop,duty_1,duty_2,duty_3}; unsigned char cmd; char tram; char i; OSCCON = 0b01101010; // 内部クロック4Mhz // OSCCON = 0b01111010; // 内部クロック 16 MHz ANSELA = 0; // 全てデジタルIO ANSELC = 0; // 全てデジタルIO PORTA = 0x00; PORTC = 0x00; TRISA = 0b11111011; //PortA すべて入出力 TRISC = 0b11000000; //PortC すべて出力 //---------------------- Timer 0 設定 (66mSecごと)-------------------- OPTION_REG = 0b00000111; // 内部クロックをカウント PS_1/256 TMR0IF = 0; // タイマー0 フラッグをクリア //---------------------- Timer 1 設定 -------------------------------- // リモコンパルス幅の計測にT1を使用する Ti は、1カウント 1uS T1CON = 0b00000001; // Timer 1 FOSC/4 1/1 sync ON // T1CON = 0b00100001; // Timer 1 FOSC/4 1/4 sync ON for 16MHz //---------------------- PWM 設定 ------------------------------------ // T2CON = 0b00000100; // Timer 2 PS1/1設定 T2CON = 0b00000101; // Timer 2 PS1/4設定 PR2 = 0xFF; // Timer2 Period Register設定 PWM1DCL = 0; PWM2DCL = 0; PWM3DCL = 0; PWM4DCL = 0; //---------------------- 変数初期設定 -------------------------------- delay[inner] = 0; delay[outer] = 0; speed[inner] = stop; speed[outer] = stop; runDir[inner] = left; runDir[outer] = left; autoRun[inner] = none; autoRun[outer] = none; // ---------------- 以下は受信の繰り返し -------------------------- while(1){ cmd = IrCmdRcv(); // ---- 受信コマンドの処理 ----------------------------------- switch (cmd){ case 0: // コマンド無 ---------------------------- break; // +++++++++++++++++++++++++++++ 照明コマンド ++++++++++++++++ case 0x21: // key1 --------------ビル1 照明 ON/OFF ----------- LGT1 = !LGT1; break; case 0x22: // key2 --------------ビル2 照明 ON/OFF ----------- LGT2 = !LGT2; break; case 0x23: // key3 --------------ビル3 照明 ON/OFF ----------- LGT3 = !LGT3; break; // +++++++++++++++++++++++++++++ 外側トラムコマンド +++++++++++ case 0x24: // key4 --------------外側 方向転換 ------------------- speed[outer] = stop; run(stop,runDir[outer],outer); delay[outer] = delay_sec(1); autoRun[outer] = none; break; case 0x27: // key7 --------------外側 最高速 --------------------- speed[outer] = spd_3; run(spdVal[spd_3],runDir[outer],outer); delay[outer] = 0; autoRun[outer] = none; break; case 0x2A: // key10 --------------外側 停止 ---------------------- speed[outer] = stop; run(stop,runDir[outer],outer); delay[outer] = 0; autoRun[outer] = none; break; case 0x1E: // key 大 --------------外側 加速 ---------------------- if(speed[outer] < spd_3)speed[outer]++; run(spdVal[speed[outer]],runDir[outer],outer); delay[outer] = 0; autoRun[outer] = none; break; case 0x1F: // key 小-------------- 外側 減速 ---------------------- if(speed[outer] > stop)speed[outer]--; run(spdVal[speed[outer]],runDir[outer],outer); delay[outer] = 0; autoRun[outer] = none; break; // ++++++++++++++++++++++++++++++ 内側トラムコマンド +++++++++++++++ case 0x26: // key6 -------------- 内側 方向転換 ------------------- speed[inner] = stop; run(stop,runDir[inner],inner); delay[inner] = delay_sec(1); autoRun[inner] = none; break; case 0x29: // key9 -------------- 内側 最高速 --------------------- speed[inner] = spd_3; run(spdVal[spd_3],runDir[inner],inner); delay[inner] = 0; autoRun[inner] = none; break; case 0x2C: // key12 -------------- 内側 停止 ---------------------- speed[inner] = stop; run(stop,runDir[inner],inner); delay[inner] = 0; autoRun[inner] = none; break; case 0x19: // keyPLS -------------- 内側 加速 ----------------------- if(speed[inner] < spd_3)speed[inner]++; run(spdVal[speed[inner]],runDir[inner],inner); delay[inner] = 0; autoRun[inner] = none; break; case 0x18: // keyMNS -------------- 内側 減速 ------------------------ if(speed[inner] > stop)speed[inner]--; run(spdVal[speed[inner]],runDir[inner],inner); delay[inner] = 0; autoRun[inner] = none; break; // ++++++++++++++++++++++++++++++自動走行コマンド ++++++++++++++++++ case 0x28: // key8 for(tram = inner;tram <= outer;tram++){ if(!autoRun[tram]){ autoRun[tram] = startauto; delay[tram] = delay_sec(1); speed[tram] = stop; runDir[tram] = invert(runDir[invert(tram)]); if(tram == inner)timeRun[inner] = autoRunSec; else timeRun[outer] = autoRunSec + 1; run(stop,runDir[tram],tram); } } break; case 0x2B: // key11 for(tram = inner;tram <= outer;tram++){ if(!autoRun[tram]){ autoRun[tram] = startauto; delay[tram] = delay_sec(1); speed[tram] = stop; runDir[tram] = invert(runDir[invert(tram)]); timeRun[tram] = autoRunSec; run(stop,runDir[tram],tram); } } break; // ++++++++++++++++++++++++++ その他 リセットコマンド ++++++++++ default: LGT1 = 0; LGT2 = 0; LGT3 = 0; autoRun[inner] = none; autoRun[outer] = none; delay[inner] = 0; delay[outer] = 0; speed[inner] = stop; speed[outer] = stop; runDir[inner] = left; runDir[outer] = left; run(stop,runDir[inner],inner); run(stop,runDir[outer],outer); break; } // ---- 受信コマンドの処理 (ここまで) ------------------- // ---- Timer0による定期処理 (66mSec毎) ---------------------- if(TMR0IF){ TMR0IF = 0; // ---- 方向転換コマンドの後処理 ------------------------- for(tram = inner;tram <= outer;tram++){ if(delay[tram]){ delay[tram]--; // ---- 外側コマンドの処理 ----------------------- if(delay[tram]==0){ switch(autoRun[tram]){ case none: // 方向反転 --------------- runDir[tram] = invert(runDir[tram]); speed[tram] = spd_1; run(spdVal[spd_1],runDir[tram],tram); break; case startauto: // 自動走行 開始--------------- speed[tram] = spd_1; run(spdVal[spd_1],runDir[tram],tram); delay[tram] = delay_sec(1)/2; autoRun[tram] = accel; break; case accel: // 自動走行 加速--------------- speed[tram] = spd_2; run(spdVal[spd_2],runDir[tram],tram); delay[tram] = delay_sec(1)/2; autoRun[tram] = cruise; break; case cruise: // 自動走行 走行--------------- speed[tram] = spd_3; run(spdVal[spd_3],runDir[tram],tram); delay[tram] = delay_sec(timeRun[tram]); autoRun[tram] = turnback; break; case turnback: // 自動走行 反転--------------- runDir[tram] = invert(runDir[tram]); speed[tram] = spd_1; run(spdVal[spd_1],runDir[tram],tram); delay[tram] = delay_sec(1)/2; autoRun[tram] = accel; break; default: speed[tram] = stop; run(stop,runDir[tram],tram); autoRun[tram] = 0; break; } } } } // ---- 方向転換(ここまで) --------------------------- } // -------- Timer0による定期処理 (ここまで) ---------- } } //******* 秒単位のTimer0によるディレーを得るためのカウント数を計算する ***** int delay_sec(char sec){ return (sec*1008)/66;} //*************** 赤外線コマンドを受信する ***************************** // 正常にコマンドを受信したときは、そのコマンドを関数の返り値とし // 無信号、エラー時は、0x00 を返す。 // ビクターの信号体系(NEC系)を受信 // リーダ受信確認、カスタムコード(8bits)確認、コマンド受信(8bits)する //********************************************************************** char IrCmdRcv(void){ int rcvData; if(IrIN)return 0; // 無信号「1」時は復帰 // ------- Start Bit (リーダー)の確認 ----------------------------- //OnおよびOFFの時間を確認しノイズか有効データか判断する // ------------------------------------------------------------------ TMR1 = 0; // -- リーダーのON 時間測定 TMR1GIF = 0; // while(TMR1 < L_ON_Min){ // リーダーの最小継続に達しないのに if(IrIN)return 0; // IR Space「1」無信号になったら } // ノイズなので復帰 while(!IrIN); // IR Space 待ち TMR1 = 0; // -- リーダーのOFF時間測定 TMR1GIF = 0; // while(IrIN){ // リーダーの終了時間を越えても待ち if(TMR1 > L_ON_Min)return 0; // data開始(IrON)しなければ復帰 } // ------- カスタムコードの確認 ------------------------------------------- // 受信、カスタムコードの空読み // ------------------------------------------------------------------------ rcvData=IrByteRcv(); //8ビット受信 if((rcvData & 0xFF00) != 0x0800)return 0; //受信ビット数確認 // ------- コマンドコードの確認 -------------------------------------------- // コマンドを受信 // ------------------------------------------------------------------------- rcvData=IrByteRcv(); if((rcvData & 0xFF00) != 0x0800)return 0; return rcvData & 0xFF; } //****** データ Byteを受信する *********************************************** // 8ビット受信する。 // 受信ビット数を 上位8ビットに // 受信データを 下位8ビットに組み合わせて 戻る // 「1」「0」判定は「0」の時間を DataTH と比較する // Stop ビット受信時は、その時点で復帰する //********************************************************************** int IrByteRcv(void){ unsigned char i,data; data = 0; for(i = 0; i < 8; i++){ // 1ビットずつ8回繰り返す data >>= 1; // ------------ space 待ち ------------------------------------ while(!IrIN); // space 開始を待つ TMR1 = 0; // Bitのspace時間測定開始 TMR1GIF = 0; // ------------ space 終了待ち -------------------------------- while(IrIN) // space 終了を待つ if(TMR1 > D_Off_Max) // 2mS以上ならStopなので return (int)i * 256 + data; // 復帰 if(TMR1 > DataTH) // 時間取得「1」「0」判定 data += 0x80; // 「1」を立てる } return (int)i * 256 + data; // 受信Bit数と内容合成 } //****** トラムの走行指示 *************************************** // spd: stop,spd_1,spd_2,spd_3 // dir: left,right // tram: inner,outer //********************************************************************** void run(unsigned char spd,char dir,char tram){ if(tram == inner){ // 内回り if(spd==stop){ innerN1= 0; innerN2= 0; }else{ innerD1 = spd;innerD2 = spd; switch(dir){ case left: innerN2= 0; innerN1= 0xC0; break; // 左走行 case right: innerN1= 0; innerN2= 0xC0; break; // 右走行 default: innerN1= 0; innerN2= 0; break; // 停止 } } }else if(tram == outer){ // 外回り if(spd==stop){ outerN1= 0; outerN2= 0; }else{ outerD1 = spd;outerD2 = spd; switch(dir){ case left: outerN2= 0; outerN1= 0xC0; break; // 左走行 case right: outerN1= 0; outerN2= 0xC0; break; // 右走行 default: outerN1= 0; outerN2= 0; break; // 停止 } } } } // ********* 1−0 を反転する ***************** char invert(char i){ if(i)return 0; return 1; }