Chinaunix首页 | 论坛 | 博客
  • 博客访问: 8451
  • 博文数量: 5
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 10
  • 用 户 组: 普通用户
  • 注册时间: 2014-10-08 15:28
文章分类
文章存档

2017年(1)

2015年(2)

2014年(2)

我的朋友

分类: 嵌入式

2017-11-04 11:32:29

用stm32f103单片机实现语音功能
 在 很多设备中需要加入语音播报功能,比如常见的家用电器、汽车、专用的手持设备等等。实现语音功能的方法比较多,比如用mp3解码芯片对mp3音频数据进行 解码,或用单片机直接对mp3文件解码,或直接是用语音芯片。这些方法各有各的优缺点,mp3解码芯片价格贵,能对mp3软解码的单片机性能要求高,用语 音IC需要定制,有不够灵活。基于这样的原因,我使用了一种既灵活成本也不高的方法,那就是用stm32f103rcbt6单片机+I2S芯片+功放芯片 实现语音功能。音频文件用.wav格式的,这是一种未经压缩的的音频文件,wav格式文件的结构是前44 Byte放的是文件的基本信息,第44Btye后的数据就是音频数据;头信息数据结构如下所示:

点击(此处)折叠或打开

  1. typedef struct
  2. {
  3.     u8 chRIFF[4];//固定为RIFF
  4.     u32 wavlen; //文件长度
  5.     u8 chWAV[4]; //固定为WAVE
  6.     u8 chFMT[4]; //固定为fmt
  7.     u16 formart; //格式类别
  8.     u16 CHnum; //通道数,为1代表是单声道
  9.     u32 SampleRate;//采样率
  10.     u32 speed; //音频传送速率
  11.     u16 ajust; //数据块调速数
  12.     u16 SampleBits;//样本数据位数
  13.     u8 chDATA[4]; //固定为data
  14.     u32 DATAlen; //数据长度,数据后面跟的是音频数据
  15. }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   芯片                                                                           功放芯片

电路不多讲,下面看代码,这才是核心部分:


