725°

基于STM32F429,Cubemx的SAI音频播放实验

书接上文:https://www.cnblogs.com/feiniaoliangtiangao/p/11060674.html 和 https://www.cnblogs.com/feiniaoliangtiangao/p/11023636.html

请阅读完上面的两篇博文作为基础,再阅读本篇博文,如若已了解SD卡,内存管理,Fatfs,请跳过。

1.实验介绍

    读取并解码SDHC卡里的WAV音频文件,然后通过SAI协议传输到WM8978播放

   WAV介绍:   WAV WAVE 文件, WAV 是计算机领域最常用的数字化声音文件格式之一,它是微软

   专门为 Windows 系统定义的波形文件格式(Waveform Audio),由于其扩展名为"*.wav"
   符合 RIFF(Resource Interchange File Format)文件规范,用于保存 Windows 平台的音频信息资源,
   被 Windows 平台及其应用程序所广泛支持,该格式也支持 MSADPCMCCITT A LAW 等多种
   压缩运算法,支持多种音频数字,取样频率和声道,标准格式化的 WAV 文件和 CD 格式一样,
  也是 44.1K 的取样频率, 16 位量化数字,因此在声音文件质量和 CD
相差无几。

  WM8978介绍:WM8978 是欧胜(Wolfson) 推出的一款全功能音频处理器。它带有一个 HI-FI 级数字信号
 处理内核,支持增强 3D 硬件环绕音效,以及 5 频段的硬件均衡器,可以有效改善音质;并有
 一个可编程的陷波滤波器,用以去除屏幕开、切换等噪音。


  SAI介绍:SAI可以说是I2S的强化版,但相差也不大,只是功能多了点,而I2S也只是比I2C多了一条声道线FS_A/B.

 

 

 

2.实验软件

  keil5,Cubemx5.21 

3.Cube配置

 

 

打开SAI功能,然后选择为主机模式,参照下面原子的例程配置参数。

//SAI Block A初始化,I2S,飞利浦标准
//mode:工作模式,可以设置:SAI_MODEMASTER_TX/SAI_MODEMASTER_RX/SAI_MODESLAVE_TX/SAI_MODESLAVE_RX
//cpol:数据在时钟的上升/下降沿选通,可以设置:SAI_CLOCKSTROBING_FALLINGEDGE/SAI_CLOCKSTROBING_RISINGEDGE
//datalen:数据大小,可以设置:SAI_DATASIZE_8/10/16/20/24/32
void SAIA_Init(u32 mode,u32 cpol,u32 datalen)
{
    HAL_SAI_DeInit(&SAI1A_Handler);                          //清除以前的配置
    SAI1A_Handler.Instance=SAI1_Block_A;                     //SAI1 Bock A
    SAI1A_Handler.Init.AudioMode=mode;                       //设置SAI1工作模式
    SAI1A_Handler.Init.Synchro=SAI_ASYNCHRONOUS;             //音频模块异步
    SAI1A_Handler.Init.OutputDrive=SAI_OUTPUTDRIVE_ENABLE;   //立即驱动音频模块输出
    SAI1A_Handler.Init.NoDivider=SAI_MASTERDIVIDER_ENABLE;   //使能主时钟分频器(MCKDIV)
    SAI1A_Handler.Init.FIFOThreshold=SAI_FIFOTHRESHOLD_1QF;  //设置FIFO阈值,1/4 FIFO
    SAI1A_Handler.Init.ClockSource=SAI_CLKSOURCE_PLLI2S;     //SIA时钟源为PLL2S
    SAI1A_Handler.Init.MonoStereoMode=SAI_STEREOMODE;        //立体声模式
    SAI1A_Handler.Init.Protocol=SAI_FREE_PROTOCOL;           //设置SAI1协议为:自由协议(支持I2S/LSB/MSB/TDM/PCM/DSP等协议)
    SAI1A_Handler.Init.DataSize=datalen;                     //设置数据大小
    SAI1A_Handler.Init.FirstBit=SAI_FIRSTBIT_MSB;            //数据MSB位优先
    SAI1A_Handler.Init.ClockStrobing=cpol;                   //数据在时钟的上升/下降沿选通
</span><span style="color: #008000;">//</span><span style="color: #008000;">帧设置</span>
SAI1A_Handler.FrameInit.FrameLength=<span style="color: #800080;">64</span>;                  <span style="color: #008000;">//</span><span style="color: #008000;">设置帧长度为64,左通道32个SCK,右通道32个SCK.</span>
SAI1A_Handler.FrameInit.ActiveFrameLength=<span style="color: #800080;">32</span>;            <span style="color: #008000;">//</span><span style="color: #008000;">设置帧同步有效电平长度,在I2S模式下=1/2帧长.</span>
SAI1A_Handler.FrameInit.FSDefinition=SAI_FS_CHANNEL_IDENTIFICATION;<span style="color: #008000;">//</span><span style="color: #008000;">FS信号为SOF信号+通道识别信号</span>
SAI1A_Handler.FrameInit.FSPolarity=SAI_FS_ACTIVE_LOW;    <span style="color: #008000;">//</span><span style="color: #008000;">FS低电平有效(下降沿)</span>
SAI1A_Handler.FrameInit.FSOffset=SAI_FS_BEFOREFIRSTBIT;  <span style="color: #008000;">//</span><span style="color: #008000;">在slot0的第一位的前一位使能FS,以匹配飞利浦标准    

</span><span style="color: #008000;">//</span><span style="color: #008000;">SLOT设置</span>
SAI1A_Handler.SlotInit.FirstBitOffset=<span style="color: #800080;">0</span>;                 <span style="color: #008000;">//</span><span style="color: #008000;">slot偏移(FBOFF)为0</span>
SAI1A_Handler.SlotInit.SlotSize=SAI_SLOTSIZE_32B;        <span style="color: #008000;">//</span><span style="color: #008000;">slot大小为32位</span>
SAI1A_Handler.SlotInit.SlotNumber=<span style="color: #800080;">2</span>;                     <span style="color: #008000;">//</span><span style="color: #008000;">slot数为2个    </span>
SAI1A_Handler.SlotInit.SlotActive=SAI_SLOTACTIVE_0|SAI_SLOTACTIVE_1;<span style="color: #008000;">//</span><span style="color: #008000;">使能slot0和slot1</span>
HAL_SAI_Init(&SAI1A_Handler); //初始化SAI __HAL_SAI_ENABLE(&SAI1A_Handler); //使能SAI }

 

   由上面的WM8979原理图可知,除了SAI的5条线传输信号,还有2条IIC的线用来控制WM8978

所以要手工配成输出模式,自己添加模拟IIC协议。

处此之外,请按照开头链接的例程配置SDIO卡和Fatfs,这些要用到。

    

 4.程序讲解

      由于该例程代码较为繁复,只能点出重点,细节可能会忽略

1,WM8979.C

     芯片通过 IIC 接口(MODE=0)连接 WM8978,不过 WM8978 IIC 接口比较特殊:
     1,只支持写,不支持读数据; 2,寄存器长度为 7 位,数据长度为 9 位。 3,寄存器字节的最低
     位用于传输数据的最高位(也就是 9 位数据的最高位, 7 位寄存器的最低位)。 WM8978 IIC
     读地址固定为: 0X34。 
  

#include "WM8978.h"
#include "IIC.h"
#include "stdio.h"

//WM8978寄存器值缓存区(总共58个寄存器,0~57),占用116字节内存 //因为WM8978的IIC操作不支持读操作,所以在本地保存所有寄存器值 //写WM8978寄存器时,同步更新到本地寄存器值,读寄存器时,直接返回本地保存的寄存器值. //注意:WM8978的寄存器值是9位的,所以要用u16来存储. static uint16_t WM8978_REGVAL_TBL[58]= { 0X0000,0X0000,0X0000,0X0000,0X0050,0X0000,0X0140,0X0000, 0X0000,0X0000,0X0000,0X00FF,0X00FF,0X0000,0X0100,0X00FF, 0X00FF,0X0000,0X012C,0X002C,0X002C,0X002C,0X002C,0X0000, 0X0032,0X0000,0X0000,0X0000,0X0000,0X0000,0X0000,0X0000, 0X0038,0X000B,0X0032,0X0000,0X0008,0X000C,0X0093,0X00E9, 0X0000,0X0000,0X0000,0X0000,0X0003,0X0010,0X0010,0X0100, 0X0100,0X0002,0X0001,0X0001,0X0039,0X0039,0X0039,0X0039, 0X0001,0X0001 };

//WM8978 写方法 // uint8_t WM8978_Write_Reg(IIC_HandleTypedef * iicHandle, uint8_t Register_Address, uint16_t Data_Byte) { vIIC_Start_Signal(iicHandle); //1. IIC_Start ; 起始信号 vIIC_SendByte(iicHandle, Slave_Address); //2. IIC_Send Device Address(W); 发送设备地址

if(!bIIC_ReadACK(iicHandle)) //3. IIC_ReadAck ; 等待应答 {
vIIC_Stop_Signal(iicHandle);
return FALSE;
}

