PIC16F18346の PWM を使用し、約15秒までの言葉を喋るようなプログラムを作ります。PIC本体のメモリーには音声データを収めることができないので、1MbitのEEPROMを接続します。ハードウエアや制御プログラム自体はそれほど難易度の高いものではありません。一方で実際に喋る言葉のデータを準備するのに時間がかかりました。
32MHzのシステムクロックでTimer2と連携したPWM1を使いPCM音声データを再生させます。Fosc/4をプリスケラー 1/1, PR2=255 ポストスケーラー 1/4 とすると、PWM周期は31KHz. Timer2カウントオーバーは、7.8KHzになります。
音声データは、8KHzサンプリングのunsigned 8bit PCMで準備し、Timer2カウントオーバーごとにPWMデュティサイクルレジスタに書き込めば音声が再生できます。
電源を供給すると、EEPROMに記録された音声が再生され終了後にPushSWを押すと、再スタートします。音声データは、WAVE形式で記録したものをEEPROMに記録してあります。
8KHz 8bitの記録なので1Mbit(128byte)に16秒の音声が収まります。
EEPROMに記録したWAVEデータのうち実際のPCM音声データの開始アドレスと終了アドレスをこのプログラムのmain.cに書込む必要があります。WAVEファイルをバイナリエディターで開き、これらアドレスを調べてください。
プログラムを示します。
Curiosity用にconfigを、MCLRE = ON,LVP = ONとしてあります。
/*************************************
* File name: main.c EEPROM 音声再生
* EEPROM に記録された 8kHz 8bit PCMデータを再生
* System ClockはConfigで内部32MHzに設定
* LEDも連動した点灯
* PWM(LED) RC5
* PushSW RC4
*
* Language: MPLABX XC8 v2.10
* PIC16F18346 on 20pin demoboard
* Created on May 25, 2020, 2:37 PM
**************************************/
#include <xc.h>
#include "I2C_EEPROM.h"
#define _XTAL_FREQ 32000000 // delay_ms(x) のための定義
#define PushSW RC4
#pragma config FEXTOSC = OFF,RSTOSC = HFINT32 // HFINTOSC (32MHz)
#pragma config CLKOUTEN = OFF,CSWEN = OFF,FCMEN = OFF
#pragma config MCLRE = ON,PWRTE = OFF,WDTE = OFF,LPBOREN = OFF
#pragma config BOREN = OFF,BORV = LOW,PPS1WAY = OFF,STVREN = ON
#pragma config DEBUG = OFF
#pragma config WRT = OFF,LVP = ON,CP = OFF,CPD = OFF
// ******************* main *************************************
void main() {
char mBlock; // EEPROM memory block number
unsigned long addr = 0; // EEPROM memory address
char data = 0;
// TRISC2 = 0; // Outputs RC0 for LED
// TRISC3 = 0; // Outputs RC0 for LED
i2c_MasterInit(400000); // i2c 関連初期化 400KHz クロック
TRISC4 = 1; // RC4 入力
ANSELC = 0; // すべてデジタル
TRISC5 = 0; // Outputs RC5 for PWM5
RC5PPS = 2; // PWM5をRC5に出力
//------------ Initialize PWM -----------------------
// Timer2 は、Fosc/4(8MHz)をPreScalerで 1/1(8MHz)
// 8bitで255カウントする 8M / 255 = 31KHz
// PostScaler 1/4 でTMR2IFは7.8KHz周期で「1」になる
// ---------------------------------------------------
T2CON = 0b00011100; // Timer 2 Post 1/4 Pre 1/1設定
PR2 = 0xFF; // Timer2 Period Register設定
// --------------------------------------------
// PWM5は、TMR2と連携し、
// 初期値デュティサイクルは、50%
// --------------------------------------------
PWM5CON= 0b11000000; // 正論理出力
PWM5DCH= 0x80; // デュティサイクルを設定
PWMTMRS= 0b00000101; // TMR2と連携(初期値)
while(1){ // 繰り返しループ
addr = 0x0002C; // PCM 開始アドレス
mBlock = EEPROM_AddressSet(addr); // 内部アドレス設定
while(addr++ < 0x1BD84){ // データ終了まで
while(! TMR2IF); // Timerカウントアップ待つ
TMR2IF = 0; // フラッグをクリア
data = EEPROM_ReadCurrent(mBlock); // デュティサイクルを設定
PWM5DCH = data; // デュティサイクルを設定
// if(data > 0x88)LATC3=1;else LATC3=0;
// if(data > 0x98)LATC2=1;else LATC2=0;
if (addr == 0x10000) // ブロック境界なら
mBlock = EEPROM_AddressSet(addr); // 内部アドレス書換え
}
// PWM5DCH = 0; // LED D7 Off
while (PushSW); // 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 32000000
#define SlaveAdd_R 0xA1
#define SlaveAdd_W 0xA0
//--------------- I2C Routines -------------------
void i2c_MasterInit(const unsigned long baud){
// pin設定 ------------------------------------------------
TRISB4 = 1; // RB4を入力
TRISB6 = 1; // RB6を入力
ANSB4 = 0; // RB4をデジタル
ANSB6 = 0; // RB6をデジタル
ODCB4 = 1; // RB4をオープンドレイン
ODCB6 = 1; // RB6をオープンドレイン
// PPS設定 -----------------------------------------------
SSP1CLKPPS = 0x0E; // RB6をCLK入力に指定
RB6PPS = 24; // RB6をCLK出力に指定
SSP1DATPPS = 0x0C; // RB4をDATに入力指定
RB4PPS = 25; // RB4をDAT出力に指定
// SSP1設定 -----------------------------------------------
SSP1STAT = 0b10000000; // スルーレート制御はOff
SSP1ADD = (_XTAL_FREQ/(4*baud))-1; // SSP1ADD = 0x77;
SSP1CON1 = 0b00101000; // I2C Master modeにする
}
/********************************************************
* 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;
}