PIC 12F1822とI2CROMでWAVファイルを再生してみた。
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