PAGE TOP

鉄道模型レイアウト・プラモデル展示台用の音(PICでWAV再生)

PIC24FJ64GA002でWAV再生

Nゲージ 踏切の製作」は、遮断機の部分を作っている途中であるが、踏切の音にも、昔ながらの電鈴式の音、JRの音、私鉄の音、構内踏切の音などいろいろあるということで、いろんな音にしてみたり、遮断機の有無などでバリエーションのある踏切を5個量産中。

なのであるが、踏切ばかりでも能がない。

ちょっと脱線してPICマイコンで、鉄道模型のレイアウトやらプラモデルの展示台に内臓して音を出すためのモジュールを作ってみた。

ディーゼルエンジンのアイドリング音、交通信号のカッコー、ピヨピヨやら、単調な音の繰り返しはSDカードに記録した2~3秒くらいの音を繰り返して再生し、BGMの音楽は市販のMP3再生ボード(MK-144)と連動させて。
SDカードにデータを記録することで、交通信号機も昔ながらの「故郷の空」と「通りゃんせ」などが再生できる。

また、鉄道模型のレイアウト用以外にもプラモデルの展示台にも内蔵させることができ、プラモデルにあった音楽が流せる。

使ったPICマイコンは、PIC24FJ64GA002

4つのスイッチにBCDコードがだせるロータリーコードスイッチを使って音楽を切換え、同時に2つのWAVファイルが再生できる。

音声はそれぞれ別の出力ポートに。
汽笛など単発的で繰り返しのない音は3つ目の音声として、一時的に1つの音を止めて出力。

また、このロータリーコードスイッチに応じてMK-144に登録されたMP3ファイルも再生。

等々。

もうちょっと詳しいことは下のソースのヘッダ部分に書いてます。

PIC24FJ64GA002でWAV再生

(写真上) 左の緑のボードは市販のMP3再生ボード(MK-144)を制御するボードMK-148

MK-148上にはMP3ファイルの再生用タクトスイッチが並んでいる。

右のが今回作ったボード。

不要になったMK-148を再利用するために使ったのでアンプはMK-148上の物にミキシング。

2つのボードを並べるとちょっと大きいが、MK-148のタクトスイッチ部分を取り除いたり、MP3の再生機能自体をなくしてWAVファイルの再生だけに特化するともっとコンパクトにできる。

実際にレイアウトにつける時は、このボードは手元において、スピーカー部だけをレイアウト上に置くことになろうかと。
そのときはスイッチは操作しやすいようにつけかえますが。

 

PIC24FJ64GA002でWAV再生

(写真上) やはり、ちぃっちゃいスピーカでは音が・・

プラモデルの展示台等ではコンパクトにしたいのでこれでも大きい方ではあるけど。

ためしに980円のスピーカーを繋いでみると、まあ使えるかも。
・・場所とりますが。

 


追記

ちょっと小さめのものを作ってみました。
鉄道模型レイアウト・プラモデル展示台用の音(PICでWAV再生) 2

 


なお、この動画の中の音にはWAVだけでなく一部MP3ボードで再生したものも含まれています。


ソース

[c]

// ***********************************************
//
// PIC24FJ64GA002
//	8kHz sampling , 16bit モノラル Wav再生
//
//
// ・SDカードからwavファイルを読み込んで再生する
// ・音声はch1とch2の2つに出力する
// ・wavファイルは#0から#2までの最大3つの音声ファイルで1グループを構成する
// ・#0~#2の3つの再生スイッチで、それぞれのファイルを再生する
// ・スイッチに対応するファイルが存在しないときはそのスイッチに対応する音声は再生されない
// ・#0と#1スイッチが押されている間は、それに対応したファイルは繰り返して再生される
// ・#0と#1スイッチはトグルスイッチ、押しボタンスイッチでもよいがトグルスイッチを想定している
// ・#2スイッチが押されたときは、そのファイルが終了するまで再生し、繰り返さない。
//	  ただし、再生終了時に#2スイッチが押されていると再び再生される。
//	  #2の再生を途中で終了させるボタンはない。
//	  途中で終了させたい場合はロータリーコードスイッチを切り替える
// ・#0と#1スイッチはトグルスイッチ、押しボタンスイッチでもよいが押しボタンスイッチを想定している
// ・#0のファイルの音声はch1に出力する
// ・#1と#2のファイルの音声はch2に出力する
//	  #2の方が優先順位は高い。
//	  優先順位が高いものが再生されているときは、優先順位が低いものは再生されない
//	  #1と#2も再生するファイルがない、または再生スイッチが押されていないときは
//	  ch1と同じ音声(#0ファイルの音声)が出力される
// ・wavファイルのグループは最大10グループもてる
// ・グループはBCDコードが出力できるロータリーコードスイッチを使用し、その4bitをPICに入力する
// ・エラー時は内部に記録したチャイム音を再生する
// ・押しボタンで市販のMP3プレーヤーボードMK144の再生、停止を制御する(UART)
//
// ピンアサイン
// #1  MCLR
// #2  RA0/CN2 (MK-144制御用 input スタート/ストップ) 内部プルアップ
// #3  RA1/CN3 (MK-144制御用 input BUSYチェック) 内部プルアップ Low:busy Hi:not busy 
// #4  RB0/RP0/OC1 ch1音声
// #5  RB1/RP1/OC2 ch2音声
// #6  RP2 TX (MK-144制御用 output USART)
// #7  RP3 RX (MK-144制御用 input USART)
// #8  Vss
// #9  OCSI
// #10 OCSO
// #11 SD-CARD DAT3
// #12 RA4/CN0 #0音声再生(input) (active low--lowの間再生) 内部プルアップ
// #13 VDD
// #14 SD-CARD CMD/DI
// #15 SD-CARD CLK
// #16 SD-CARD DAT0
// #17 SD-CARD CD
// #18 SD-CARD WP
// #19 DISVREG (GND)
// #20 VCSP
// #21 RB10/CN16 #1音声再生(input) (active low--lowの間再生) 内部プルアップ
// #22 RB11/CN15 #2音声再生(input) (active low--lowの間再生) 内部プルアップ
// #23 RB12/CN14 (in ロータリーコードスイッチ BCD Bit0) 内部プルアップ
// #24 RB13/CN13 (in ロータリーコードスイッチ BCD Bit1) 内部プルアップ
// #25 RB14/CN12 (in ロータリーコードスイッチ BCD Bit2) 内部プルアップ
// #26 RB15/CN11 (in ロータリーコードスイッチ BCD Bit3) 内部プルアップ
// #27 VDD
// #28 VSS
//
//
// ***********************************************