vIIC_SendByte(iicHandle, (Register_Address<<1)|((Data_Byte>>8)&0X01)); //4. IIC_Send Register Address ; 发送要操作的寄存器地址 bIIC_ReadACK(iicHandle); //5. IIC_ReadAck ; 等待应答 vIIC_SendByte(iicHandle, Data_Byte&0XFF); //7. IIC_Send the data to Reg ; 发送操作数据 bIIC_ReadACK(iicHandle); //8. IIC_ReadAck ; 等待应答 vIIC_Stop_Signal(iicHandle); //9. IIC_Stop ; 结束信号 WM8978_REGVAL_TBL[Register_Address]=Data_Byte; //保存寄存器值到本地 return 0; }

//WM8978初始化 //返回值:0,初始化正常 // 其他,错误代码 uint8_t WM8978_Init(IIC_HandleTypedef * iicHandle) { uint8_t res; res=WM8978_Write_Reg(iicHandle,0,0); //软复位WM8978 if(res)return 1; //发送指令失败,WM8978异常 //以下为通用设置 WM8978_Write_Reg(iicHandle,1,0X1B); //R1,MICEN设置为1(MIC使能),BIASEN设置为1(模拟器工作),VMIDSEL[1:0]设置为:11(5K) WM8978_Write_Reg(iicHandle,2,0X1B0); //R2,ROUT1,LOUT1输出使能(耳机可以工作),BOOSTENR,BOOSTENL使能 WM8978_Write_Reg(iicHandle,3,0X6C); //R3,LOUT2,ROUT2输出使能(喇叭工作),RMIX,LMIX使能 WM8978_Write_Reg(iicHandle,6,0); //R6,MCLK由外部提供 WM8978_Write_Reg(iicHandle,43,1<<4); //R43,INVROUT2反向,驱动喇叭 WM8978_Write_Reg(iicHandle,47,1<<8); //R47设置,PGABOOSTL,左通道MIC获得20倍增益 WM8978_Write_Reg(iicHandle,48,1<<8); //R48设置,PGABOOSTR,右通道MIC获得20倍增益 WM8978_Write_Reg(iicHandle,49,1<<1); //R49,TSDEN,开启过热保护 WM8978_Write_Reg(iicHandle,49,1<<2); //R49,SPEAKER BOOST,1.5x WM8978_Write_Reg(iicHandle,10,1<<3); //R10,SOFTMUTE关闭,128x采样,最佳SNR WM8978_Write_Reg(iicHandle,14,1<<3); //R14,ADC 128x采样率 printf("WM8978 Yes!!!!!!!!!!!\n");

</span><span style="color: #0000ff;">return</span> <span style="color: #800080;">0</span><span style="color: #000000;">;

}

//WM8978读寄存器 //就是读取本地寄存器值缓冲区内的对应值 //reg:寄存器地址 //返回值:寄存器值 uint16_t WM8978_Read_Reg(uint8_t reg) {
return WM8978_REGVAL_TBL[reg];
}

//WM8978 DAC/ADC配置 //adcen:adc使能(1)/关闭(0) //dacen:dac使能(1)/关闭(0) void WM8978_ADDA_Cfg(IIC_HandleTypedef * iicHandle,uint8_t dacen,uint8_t adcen) { uint16_t regval; regval=WM8978_Read_Reg(3); //读取R3 if(dacen) regval|=3<<0; //R3最低2个位设置为1,开启DACR&DACL else regval&=~(3<<0); //R3最低2个位清零,关闭DACR&DACL. WM8978_Write_Reg(iicHandle,3,regval); //设置R3 regval=WM8978_Read_Reg(2); //读取R2 if(adcen)regval|=3<<0; //R2最低2个位设置为1,开启ADCR&ADCL else regval&=~(3<<0); //R2最低2个位清零,关闭ADCR&ADCL. WM8978_Write_Reg(iicHandle,2,regval); //设置R2 }

//WM8978 AUXR,AUXL(PWM音频部分)增益设置(AUXR/L-->ADC输入部分的增益) //gain:0~7,0表示通道禁止,1~7,对应-12dB~6dB,3dB/Step void WM8978_AUX_Gain(IIC_HandleTypedef * iicHandle,uint8_t gain) { uint16_t regval; gain&=0X07; regval=WM8978_Read_Reg(47); //读取R47 regval&=~(7<<0); //清除原来的设置 WM8978_Write_Reg(iicHandle,47,regval|gain<<0);//设置R47 regval=WM8978_Read_Reg(48); //读取R48 regval&=~(7<<0); //清除原来的设置 WM8978_Write_Reg(iicHandle,48,regval|gain<<0);//设置R48 }

