PIC 12F1822とI2CROMでWAVファイルを再生してみた。

I2CのシリアルROMを読み書きできるようになった(前回の記事参照)ので、ROMに音声ファイルのデータを書き込んで、PIC 12F1822 で再生できるかやってみた。
f:id:manpukukoji:20171123092800j:plain
マイコンで音声データを再生する方法として、PWMでやる方法と、D/Aコンバータでやる方法があると思うのだけど、12F1822は、やろうと思えば、どっちでもできる。 で、やってみたけど、今回は、PWMに軍配があがった。
理由は、12F1822 のDACの出力レベルが32しかないことと、出力にバッファをつけてやらないとだめなこと、で、出力も非常に小さいので、蚊の鳴くような音しか聞こえませんでした。もちろん、ちゃんと作りこめばそれなりのものは、できると思いますけど、今回は、PWMで・・・。と、言うことになりました。
が、PWMでも一応、スピーカも鳴ってはいますが、簡単なアンプをつけてやった方が良さそうではあります。
回路は、以下のようにシンプルにPICとROMをI2Cでつなぐだけです。あとは、PICに発音のスイッチと出力にコンデンサをつけてやるだけです。
抵抗2本は、I2Cのプルアップ用です。
f:id:manpukukoji:20171123091801p:plain
PICのプログラムも、ROMを読んで、データの値に応じたPWM周期をセットするだけですので、データを読み込みさえすれば、後は、いたって簡単ですが、I2Cの読み込みが間に合わなくてはまりました。
I2Cの読み込みに関しては、以下のサイトを参考に(コピー)させてもらいました。
I2CでEEPROMを使う(PIC16F886)

WAVファイルのデータを8bit 8kHz monoral にすると、ちょうどPWMの周期を8bit 256ステップで置き換えられ、8kHzの割り込みでデータを更新していけばいけるだろうと思ったんですが、うまくいかなかったので、いろいろ調べてみたら、ROMの読み込みが間に合っていませんでした。
PICは、8MHzから16MHzに切り替え、I2Cの読み込みも毎回アドレス指定をしてランダムリードしていたのをシーケンシャルリードに変えたところで、なんとか間に合うようになりました。
まぁ、32MHzとかのクロックもあるようなので、今度、試してみようかな・・・? そうすると、16KHzサンプリングとかのデータでもう少しクオリティも良くなるかもしれないしね。
発音データは、あちこちから拾ってきて、WAVファイルにし、必要なデータ部分をテキストファイルに落として、作っています。 動物の鳴き声とか、虫の音とかは、ちょっとわかりにくく、やっぱり、人の音声が使いやすそうですので、一言メッセージとかには、使えるかも。 いろいろためしていると、女性のアニメ声は、他に比べて格段に明瞭なのがよくわかります。逆に、虫の音は、なんだか、言われてみないと良くわからないです。 周波数成分の問題なんでしょうかね・・・?

PIC 12F1822とI2CROMでWAVファイルを再生してみた

できれば、普段は Sleep させて、省電力を試みようかと思ったんですが、Sleepから起こすためのINTPINがI2Cのピンと重なっちゃって使えそうもなかったので、WDTでなんとかならないかと四苦八苦しましたが、あえなく玉砕しました。
PICのプログラムは、以下参照ください。 例によって、古いHiTech-C です。ぼちぼち、アップデートしないと時代遅れなのはわかっているのですが、今度、古いPC新しいのに替えたら、アップデートしようかなと思っています。

/**************************************************************
	12F1822 Play WAV
		Pin assignment:
	(1)Vdd
	(2)RA5 PWM out for WAV play
	(3)RA4 Speak SW
	(4)RA3 MCLR 10Kohm Resister between Vdd 
	(5)RA2 i2c(SDA)
	(6)RA1 i2c(SCL) / ICSPCLK
	(7)RA0 ICSPDAT /(Tx)
	(8)Gnd
**************************************************************/
#include <pic.h>
#define _XTAL_FREQ 16000000

__CONFIG(	
        FOSC_INTOSC & WDTE_OFF & PWRTE_ON & MCLRE_OFF & CP_OFF 
        & CPD_OFF & BOREN_ON & CLKOUTEN_OFF & IESO_OFF & FCMEN_OFF
);

__CONFIG(
	WRT_OFF & PLLEN_OFF & STVREN_ON & BORV_25 & LVP_OFF
);


void outchar(char);
void outbyte(char);
unsigned char i2c_read(unsigned char);

int vdata=128;
unsigned char fl;

//Delay 100msec --------------------------------
void Delay_10ms(unsigned char time){
	while(time--){
		__delay_ms(10);
	}
}


void i2c_start(void){
	
	SEN=1;
	while(SEN);
	return;
}