//	コンパイラ XC16
//	SDカードのI/OはMicrochip C30のMicrochip Memory Disk Drive File Systemを使用
//	(FSIO.hからインクルードされるモジュールも環境に準備しておく)
//

#include <xc.h>
#include <uart.h>

// CONFIG2
#pragma config POSCMOD = XT 		// Primary Oscillator Select (XT Oscillator mode selected)
#pragma config I2C1SEL = PRI		// I2C1 Pin Location Select (Use default SCL1/SDA1 pins)
#pragma config IOL1WAY = OFF		// IOLOCK Protection (IOLOCK may be changed via unlocking seq)
#pragma config OSCIOFNC = OFF		// Primary Oscillator Output Function (OSC2/CLKO/RC15 functions as CLKO (FOSC/2))
#pragma config FCKSM = CSDCMD		// Clock Switching and Monitor (Clock switching and Fail-Safe Clock Monitor are disabled)
#pragma config FNOSC = PRIPLL		// Oscillator Select (Primary Oscillator with PLL module (HSPLL, ECPLL))
#pragma config SOSCSEL = SOSC		// Sec Oscillator Select (Default Secondary Oscillator (SOSC))
#pragma config WUTSEL = LEG 		// Wake-up timer Select (Legacy Wake-up Timer)
#pragma config IESO = OFF			// Internal External Switch Over Mode (IESO mode (Two-Speed Start-up) disabled)

// CONFIG1
#pragma config WDTPS = PS32768		// Watchdog Timer Postscaler (1:32,768)
#pragma config FWPSA = PR128		// WDT Prescaler (Prescaler ratio of 1:128)
#pragma config WINDIS = ON			// Watchdog Timer Window (Standard Watchdog Timer enabled,(Windowed-mode is disabled))
#pragma config FWDTEN = OFF 		// Watchdog Timer Enable (Watchdog Timer is disabled)
#pragma config ICS = PGx1			// Comm Channel Select (Emulator EMUC1/EMUD1 pins are shared with PGC1/PGD1)
#pragma config GWRP = OFF			// General Code Segment Write Protect (Writes to program memory are allowed)
#pragma config GCP = OFF			// General Code Segment Code Protect (Code protection is disabled)
#pragma config JTAGEN = OFF 		// JTAG Port Enable (JTAG port is disabled)

#include <stdio.h>
#include <string.h>

#include "FSIO.h"					// SDカード i/o用

// クロック周波数指定(__delay_msとボーレート計算用)
//#define FCY 8000000L				// クロック 8MHz
#define FCY 16000000L				// クロック 16MHz
//#define FCY 32000000L 			// クロック 32MHz

#include <libpic30.h>				//delayマクロ用

#include "chime.h"					// エラーチャイムWAVファイル波形データ

#define FHANDLE 3
#define BUFSIZE 256
#define FILENAMEMAX 12


BYTE sdReadyFlag = FALSE;			// SD i/o 初期化 TRUE(1):OK / FALSE(0); NG
BYTE errorFlag = FALSE; 			// TRUE(1):チャイム鳴動 / FALSE(0)
unsigned int chimeidx = 0;			// エラーチャイム配列参照添字


// i/oバッファ

struct buffer {
	int status; 					// 0:無効 1:データ読み込み済 2:データ出力済
	char data[BUFSIZE]; 			// 読み込みバッファ
	int datasize;					// 有効レングス
	unsigned int outputptr; 		// 出力用添字
};

// WAVファイル 先頭部分
// WAVファイルのフォーマットに合わせて修正

struct wavheader {
	char nu[36];					// (読み飛ばし部分)
	char datachid[4];				// dataチャンクID
	unsigned long chunkleng;		// 波形データレングス
};

// ハンドラ

struct handler {
	char fname[FILENAMEMAX];		// ファイル名
	int playFlag;					// 再生フラグ 0:停止 1:再生
	int repeatFlag; 				// 繰り返しフラグ
									// 0:繰り返しあり
									// 1:繰り返しなし(データ未読み込み)
									// 2:繰り返しなし(初期値/データ読み込み完了)
	FSFILE *fptr;					// ファイルポインタ
	unsigned long remaining;		// 波形データ残りバイト数
	int nextRead;					// i/oバッファ参照用添字(読み込み用)
	int currOut;					// i/oバッファ参照用添字(出力用)
	struct buffer buf[2];			// i/oバッファ
	struct wavheader whdr;			// WAVファイル 先頭部分
} fhandle[FHANDLE];

