- 記事公開日
プラレール音声再生システムを作ってみた(PICプログラミング)
プラレール音声再生システムとは?
プラレールの情景パーツの下を車両が通ると、自作の音声が再生される装置を作ってみました。
光センサによって、車両の通過を検知しています。
使用したのは、「ディズニーシー・エレクトリックレールウェイ プラレール プレイセット トイ・ストーリー・マニア!」です。
作り方[目次]
【1】電子回路の作成
回路図
部品表
部品名 | 規格 | 数量 |
---|---|---|
IC | PIC12f12822 | 1 |
EEPROM | 24LC512 | 1 |
CDS | MI5127 | 1 |
PAM8012アンプモジュール | AE-PAM8012 | 1 |
アンプ用ソケット | 2212S-20SG-36 | 6 |
アンプ用ピンヘッダ | 2211S-40G-774 | 6 |
マイクロスピーカー | 赤・黒リード付 8Ω | 1 |
ICソケット(8Pin) | 2227MC-08-03 | 2 |
抵抗 | 10kΩ | 3 |
積層セラミックコンデンサー | 104K(0.1μF) | 1 |
電池ボックス(単3×2本) | SBH-321-3AS150 | 1 |
ユニバーサル基板 | 両面スルーホール(72×47mm) | 1 |
スペーサー | 10mmタイプ | 1 |
アクリルパネル(2mm/透明) | LPM001T2-C | 1 |
アクリルパネル(2mm/スモーク) | LPM530T2-C | 1 |
マジックテープ | 4 |
※全て『秋月電子通商』で購入しました。
【2】プログラムの作成
PICマイコンにプログラムを書き込む際に必要なもの
- PICkit3
- 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】プラレールへの組み込み
回路本体の取り付けにはマジックテープを使用しました(百均にて購入)
スピーカーの上にカバーをかぶせてしまうと音が小さくなってしまうので、ケーブルを長めに確保しておいて、最後に取り付けます。
バッテリーは回路カバーの上にマジックテープで貼りつけました。
レイアウトに組み込み。
電池が重いのでバランス崩れるかな?と不安でしたが、意外に大丈夫でした。
光センサが車両にかするぐらいに調整(車両によって高さが違うので調整が難しいかも)
車両が走る音が大きくて、ウッディの声が聞こえにくいので、それが課題。
スピーカーをもっと大きくするか?アンプをもっと高性能なものに変えるか?