記事公開日

プラレール音声再生システムを作ってみた(PICプログラミング)

プラレール音声再生システム

プラレール音声再生システムとは?

プラレールの情景パーツの下を車両が通ると、自作の音声が再生される装置を作ってみました。

光センサによって、車両の通過を検知しています。

使用したのは、「ディズニーシー・エレクトリックレールウェイ プラレール プレイセット トイ・ストーリー・マニア!」です。

作り方[目次]

【1】電子回路の作成

プラレール音声再生システムの電子回路(表)
回路(表)
プラレール音声再生システムの電子回路(裏)
回路(裏)

回路図

プラレール音声再生システムの電子回路図

部品表

部品名規格数量
ICPIC12f128221
EEPROM24LC5121
CDSMI51271
PAM8012アンプモジュールAE-PAM80121
アンプ用ソケット2212S-20SG-366
アンプ用ピンヘッダ2211S-40G-7746
マイクロスピーカー赤・黒リード付 8Ω1
ICソケット(8Pin)2227MC-08-032
抵抗10kΩ3
積層セラミックコンデンサー104K(0.1μF)1
電池ボックス(単3×2本)SBH-321-3AS1501
ユニバーサル基板両面スルーホール(72×47mm)1
スペーサー10mmタイプ1
アクリルパネル(2mm/透明)LPM001T2-C1
アクリルパネル(2mm/スモーク)LPM530T2-C1
マジックテープ4

※全て『秋月電子通商』で購入しました。

【2】プログラムの作成

PICkit3を使いPICマイコンにプログラムデータを書き込む
PICkit3を使いPICマイコンにプログラムデータを書き込む

PICマイコンにプログラムを書き込む際に必要なもの

  • PICkit3
  • PICkit対応アダプタキット(自作でも可)
PICkitアダプタキット
最安値・レビューをチェック

PICマイコンに書き込むソースコード

言語:XC8コンパイラ(C言語)

/*
 * 
 *  PIC12F1822 × EEPROM(24LS256)
 * シリアル通信で外部シリアルROMへのデータ書き込み
 *                               - 2021/02/08 -
 * 
 */

//ヘッダファイルの読み込み
//--------------------------------------------------------------
#include <xc.h>


// PIC12F1822 Configuration Bit Settings
//--------------------------------------------------------------
// CONFIG1
#pragma config FOSC = INTOSC    // 内部クロックを使用する
#pragma config WDTE = OFF       // フリーズしたときに強制リセットするか?(OFF:リセットしない)
#pragma config PWRTE = OFF      // 起動時、電源が安定するまで待つか?(OFF:待たない)
#pragma config MCLRE = OFF      // 外部からのリセット信号を受け付けるか?(OFF:受けつけない)
#pragma config CP = OFF         // プログラムの外部読み取りを許可するか?(OFF:許可)
#pragma config CPD = OFF        // データの外部読み取りを許可するか?(OFF:許可)
#pragma config BOREN = ON       // 電源が不安定になったとき動作を停止するか?(ON:停止する)
#pragma config CLKOUTEN = OFF   // 外部クロックを使用する場合の3番Pinの設定(OFF:3番Pinを入出力Pinに設定)
#pragma config IESO = OFF       // 外部クロックが安定するまで内部クロックを使用するか?(OFF:使用しない)
#pragma config FCMEN = OFF      // 外部クロックが止まったとき内部クロックを使用するか?(OFF:使用しない)
// CONFIG2
#pragma config WRT = OFF        // データの書き込み禁止エリアを設定するか?(OFF:設定しない)
#pragma config PLLEN = OFF      // 内部クロックを32MHz(8MHz×4倍)で使用するか? (OFF:使用しない)
#pragma config STVREN = OFF     // プログラムが暴走したときにリセットを行うか?(OFF:行わない)
#pragma config BORV = LO        // 電圧不足と判断してリセットされるときの電圧を設定(LO:低い電圧に設定)
#pragma config LVP = OFF        // PICKIT3以外のライターで書き込む場合に低電圧で書き込み可能にする(OFF:低電圧書き込み無効)
 
 
// クロック周波数指定
//--------------------------------------------------------------
// __delay_ms()関数はこの数値を元にwaitする
#define _XTAL_FREQ 16000000


// 割り込み関数のプロトタイプ宣言
//--------------------------------------------------------------
// C言語では使用する関数をあらかじめ宣言しておく必要がある
// void:データ型なし、unsigned char:0~255の整数)
void I2C_write_set(unsigned char addr1 ,unsigned char addr2 ,unsigned char send_data);
void I2C_read_set(unsigned char addr1 ,unsigned char addr2);
void I2C_start(void);
void I2C_send(unsigned char send_data);
void I2C_receive(void);
void I2C_ack(void);
void I2C_nack(void);
void I2C_stop(void);