// ロータリーコードスイッチチェック用
// (0~9までのBCDコードをポートから入力して、再生するファイルグループを指定)
typedef union {
	unsigned char selector;

	struct {
		unsigned selB0 : 1; 		//bit0
		unsigned selB1 : 1; 		//bit1
		unsigned selB2 : 1; 		//bit2
		unsigned selB3 : 1; 		//bit3
		unsigned fu : 4;
	} selectorStatusBits;
} selectorStatus;
selectorStatus sel;

BYTE Mk144Switch = 0;				// Mk144制御スイッチ 0:停止 1:再生
BYTE Mk144SwitchSave = 0;			// Mk144制御スイッチ 0:停止 1:再生

// ファイルグループID(ファイル名の先頭)
char *groupid[] = {"00", "01", "02", "03", "04", "05", "06", "07", "08", "09", "xx"};

// UART
//	MK-144の規格 4800bps パリティなし データ8bit ストップビット1 フロー制御なし

const unsigned int UARTMode
		= UART_EN					// UARTモジュール - 有効
		& UART_NO_PAR_8BIT			// パリティなし/データ 8ビット
		& UART_1STOPBIT 			// ストップビット - 1ビット
		& UART_IDLE_CON 			// アイドルモード - Work in IDLE mode
		& UART_MODE_SIMPLEX 		//UxRTSピンモード
		& UART_DIS_WAKE 			// ウェイクアップ - 無効
		& UART_UEN_00				//Port latch control
		& UART_IrDA_DISABLE 		// IrDAエンコーダ 有効/無効
		& UART_DIS_LOOPBACK 		// ループバック - 無効
		& UART_DIS_ABAUD			// 自動ボーレート - 無効
		& UART_UXRX_IDLE_ONE		//Idle state UxRX Idle state is one
		& UART_BRGH_SIXTEEN 		//BRG BRG generates 16 clocks per bit period
		;

const unsigned int UARTSta
		= UART_TX_ENABLE			// 送信 - 有効
		& UART_INT_TX_BUF_EMPTY 	//送信の割り込み条件 送信バッファが空になったとき
		& UART_IrDA_POL_INV_ZERO	//IrDAエンコード
		& UART_SYNC_BREAK_ENABLED
		& UART_TX_ENABLE
		& UART_INT_RX_CHAR			// 受信の割り込み条件 - 受信するたび
		& UART_ADR_DETECT_DIS		// アドレス検出 - 無効
		& UART_RX_OVERRUN_CLEAR 	// 受信バッファオーバーランエラー - クリア
		;

//
// ボーレートクロック
// UARTBaud 8MHz :103  16MHz:207  32MHz: 416

#define BAUDRATE  4800L 						 // ボーレート [bps]
const double UARTBaud = (double) FCY / (16 * BAUDRATE) - 1;

//
// 関数定義
//
void fhandleinit(int);				// ハンドラ初期化
void bhandleinit(int, int); 		// i/oバッファ初期化
void makeFileName(void);			// ファイル名生成
int waveform(int);					// 波形作成
void wavopen(int);					// Wavファイルオープン
void wavread(int);					// Wavファイル波形部読み込み
void mk144ctrl(void);				//MK-144制御
void mk144putcmd(unsigned char);	//MK-144向けコマンド送信

/**
 * メイン関数
 */

int main(void) {
	memset(fhandle, 0x00, sizeof (fhandle));
	selectorStatus check;
	check.selector = 0x00;
	sel.selector = 0x0a;

	CLKDIV = 0;

	AD1PCFG = 0xFFFF;				// I/Oはすべてデジタル
	TRISB = 0xfffc; 				// RB0、RB1を音声出力
	// 内部プルアップ1(CN16 CN15 CN14 CN13 CN12 CN11 CN3 CN2 CN0をON)
	_CN16PUE = _CN15PUE = _CN14PUE = _CN13PUE = _CN12PUE = _CN11PUE = _CN3PUE = _CN2PUE = _CN0PUE = 1;

	CMCON = 0; // コンパレータオフ

	// SDカード用設定
	// SPIのピン割り付け
	RPINR20bits.SDI1R = 7;			// SDI1をRP7に
	RPOR3bits.RP6R = 8; 			// SCK1をRP6に
	RPOR2bits.RP5R = 7; 			// SDO1をRP5に

	// ch1音声用設定
	// タイマ2設定
	T2CON = 0x8000;
	PR2 = 159; //100kHz
	// OC1設定
	RPOR0bits.RP0R = 18;			// OC1をRP0に割付け
	OC1CON = 0x0006;				// タイマ2使用 PWM FAULTなし

	// ch2音声用設定
	// タイマ3設定
	T3CON = 0x8000;
	PR3 = 159; //100kHz
	// OC2設定
	RPOR0bits.RP1R = 19;			// OC2をRP1に割付け
	OC2CON = 0x000e;				// タイマ3使用 PWM FAULTなし

	// タイマ4(音声出力用)モード設定
	T4CON = 0x0000;
	T4CONbits.TCKPS1 = 0;
	T4CONbits.TCKPS0 = 1;
	T4CONbits.TON = 1;

	PR4 = 249;						// 125usec	8kHz
	_T4IP = 5;						// 割り込みレベル
	_T4IF = 0;						// 割り込みフラグクリア
	_T4IE = 1;						// タイマ4割り込み許可

	// MK-144制御用
	// UART用設定
	RPINR18bits.U1RXR = 3;			// UART1 RX を RP3に
	RPOR1bits.RP2R = 3; 			// UART1 TX を RP2に
	U1BRG = UARTBaud;
	U1MODE = UARTMode;
	U1STA = UARTSta;

	// RA0/CN2(MK-144制御ボタン)用CN割り込み設定
	_CN2IE = 1; 					//CN2の割り込みを許可
	_CNIP = 5;						// 割り込みレベル
	_CNIF = 0;						// CN割り込みフラグクリア
	_CNIE = 1;						// CNピン割り込み許可

	int i;

	while (1) {
		// ファイルグループ取得(ロータリーコードスイッチ(Active Low)チェック)
		check.selectorStatusBits.selB0 = !PORTBbits.RB12;
		check.selectorStatusBits.selB1 = !PORTBbits.RB13;
		check.selectorStatusBits.selB2 = !PORTBbits.RB14;
		check.selectorStatusBits.selB3 = !PORTBbits.RB15;

		if (sel.selector != check.selector) {
			// ロータリーコードスイッチが変わった
			sel = check;
			sdReadyFlag = errorFlag = FALSE;
			mk144putcmd(0xEF); //MK-144停止
			Mk144SwitchSave = Mk144Switch = 0;

		}
		if (!sdReadyFlag) {
			// 初期化時、ロータリーコードスイッチ変更時、
			// SDカード抜き差し等でi/oエラー(現仕様はseek時のエラー)となった時のリカバリ
			for (i = 0; i < FHANDLE; i++)
				fhandleinit(i);
			makeFileName();
			while (!sdReadyFlag) {
				// SDi/o 初期化完了まで無限ループ
				sdReadyFlag = FSInit(); // SDi/o 初期化
				errorFlag = !sdReadyFlag;
			}
			for (i = 0; i < FHANDLE; i++)
				wavopen(i);
		}

		// 再生スイッチ(Active Low)チェック
		fhandle[0].playFlag = !PORTAbits.RA4;
		fhandle[1].playFlag = !PORTBbits.RB10;
		fhandle[2].playFlag = !PORTBbits.RB11;
		for (i = 0; i < FHANDLE; i++) {
			if (2 == fhandle[i].repeatFlag && fhandle[i].playFlag)
				fhandle[i].repeatFlag = 1;
			wavread(i);
		}
		mk144ctrl(); //MK-144制御
	}
}

