用stm32f103单片机实现语音功能
在
很多设备中需要加入语音播报功能,比如常见的家用电器、汽车、专用的手持设备等等。实现语音功能的方法比较多,比如用mp3解码芯片对mp3音频数据进行
解码,或用单片机直接对mp3文件解码,或直接是用语音芯片。这些方法各有各的优缺点,mp3解码芯片价格贵,能对mp3软解码的单片机性能要求高,用语
音IC需要定制,有不够灵活。基于这样的原因,我使用了一种既灵活成本也不高的方法,那就是用stm32f103rcbt6单片机+I2S芯片+功放芯片
实现语音功能。音频文件用.wav格式的,这是一种未经压缩的的音频文件,wav格式文件的结构是前44
Byte放的是文件的基本信息,第44Btye后的数据就是音频数据;头信息数据结构如下所示:
-
typedef struct
-
{
-
u8 chRIFF[4];//固定为RIFF
-
u32 wavlen; //文件长度
-
u8 chWAV[4]; //固定为WAVE
-
u8 chFMT[4]; //固定为fmt
-
u16 formart; //格式类别
-
u16 CHnum; //通道数,为1代表是单声道
-
u32 SampleRate;//采样率
-
u32 speed; //音频传送速率
-
u16 ajust; //数据块调速数
-
u16 SampleBits;//样本数据位数
-
u8 chDATA[4]; //固定为data
-
u32 DATAlen; //数据长度,数据后面跟的是音频数据
-
}WAV_file;
用FlexHEX软件打开一个音频文件,可以看到整个文件的数据,如下:
t
图
中泉内的数据就是wav文件的头信息,对照头信可以得到wav文件的基本数据是:单声道,采样率=0x5622(十进制为22050,即为22kHz),
数据率是0xAC44(十进制是44100,即44kHz)。我们最需要关心的是文件的采样率,从Flash读出wav文件的头信息后就可以对单片机的
I2S接口配置,然后DMA向I2S接口发送音频数据。
从flash读取wav文件数据前得先把文件放到flash里面,怎么放呢?用专业设备写
进去?不,那太麻烦了,stm32本身就带USB的硬件模块,我只要利用ST的USB库,把Flash模拟成一个U盘,就可以插上USB数据线,就可以在
电脑上直接打开,然后把要用的文件拷进去就行了,当然,说起来容易,要实现还是够折腾一阵子的。另外,单片机要读取文件的加上文件系统,这里使用的是
FatFs文件系统,这开源的,简单易用,当然也得花点时间内学习。
下面看看电路图:
单片机
USB
Flash
I2S 芯片 功放芯片
电路不多讲,下面看代码,这才是核心部分:
-
//配置单片机I2S模块
-
static void codec_i2s_init(uint32_t sampleRate)
-
{
-
I2S_InitTypeDef I2S_InitStructure;
-
-
RCC_APB1PeriphClockCmd(RCC_APB1Periph_SPI2, ENABLE);
-
-
SPI_I2S_DeInit(SPI2);
-
I2S_InitStructure.I2S_AudioFreq = sampleRate;
-
I2S_InitStructure.I2S_Standard =I2S_Standard_MSB;
-
I2S_InitStructure.I2S_DataFormat =I2S_DataFormat_16b;
-
I2S_InitStructure.I2S_CPOL = I2S_CPOL_Low;
-
I2S_InitStructure.I2S_Mode = I2S_Mode_MasterTx;
-
I2S_InitStructure.I2S_MCLKOutput = I2S_MCLKOutput_Disable;
-
-
I2S_Init(SPI2, &I2S_InitStructure);
-
I2S_Cmd(SPI2, ENABLE);
-
}
-
-
//配置DMA中断
-
static void DMA_NVIC_Config(void)
-
{
-
NVIC_InitTypeDef NVIC_InitStructure;
-
-
/* Configure one bit for preemption priority */
-
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);
-
-
/* 配置P[A|B|C|D|E]0为中断源 */
-
NVIC_InitStructure.NVIC_IRQChannel = DMA1_Channel5_IRQn;
-
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
-
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
-
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
-
NVIC_Init(&NVIC_InitStructure);
-
}
-
-
//配置DMA
-
void DMA_Config(void)
-
{
-
DMA_InitTypeDef DMA_InitStructure;
-
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); //开启DMA时钟
-
DMA_NVIC_Config(); //配置DMA中断
-
/*设置DMA源:内存地址&串口数据寄存器地址*/
-
DMA_InitStructure.DMA_PeripheralBaseAddr = SPI2_DR_Base;
-
/*内存地址(要传输的变量的指针)*/
-
DMA_InitStructure.DMA_MemoryBaseAddr = (u32)sendbuf;
-
/*方向:从内存到外设*/
-
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST;
-
/*传输大小DMA_BufferSize=SENDBUFF_SIZE*/
-
DMA_InitStructure.DMA_BufferSize = SENDBUFF_SIZE;
-
/*外设地址不增*/
-
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
-
/*内存地址自增*/
-
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
-
/*外设数据单位*/
-
DMA_InitStructure.DMA_PeripheralDataSize =DMA_PeripheralDataSize_HalfWord; //DMA_PeripheralDataSize_Byte;
-
/*内存数据单位 8bit*/
-
DMA_InitStructure.DMA_MemoryDataSize =/*DMA_MemoryDataSize_Word;*/ DMA_MemoryDataSize_HalfWord; /*DMA_MemoryDataSize_Byte;*/
-
/*DMA模式:一次传输,循环*/
-
DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;//DMA_Mode_Normal ;
-
/*优先级:中*/
-
DMA_InitStructure.DMA_Priority = DMA_Priority_Medium;
-
/*禁止内存到内存的传输 */
-
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
-
/*配置DMA1的4通道*/
-
DMA_Init(DMA1_Channel5, &DMA_InitStructure);
-
DMA_Cmd (DMA1_Channel5,ENABLE); //使能DMA
-
DMA_ITConfig(DMA1_Channel5,DMA_IT_TC,ENABLE); //配置DMA发送完成后产生中断
-
}
-
-
//配置必要的IO端口
-
void GPIO_Configuration(void)
-
{
-
GPIO_InitTypeDef GPIO_InitStructure;
-
/* 使能SPI2 时钟 */
-
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC |RCC_APB2Periph_GPIOB|RCC_APB2Periph_AFIO,ENABLE);
-
-
-
GPIO_InitStructure.GPIO_Pin =GPIO_Pin_6;
-
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
-
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
-
GPIO_Init(GPIOC, &GPIO_InitStructure);
-
GPIO_SetBits( GPIO_Pin_6);
-
-
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12|GPIO_Pin_13|GPIO_Pin_15;
-
GPIO_InitStructure.GPIO_Mode =GPIO_Mode_AF_PP;
-
GPIO_Init(GPIOB, &GPIO_InitStructure);
-
}
-
//
-
-
u8 Check_Ifo(u8* pbuf1,u8* pbuf2)
-
{
-
u8 i;
-
for(i=0;i<4;i++)if(pbuf1[i]!=pbuf2[i])return 1;//不同
-
return 0;//相同
-
}
-
-
u32 Get_num(u8* pbuf,u8 len)
-
{
-
u32 num;
-
if(len==2)num=(pbuf[1]<<8)|pbuf[0];
-
else if(len==4)num=(pbuf[3]<<24)|(pbuf[2]<<16)|(pbuf[1]<<8)|pbuf[0];
-
return num;
-
}
-
-
//初始化wav头信息数据结构
-
u8 WAV_Init(u8* pbuf)//初始化并显示文件信息
-
{
-
if(Check_Ifo(pbuf,"RIFF"))return 1;//RIFF标志错误
-
wav1.wavlen=Get_num(pbuf+4,4);//文件长度,数据偏移4byte
-
if(Check_Ifo(pbuf+8,"WAVE"))return 2;//WAVE标志错误
-
if(Check_Ifo(pbuf+12,"fmt "))return 3;//fmt标志错误
-
wav1.formart=Get_num(pbuf+20,2);//格式类别
-
wav1.CHnum=Get_num(pbuf+22,2);//通道数
-
//CHanalnum=wav1.CHnum;
-
wav1.SampleRate=Get_num(pbuf+24,4);//采样率
-
wav1.speed=Get_num(pbuf+28,4);//音频传送速率
-
wav1.ajust=Get_num(pbuf+32,2);//数据块调速数
-
wav1.SampleBits=Get_num(pbuf+34,2);//样本数据位数
-
// Bitnum=wav1.SampleBits;
-
if(Check_Ifo(pbuf+36,"data"))return 4;//data标志错误
-
wav1.DATAlen=Get_num(pbuf+40,4);//数据长度
-
return 0;
-
}
-
-
//读取指定的语音文件进行播音
-
extern void delay_ms(u32 nms);
-
void SpeakerVoice(const char *sound)
-
{
-
u32 br;
-
u16 buffer[44]={0};//头信息缓存区
-
-
GPIO_SetBits(GPIOC,GPIO_Pin_6);//使能功放IC
-
if(FR_OK!=f_open(&file,(const TCHAR*)sound,FA_READ)) //打开sound指定的文件
-
return;
-
if(FR_OK!=f_read(&file,buffer,44,&br))//从文件读取头信息到缓存区
-
return;
-
WAV_Init((u8*)buffer);//初始化头信息数据结构wav1
-
-
codec_i2s_init(wav1.SampleRate/2);//用读到的文件的采样率配置I2S模块。
-
-
f_lseek(&file,44);//丁文到文件第44Byte的位置。
-
f_read(&file,(u8*)sendbuf,SENDBUFF_SIZE*2,&br);//读取512个直接到发送缓存区。
-
readlength=1;
-
-
DMA_Config();//配置DMA
-
delay_ms(250);//延时等待功放启动
-
SPI_I2S_DMACmd(SPI2,SPI_I2S_DMAReq_Tx,ENABLE); //启动DMA传输
-
-
}
-
-
//DMA中断函数
-
void DMA1_Channel5_IRQHandler(void)
-
{
-
u32 br;
-
//判断是否为DMA发送完成中断
-
if(DMA_GetFlagStatus(DMA1_FLAG_TC5)==SET)
-
{
-
DMA_ClearFlag(DMA1_FLAG_TC5);
-
-
if((wav1.DATAlen/(SENDBUFF_SIZE*2)) > readlength)
-
{
-
f_lseek(&file,readlength*SENDBUFF_SIZE*2+44);
-
f_read(&file,(u8*)sendbuf,SENDBUFF_SIZE*2,&br);
-
readlength++;
-
}else
-
{
-
f_lseek(&file,readlength*SENDBUFF_SIZE*2+44);
-
f_read(&file,(u8*)sendbuf,wav1.DATAlen % (SENDBUFF_SIZE*2),&br);
-
readlength=0;
-
delay_us(wav1.DATAlen % (SENDBUFF_SIZE*2)*16);
-
DMA_Cmd (DMA1_Channel5,DISABLE); //失能DMA
-
f_close(&file);
-
GPIO_ResetBits(GPIOC,GPIO_Pin_6);
-
}
-
}
-
}
-
-
void speaker_io_ini(void)
-
{
-
GPIO_Configuration();
-
}
以上就是语音实现的核心代码了,现在只需要调用void SpeakerVoice(const char *sound),填写指定路径的wav文件就以了,比如:SpeakerVoice("0:/finger1.wav");
阅读(2809) | 评论(0) | 转发(0) |