I2CのシリアルROMを読み書きできるようになった(前回の記事参照)ので、ROMに音声ファイルのデータを書き込んで、PIC 12F1822 で再生できるかやってみた。
マイコンで音声データを再生する方法として、PWMでやる方法と、D/Aコンバータでやる方法があると思うのだけど、12F1822は、やろうと思えば、どっちでもできる。 で、やってみたけど、今回は、PWMに軍配があがった。
理由は、12F1822 のDACの出力レベルが32しかないことと、出力にバッファをつけてやらないとだめなこと、で、出力も非常に小さいので、蚊の鳴くような音しか聞こえませんでした。もちろん、ちゃんと作りこめばそれなりのものは、できると思いますけど、今回は、PWMで・・・。と、言うことになりました。
が、PWMでも一応、スピーカも鳴ってはいますが、簡単なアンプをつけてやった方が良さそうではあります。
回路は、以下のようにシンプルにPICとROMをI2Cでつなぐだけです。あとは、PICに発音のスイッチと出力にコンデンサをつけてやるだけです。
抵抗2本は、I2Cのプルアップ用です。
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