/**
 * ハンドラ初期化
 * @param int i 初期化するハンドラ(fhandle)の番号
 */
void fhandleinit(int i) {
	fhandle[i].fname[0] = 0x00;
	fhandle[i].playFlag = 0;
	// ファイル#0、#1は繰り返し再生 #2は繰り返さない
	fhandle[i].repeatFlag = (0 == i || 1 == i) ? 0 : 2;

	if (NULL != fhandle[i].fptr)
		FSfclose(fhandle[i].fptr);
	fhandle[i].fptr = NULL;
	fhandle[i].remaining = 0L;
	fhandle[i].nextRead = 0;
	fhandle[i].currOut = 0;
	bhandleinit(i, 0);
	bhandleinit(i, 1);
}

/**
 * i/oバッファ初期化
 * @param int  i 初期化するハンドラ(fhandle)の番号
 * @param int  j 初期化するi/oバッファ(buf)の番号
 */
void bhandleinit(int i, int j) {
	fhandle[i].buf[j].status = 0;
	fhandle[i].buf[j].datasize = 0;
	fhandle[i].buf[j].outputptr = 0;
}

/**
 * ファイル名生成
 *
 * ファイル名は、
 * groupid[ロータリーコードスイッチの値で修飾]
 * +
 * 音声再生スイッチの番号(1~3)
 * +
 * '.wav'
 *
 * ロータリーコードスイッチが1の時 :
 *#1音声再生スイッチに対応するファイル名は '01' + '1' + '.wav'
 * → '011.wav'
 *#2音声再生スイッチに対応するファイル名は '01' + '2' + '.wav'
 * → '012.wav'
 *#3音声再生スイッチに対応するファイル名は '01' + '3' + '.wav'
 * → '013.wav'
 *
 */
void makeFileName(void) {
	int i;
	char wk[] = "0.wav";
	for (i = 0; i < FHANDLE; i++) {
		strcpy(fhandle[i].fname, groupid[sel.selector]);
		wk[0] = '0' + (char) (i + 1);
		strcat(fhandle[i].fname, wk);
	}
}

/**
 * タイマ4割り込み処理関数
 */
void __attribute__((interrupt, no_auto_psv)) _T4Interrupt(void) {
	unsigned int e, ch1, ch2;

	if (errorFlag) {
		// エラー時のチャイム
		// chime配列は偶数バイトで作成しておくこと
		e = chime[chimeidx] | (chime[chimeidx + 1] << 8);
		e = e + 32768;
		e = e >> 8;
		e = e & 0xff;
		chimeidx += 2;
		if (chimeidx > (sizeof chime))
			chimeidx = 0;
		OC1RS = (PR2 + 1) * e / 256; // ch1:RP0出力
		OC2RS = (PR3 + 1) * e / 256; // ch2:RP1出力
	} else {
		// ch1は常にファイル#0を再生
		ch1 = waveform(0);
		ch1 = (0 == ch1) ? 128 : ch1; // ch1が0なら無音
		OC1RS = (PR2 + 1) * ch1 / 256; // ch1:RP0出力

		// ch2は、再生指示のあるものをファイル#2、#1、#0の順に判定していずれかを出力
		ch2 = waveform(2);
		if (0 != ch2) {
			// ファイル#2をRP1に出力
			OC2RS = (PR3 + 1) * ch2 / 256;
		} else {
			// ファイル#1か#0をRP1に出力
			ch2 = waveform(1);
			OC2RS = (0 != ch2) ? ((PR3 + 1) * ch2 / 256) : ((PR3 + 1) * ch1 / 256);
		}
	}
	_T4IF = 0; // 割り込みフラグクリア
}

/**
 * 波形作成
 * @param int  i: ハンドラ(fhandle)参照用添字
 * @return int 0:出力データなし その他:波形データ
 */