// ============================================================
//
//                  メインプログラム
//
// ============================================================

/*              12F1822
 *                   ------        
 *             VDD -| 1      8 |- VSS
 *      RA5 → PWM -| 2      7 |- AN0/RA0
 *  AN3/RA4 → A/D -| 3      6 |- AN1/RA1/SCL → ROM(6Pin)
 *      RA3 →  IN -| 4      5 |- AN2/RA2/SDA → ROM(5Pin)
 *                   ------      
 */

void main(void) {
 
    //----------------------------------------------------------
    //               PICマイコン初期設定
    //----------------------------------------------------------
    
    // 内部クロック周波数
    // | 0 | x | x | x | x | 0 | 0 | 0 |
    //----------------------------------------------------------
    // OSCCON = 0b01011000;  // 1MHz
    // OSCCON = 0b01100000;  // 2MHz
    // OSCCON = 0b01101000;  // 4MHz
    OSCCON = 0b01110000;  // 8MHz
    //OSCCON = 0b01111000;     // 16MHzに設定
    
    // デジタル・アナログ設定(※RA3[4Pin]はデジタル固定)
    // | 0 | 0 | 0 | RA4 | RA3 | RA2 | RA1 | RA0 |
    //----------------------------------------------------------
    // ANSELA = 0b00000000; // 全てデジタルモード
    ANSELA = 0b00010000;    // RA4[3Pin]:アナログ、その他:デジタル
    
    // 入出力Pin設定(※RA3[4Pin]は入力固定)
    // | 0 | 0 | RA5 | RA4 | RA3 | RA2 | RA1 | RA0 |
    //----------------------------------------------------------
    TRISA  = 0b00011110;    // RA5:出力、RA4:入力、RA3:入力、RA2:入力、RA1:入力、RA0:出力
                            // I2Cを使う場合、RA1[6Pin]とRA2[5Pin]は必ず入力にする必要がある
    
    
    //----------------------------------------------------------
    //                   A/D変換設定
    //----------------------------------------------------------
    
    // アナログ入力チャネルの設定
    // | 0 | x | x | x | x | x | 0 | 1 |
    //----------------------------------------------------------
    // ADCON0 = 0b00000001; // RA0[7Pin]
    // ADCON0 = 0b00000101; // RA1[6Pin]
    // ADCON0 = 0b00001001; // RA2[5Pin]
    // ADCON0 = 0b00001101; // RA3{4Pin]
    ADCON0 = 0b00001101; // RA4[3Pin]
    
    // 出力フォーマット、クロック設定
    // | y | x | x | x | 0 | 0 | 0 | 0 |
    //----------------------------------------------------------
    // ADCON1 = 0b10100000; // 右詰め、Fosc/32
    ADCON1 = 0b01010000; // 左詰め、Fosc/16
    // ADCON1 = 0b00010000; // 左詰め、Fosc/8
    // ADCON1 = 0b01000000; // 左詰め、Fosc/4
    // ADCON1 = 0b00000000; // 左詰め、Fosc/2
    
    
    //----------------------------------------------------------
    //                    PWM設定
    //----------------------------------------------------------
    
    // PWA機能をRA5[2Pin]に設定
    //----------------------------------------------------------
    CCP1SEL = 1; 
    
    //PWM機能を有効
    //----------------------------------------------------------
    CCP1CON = 0b00001100;	
    
    // TMR2プリスケーラ値を設定
    // | 0 | 0 | 0 | 0 | 0 | 0 | x | x |
    //----------------------------------------------------------
    T2CON   = 0b00000000; // 1倍
    // T2CON   = 0b00000001; // 4倍
    // T2CON   = 0b00000010; // 16倍
    // T2CON   = 0b00000011; // 64倍
    
    // PWMの周期を設定 
    //----------------------------------------------------------
    PR2 = 128;
    
    // PWMスタート
    //----------------------------------------------------------
    TMR2ON  = 1; 
    
    //----------------------------------------------------------
    //               シリアル通信(I2C)設定
    //----------------------------------------------------------
    
    // クロック信号速度
    // | x | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
    //----------------------------------------------------------  
    SSP1STAT = 0b10000000;  // 標準モード(100kHz)
    //SSP1STAT = 0b00000000;  // 高速モード(400kHz)
    
    // マスター(制御する側) or スレーブ(制御される側)
    //----------------------------------------------------------
    SSP1CON1 = 0b00101000;  // マスターモードに設定
    
    // 通信速度設定
    //----------------------------------------------------------
    // クロック信号の速度(周波数) = PICマイコンの動作周波数 / ((SSP1ADDの値 + 1) x 4)
    // PICマイコンの動作周波数:16MHz、I2Cのクロック信号速度:400kHzに設定
    // 16000kHz / 400kHz x 4) - 1 = 16000/1600 - 1 = 10 - 1 = 9
    SSP1ADD = 6; // 声が低くなったので微調整
	 
    //----------------------------------------------------------
    //                 シリアル通信 動作テスト
    //----------------------------------------------------------
    
    /*
     *              24LC256
     *               ------        
     *   A0 → GND -| 1      8 |- VCC
     *   A1 → GND -| 2      7 |- 
     *   A2 → GND -| 3      6 |- SCL → PIC(6Pin)
     *  VSS → GND -| 4      5 |- SDA → PIC(5Pin)
     *               ------      
     */
    
    /* 
     * メモリ:256Kbit
     * 
     *      256000bits÷8bits = 32000Byte = 32kByte
     *      32×1024 = 32768bit 
     *      0~32767番地まで記憶可能
     * 
     *          ・ 10進数 → 最大「32767」
     *          ・ 16進数 → 最大「7FFF」
     *          ・ 2進数  → 最大「0111111111111111」 16桁
     */         

    // データの書き込みテスト
    //----------------------------------------------------------
    // 「1」番地に「5」というデータを書き込む
    // 2進数:0000000000000001 → 10進数:1 → 16進数:01
    // 2進数:00000101 → 10進数:5 → 16進数:05
    
    // 引数1:書き込む番地(上位8bit)
    // 引数2:書き込む番地(下位位8bit)
    // 引数3:書き込むデータ
    // I2C_write_set(0b00000000,0b00000001,0b00000101);    
      
    // 書き込み処理が終わるまで待機(もっと短くても大丈夫)
    //----------------------------------------------------------
    //__delay_ms(100);
    
    // データの読み込みテスト
    //----------------------------------------------------------
    // 引数1:読み込むm番地(上位8bit)
    // 引数2:読み込む番地(下位位8bit)
    // I2C_read_set(0b00000000,0b00000001);
    
    
    //----------------------------------------------------------
    //               CDS(光センサ)の反応を検知
    //----------------------------------------------------------
    
   // 永久ループ
   while(1) {
       
       // AD変換準備時間
        __delay_us(25); 
        
         // AD変換スタート 
        GO = 1;    
        
        // AD変換終了まで待機(終了するとGOが0になる)
        while(GO);      
        
        // CDS感度の調整(小さくすると感度アップ)
        if (ADRESH > 200) { 
            
            // EEPROMの読み込み(シリアル通信)
            I2C_read_set(0x00,0x00);
            
        }
        
   } 
    
}