点击(此处)折叠或打开

  1. //配置单片机I2S模块
  2. static void codec_i2s_init(uint32_t sampleRate)
  3. {
  4.     I2S_InitTypeDef I2S_InitStructure;

  5.     RCC_APB1PeriphClockCmd(RCC_APB1Periph_SPI2, ENABLE);

  6.     SPI_I2S_DeInit(SPI2);
  7.     I2S_InitStructure.I2S_AudioFreq = sampleRate;
  8.     I2S_InitStructure.I2S_Standard =I2S_Standard_MSB;
  9.     I2S_InitStructure.I2S_DataFormat =I2S_DataFormat_16b;
  10.     I2S_InitStructure.I2S_CPOL = I2S_CPOL_Low;
  11.     I2S_InitStructure.I2S_Mode = I2S_Mode_MasterTx;
  12.     I2S_InitStructure.I2S_MCLKOutput = I2S_MCLKOutput_Disable;
  13.    
  14.     I2S_Init(SPI2, &I2S_InitStructure);
  15.     I2S_Cmd(SPI2, ENABLE);
  16. }

  17. //配置DMA中断
  18. static void DMA_NVIC_Config(void)
  19. {
  20.   NVIC_InitTypeDef NVIC_InitStructure;
  21.  
  22.   /* Configure one bit for preemption priority */
  23.   NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);
  24.  
  25.   /* 配置P[A|B|C|D|E]0为中断源 */
  26.   NVIC_InitStructure.NVIC_IRQChannel = DMA1_Channel5_IRQn;
  27.   NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
  28.   NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
  29.   NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
  30.   NVIC_Init(&NVIC_InitStructure);
  31. }

  32. //配置DMA
  33. void DMA_Config(void)
  34. {
  35.     DMA_InitTypeDef DMA_InitStructure;
  36.     RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); //开启DMA时钟
  37.     DMA_NVIC_Config(); //配置DMA中断
  38.      /*设置DMA源:内存地址&串口数据寄存器地址*/
  39.     DMA_InitStructure.DMA_PeripheralBaseAddr = SPI2_DR_Base;
  40.     /*内存地址(要传输的变量的指针)*/
  41.     DMA_InitStructure.DMA_MemoryBaseAddr = (u32)sendbuf;
  42.     /*方向:从内存到外设*/
  43.     DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST;
  44.     /*传输大小DMA_BufferSize=SENDBUFF_SIZE*/
  45.     DMA_InitStructure.DMA_BufferSize = SENDBUFF_SIZE;
  46.     /*外设地址不增*/
  47.     DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
  48.     /*内存地址自增*/
  49.     DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
  50.     /*外设数据单位*/
  51.     DMA_InitStructure.DMA_PeripheralDataSize =DMA_PeripheralDataSize_HalfWord; //DMA_PeripheralDataSize_Byte;
  52.     /*内存数据单位 8bit*/
  53.     DMA_InitStructure.DMA_MemoryDataSize =/*DMA_MemoryDataSize_Word;*/ DMA_MemoryDataSize_HalfWord; /*DMA_MemoryDataSize_Byte;*/
  54.     /*DMA模式:一次传输,循环*/
  55.     DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;//DMA_Mode_Normal ;
  56.     /*优先级:中*/
  57.     DMA_InitStructure.DMA_Priority = DMA_Priority_Medium;
  58.     /*禁止内存到内存的传输 */
  59.     DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
  60.     /*配置DMA1的4通道*/
  61.     DMA_Init(DMA1_Channel5, &DMA_InitStructure);
  62.     DMA_Cmd (DMA1_Channel5,ENABLE); //使能DMA
  63.     DMA_ITConfig(DMA1_Channel5,DMA_IT_TC,ENABLE); //配置DMA发送完成后产生中断
  64. }

  65. //配置必要的IO端口
  66. void GPIO_Configuration(void)
  67. {
  68.     GPIO_InitTypeDef GPIO_InitStructure;
  69.   /* 使能SPI2 时钟 */
  70.     RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC |RCC_APB2Periph_GPIOB|RCC_APB2Periph_AFIO,ENABLE);


  71.     GPIO_InitStructure.GPIO_Pin =GPIO_Pin_6;
  72.     GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  73.     GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
  74.     GPIO_Init(GPIOC, &GPIO_InitStructure);
  75.     GPIO_SetBits( GPIO_Pin_6);

  76.     GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12|GPIO_Pin_13|GPIO_Pin_15;
  77.     GPIO_InitStructure.GPIO_Mode =GPIO_Mode_AF_PP;
  78.     GPIO_Init(GPIOB, &GPIO_InitStructure);
  79. }
  80. //

  81. u8 Check_Ifo(u8* pbuf1,u8* pbuf2)
  82. {
  83.     u8 i;
  84.     for(i=0;i<4;i++)if(pbuf1[i]!=pbuf2[i])return 1;//不同
  85.     return 0;//相同
  86. }

  87. u32 Get_num(u8* pbuf,u8 len)
  88. {
  89.     u32 num;
  90.     if(len==2)num=(pbuf[1]<<8)|pbuf[0];
  91.     else if(len==4)num=(pbuf[3]<<24)|(pbuf[2]<<16)|(pbuf[1]<<8)|pbuf[0];
  92.     return num;
  93. }

  94. //初始化wav头信息数据结构
  95. u8 WAV_Init(u8* pbuf)//初始化并显示文件信息
  96. {
  97.     if(Check_Ifo(pbuf,"RIFF"))return 1;//RIFF标志错误
  98.     wav1.wavlen=Get_num(pbuf+4,4);//文件长度,数据偏移4byte
  99.     if(Check_Ifo(pbuf+8,"WAVE"))return 2;//WAVE标志错误
  100.     if(Check_Ifo(pbuf+12,"fmt "))return 3;//fmt标志错误
  101.     wav1.formart=Get_num(pbuf+20,2);//格式类别
  102.     wav1.CHnum=Get_num(pbuf+22,2);//通道数
  103.     //CHanalnum=wav1.CHnum;
  104.     wav1.SampleRate=Get_num(pbuf+24,4);//采样率
  105.     wav1.speed=Get_num(pbuf+28,4);//音频传送速率
  106.     wav1.ajust=Get_num(pbuf+32,2);//数据块调速数
  107.     wav1.SampleBits=Get_num(pbuf+34,2);//样本数据位数
  108. // Bitnum=wav1.SampleBits;
  109.     if(Check_Ifo(pbuf+36,"data"))return 4;//data标志错误
  110.     wav1.DATAlen=Get_num(pbuf+40,4);//数据长度
  111.     return 0;
  112. }

  113. //读取指定的语音文件进行播音
  114. extern void delay_ms(u32 nms);
  115. void SpeakerVoice(const char *sound)
  116. {
  117.     u32 br;
  118.     u16 buffer[44]={0};//头信息缓存区

  119.     GPIO_SetBits(GPIOC,GPIO_Pin_6);//使能功放IC
  120.     if(FR_OK!=f_open(&file,(const TCHAR*)sound,FA_READ)) //打开sound指定的文件
  121.         return;
  122.     if(FR_OK!=f_read(&file,buffer,44,&br))//从文件读取头信息到缓存区
  123.         return;
  124.     WAV_Init((u8*)buffer);//初始化头信息数据结构wav1
  125.         
  126.     codec_i2s_init(wav1.SampleRate/2);//用读到的文件的采样率配置I2S模块。
  127.   
  128.     f_lseek(&file,44);//丁文到文件第44Byte的位置。
  129.     f_read(&file,(u8*)sendbuf,SENDBUFF_SIZE*2,&br);//读取512个直接到发送缓存区。
  130.     readlength=1;

  131.     DMA_Config();//配置DMA
  132.     delay_ms(250);//延时等待功放启动
  133.     SPI_I2S_DMACmd(SPI2,SPI_I2S_DMAReq_Tx,ENABLE); //启动DMA传输
  134.   
  135. }

  136. //DMA中断函数
  137. void DMA1_Channel5_IRQHandler(void)
  138. {
  139.     u32 br;
  140.     //判断是否为DMA发送完成中断
  141.     if(DMA_GetFlagStatus(DMA1_FLAG_TC5)==SET)
  142.     {
  143.         DMA_ClearFlag(DMA1_FLAG_TC5);
  144.        
  145.         if((wav1.DATAlen/(SENDBUFF_SIZE*2)) > readlength)
  146.         {
  147.             f_lseek(&file,readlength*SENDBUFF_SIZE*2+44);
  148.             f_read(&file,(u8*)sendbuf,SENDBUFF_SIZE*2,&br);
  149.             readlength++;
  150.         }else
  151.         {
  152.             f_lseek(&file,readlength*SENDBUFF_SIZE*2+44);
  153.             f_read(&file,(u8*)sendbuf,wav1.DATAlen % (SENDBUFF_SIZE*2),&br);
  154.             readlength=0;
  155.             delay_us(wav1.DATAlen % (SENDBUFF_SIZE*2)*16);
  156.             DMA_Cmd (DMA1_Channel5,DISABLE); //失能DMA
  157.             f_close(&file);
  158.             GPIO_ResetBits(GPIOC,GPIO_Pin_6);
  159.         }
  160.     }
  161. }

  162. void speaker_io_ini(void)
  163. {
  164.     GPIO_Configuration();
  165. }



以上就是语音实现的核心代码了,现在只需要调用void SpeakerVoice(const char *sound),填写指定路径的wav文件就以了,比如:SpeakerVoice("0:/finger1.wav");





阅读(2809) | 评论(0) | 转发(0) |
0

上一篇:makefile中的patsubst

下一篇:没有了

给主人留下些什么吧!~~