int waveform(int i) {
	unsigned int d;
	unsigned char wk1, wk2;

	if (1 != fhandle[i].buf[fhandle[i].currOut].status ||
			(!fhandle[i].playFlag && 0 == fhandle[i].repeatFlag)) {
		fhandle[i].buf[fhandle[i].currOut].status = 2;
		fhandle[i].currOut = (0 == fhandle[i].currOut) ? 1 : 0;
		return 0;
	}
	if (fhandle[i].buf[fhandle[i].currOut].outputptr >= (fhandle[i].buf[fhandle[i].currOut].datasize))
		wk1 = wk2 = 0x00;
	else {
		wk1 = fhandle[i].buf[fhandle[i].currOut].data[ fhandle[i].buf[fhandle[i].currOut].outputptr++];
		wk2 = (fhandle[i].buf[fhandle[i].currOut].outputptr >= (fhandle[i].buf[fhandle[i].currOut].datasize)) ?
				0x00 : fhandle[i].buf[fhandle[i].currOut].data[ fhandle[i].buf[fhandle[i].currOut].outputptr++];
	}

	if (fhandle[i].buf[fhandle[i].currOut].outputptr >= (fhandle[i].buf[fhandle[i].currOut].datasize)) {
		fhandle[i].buf[fhandle[i].currOut].status = 2;
		fhandle[i].currOut = (0 == fhandle[i].currOut) ? 1 : 0;
	}
	d = wk1 | (wk2 << 8);
	d = d + 32768;
	d = d >> 8;
	d = d & 0xff;
	return d;

}

/**
 * Wavファイルオープン
 * @param int  i ハンドラ(fhandle)参照用添字
 */
void wavopen(int i) {
	if (NULL == fhandle[i].fptr && (0x00 != fhandle[i].fname[0])) {
		fhandle[i].fptr = FSfopen(fhandle[i].fname, "r"); // ファイルオープン
		if (NULL == fhandle[i].fptr) {
			fhandle[i].fname[0] = 0x00; // ファイル名無効化(次からオープンしない)
			return;
		}

		// ヘッダ部分読み込み
		if (FSfread(&fhandle[i].whdr, 1, (size_t)sizeof (struct wavheader), fhandle[i].fptr)
				== (size_t)sizeof (struct wavheader)) {
			if (0 == strncmp(fhandle[i].whdr.datachid, "data", 4)) {
				if (0L != fhandle[i].whdr.chunkleng) {
					fhandle[i].remaining = fhandle[i].whdr.chunkleng;
					errorFlag = FALSE;
					return;
				}
			}
		}
		fhandleinit(i);
		errorFlag = TRUE;
	}
}

/**
 * Wavファイル波形部読み込み
 * @param int  i: ハンドラ(fhandle)参照用添字
 */
void wavread(int i) {
	unsigned long readsize;

	if (sdReadyFlag &&
			(!errorFlag) &&
			(NULL != fhandle[i].fptr) &&
			(1 != fhandle[i].buf[fhandle[i].nextRead].status)) {

		if (!fhandle[i].playFlag && 1 != fhandle[i].repeatFlag) {
			// 再生停止中
			fhandle[i].remaining = 0L; // 再スタートは最初から
			return;
		}
		if (0L == fhandle[i].remaining) {
			// 波形部を全て読み込んでいたら、波形部先頭に移動
			if (0 != FSfseek(fhandle[i].fptr, (long) sizeof (struct wavheader), SEEK_SET)) {
				errorFlag = TRUE;
				sdReadyFlag = FALSE; // 要リカバリ
				return;
			}
			fhandle[i].remaining = fhandle[i].whdr.chunkleng;
		}
		bhandleinit(i, fhandle[i].nextRead);
		readsize = (fhandle[i].remaining >= BUFSIZE) ? BUFSIZE : fhandle[i].remaining;

		// BUFSIZEバイトファイルリード
		fhandle[i].buf[fhandle[i].nextRead].datasize =
				(int) FSfread(fhandle[i].buf[fhandle[i].nextRead].data, 1, (size_t) readsize, fhandle[i].fptr);
		fhandle[i].remaining -= (unsigned long) fhandle[i].buf[fhandle[i].nextRead].datasize;
		fhandle[i].buf[fhandle[i].nextRead].status = 1;
		// 次回読み込みバッファ切り替え
		fhandle[i].nextRead = (0 == fhandle[i].nextRead) ? 1 : 0;
		if (1 == fhandle[i].repeatFlag && 0 == fhandle[i].remaining)
			fhandle[i].repeatFlag = 2;
	}
}

/**
 * CN入力変化割り込み(Mk144制御用)
 */
void __attribute__((interrupt, no_auto_psv)) _CNInterrupt(void) {
	__delay_ms(20);//チャタリング防止
	if (!PORTAbits.RA0) {
		// onになった
		Mk144Switch = !Mk144Switch;
	}
	_CNIF = 0; // 割り込みフラグクリア
}

/**
 * MK-144制御
 */
void mk144ctrl(void) {
	unsigned char folder;

	if (Mk144Switch != Mk144SwitchSave) {
		Mk144SwitchSave = Mk144Switch;
		if (Mk144SwitchSave) {
			mk144putcmd(0xEF); //停止
	 // mk-144のルートディレクトリはフォルダ1
		   //ロータリーコードスイッチが0、1の時はルートディレクトリ参照
			folder = (0 == sel.selector) ? 1 : sel.selector;
			folder += 240;
			mk144putcmd(folder); //フォルダ指定
			mk144putcmd(0x01); //ファイル1再生
			mk144putcmd(0xEC); //再生
		} else {
			mk144putcmd(0xEF); //停止
		}
	}
}

