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;
}