void i2c_restart(){
	SSP1IF = 0;
	RSEN = 1;
	while(RSEN);
	return;
}

void i2c_stop(void){
	
	SSP1IF = 0;
	PEN=1;
	while(PEN);
	SSP1IF = 0;
	return;
}

void i2c_send(unsigned char send_data){
	
	SSP1IF = 0;
	SSP1BUF = send_data;
	while(!SSP1IF);
	return;
}

unsigned char i2c_rcv(){
	unsigned char d;

	SSP1IF = 0;
	RCEN = 1;
	while(RCEN);
	d=SSP1BUF;
	return(d);
}

//------------------------------------------------

// ==================== I2C ACK check =========================
unsigned char i2c_ackchk() {
	unsigned char i2c_data;
 	
	if (ACKSTAT) {
		i2c_data = 1;
	} else {
		i2c_data = 0;
	}

	return(i2c_data);
}

// ==================== I2C ACK send ==========================
void i2c_acksnd() {
	ACKDT = 0;
	ACKEN = 1;
	while (ACKEN);

	return;
}

// ==================== I2C NACK send =========================
void i2c_nacksnd() {
	
	ACKDT = 1;
	ACKEN = 1;
	//while (ACKEN);

	return;
}

// ==================== I2C Recive char ========================
unsigned char i2c_rcv_char(unsigned char addr1 ,unsigned char addr2) {
		unsigned char c;

		i2c_start();
		i2c_send(0b10100000);		// スレーブアドレス & Write
		i2c_send(addr1);		// 読み出しアドレス1
		i2c_send(addr2);		// 読み出しアドレス2

		i2c_start();
		i2c_send(0b10100001);	// スレーブアドレス & Read
		c = i2c_rcv();

		i2c_nacksnd();

		i2c_stop();

	return(c);
}


//------------------------------------------------
void interrupt pwav(void){
	if(TMR0IF==1){
		TMR0 = 6;
		TMR0IF = 0;
		
		CCPR1L=vdata; //PWM set
		fl=1;
	}
}

//------------------main
void main(void){

	unsigned int ndata,mi;
	unsigned char a0, a1, flag, c, ii;

//Initialization
	OSCCON = 0b01111000; // PLL disable, 16MHz internal clock
 	ANSELA = 	0b00000000;	//All digital 
    TRISA 	=	0b00111110;		//GPIO dirction setting  
    PORTA	=	0b00000000;		//GPIO initialize
//---------------------------------------------------------
//PWM
     CCP1SEL = 1;          // 2番ピン(RA5)をCCP1/P1Aピンとして出力
	 CCP1CON=0b00001100;	//PWM mode: P1A, P1C active-high; P1B, P1D active-high
     						// PWM機能(シングル)を使用する
     T2CON   = 0b00000000 ; // TMR2プリスケーラ値を1倍に設定
     CCPR1L  = 128 ;          // デューティ値は128で初期化
     PR2     = 250 ;        // PWMの周期を設定
     TMR2ON  = 1 ;          // TMR2(PWM)スタート
	 while(TMR2IF == 0);
	 TRISA5=0;
	 WPUA4=1;				//Week Pull Up for RA4 SW
	 
//----------------------------------------------------------
//i2c
    SSP1ADD = 9;
    SSP1CON1 = 0b00101000;

//----------------------------------------------------------
//Serial
    SPBRG=103;
    TXSTA=0b00100100;
    TXCKSEL=0;
    SPEN=1;
    TXEN=1;

//----------------------------------------------------------
//TIMER0 INTERRUPT
	OPTION_REG=0b00000000; //pre-Scaler=1:2
	TMR0=6; 
	TMR0IE=0;
	GIE=1;	
//----------------------------------------------------------

		Delay_10ms(50);

		a0=i2c_rcv_char(0,0);
		a1=i2c_rcv_char(0,1);
		ndata=256*a0+a1;


	while(1){

//**********************************************************
		while(RA4==0){
			i2c_start();
			i2c_send(0b10100000);   //Write
			i2c_send(0);
			i2c_send(0x10);  //First data address
			i2c_start();
			i2c_send(0b10100001);   //Read
			vdata=i2c_rcv();
			i2c_acksnd();
			TMR0IE=1;
			while(fl==0);
			fl=0;

			for(mi=0;mi<ndata;mi++){
				vdata=i2c_rcv();
				i2c_acksnd();
				while(fl==0);
				fl=0;

			}
		
			TMR0IE=0;
			i2c_rcv();
			i2c_nacksnd();
			i2c_stop();

			Delay_10ms(70);

		}	//end of while (RA4==0)

//***********************************************************

	}  //end of while
}  //end of main