// ============================================================
//
//                  I2C 書き込み設定
//
// ============================================================
void I2C_write_set(unsigned char addr1 ,unsigned char addr2 ,unsigned char send_data) {
      
    // 通信開始の合図を送る
	I2C_start();
    
    /*
     * スレーブアドレスとは?
     * スレーブアドレスは、複数のデバイスが回路上にあるときの見分けを付けるためのもの。
     * 
     * 24LC256のスレーブアドレスは、1010[A2][A1][A0] となっている。
     * 24LC256のA2[3Pin]・A1[2Pin]・A0[1Pin]をGNDに繋いだら「0」、VCCに繋いだら「1」
     * 例えば、A2・A1・A0をすべてGNDに繋いだら、スレーブアドレスは「1010000」となる。
     * 例えば、A2・A1・A0をすべてVCCに繋いだら、スレーブアドレスは「1010111」となる。
     * 
     * ↑の7ビットの最後に、ROMの読み書き設定を1ビット追加する。
     * 書き込むときは「1」、読み込むときは「0」
     * もし1個しかROMを付けないなら、「0b10100000」(書き込みの場合)とすればいい。
     */
    
    // スレーブアドレスの送信(書き込みモード)
    I2C_send(0b10100000);

    // 書き込む番地(上位8bit)の送信
	I2C_send(addr1);

    // 書き込む番地(下位8bit)の送信
	I2C_send(addr2);

    //  書き込むデータの送信    
    I2C_send(send_data);

    // 通信終了の合図を送る
    I2C_stop();

	return;

}

// ============================================================
//
//                  I2C 読み取り設定
//
// ============================================================
void I2C_read_set(unsigned char addr1 ,unsigned char addr2) {

    // 通信開始の合図を送る
	I2C_start();
        
    // スレーブアドレスの送信(※書き込みモード)
    // 読み込みだけど、一旦書き込みモードでデータを送信する必要がある
    I2C_send(0b10100000);

    // 読み取り番地(上位8bit)の送信
	I2C_send(addr1);
    
    // 読み取り番地(下位8bit)の送信
	I2C_send(addr2);
    
    // 通信開始の合図を送る
	I2C_start();
    
     // スレーブアドレスの送信(※読み取りモード)
    I2C_send(0b10100001);

    // ROM内の全てのデータを受信する:512K(512000bits÷8bits = 64000Byte)
    for(unsigned short int i=1; i<64000; i++) {
            I2C_receive();
            I2C_ack();
    }
    
    // 読み取り終了の応答
    I2C_nack();

    // 通信終了の合図を送る
	I2C_stop();

	return;
    
}