//WM8978 L2/R2(也就是Line In)增益设置(L2/R2-->ADC输入部分的增益) //gain:0~7,0表示通道禁止,1~7,对应-12dB~6dB,3dB/Step void WM8978_LINEIN_Gain(IIC_HandleTypedef * iicHandle,uint8_t gain) { uint16_t regval; gain&=0X07; regval=WM8978_Read_Reg(47); //读取R47 regval&=~(7<<4); //清除原来的设置 WM8978_Write_Reg(iicHandle,47,regval|gain<<4);//设置R47 regval=WM8978_Read_Reg(48); //读取R48 regval&=~(7<<4); //清除原来的设置 WM8978_Write_Reg(iicHandle,48,regval|gain<<4);//设置R48 }

void WM8978_MIC_Gain(IIC_HandleTypedef * iicHandle,uint8_t gain) { gain&=0X3F; WM8978_Write_Reg(iicHandle,45,gain); //R45,左通道PGA设置 WM8978_Write_Reg(iicHandle,46,gain|1<<8); //R46,右通道PGA设置 }

//设置I2S工作模式 //fmt:0,LSB(右对齐);1,MSB(左对齐);2,飞利浦标准I2S;3,PCM/DSP; //len:0,16位;1,20位;2,24位;3,32位; void WM8978_I2S_Cfg(IIC_HandleTypedef * iicHandle,uint8_t fmt,uint8_t len) { fmt&=0X03; len&=0X03;//限定范围 WM8978_Write_Reg(iicHandle,4,(fmt<<3)|(len<<5)); //R4,WM8978工作模式设置 }

//WM8978 输入通道配置 //micen:MIC开启(1)/关闭(0) //lineinen:Line In开启(1)/关闭(0) //auxen:aux开启(1)/关闭(0) void WM8978_Input_Cfg(IIC_HandleTypedef * iicHandle,uint8_t micen,uint8_t lineinen,uint8_t auxen) { uint16_t regval;
regval
=WM8978_Read_Reg(2); //读取R2 if(micen)regval|=3<<2; //开启INPPGAENR,INPPGAENL(MIC的PGA放大) else regval&=~(3<<2); //关闭INPPGAENR,INPPGAENL. WM8978_Write_Reg(iicHandle,2,regval); //设置R2
regval
=WM8978_Read_Reg(44); //读取R44 if(micen)regval|=3<<4|3<<0; //开启LIN2INPPGA,LIP2INPGA,RIN2INPPGA,RIP2INPGA. else regval&=~(3<<4|3<<0); //关闭LIN2INPPGA,LIP2INPGA,RIN2INPPGA,RIP2INPGA. WM8978_Write_Reg(iicHandle,44,regval);//设置R44

<span style="color: #0000ff;">if</span><span style="color: #000000;">(lineinen)
    WM8978_LINEIN_Gain(iicHandle,</span><span style="color: #800080;">5</span>);<span style="color: #008000;">//</span><span style="color: #008000;">LINE IN 0dB增益</span>
<span style="color: #0000ff;">else</span><span style="color: #000000;"> 
    WM8978_LINEIN_Gain(iicHandle,</span><span style="color: #800080;">0</span>);    <span style="color: #008000;">//</span><span style="color: #008000;">关闭LINE IN</span>
<span style="color: #0000ff;">if</span><span style="color: #000000;">(auxen)
    WM8978_AUX_Gain(iicHandle,</span><span style="color: #800080;">7</span>);<span style="color: #008000;">//</span><span style="color: #008000;">AUX 6dB增益</span>
<span style="color: #0000ff;">else</span><span style="color: #000000;"> 
    WM8978_AUX_Gain(iicHandle,</span><span style="color: #800080;">0</span>);    <span style="color: #008000;">//</span><span style="color: #008000;">关闭AUX输入  </span>

}

//WM8978 输出配置 //dacen:DAC输出(放音)开启(1)/关闭(0) //bpsen:Bypass输出(录音,包括MIC,LINE IN,AUX等)开启(1)/关闭(0) void WM8978_Output_Cfg(IIC_HandleTypedef * iicHandle,uint8_t dacen,uint8_t bpsen) { uint16_t regval=0; if(dacen) regval|=1<<0; //DAC输出使能 if(bpsen) { regval|=1<<1; //BYPASS使能 regval|=5<<2; //0dB增益 } WM8978_Write_Reg(iicHandle,50,regval);//R50设置 WM8978_Write_Reg(iicHandle,51,regval);//R51设置 }

//设置耳机左右声道音量 //voll:左声道音量(0~63) //volr:右声道音量(0~63) void WM8978_HPvol_Set(IIC_HandleTypedef * iicHandle,uint8_t voll,uint8_t volr) { voll&=0X3F; volr&=0X3F;//限定范围 if(voll==0)voll|=1<<6;//音量为0时,直接mute if(volr==0)volr|=1<<6;//音量为0时,直接mute WM8978_Write_Reg(iicHandle,52,voll); //R52,耳机左声道音量设置 WM8978_Write_Reg(iicHandle,53,volr|(1<<8)); //R53,耳机右声道音量设置,同步更新(HPVU=1) }

//设置喇叭音量 //voll:左声道音量(0~63) void WM8978_SPKvol_Set(IIC_HandleTypedef * iicHandle,uint8_t volx) { volx&=0X3F;//限定范围 if(volx==0)volx|=1<<6;//音量为0时,直接mute WM8978_Write_Reg(iicHandle,54,volx); //R54,喇叭左声道音量设置 WM8978_Write_Reg(iicHandle,55,volx|(1<<8)); //R55,喇叭右声道音量设置,同步更新(SPKVU=1) }

//设置3D环绕声 //depth:0~15(3D强度,0最弱,15最强) void WM8978_3D_Set(IIC_HandleTypedef * iicHandle,uint8_t depth) { depth&=0XF;//限定范围 WM8978_Write_Reg(iicHandle,41,depth); //R41,3D环绕设置 }

//设置EQ/3D作用方向 //dir:0,在ADC起作用 // 1,在DAC起作用(默认) void WM8978_EQ_3D_Dir(IIC_HandleTypedef * iicHandle,uint8_t dir) { uint16_t regval; regval=WM8978_Read_Reg(0X12); if(dir)regval|=1<<8; else regval&=~(1<<8); WM8978_Write_Reg(iicHandle, 18,regval);//R18,EQ1的第9位控制EQ/3D方向 }

//设置EQ1 //cfreq:截止频率,0~3,分别对应:80/105/135/175Hz //gain:增益,0~24,对应-12~+12dB void WM8978_EQ1_Set(IIC_HandleTypedef * iicHandle,uint8_t cfreq,uint8_t gain) { uint16_t regval; cfreq&=0X3;//限定范围 if(gain>24)gain=24; gain=24-gain; regval=WM8978_Read_Reg(18); regval&=0X100; regval|=cfreq<<5; //设置截止频率 regval|=gain; //设置增益 WM8978_Write_Reg(iicHandle,18,regval);//R18,EQ1设置 }

