PIC16F1459の PWM を使用し、約15秒までの言葉を喋るようなプログラムを作ります。PIC本体のメモリーには音声データを収めることができないので、1MbitのEEPROMを接続します。ハードウエアや制御プログラム自体はそれほど難易度の高いものではありません。一方で実際に喋る言葉のデータを準備するのに時間がかかりました。
<回路図>
48MHzのシステムクロックでTimer2と連携したPWM1を使いPCM音声データを再生させます。Fosc/4をプリスケラー 1/1, PR2=255 ポストスケーラー 1/6 とすると、PWM周期は47KHz. Timer2カウントオーバーは、7.8KHzになります。
音声データは、8KHzサンプリングのunsigned 8bit PCMで準備し、Timer2カウントオーバーごとにPWMデュティサイクルレジスタに書き込めば音声が再生できます。
電源を供給すると、EEPROMに記録された音声が再生され終了後にPushSWを押すと、再スタートします。音声データは、WAVE形式で記録したものをEEPROMに記録してあります。
8KHz 8bitの記録なので1Mbit(128byte)に16秒の音声が収まります。
EEPROMに記録したWAVEデータのうち実際のPCM音声データの開始アドレスと終了アドレスをこのプログラムのmain.cに書込む必要があります。WAVEファイルをバイナリエディターで開き、これらアドレスを調べてください。
音声再生ソースファイル B10_voice.zip |
<プログラム main.c>
/* *****************************************
* File name: EEPROM 音声再生
* EEPROM に記録された 8kHz 8bit PCMデータを再生
* Notes: 48MHz内部クロック PLLはOn
* PWM出力 RC5
* PushSW RA5
*
* Created on May 5, 2020, 6:31 PM
***************************************** */
#include <xc.h>
#include "I2C_EEPROM.h"
#define _XTAL_FREQ 48000000
#define SW2 RA5
#pragma config FOSC = INTOSC, WDTE = OFF, PWRTE = OFF, MCLRE = OFF, CP = OFF
#pragma config BOREN = ON, CLKOUTEN = OFF, IESO = OFF, FCMEN = OFF
#pragma config WRT = OFF, CPUDIV = NOCLKDIV, USBLSCLK = 48MHz, PLLMULT = 3x
#pragma config PLLEN = ENABLED, STVREN = ON, BORV = LO, LPBOR = OFF, LVP = OFF
void main(void) {
char mBlock; // EEPROM memory block number
unsigned long addr = 0; // EEPROM memory address
OSCCON = 0b11111100; // SPLLEN = ON, 16MHz(x3=48MHz)
TRISC5 = 0; // PWM output pins
i2c_MasterInit(400000); // i2c 関連初期化 400KHz クロック
// -------------------------------------------------------
// Timer2 は、Fosc/4(12MHz)をPreScalerで 1/1
// 8bitで256カウントする 12M / 255 = 47KHz
// PostScaler 1/6 でTMR2IFが「1」になる 7.8KHz周期
// TMR2IFを常時モニタし、TMR2IFセットで音声データ更新
// -------------------------------------------------------
T2CON = 0b00101100; // Timer 2 Post 1/6 PS 1/1 設定
PR2 = 0xFF; // Timer2 Period Register設定
PWM1CON= 0b11000000; // 正論理出力
while(1){
addr = 0x0002C; // PCM 開始アドレス
mBlock = EEPROM_AddressSet(addr); // 内部アドレス設定
while(addr++ < 0x1BD84){ // データ終了まで
while(! TMR2IF); // Timerカウントアップ待つ
TMR2IF = 0; // フラッグをクリア
PWM1DCH = EEPROM_ReadCurrent(mBlock); // デュティサイクルを設定
if (addr == 0x10000) // ブロック境界なら
mBlock = EEPROM_AddressSet(addr); // 内部アドレス書換え
}
while (SW2); //SWが押されたら繰り返し
}
}
I2C_EEPROM.h
// File name: I2C_EEPROM.h // -------------- I2C関連定義 -------------- // 各シーケンスの終了を待って復帰 // I2C特殊ビット #define i2c_Idle() while((SSPCON2 & 0x1F)|(SSPSTATbits.R_nW)) #define i2c_Start() SSPCON2bits.SEN=1;while(SSPCON2bits.SEN) #define i2c_Stop() SSPCON2bits.PEN=1;while(SSPCON2bits.PEN) #define i2c_reStart() SSPCON2bits.RSEN=1;while(SSPCON2bits.RSEN) #define i2c_ACK() SSPCON2bits.ACKDT=0;SSPCON2bits.ACKEN=1;while(SSPCON2bits.ACKEN) #define i2c_NACK() SSPCON2bits.ACKDT=1;SSPCON2bits.ACKEN=1;while(SSPCON2bits.ACKEN) // -------------- プロトタイプ -------------------------- void i2c_MasterInit(const unsigned long baud); char i2c_Write(char data); char i2c_Read( void ); // -------------- EEPROM -------------------------- char EEPROM_AddressSet(unsigned long add); char EEPROM_ReadCurrent(char block);
I2C_EEPROM.c
/* *****************************************
* File name: I2C_EEPROM.c
* Notes: 48MHz内部クロック
* PWM出力 RC5 PushSW RA5
* Target PIC16F1459
* Created on May 5, 2020, 6:31 PM
***************************************** */
//
#include <xc.h>
#include "I2C_EEPROM.h"
#define _XTAL_FREQ 48000000
#define SlaveAdd_R 0xA1
#define SlaveAdd_W 0xA0
//--------------- I2C Routines -------------------
void i2c_MasterInit(const unsigned long baud){
ANSB4 = 0;
TRISB4 = 1; // SDA TRIS
TRISB6 = 1; // SCL TRIS
SSP1CON1 = 0b00101000;
SSP1CON2 = 0;
SSP1ADD = (_XTAL_FREQ/(4*baud))-1; // SSP1ADD = 0x77;
SSP1STAT = 0;
}
/********************************************************
* i2c Library Master Mode Only
*********************************************************/
char i2c_Write(char data){
SSPBUF = data; // データセット
PIR1bits.SSP1IF = 0; // 終了フラグクリア
while(!PIR1bits.SSP1IF); // 送信終了待ち
return SSPCON2bits.ACKSTAT; // ACKなら「0」で復帰
}
char i2c_Read( void )
{
SSPCON2bits.RCEN = 1; // データ受信を指示
while ( !SSPSTATbits.BF ); // 8ビット受信の完了を待つ
return SSPBUF; // 受信データで復帰
}
/**************************************************************
* EEPROM 内部アドレスポインタをセットする
* 指定アドレスのメモリーブロックを戻値で返す
* メモリーブロック 0:00000-0FFFF 1:10000-1FFFF
**************************************************************/
char EEPROM_AddressSet(unsigned long add){
char block = (add > 0xFFFF);
i2c_Idle(); // busアイドルの確認
i2c_Start(); // Start bit
i2c_Write(SlaveAdd_W | block*8); // 制御コード送信
i2c_Write(add >> 8); // アドレス送信
i2c_Write((char)add); // アドレス送信
i2c_Stop(); // Stop bit
return block; //
}
/**************************************************************
* EEPROM 内部アドレスポインタの示すデータを読み出す
* 読み出し後にアドレスポインタを +1 する
* 引数:メモリブロック番号
**************************************************************/
char EEPROM_ReadCurrent(char block){
char Data;
i2c_Idle(); // busアイドルの確認
i2c_Start(); // Start bit
i2c_Write(SlaveAdd_R | block*8); // 制御コード送信
Data = i2c_Read(); // データ受信
i2c_NACK(); // NACK送信
i2c_Stop(); // Stop bit
return Data;
}
このプログラムで使用する音声データは、無料の音声編集ソフト「Audacity」、バイナリーエディタ「BZ」、テキストエディタ『Atom』を使用して作成し最終的に wOha.h にバイト単位の配列定数として収録しました。
ファイル作成にはいろいろな方法が考えられます。
例えば
下の画面は取得した音声ファイルを、「Audacity」で開いた時のものです。
音声ファイルには、余分な空白や雑音なども一緒に録音されているので、必要な長さ音の大きさに「Audacity」を使い調整します。
調整項目は
下の画面は各種調整を終えた音声データです。
調整を終えたら
の音声ファイルで「書き出し」ます。
wave形式ファイルからPCMデータの開始、終了アドレスを調べ手から、EEPROMにwave形式ファイルのデータを記録します
下の画面は取得した音声ファイルを、バイナリーエディタ「BZ」で開いた時のものです。