// ============================================================
//
//                     I2C 通信開始
//
// ============================================================
void I2C_start() {
    
    // スタートコンディションを生成
    // SENをセットすることで、通信スタートの合図となる
	SEN = 1;
    
    // スタートコンディション発行終了まで待機
    // SENは発行終了後に0になる
	while (SEN) {};
  
	return;
}

// ============================================================
//
//                    I2C データ送信
//
// ============================================================
void I2C_send(unsigned char send_data) {
    
    // データ受信フラグをクリア(0:データが受信できる状態)
	SSP1IF = 0;
    
    // 送信モード有効
    // SSPIFが0のときにSSPBUFをセットすると、データが送信される
	SSP1BUF = send_data;
    
    // データ送信完了まで待機
    // 正常にデータが送信されると、SSPIFが1になる
	while (!SSP1IF) {}
    
	return;
}

// ============================================================
//
//                   I2C データ読み取り
//
// ============================================================
void I2C_receive() {
    
    // データ受信フラグをクリア(0:データが受信できる状態)
	SSP1IF = 0;
    
    // 受信モード有効
    // SSPIFが0のときにRCENをセットすると、データが受信される
	RCEN = 1;
    
    // データ受信完了まで待機
    // 正常にデータが受信されると、RCENが0になる
	while (RCEN) {}

    // 受信したデータは、SSP1BUFに入る
    // SSP1BUFを変数(reveive_data)に代入
    unsigned char receive_data;
    receive_data = SSP1BUF;
    
    // 受信データを音源に(パルス幅を変更)
    CCPR1L = receive_data;

    return;
}

// ============================================================
//
//                     I2C 継続応答
//
// ============================================================
void I2C_ack() {
    
    // 受信データの続きがあることを示す「ACK:0」を設定
	ACKDT = 0;
    
    // ACKDTを送信
	ACKEN = 1;
    
    // データ送信完了まで待機
    // 送信完了後、ACKENが0になる
	while (ACKEN) {}

	return;
}

// ============================================================
//
//                     I2C 終了応答
//
// ============================================================
void I2C_nack() {
	
    // 受信データの終わりを示す「NO-ACK:0」を設定
	ACKDT = 1;
    
    // ACKDTを送信
	ACKEN = 1;
    
    // データ送信完了まで待機
    // 送信完了後、ACKENが0になる
    while (ACKEN);

	return;
    
}

// ============================================================
//
//                     I2C 通信終了
//
// ============================================================
void I2C_stop() {
    
    // ストップコンディションの生成
    // PENをセットすることで、通信終了の合図となる
	PEN=1;
    
    // ストップコンディション発行終了まで待機
    // PENは発行終了後に0待つ
	while(PEN);
    
     // データ受信フラグをクリア(0:データが受信できる状態)
	SSP1IF = 0;
    
	return;
}

【3】プラレールへの組み込み

回路本体の取り付けにはマジックテープを使用しました(百均にて購入)

マジックテープを貼り付け

スピーカーの上にカバーをかぶせてしまうと音が小さくなってしまうので、ケーブルを長めに確保しておいて、最後に取り付けます。

プラレールと音声再生回路を組付け

バッテリーは回路カバーの上にマジックテープで貼りつけました。

回路本体とスピーカーをマジックテープで取り付け
回路とバッテリーを合体
音声再生回路とプラレールのパーツを合体

レイアウトに組み込み。

電池が重いのでバランス崩れるかな?と不安でしたが、意外に大丈夫でした。

プラレールのレイアウトに音声再生システムを合体

光センサが車両にかするぐらいに調整(車両によって高さが違うので調整が難しいかも)

プラレールのレイアウト(別アングル)

車両が走る音が大きくて、ウッディの声が聞こえにくいので、それが課題。

スピーカーをもっと大きくするか?アンプをもっと高性能なものに変えるか?

ウッディの下を通るディスニーの車両
この記事のURLをコピー

メールアドレスは公開されませんのでご安心ください。また、* が付いている欄は必須項目となります。

内容に問題なければ、下記の「コメントを送信する」ボタンを押してください。

関連情報

運営者プロフィール
ガッタン

2歳の息子の為に買ったプラレールでしたが、気付いたら自分がドハマりしていました。美しく実用的なレイアウトを追い求める30代・2児の父。職業はIT系フリーランス。電子配線の業務経験あり。