//设置EQ2 //cfreq:截止频率,0~3,分别对应:80/105/135/175Hz //gain:增益,0~24,对应-12~+12dB void WM8978_EQ2_Set(IIC_HandleTypedef * iicHandle,uint8_t cfreq,uint8_t gain) { uint16_t regval; cfreq&=0X3;//限定范围 if(gain>24)gain=24; gain=24-gain; regval|=cfreq<<5; //设置截止频率 regval|=gain; //设置增益 WM8978_Write_Reg(iicHandle,19,regval);//R18,EQ1设置 }

//设置EQ3 //cfreq:截止频率,0~3,分别对应:80/105/135/175Hz //gain:增益,0~24,对应-12~+12dB void WM8978_EQ3_Set(IIC_HandleTypedef * iicHandle,uint8_t cfreq,uint8_t gain) { uint16_t regval; cfreq&=0X3;//限定范围 if(gain>24)gain=24; gain=24-gain; regval|=cfreq<<5; //设置截止频率 regval|=gain; //设置增益 WM8978_Write_Reg(iicHandle,20,regval);//R18,EQ1设置 }

//设置EQ4 //cfreq:截止频率,0~3,分别对应:80/105/135/175Hz //gain:增益,0~24,对应-12~+12dB void WM8978_EQ4_Set(IIC_HandleTypedef * iicHandle,uint8_t cfreq,uint8_t gain) { uint16_t regval; cfreq&=0X3;//限定范围 if(gain>24)gain=24; gain=24-gain; regval|=cfreq<<5; //设置截止频率 regval|=gain; //设置增益 WM8978_Write_Reg(iicHandle,21,regval);//R18,EQ1设置 }

//设置EQ4 //cfreq:截止频率,0~3,分别对应:80/105/135/175Hz //gain:增益,0~24,对应-12~+12dB void WM8978_EQ5_Set(IIC_HandleTypedef * iicHandle,uint8_t cfreq,uint8_t gain) { uint16_t regval; cfreq&=0X3;//限定范围 if(gain>24)gain=24; gain=24-gain; regval|=cfreq<<5; //设置截止频率 regval|=gain; //设置增益 WM8978_Write_Reg(iicHandle,22,regval);//R18,EQ1设置 }

主函数的WM8978初始化内容

WM8978_Init(&hIIC1); //初始化WM8978
WM8978_HPvol_Set(&hIIC1,100,100); //耳机音量设置
WM8978_SPKvol_Set(&hIIC1,40); //喇叭音量设置

 

2.SAI.C

在SAI的函数里除了生成Cube配置出的代码外,还要添加一些东西,用来开启DMA通道传输,节省CPU资源。

//SAI Block A采样率设置
//采样率计算公式:
//MCKDIV!=0: Fs=SAI_CK_x/[512*MCKDIV]
//MCKDIV==0: Fs=SAI_CK_x/256
//SAI_CK_x=(HSE/pllm)*PLLI2SN/PLLI2SQ/(PLLI2SDIVQ+1)
//一般HSE=25Mhz
//pllm:在Stm32_Clock_Init设置的时候确定,一般是25
//PLLI2SN:一般是192~432
//PLLI2SQ:2~15
//PLLI2SDIVQ:0~31
//MCKDIV:0~15
//SAI A分频系数表@pllm=25,HSE=25Mhz,即vco输入频率为1Mhz
const uint16_t SAI_PSC_TBL[][5]=
{
{800 ,344,7,0,12}, //8Khz采样率
{1102,429,2,18,2}, //11.025Khz采样率
{1600,344,7, 0,6}, //16Khz采样率
{2205,429,2,18,1}, //22.05Khz采样率
{3200,344,7, 0,3}, //32Khz采样率
{4410,429,2,18,0}, //44.1Khz采样率
{4800,344,7, 0,2}, //48Khz采样率
{8820,271,2, 2,1}, //88.2Khz采样率
{9600,344,7, 0,1}, //96Khz采样率
{17640,271,2,2,0}, //176.4Khz采样率
{19200,344,7,0,0}, //192Khz采样率
};

//开启SAI的DMA功能,HAL库没有提供此函数
//因此我们需要自己操作寄存器编写一个
void SAIA_DMA_Enable(void)
{
uint32_t tempreg=0;
tempreg=SAI1_Block_A->CR1; //先读出以前的设置
tempreg|=1<<17; //使能DMA
SAI1_Block_A->CR1=tempreg; //写入CR1寄存器中
}

//设置SAIA的采样率(@MCKEN)
//samplerate:采样率,单位:Hz
//返回值:0,设置成功;1,无法设置.
uint8_t SAIA_SampleRate_Set(uint32_t samplerate)
{
uint8_t i=0;

RCC_PeriphCLKInitTypeDef RCCSAI1_Sture;
for(i=0;i<(sizeof(SAI_PSC_TBL)/10);i++)//看看改采样率是否可以支持
{
if((samplerate/10)==SAI_PSC_TBL[i][0])break;
}
if(i==(sizeof(SAI_PSC_TBL)/10))return 1;//搜遍了也找不到
RCCSAI1_Sture.PeriphClockSelection=RCC_PERIPHCLK_SAI_PLLI2S;//外设时钟源选择
RCCSAI1_Sture.PLLI2S.PLLI2SN=(uint32_t)SAI_PSC_TBL[i][1]; //设置PLLI2SN
RCCSAI1_Sture.PLLI2S.PLLI2SQ=(uint32_t)SAI_PSC_TBL[i][2]; //设置PLLI2SQ
//设置PLLI2SDivQ的时候SAI_PSC_TBL[i][3]要加1,因为HAL库中会在把PLLI2SDivQ赋给寄存器DCKCFGR的时候减1
RCCSAI1_Sture.PLLI2SDivQ=SAI_PSC_TBL[i][3]+1; //设置PLLI2SDIVQ
HAL_RCCEx_PeriphCLKConfig(&RCCSAI1_Sture); //设置时钟

__HAL_RCC_SAI_BLOCKACLKSOURCE_CONFIG(RCC_SAIACLKSOURCE_PLLI2S); //设置SAI1时钟来源为PLLI2SQ

__HAL_SAI_DISABLE(&SAI1A_Handler); //关闭SAI
SAI1A_Handler.Init.AudioFrequency=samplerate; //设置播放频率
HAL_SAI_Init(&SAI1A_Handler); //初始化SAI
SAIA_DMA_Enable(); //开启SAI的DMA功能
__HAL_SAI_ENABLE(&SAI1A_Handler); //开启SAI
return 0;
}

