zoukankan      html  css  js  c++  java
  • 基于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;                   //数据在时钟的上升/下降沿选通
        
        //帧设置
        SAI1A_Handler.FrameInit.FrameLength=64;                  //设置帧长度为64,左通道32个SCK,右通道32个SCK.
        SAI1A_Handler.FrameInit.ActiveFrameLength=32;            //设置帧同步有效电平长度,在I2S模式下=1/2帧长.
        SAI1A_Handler.FrameInit.FSDefinition=SAI_FS_CHANNEL_IDENTIFICATION;//FS信号为SOF信号+通道识别信号
        SAI1A_Handler.FrameInit.FSPolarity=SAI_FS_ACTIVE_LOW;    //FS低电平有效(下降沿)
        SAI1A_Handler.FrameInit.FSOffset=SAI_FS_BEFOREFIRSTBIT;  //在slot0的第一位的前一位使能FS,以匹配飞利浦标准    
    
        //SLOT设置
        SAI1A_Handler.SlotInit.FirstBitOffset=0;                 //slot偏移(FBOFF)为0
        SAI1A_Handler.SlotInit.SlotSize=SAI_SLOTSIZE_32B;        //slot大小为32位
        SAI1A_Handler.SlotInit.SlotNumber=2;                     //slot数为2个    
        SAI1A_Handler.SlotInit.SlotActive=SAI_SLOTACTIVE_0|SAI_SLOTACTIVE_1;//使能slot0和slot1
        
        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!!!!!!!!!!!
    ");
        
        return 0;
        
    
    } 
    
    //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
        
        if(lineinen)
            WM8978_LINEIN_Gain(iicHandle,5);//LINE IN 0dB增益
        else 
            WM8978_LINEIN_Gain(iicHandle,0);    //关闭LINE IN
        if(auxen)
            WM8978_AUX_Gain(iicHandle,7);//AUX 6dB增益
        else 
            WM8978_AUX_Gain(iicHandle,0);    //关闭AUX输入  
    }
    
    
    //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:每次传输数据量
    //位宽(存储器和外设,同时设置),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='';//后缀名
        uint8_t i=0,j;
        while(i<250)
        {
            i++;
            if(*fname=='')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
    ",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
    ",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);        
                 
            //显示总时间   
         printf("总时间    %d / %d",totsec/60,totsec%60);                
               
            //显示位率    
         printf("位率%d",bitrate/1000);                
           
        }          
    }
    
    //播放某个音频文件
    uint8_t audio_play_song(IIC_HandleTypedef * iicHandle,uint8_t* fname)
    {
        uint8_t res;  
        res=f_typetell(fname); 
    
        switch(res)
        {
            case T_WAV:                                                   
                res=wav_play_song(iicHandle,fname);
                break;
            default://其他文件,自动跳转到下一曲
                printf("can't play:%s no wav
    ",fname);
                res=KEY0_PRES;
                break;
        }
        return 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=audio_get_tnum("0:/MUSIC"); //得到总有效文件数
      while(totwavnum==NULL)//音乐文件总数为0        
         {        
                  
        }                                           
        wavfileinfo=(FILINFO*)malloc_allot(sizeof(FILINFO));    //申请内存
      pname=malloc_allot(_MAX_LFN*2+1);                    //为带路径的文件名分配内存
         wavoffsettbl=malloc_allot(4*totwavnum);                //申请4*totwavnum个字节的内存,用于存放音乐文件off block索引
         while(!wavfileinfo||!pname||!wavoffsettbl)//内存分配出错
         {        
                  
        }       
         //记录索引
      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
    ",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
    ",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);                      //播放这个音频文件
        
            if(key==KEY2_PRES)        //上一曲
            {
                printf("上一曲");
                if(curindex)
                    curindex--;
                else 
                    curindex=totwavnum-1;
             }
            else if(key==KEY0_PRES)//下一曲
            {
                    printf("下一曲");
                  curindex++;               
                if(curindex>=totwavnum)
                    curindex=0;//到末尾的时候,自动从头开始
             }
            else 
            {
                printf("有错误产生!!!!!!!!!!!!!!!!!!!
    ");
                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)
    {
        FIL*ftemp;
        uint8_t *buf; 
        uint32_t br=0;
        uint8_t res=0;
        
        ChunkRIFF *riff;
        ChunkFMT *fmt;
        ChunkFACT *fact;
        ChunkDATA *data;
        ftemp=(FIL*)mymalloc(sizeof(FIL));
        buf=mymalloc(512);
        if(ftemp&&buf)    //内存申请成功
        {
            printf("内存申请成功 
    ");
            res=f_open(ftemp,(TCHAR*)fname,FA_READ);//打开文件
            if(res==FR_OK)
            {
                printf("打开文件成功
    ");
                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->ByteRate*8;            //得到位速
                        wavx->blockalign=fmt->BlockAlign;        //块对齐
                        wavx->bps=fmt->BitsPerSample;            //位数,16/24/32位
                        
                        wavx->datasize=data->ChunkSize;            //数据块大小
                        wavx->datastart=wavx->datastart+8;        //数据流开始的地方. 
                         
                        printf("wavx->audioformat:%d
    ",wavx->audioformat);
                        printf("wavx->nchannels:%d
    ",wavx->nchannels);
                        printf("wavx->samplerate:%d
    ",wavx->samplerate);
                        printf("wavx->bitrate:%d
    ",wavx->bitrate);
                        printf("wavx->blockalign:%d
    ",wavx->blockalign);
                        printf("wavx->bps:%d
    ",wavx->bps);
                        printf("wavx->datasize:%d
    ",wavx->datasize);
                        printf("wavx->datastart:%d
    ",wavx->datastart);  
                    }
                    else 
                    { 
                        printf("data区域未找到
    ");
                      res=3;//data区域未找到.
                    }
                }
                else 
                {
                    printf("非wav文件
    ");
                    res=2;//非wav文件
                }
                
                
            }
            else 
            {
                    printf("打开文件错误
    ");
                  res=1;
            }
            
        }
        f_close(ftemp);
        myfree(ftemp);//释放内存
        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=wav_decode_init(fname,&wavctrl);//得到文件的信息
            printf("文件的信息:%d",res);
            if(res==0)//解析文件成功
            {
                
                    WM8978_I2S_Cfg(iicHandle,2,0);    //飞利浦标准,16位数据长度
            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++;
                        
                            if((audiodev.status&0X01)==0)
                            HAL_Delay(4);
                            else break;
                        }
                    }
                    audio_stop(); 
                }else res=0XFF; 
            }else res=0XFF;
        }else res=0XFF; 
        myfree(audiodev.tbuf);    //释放内存
        myfree(audiodev.saibuf1);//释放内存
        myfree(audiodev.saibuf2);//释放内存 
        myfree(audiodev.file);    //释放内存 
        return res;
    } 
        

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

      

    4.测试

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

     /* USER CODE BEGIN 2 */
        HAL_SD_Init(&hsd);
        HAL_SD_InitCard(&hsd);
        
        vIIC_Handle_Init(&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=(FATFS*)malloc_allot(sizeof(FATFS));    //为SD卡的文件参数申请内存空间    
        retSD=f_mount(SDFatFS, "0:/",0);                //挂载文件卷0                                                                         
        KEY_Init();                                     //初始化按键
        
      /* USER CODE END 2 */
    
      /* Infinite loop */
      /* USER CODE BEGIN WHILE */
      while (1)
      {
        /* USER CODE END WHILE */
    
            
        /* USER CODE BEGIN 3 */
        
        audio_play(&hIIC1);    
    
            
      }

       

     

     

  • 相关阅读:
    WEB环境搭建(tomcat)、Eclipse连接tomcat
    spring—springmvc整合
    声明式事务
    mybatis—当表的字段名和实体类的列名不对应时的三种处理方式
    Spring整合MyBatis
    mybatis关系映射(1对1,1对多,多对多)
    mybatis
    编程式事务
    使用maven在netbeans下构建wicket项目
    mysql问题Connection using old (pre-4.1.1) authentication protocol refused (client option 'secure_auth' enabled)的解决方法
  • 原文地址:https://www.cnblogs.com/feiniaoliangtiangao/p/11107231.html
Copyright © 2011-2022 走看看