/**
 * MK-144向けコマンド送信
 * @param unsigned char cmd 送信文字
 */
void mk144putcmd(unsigned char cmd) {
	while (BusyUART1());
	U1TXREG = (unsigned int) cmd;
	__delay_ms(20);
}


[/c]

chime.h
エラー時にならすチャイムの音のWAVファイルの波形データ部を記述したファイルです。

PICでWav再生にある手順で作成しています。


回路等

  • ソース中のピンアサインに従って入出力を接続してください(^^;
  • 音声の出力(#4、#5)の出力はLM386などのオーディオ・パワーアンプに繋いでください。

PICマイコンでメモリ上のWavファイルのデータを再生

PIC24FJ64GA002でWav再生


Nゲージ用のコントローラやレイアウトなどを音が出るものにしようとして、MP3プレーヤボードを買って、PICマイコンで制御するように製作中。
だが、単純な効果音の再生用としてはPIC24FJ64BA002(16ビットマイコン)を使って、Wavファイルを再生してみようかなと。
ひとまずはメモリ上に置いたWavファイルのデータを出力する部分の試作。


PICマイコンでMP3プレーヤ相当のものを作るのに、VS1011eというデコーダICを使っている例がみられる。

アタイも検討してみたが、制御するPICマイコンやVS1011e、MP3ファイルを格納するSDかメモリチップなどを購入すると千数百円はかかりそう。

一方、マイコンキットドットコムからMK144というMP3プレーヤボードが1980円で販売されている。
制御するタクトスイッチや制御用の基板を購入すると、もう数百円は単価は上がりそうだが。

 

費用的には自作のほうがちょっぴりお得か?等々、考えてみたが、最終的にはMK144を複数買って、それを自作のコントローラで制御しようかなぁ、とゴソゴソと作業を始めた。


しかし・・まだ、完成したわけではないが途中でフッ..と思った。

駅に停車中のディーゼルカーの「ゴロッ、ゴロッ、ゴロッ」というエンジンのアイドリング音や、蒸気機関車のドンキーポンプの音などは単調な音が繰り返されているだけ。

0.5秒程度の「ゴロッ」という音の繰り返しや、数秒程度の繰り返し音などは別にMP3プレーヤを使って再生させなくてもいいんじゃないかなぁと。

MP3ボードを制御するのにPICマイコンを使用したとしても、PICマイコンは再生開始や停止を指示するコマンドを送信するだけが仕事で、MP3ボードが音を再生している間は基本的には・・暇。

ならば、単調な繰り返し音はちょっと性能の良いPICマイコンを買って、それでやってしまおう。

 

現在作っているNゲージの踏切の警報音などもPICマイコンで出力しているが、音のデータを格納できるサイズは本当に限られていて、長い音の格納はできない。
また、1つの音源ごとにPICマイコンを使っていてはPICマイコンだらけになってしまう。
やっぱ音源はSDカード類から読込んで、スイッチによって複数の音源を切り替えて再生できる仕組みが必要だろう。

本格的に作ってみる前に・・

Wavファイルのデータを出力する部分の試作

ひとまずはメモリ上に置いたWavファイルのデータを出力する部分の試作を開始。

PIC24FJ64BA002(16ビットマイコン)を使って、メモリ内に書き込んでおいた数秒程度のWavファイルを再生してみることにした。

手元にはマイコンキットドットコムで購入したMP3 プレーヤボード用コントローラキットMK148がある。
この部分は代替品を自作することにしたのでお役御免の予定だった。

しかし、基板上にはアンプ(LM386)が2つ載っているし、usart 通信でMP3 プレーヤーボードを制御するための配線も簡単に作れそうなので再度登板。

 

PIC24FJ64GA002でWav再生

(写真上) PIC24FJ64GA002を載せたユニバーサル基板(右のオレンジ色の基板)とMK148(左の緑色の基盤)を裏側でアクリル板を使ってくっつけてみた

電源はMK148からPIC24FJ64GA002のボード側に供給。

PICマイコンからの音声の出力はMK148上のアンプ(LM386)で本来のMP3ボードの音声とミキシングして出力するように変更。

メモリ内に書き込んだWavファイルを再生するのであるが、この次のステップとしてSDカードからWavファイルを読み込ませようとしている。
そのため、ユニバーサル基板上にはSDカードスロットも載っけて配線もすませておいた。
(写真のオレンジ色の基盤の右上の部分)

この基板の空き地には、ファイルセレクタ用のスイッチやら、開始、停止用のスイッチなどが入る予定。

仕様とプログラム

・Wavファイルは「8kHz 16bit モノラル」のみを対象
・今回のサンプルプログラムはRB0(4ピン)、RB1(5ピン)に同じデータを出力
・Wavファイルはメモリに格納できるだけのサイズのものを使用してフラッシュメモリに格納

main.c

/**********************************************
*
* PIC24FJ64GA002
*
*   8kHz sampling , 16bit モノラル Wav再生テスト
*   コンパイラ XC16
***********************************************/
#include

// CONFIG2
#pragma config POSCMOD = HS
#pragma config I2C1SEL = PRI
#pragma config IOL1WAY = OFF
#pragma config OSCIOFNC = OFF
#pragma config FCKSM = CSDCMD
#pragma config FNOSC = PRIPLL
#pragma config SOSCSEL = SOSC
#pragma config WUTSEL = LEG
#pragma config IESO = OFF

// CONFIG1
#pragma config WDTPS = PS32768
#pragma config FWPSA = PR128
#pragma config WINDIS = ON
#pragma config FWDTEN = OFF
#pragma config ICS = PGx1
#pragma config GWRP = OFF
#pragma config GCP = OFF
#pragma config JTAGEN = OFF

#include "chime.h"

unsigned int chimeidx;

int playFlag;

/***********************
* タイマ4割り込み処理関数
***********************/
void  __attribute__((interrupt, no_auto_psv)) _T4Interrupt(void)
{
	int d = 128;

	if(playFlag){
		// chimeデータの配列は偶数バイトで作成しておくこと
		d = chime[chimeidx] | (chime[chimeidx+1]<<8);  		d = d + 32768;  		d = d >>8;
                   d = d & 0xff;
		chimeidx += 2;
		if(chimeidx>(sizeof chime)){
			playFlag = chimeidx = 0;
		}
	}
	OC1RS = (PR2+1)*d/256;	// RP0出力
	OC2RS = (PR2+1)*d/256;	// RP1出力

	IFS1bits.T4IF = 0;					// 割り込みフラグクリア
}

/**************************
* メイン関数
***************************/
int main(void)
{
	playFlag = chimeidx = 0;

	CLKDIV = 0;

	AD1PCFG = 0xFFFF;	// I/Oをすべてデジタルに
	TRISB = 0xfffc; 	// RB0(4ピン)、RB1(5ピン)を音声出力に

	CMCON = 0;				// コンパレータオフ

	// タイマ2設定
	T2CON = 0b1000000000000000;		// タイマ2モード設定
	PR2 = 159; //100kHz

	// OC1
	RPOR0bits.RP0R = 18;	// OC1をRP0(4ピン)に割付け
	OC1CON = 0x0006;		// タイマ2使用,PWM mode,OCFx disabled
	OC1RS = (PR2+1)/2;		// 50%

	// OC2
	RPOR0bits.RP1R = 19;	// OC2をRP1(5ピン)に割付け
	OC2CON = 0x0006;		// タイマ2使用,PWM mode,OCFx disabled
	OC2RS = (PR2+1)/2;		 //50%

	// タイマ4モード設定
	T4CON = 0b1000000000010000;
	PR4 = 249;				// 125usec  8kHz
	IPC6bits.T4IP = 5;		// 割り込みレベル = 5
	IFS1bits.T4IF = 0;
	IEC1bits.T4IE = 1;		// 割り込み許可

	while(1){
		playFlag = 1;
	}
}

音声データ

音声のデータとなるWavファイルは「8kHz 16bit モノラル」で2~3秒のチャイム音を16進で記述してヘッダファイルとして作成し、プログラム内に格納した。

作成方法は、次のとおり

(1)Wavファイルをバイナリエディタで開く
(2)dataチャンクがあらわれるまで(‘data’の文字列があるのでわかります)を削除
(3)dataチャンクのID(‘data’の文字)とレングスを示す4バイト分を削除
(4)その後以降が波形データであるが、末尾の作成したソフトな名前などが入っている場合があるので、その部分があれば削除
(5)1バイト毎に0xと,をつけたりしてプログラムのヘッダファイルとして使用できるように編集。
編集は・・
秀丸等のエディタにデータを貼り付けて、置換コマンドで各バイトの先頭に0xをつけたり、,をつけたり。
1行を80文字くらいで改行したければ、同じく置換コマンドで「^.{80}\f\0\n」を「\0\n」に置換したりして編集。

以上のファイルをヘッダファイルにして、main関数でインクルードする。

ちなみに、今回はチャイム音を使ってみた。

 

chime.h

/********************************************************************
 チャイム音 Wav 8kHz モノラル 16bit

********************************************************************/
const unsigned char chime[] = {
	// dataチャンクID'data'とサイズの後から、末尾の拡張部分の前までを登録

 0xBD , 0xFF , 0x25 , 0x00 , 0xC5 , 0xFF , 0x97 , 0xFF ,
 0xB9 , 0x00 , 0x46 , 0x00 , 0xD2 , 0xFF , 0x62 , 0x00 ,
 0x85 , 0x01 , 0xD6 , 0xFF , 0xA6 , 0xFB , 0xFF , 0xF8 ,
 0xBA , 0xFD , 0xEF , 0x08 , 0x9A , 0x0B , 0xFF , 0x03 ,
 0x6A , 0x00 , 0x6F , 0xF3 , 0x5A , 0xEE , 0xAC , 0xFC ,
 0xDE , 0x14 , 0x4F , 0x09 , 0x54 , 0xFB , 0xE0 , 0x05 ,
 0x27 , 0x04 , 0xDA , 0xEC , 0x06 , 0xED , 0x42 , 0x02 ,

~
省略
~
  };

 

本番のプログラム(SDカードからファイルを読んで再生するプログラム)を作成するとき、動作中にエラーがあった場合にエラーを表示する機能が欲しい。
そのエラー表示にLEDを点滅させようとすると、PICマイコンのポートを占有してしまう。

そこで、今回作ったチャイム音データは本番プログラムでもプログラム内に格納しておいて、エラーがあった場合にはこのチャイム音を鳴らすことにした。

 

こうすることで、

・エラー音をSDファイルに格納しておくと、SDファイルが読めない時にエラー音が出せないが、プログラム内に格納しておくとその心配はない。
・一度エラー音が出ることを確認しておけば、サンプリングなどに問題があって再生できないファイルなのか、再生モジュール自体に問題があって音が出力ができないのか、プログラムにデグレードがあるのかなどの切り分けができる。

というメリットがありそう。

試作した結果

回路図エディタ持ってないので図面は省略(^^;

プログラム中のコメントにPICマイコンのどのピンに出力しているのか書いときましたんで、それをアンプにつなげて・・
その他は、PICマイコンへの電源供給とPIC24FJ64BA002の場合はコンデンサなどの接続等々が必要。
それらはPICマイコンのメーカーが出している説明書に書かれているポートに配線して・・

という感じでご自身で・・(^^;

 

ひとまず、アタイの試作品は、メモリ内に格納したWavファイルの音声は無事出力できた。

 

次のステップとしては、

PIC24FJ64BA002を使ったWavファイルプレーヤーの次のステップでは、
・SDファイルから音声データを読込んで、それを出力する。
・ついでにusart 通信で市販のMP3プレーヤーボードを制御する。
ということになる。

このプログラムで、2つのポートから同じ音声を出力しているのは、2ファイルを読みながら音声を出力できるだけの能力がでるようにプロクラムが組めるか検証するため。
PICマイコンにその性能があれば、2種類の音が同時にだせるのではないかともくろんでいるからなのだが。

スイッチをつけてSDファイルから読むデータを切り替えられるようになったら、プラモデルやNゲージのシーナリーなどを置く台の下などに格納できるようにするつもり。

予定では、mp3のBGMが流れると同時にWavの繰り返し音が流れるのであった。

・雪なんかとディーゼルカーがあるNゲージのレイアウトのセクションでは気動車のエンジンのアイドリング音と「鉄道員(ぽっぽや)」のBGM
・三陸鉄道の36系があるNゲージのセクションでは気動車のエンジンのアイドリング音と「あまちゃん」のBGM
・ハーレーのバイクがあるプラモデルの飾り台では3拍子のアイドリング音とBGMは「Born to Be Wild」….

まぁ、そんなことを考えながら、次のステップに進もう。

寄り道が多くて、なかなかNゲージのコントローラなどは完成しないのであるが..


追記

SDカードから読み込んで再生するものを作ってみた。
2ファイルを読みながら、2つのモノラルの音声を出力することができる。
鉄道模型レイアウト・プラモデル展示台用の音(PICでWAV再生)

Nゲージ電機用コントローラ (その5) 音の準備

MP3プレーヤーボード MK-144

最近ではPCM電源を使ってモーターから音を出すこともできるんだそうですなぁ。
そんなこともやってみたいが、今作っているコントローラは運転台風のもの。遠くを走る機関車から音がしても、運転台自体が無音というのもなぁ。

やはり、手元の運転台から音を出す仕組みが必要か。
それにレイアウトを作る時には駅などにもATOSの音や発車時のベルの音など出したい。

ということから、MP3プレーヤーボードのキットを買ってみた。

MK-144というMP3プレーヤーボードで、「こんな感じ」で作った。

 

 

MP3プレーヤーボード MK-144

(写真上) メモリ内に格納した8個のMP3のファイルを8個のスイッチでダイレクトに再生できる

PICマイコンで制御すれば、8個以上の再生も制御できる。
あまりそちらにのめりこんでもねぇ、とは思うのだが、やっぱ音は欲しい。

動的に音の合成はできないので複数のプレーヤーボードを使う方が現実的かも。
まぁいろいろと音が出るレイアウトは良さそうなので、プレーヤー部をあと2~3個買ってみようかなと思うのだが。

 

ただ、パラパラと音源を用意していたらすぐに8ファイルになってしまう。
それに音を出したいときに常にボタンを押す必要があると、コントローラーで運転を楽しんでいるのか、音を出すスイッチの操作をしているのだかわからなくなってします。

ひとまず合成してよい音は1つのファイルに合成してみることにした。

 

RadioLine Free(ラジオラインフリー)というフリーの音楽編集、音声録音のソフトを利用させてもらった。
4つの音の音量を調整したりフェードしながら合成ができる。

試しに合成したのが、ATSの音。

まぁ、実際はいろいろとあるんでしょうが、模型としては、

  • 赤信号の区間か停車駅に進入したらここで合成した音を流し始める
  • 5秒以内にブレーキの操作をしたらこの音の再生はやめて、チャイムの音だけのファイルを再生する
  • 5秒たったら、この音の再生は続けたままコントローラ自身がブレーキをかける

という感じにしたい。

で、音としては、

  • 最初にリリリーンというベルが鳴り始める
  • ベルの音からわずかに遅れてチャイムが鳴り始める
  • 5秒後くらいに非常ブレーキの音としてエアーが抜ける音がする
  • ちょっとして、コンプレッサーが回りだす

というのをイメージして音を合成してみた。

 

ATS用音源の合成

(写真上) こんな感じで合成中

上から、

  • ベルの音
  • チャイムの音
  • エアが抜ける音
  • コンプレッサーが回る音

 

で、試しにできた音の一部はこんな感じ。

 

 

フムフム、これで1ファイルに一連の動作の音を流れにあまり無理がない状態で入れることができる・・と、まぁ自己満足。

家族からみたら、ベルやチャイムの音を鳴らしてどこがうれしいのかわからん・・といった感じで見てたんでしょうなぁ。

ただ、この音源ファイル、使えるシチュエーションは限られる。
ちなみに、今回作った音は赤信号の区間に突っ込まないと鳴らない音。

ガタン、ゴトンというレールの継ぎ目の音も列車の速度が変わると音色も変わる、そんな走行時の音などはmp3を再生する仕組みでは当然無理がある。

そのあたりの音を楽しみたいのなら、実際のレイアウトで模型を走らせながら・・というより、ゲームやらPCのシミュレータで楽しむんでしょうかねぇ。

ひとまず、音は再考の余地あり。

 

=2014年2月=

PICマイコンでWAVを再生するボードを作ったので、これをベースにこのコントローラでどのように使えるか検討することにした。

 

1 2
趣味の部屋 スマホ/タブレットサイト 趣味の部屋 PCサイト