//SAIA TX DMA配置
//设置为双缓冲模式,并开启DMA传输完成中断
//buf0:M0AR地址.
//buf1:M1AR地址.
//num:每次传输数据量
//width:位宽(存储器和外设,同时设置),0,8位;1,16位;2,32位;
void SAIA_TX_DMA_Init(uint8_t* buf0,uint8_t *buf1,uint16_t num)
{
uint32_t memwidth=0,perwidth=0; //外设和存储器位宽

__HAL_RCC_DMA2_CLK_ENABLE(); //使能DMA2时钟
__HAL_LINKDMA(&SAI1A_Handler,hdmatx,SAI1_TXDMA_Handler); //将DMA与SAI联系起来
SAI1_TXDMA_Handler.Instance=DMA2_Stream3; //DMA2数据流3
SAI1_TXDMA_Handler.Init.Channel=DMA_CHANNEL_0; //通道0
SAI1_TXDMA_Handler.Init.Direction=DMA_MEMORY_TO_PERIPH; //存储器到外设模式
SAI1_TXDMA_Handler.Init.PeriphInc=DMA_PINC_DISABLE; //外设非增量模式
SAI1_TXDMA_Handler.Init.MemInc=DMA_MINC_ENABLE; //存储器增量模式
SAI1_TXDMA_Handler.Init.PeriphDataAlignment=DMA_MDATAALIGN_HALFWORD; //外设数据长度:16/32位
SAI1_TXDMA_Handler.Init.MemDataAlignment=DMA_PDATAALIGN_HALFWORD; //存储器数据长度:16/32位
SAI1_TXDMA_Handler.Init.Mode=DMA_CIRCULAR; //使用循环模式
SAI1_TXDMA_Handler.Init.Priority=DMA_PRIORITY_HIGH; //高优先级
SAI1_TXDMA_Handler.Init.FIFOMode=DMA_FIFOMODE_DISABLE; //不使用FIFO
// SAI1_TXDMA_Handler.Init.MemBurst=DMA_MBURST_SINGLE; //存储器单次突发传输
// SAI1_TXDMA_Handler.Init.PeriphBurst=DMA_PBURST_SINGLE; //外设突发单次传输
HAL_DMA_DeInit(&SAI1_TXDMA_Handler); //先清除以前的设置
HAL_DMA_Init(&SAI1_TXDMA_Handler); //初始化DMA

HAL_DMAEx_MultiBufferStart(&SAI1_TXDMA_Handler,(uint32_t)buf0,(uint32_t)&SAI1_Block_A->DR,(uint32_t)buf1,num);//开启双缓冲
__HAL_DMA_DISABLE(&SAI1_TXDMA_Handler); //先关闭DMA
HAL_Delay(10); //10us延时,防止-O2优化出问题
__HAL_DMA_ENABLE_IT(&SAI1_TXDMA_Handler,DMA_IT_TC); //开启传输完成中断
__HAL_DMA_CLEAR_FLAG(&SAI1_TXDMA_Handler,DMA_FLAG_TCIF3_7); //清除DMA传输完成中断标志位
HAL_NVIC_SetPriority(DMA2_Stream3_IRQn,0,0); //DMA中断优先级
HAL_NVIC_EnableIRQ(DMA2_Stream3_IRQn);
}

//SAI DMA回调函数指针
void (*sai_tx_callback)(void); //TX回调函数
//DMA2_Stream3中断服务函数
void DMA2_Stream3_IRQHandler(void)
{
if(__HAL_DMA_GET_FLAG(&SAI1_TXDMA_Handler,DMA_FLAG_TCIF3_7)!=RESET) //DMA传输完成
{
__HAL_DMA_CLEAR_FLAG(&SAI1_TXDMA_Handler,DMA_FLAG_TCIF3_7); //清除DMA传输完成中断标志位
sai_tx_callback(); //执行回调函数,读取数据等操作在这里面处理
}
}
//SAI开始播放
void SAI_Play_Start(void)
{
__HAL_DMA_ENABLE(&SAI1_TXDMA_Handler);//开启DMA TX传输

}
//关闭I2S播放
void SAI_Play_Stop(void)
{
__HAL_DMA_DISABLE(&SAI1_TXDMA_Handler); //结束播放

}

 

   3.audioplay.C

       该部分的函数功能是读取SD卡的数据,如果是WAV文件就传输到WAV.C解码,并控制音频播放

 

#define FILE_MAX_TYPE_NUM        7    //最多FILE_MAX_TYPE_NUM个大类
#define FILE_MAX_SUBT_NUM        4    //最多FILE_MAX_SUBT_NUM个小类
//音乐播放控制器
__audiodev audiodev;     

uint8_t *const FILE_TYPE_TBL[FILE_MAX_TYPE_NUM][FILE_MAX_SUBT_NUM]= { {"BIN"}, //BIN文件 {"LRC"}, //LRC文件 {"NES","SMS"}, //NES/SMS文件 {"TXT","C","H"}, //文本文件 {"WAV","MP3","APE","FLAC"},//支持的音乐文件 {"BMP","JPG","JPEG","GIF"},//图片文件 {"AVI"}, //视频文件 };

//将小写字母转为大写字母,如果是数字,则保持不变. uint8_t char_upper(uint8_t c) { if(c<'A')return c;//数字,保持不变. if(c>='a')return c-0x20;//变为大写. else return c;//大写,保持不变 }

//报告文件的类型 //fname:文件名 //返回值:0XFF,表示无法识别的文件类型编号. // 其他,高四位表示所属大类,低四位表示所属小类. uint8_t f_typetell(uint8_t *fname) { uint8_t tbuf[5]; uint8_t *attr='\0';//后缀名 uint8_t i=0,j; while(i<250) { i++; if(*fname=='\0')break;//偏移到了最后了. fname++; } if(i==250)return 0XFF;//错误的字符串. for(i=0;i<5;i++)//得到后缀名 { fname--; if(*fname=='.') { fname++; attr=fname; break; } } strcpy((char )tbuf,(const char)attr);//copy for(i=0;i<4;i++)tbuf[i]=char_upper(tbuf[i]);//全部变为大写 for(i=0;i<FILE_MAX_TYPE_NUM;i++) //大类对比 { for(j=0;j<FILE_MAX_SUBT_NUM;j++)//子类对比 { if(*FILE_TYPE_TBL[i][j]==0)break;//此组已经没有可对比的成员了. if(strcmp((const char *)FILE_TYPE_TBL[i][j],(const char *)tbuf)==0)//找到了 { return (i<<4)|j; } } } return 0XFF;//没找到 }

//开始音频播放 void audio_start(void) { audiodev.status=3<<0;//开始播放+非暂停 SAI_Play_Start(); }

//关闭音频播放 void audio_stop(void) { audiodev.status=0; SAI_Play_Stop(); }

//得到path路径下,目标文件的总个数 //path:路径
//返回值:总有效文件数 uint16_t audio_get_tnum(uint8_t path) {
uint8_t res; uint16_t rval
=0; DIR tdir; //临时目录 FILINFO
tfileinfo; //临时文件信息 tfileinfo=(FILINFO*)malloc_allot(sizeof(FILINFO));//申请内存 res=f_opendir(&tdir,(const TCHAR*)path); //打开目录 if(res==FR_OK&&tfileinfo) { while(1)//查询总的有效文件数 { res=f_readdir(&tdir,tfileinfo); //读取目录下的一个文件 if(res!=FR_OK||tfileinfo->fname[0]==0) { printf("文件读取出错:%d\n",res); break; } //错误了/到末尾了,退出 res=f_typetell((uint8_t*)tfileinfo->fname);
if((res&0XF0)==0X40)//取高四位,看看是不是音乐文件 { rval++;//有效文件数增加1 }
}
}
malloc_Outfree(tfileinfo);
//释放内存 return rval; }

