基於STM32F429,Cubemx的SAI音頻播放實驗

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

請閱讀完上面的兩篇博文做爲基礎,再閱讀本篇博文,如若已瞭解SD卡,內存管理,Fatfs,請跳過。html

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功能,而後選擇爲主機模式,參照下面原子的例程配置參數。ide

//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協議。oop

處此以外,請按照開頭連接的例程配置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");
    
    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:每次傳輸數據量
//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);        
             
        //顯示總時間   
     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\r\n",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\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);                      //播放這個音頻文件
    
        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("有錯誤產生!!!!!!!!!!!!!!!!!!!\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)
{
    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("內存申請成功 \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->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\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文件
            }
            
            
        }
        else 
        {
                printf("打開文件錯誤\n");
              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);    

        
  }
相關文章
相關標籤/搜索