PICの基本動作から応用プログラムまでを学びます。

ホーム
16F18313
16F18325
16F18346
16F1619
Curiosity
---
---
    
12F1822
16F1455
16F1459
18F14K50
18F26J50
dsPIC
その他
Nゲージ運転制御装置

VNゲージ ユニトラムの走行を赤外線リモコンで制御する装置をPICマイコンで作成しました。軌道のレイアウトは、74.5cm X 30cm と本棚に収まる大きさにしたため、奥行きが狭くなり閉ループの軌道にすることができません。このため端から端までを往復する複線の半楕円軌道です。
 市販のTV用赤外線リモコンからのコマンドを PIC16F1503 で解析し、自動往復運転をさせたり、リモコンの音量キー【大小】とチャンネルキー【+-】でトラムの速度を手動コントロールし、駅に停車させることなどができます。また、レイアウト上に設置されている建物に照明用白色LEDを取り付けました。これらのON/OFFもリモコン操作することができます。制御装置自体は銀行ビルの中にあります、銀行の窓の内側に赤外線受光センサーが位置するように配置し、レイアウト全体がすっきりするよう心がけました。


TV用赤外線リモコンは、JVCの簡単リモコンRM-225を使用し、メーカーコードは、ビクター/JVCにしています。トラムへのコマンドは、右図のキーに設定しました。

 ↑ 画像をクリックすると、他の拡大画像も閲覧できます。 (16枚)

制御部および軌道のブロック図

赤外線リモコン受信モジュール 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;
}