//显示曲目索引 //index:当前索引 //total:总文件数 void audio_index_show(uint16_t index,uint16_t total) { //显示当前曲目的索引,及总曲目数 printf("%d / %d\n",index,total);

}

//显示播放时间,比特率 信息
//totsec;音频文件总时间长度 //cursec:当前播放时间 //bitrate:比特率(位速) void audio_msg_show(uint32_t totsec,uint32_t cursec,uint32_t bitrate) {
static uint16_t playtime=0XFFFF;//播放时间标记 if(playtime!=cursec) //需要更新显示时间 { playtime=cursec; //显示播放时间 printf("播放时间 %f / %d",(float)playtime/60,playtime%60);

    </span><span style="color: #008000;">//</span><span style="color: #008000;">显示总时间   </span>
 printf(<span style="color: #800000;">"</span><span style="color: #800000;">总时间    %d / %d</span><span style="color: #800000;">"</span>,totsec/<span style="color: #800080;">60</span>,totsec%<span style="color: #800080;">60</span><span style="color: #000000;">);                
       
    </span><span style="color: #008000;">//</span><span style="color: #008000;">显示位率    </span>
 printf(<span style="color: #800000;">"</span><span style="color: #800000;">位率%d</span><span style="color: #800000;">"</span>,bitrate/<span style="color: #800080;">1000</span><span style="color: #000000;">);                
   
}          

}

//播放某个音频文件 uint8_t audio_play_song(IIC_HandleTypedef * iicHandle,uint8_t* fname) { uint8_t res;
res
=f_typetell(fname);

</span><span style="color: #0000ff;">switch</span><span style="color: #000000;">(res)
{
    </span><span style="color: #0000ff;">case</span><span style="color: #000000;"> T_WAV:                                                   
        res</span>=<span style="color: #000000;">wav_play_song(iicHandle,fname);
        </span><span style="color: #0000ff;">break</span><span style="color: #000000;">;
    </span><span style="color: #0000ff;">default</span>:<span style="color: #008000;">//</span><span style="color: #008000;">其他文件,自动跳转到下一曲</span>
        printf(<span style="color: #800000;">"</span><span style="color: #800000;">can't play:%s no wav\r\n</span><span style="color: #800000;">"</span><span style="color: #000000;">,fname);
        res</span>=<span style="color: #000000;">KEY0_PRES;
        </span><span style="color: #0000ff;">break</span><span style="color: #000000;">;
}
</span><span style="color: #0000ff;">return</span><span style="color: #000000;"> res;

}

//播放音乐 void audio_play(IIC_HandleTypedef * iicHandle) { uint8_t res; DIR wavdir; //目录 FILINFO *wavfileinfo;//文件信息 uint8_t *pname; //带路径的文件名 uint16_t totwavnum; //音乐文件总数 uint16_t curindex; //当前索引 uint8_t key; //键值 uint32_t temp; uint32_t *wavoffsettbl; //音乐offset索引表
WM8978_ADDA_Cfg(iicHandle,
1,0); //开启DAC WM8978_Input_Cfg(iicHandle,0,0,0);//关闭输入通道 WM8978_Output_Cfg(iicHandle,1,0); //开启DAC输出 while(f_opendir(&wavdir,"0:/MUSIC"))//打开音乐文件夹 {

}                                       
totwavnum</span>=audio_get_tnum(<span style="color: #800000;">"</span><span style="color: #800000;">0:/MUSIC</span><span style="color: #800000;">"</span>); <span style="color: #008000;">//</span><span style="color: #008000;">得到总有效文件数</span>

while(totwavnum==NULL)//音乐文件总数为0 {

}                                           
wavfileinfo</span>=(FILINFO*)malloc_allot(<span style="color: #0000ff;">sizeof</span>(FILINFO));    <span style="color: #008000;">//</span><span style="color: #008000;">申请内存</span>

pname=malloc_allot(_MAX_LFN*2+1); //为带路径的文件名分配内存 wavoffsettbl=malloc_allot(4totwavnum); //申请4totwavnum个字节的内存,用于存放音乐文件off block索引 while(!wavfileinfo||!pname||!wavoffsettbl)//内存分配出错 {

}       
 </span><span style="color: #008000;">//</span><span style="color: #008000;">记录索引</span>

res=f_opendir(&wavdir,"0:/MUSIC"); //打开目录 if(res==FR_OK) { curindex=0; //当前索引为0 while(1) //全部查询一遍 {
temp
=wavdir.dptr; //记录当前index res=f_readdir(&wavdir,wavfileinfo); //读取目录下的一个文件 if(res!=FR_OK||wavfileinfo->fname[0]==0) break; //错误了/到末尾了,退出 res=f_typetell((uint8_t*)wavfileinfo->fname);
if((res&0XF0)==0X40)//取高四位,看看是不是音乐文件 { wavoffsettbl[curindex]=temp;//记录索引 curindex++; }
} }
curindex
=0; //从0开始显示 res=f_opendir(&wavdir,(const TCHAR*)"0:/MUSIC"); //打开目录 printf("打开错误:%d\n",res); while(res==FR_OK)//打开成功 {
// dir_sdi(&wavdir,wavoffsettbl[curindex]); //改变当前目录索引 res=f_readdir(&wavdir,wavfileinfo); //读取目录下的一个文件 if(res!=FR_OK||wavfileinfo->fname[0]==0) { printf("文件读取出错:%d\n",res); break; } //错误了/到末尾了,退出
strcpy((
char)pname,"0:/MUSIC/"); //复制路径(目录) strcat((char)pname,(const char*)wavfileinfo->fname); //将文件名接在后面
audio_index_show(curindex
+1,totwavnum); key=audio_play_song(iicHandle,pname); //播放这个音频文件

    <span style="color: #0000ff;">if</span>(key==KEY2_PRES)        <span style="color: #008000;">//</span><span style="color: #008000;">上一曲</span>

{ printf("上一曲"); if(curindex) curindex--; else curindex=totwavnum-1; } else if(key==KEY0_PRES)//下一曲 { printf("下一曲"); curindex++;
if(curindex>=totwavnum) curindex=0;//到末尾的时候,自动从头开始 } else { printf("有错误产生!!!!!!!!!!!!!!!!!!!\n"); break; //产生了错误 }
}
malloc_Outfree(wavfileinfo);
//释放内存 malloc_Outfree(pname); //释放内存 malloc_Outfree(wavoffsettbl); //释放内存 }

 

 audioplay.h

//----------------------------------------------
//
//                                    Structure
//
//----------------------------------------------

typedef __packed struct
{  
    //2个SAI解码的BUF
    uint8_t *saibuf1;
    uint8_t *saibuf2; 
    uint8_t *tbuf;                //零时数组,仅在24bit解码的时候需要用到
    FIL *file;            //音频文件指针
    
    uint8_t status;                //bit0:0,暂停播放;1,继续播放
                            //bit1:0,结束播放;1,开启播放 
}__audiodev; 
extern __audiodev audiodev;    //音乐播放控制器
//----------------------------------------------
//
//                                    define
//
//----------------------------------------------

#define T_WAV 0X40 //WAV文件

 

4.WAV.c

该部分的函数用于解码WAV文件,然后通过SAI协议传输到WM8978播放。

__wavctrl wavctrl;        //WAV控制结构体
vu8 wavtransferend=0;    //sai传输完成标志
vu8 wavwitchbuf=0;        //saibufx指示标志

//WAV解析初始化 //fname:文件路径+文件名 //wavx:wav 信息存放结构体指针 //返回值:0,成功;1,打开文件失败;2,非WAV文件;3,DATA区域未找到. u8 wav_decode_init(u8* fname,__wavctrl* wavx) { FILftemp; uint8_t buf; uint32_t br=0; uint8_t res=0;

ChunkRIFF </span>*<span style="color: #000000;">riff;
ChunkFMT </span>*<span style="color: #000000;">fmt;
ChunkFACT </span>*<span style="color: #000000;">fact;
ChunkDATA </span>*<span style="color: #000000;">data;
ftemp</span>=(FIL*)mymalloc(<span style="color: #0000ff;">sizeof</span><span style="color: #000000;">(FIL));
buf</span>=mymalloc(<span style="color: #800080;">512</span><span style="color: #000000;">);
</span><span style="color: #0000ff;">if</span>(ftemp&amp;&amp;buf)    <span style="color: #008000;">//</span><span style="color: #008000;">内存申请成功</span>

{ printf("内存申请成功 \n"); res=f_open(ftemp,(TCHAR*)fname,FA_READ);//打开文件 if(res==FR_OK) { printf("打开文件成功\n"); f_read(ftemp,buf,512,&br); //读取512字节在数据 riff=(ChunkRIFF *)buf; //获取RIFF块 if(riff->Format==0X45564157)//是WAV文件 { fmt=(ChunkFMT *)(buf+12); //获取FMT块 fact=(ChunkFACT *)(buf+12+8+fmt->ChunkSize); //读取FACT块 if(fact->ChunkID==0X74636166||fact->ChunkID==0X5453494C) wavx->datastart=12+8+fmt->ChunkSize+8+fact->ChunkSize;//具有fact/LIST块的时候(未测试) else wavx->datastart=12+8+fmt->ChunkSize;
data
=(ChunkDATA )(buf+wavx->datastart); //读取DATA块 if(data->ChunkID==0X61746164)//解析成功! { wavx->audioformat=fmt->AudioFormat; //音频格式 wavx->nchannels=fmt->NumOfChannels; //通道数 wavx->samplerate=fmt->SampleRate; //采样率 wavx->bitrate=fmt->ByteRate8; //得到位速 wavx->blockalign=fmt->BlockAlign; //块对齐 wavx->bps=fmt->BitsPerSample; //位数,16/24/32位
wavx
->datasize=data->ChunkSize; //数据块大小 wavx->datastart=wavx->datastart+8; //数据流开始的地方.
printf(
"wavx->audioformat:%d\r\n",wavx->audioformat); printf("wavx->nchannels:%d\r\n",wavx->nchannels); printf("wavx->samplerate:%d\r\n",wavx->samplerate); printf("wavx->bitrate:%d\r\n",wavx->bitrate); printf("wavx->blockalign:%d\r\n",wavx->blockalign); printf("wavx->bps:%d\r\n",wavx->bps); printf("wavx->datasize:%d\r\n",wavx->datasize); printf("wavx->datastart:%d\r\n",wavx->datastart);
}
else { printf("data区域未找到\n"); res=3;//data区域未找到. } } else { printf("非wav文件\n"); res=2;//非wav文件 }

    }
    </span><span style="color: #0000ff;">else</span><span style="color: #000000;"> 
    {
            printf(</span><span style="color: #800000;">"</span><span style="color: #800000;">打开文件错误\n</span><span style="color: #800000;">"</span><span style="color: #000000;">);
          res</span>=<span style="color: #800080;">1</span><span style="color: #000000;">;
    }
    
}
f_close(ftemp);
myfree(ftemp);</span><span style="color: #008000;">//</span><span style="color: #008000;">释放内存</span>

myfree(buf); return 0; }

//填充buf //buf:数据区 //size:填充数据量 //bits:位数(16/24) //返回值:读到的数据个数 u32 wav_buffill(u8 buf,u16 size,u8 bits) { u16 readlen=0; u32 bread; u16 i; u32 p,pbuf; if(bits==24)//24bit音频,需要处理一下 { readlen=(size/4)3; //此次要读取的字节数 f_read(audiodev.file,audiodev.tbuf,readlen,(UINT*)&bread);//读取数据 pbuf=(u32*)buf; for(i=0;i<size/4;i++) {
p
=(u32*)(audiodev.tbuf+i*3); pbuf[i]=p[0];
} bread
=(bread*4)/3; //填充后的大小. } else { f_read(audiodev.file,buf,size,(UINT)&bread);//16bit音频,直接读取数据 if(bread<size)//不够数据了,补充0 { for(i=bread;i<size-bread;i++)buf[i]=0; } } return bread; }
//WAV播放时,SAI DMA传输回调函数 void wav_sai_dma_tx_callback(void) {
u16 i;
if(DMA2_Stream3->CR&(1<<19)) { wavwitchbuf=0; if((audiodev.status&0X01)==0) { for(i=0;i<WAV_SAI_TX_DMA_BUFSIZE;i++)//暂停 { audiodev.saibuf1[i]=0;//填充0 } } }else { wavwitchbuf=1; if((audiodev.status&0X01)==0) { for(i=0;i<WAV_SAI_TX_DMA_BUFSIZE;i++)//暂停 { audiodev.saibuf2[i]=0;//填充0 } } } wavtransferend=1; } //得到当前播放时间 //fx:文件指针 //wavx:wav播放控制器 void wav_get_curtime(FIL
fx,__wavctrl wavx) { long long fpos;
wavx
->totsec=wavx->datasize/(wavx->bitrate/8); //歌曲总长度(单位:秒) fpos=fx->fptr-wavx->datastart; //得到当前文件播放到的地方 wavx->cursec=fpos
wavx->totsec/wavx->datasize; //当前播放到第多少秒了? } //播放某个WAV文件 //fname:wav文件路径. //返回值: //KEY0_PRES:下一曲 //KEY1_PRES:上一曲 //其他:错误 u8 wav_play_song(IIC_HandleTypedef * iicHandle,u8* fname) { uint8_t key; uint8_t t=0; uint8_t res;
uint32_t fillnum; audiodev.file
=(FIL*)mymalloc(sizeof(FIL)); audiodev.saibuf1=mymalloc(WAV_SAI_TX_DMA_BUFSIZE); audiodev.saibuf2=mymalloc(WAV_SAI_TX_DMA_BUFSIZE); audiodev.tbuf=mymalloc(WAV_SAI_TX_DMA_BUFSIZE); if(audiodev.file&&audiodev.saibuf1&&audiodev.saibuf2&&audiodev.tbuf) {

    res</span>=wav_decode_init(fname,&amp;wavctrl);<span style="color: #008000;">//</span><span style="color: #008000;">得到文件的信息</span>
    printf(<span style="color: #800000;">"</span><span style="color: #800000;">文件的信息:%d</span><span style="color: #800000;">"</span><span style="color: #000000;">,res);
    </span><span style="color: #0000ff;">if</span>(res==<span style="color: #800080;">0</span>)<span style="color: #008000;">//</span><span style="color: #008000;">解析文件成功</span>

{

            WM8978_I2S_Cfg(iicHandle,</span><span style="color: #800080;">2</span>,<span style="color: #800080;">0</span>);    <span style="color: #008000;">//</span><span style="color: #008000;">飞利浦标准,16位数据长度</span>

SAIA_Init(SAI_MODEMASTER_TX,SAI_CLOCKSTROBING_RISINGEDGE,SAI_DATASIZE_16); SAIA_SampleRate_Set(wavctrl.samplerate);//设置采样率 SAIA_TX_DMA_Init(audiodev.saibuf1,audiodev.saibuf2,WAV_SAI_TX_DMA_BUFSIZE/2); //配置TX DMA,16位

// else if(wavctrl.bps==24) // { // WM8978_I2S_Cfg(iicHandle,2,2); //飞利浦标准,24位数据长度 // SAIA_Init(SAI_MODEMASTER_TX,SAI_CLOCKSTROBING_RISINGEDGE,SAI_DATASIZE_24); // SAIA_SampleRate_Set(wavctrl.samplerate);//设置采样率 // SAIA_TX_DMA_Init(audiodev.saibuf1,audiodev.saibuf2,WAV_SAI_TX_DMA_BUFSIZE/4); //配置TX DMA,32位
// }
sai_tx_callback
= wav_sai_dma_tx_callback; //回调函数指wav_sai_dma_callback audio_stop();
res
=f_open(audiodev.file,(TCHAR*)fname,FA_READ); //打开文件 if(res==0) { f_lseek(audiodev.file, wavctrl.datastart); //跳过文件头 fillnum=wav_buffill(audiodev.saibuf1,WAV_SAI_TX_DMA_BUFSIZE,wavctrl.bps); fillnum=wav_buffill(audiodev.saibuf2,WAV_SAI_TX_DMA_BUFSIZE,wavctrl.bps); audio_start();
while(res==0) { while(wavtransferend==0);//等待wav传输完成; wavtransferend=0; if(fillnum!=WAV_SAI_TX_DMA_BUFSIZE)//播放结束? { res=KEY0_PRES; break; } if(wavwitchbuf) fillnum=wav_buffill(audiodev.saibuf2,WAV_SAI_TX_DMA_BUFSIZE,wavctrl.bps);//填充buf2 else fillnum=wav_buffill(audiodev.saibuf1,WAV_SAI_TX_DMA_BUFSIZE,wavctrl.bps);//填充buf1 while(1) { key=KEY_Scan(0); if(key==WKUP_PRES)//暂停 { if(audiodev.status&0X01) audiodev.status&=~(1<<0); else audiodev.status|=0X01;
}
if(key==KEY2_PRES||key==KEY0_PRES)//下一曲/上一曲 { res=key; break; } wav_get_curtime(audiodev.file,&wavctrl);//得到总时间和当前播放的时间 //audio_msg_show(wavctrl.totsec,wavctrl.cursec,wavctrl.bitrate); t++;

                    </span><span style="color: #0000ff;">if</span>((audiodev.status&amp;<span style="color: #800080;">0X01</span>)==<span style="color: #800080;">0</span><span style="color: #000000;">)
                    HAL_Delay(</span><span style="color: #800080;">4</span><span style="color: #000000;">);
                    </span><span style="color: #0000ff;">else</span> <span style="color: #0000ff;">break</span><span style="color: #000000;">;
                }
            }
            audio_stop(); 
        }</span><span style="color: #0000ff;">else</span> res=<span style="color: #800080;">0XFF</span><span style="color: #000000;">; 
    }</span><span style="color: #0000ff;">else</span> res=<span style="color: #800080;">0XFF</span><span style="color: #000000;">;
}</span><span style="color: #0000ff;">else</span> res=<span style="color: #800080;">0XFF</span><span style="color: #000000;">; 
myfree(audiodev.tbuf);    </span><span style="color: #008000;">//</span><span style="color: #008000;">释放内存</span>
myfree(audiodev.saibuf1);<span style="color: #008000;">//</span><span style="color: #008000;">释放内存</span>
myfree(audiodev.saibuf2);<span style="color: #008000;">//</span><span style="color: #008000;">释放内存 </span>
myfree(audiodev.file);    <span style="color: #008000;">//</span><span style="color: #008000;">释放内存 </span>
<span style="color: #0000ff;">return</span><span style="color: #000000;"> res;

}

 

 

下面有请原子哥给我们讲解WAV文件的组成

  

 

4.测试

按下面添加好初始化函数后,就可以放歌了

 /* USER CODE BEGIN 2 */
    HAL_SD_Init(&hsd);
    HAL_SD_InitCard(&hsd);
vIIC_Handle_Init(</span>&amp;<span style="color: #000000;">hIIC1,IIC_SCL_GPIO_Port,IIC_SCL_Pin,IIC_SDA_GPIO_Port,IIC_SDA_Pin);

WM8978_Init(&hIIC1); WM8978_SPKvol_Set(&hIIC1,40); WM8978_HPvol_Set(&hIIC1,40,40); //耳机音量设置

SDFatFS</span>=(FATFS*)malloc_allot(<span style="color: #0000ff;">sizeof</span>(FATFS));    <span style="color: #008000;">//</span><span style="color: #008000;">为SD卡的文件参数申请内存空间    </span>
retSD=f_mount(SDFatFS, <span style="color: #800000;">"</span><span style="color: #800000;">0:/</span><span style="color: #800000;">"</span>,<span style="color: #800080;">0</span>);                <span style="color: #008000;">//</span><span style="color: #008000;">挂载文件卷0                                                                         </span>
KEY_Init();                                     <span style="color: #008000;">//</span><span style="color: #008000;">初始化按键</span>

/ USER CODE END 2 /

/ Infinite loop / / USER CODE BEGIN WHILE / while (1) { / USER CODE END WHILE /

<span style="color: #008000;">/*</span><span style="color: #008000;"> USER CODE BEGIN 3 </span><span style="color: #008000;">*/</span><span style="color: #000000;">

audio_play(</span>&amp;<span style="color: #000000;">hIIC1);    

}

 

 

   

 

 

 

 

原文链接:https://www.cnblogs.com/feiniaoliangtiangao/p/11107231.html

全部评论: 0

    我有话说: