STM32和WM8960 I2S 利用DMA雙緩沖音頻播放和錄音(二)


前面簡單講解了WM8960語音芯片工作方式,WM8960做master,之前參數配置ADC/DAC采樣速率的是44.1K,有點問題,現在改為16K,下面會解釋為什么要改成16K。

WM8960參數配置如下:注意錄音時關掉內部路徑,否則會有雜音

#ifdef ALOOPBACK   //先關掉內部路徑播放
    0x2d,0x080, //Left Input Boost Mixer to Left Output Mixer
    0x2e,0x080, //Right Input Boost Mixer to Right Output Mixer
    #endif

 

//WM8960 slave mode
// MCLK = 24MHz, SYSCLK = 12.288MHz, 
// PLL mode is fractional, MCLK div 2
const u16 wm8960_reg_master[]=
{
    0x0f,0x000,
    0x19,0x17e,
    0x1a,0x1e1,
    0x2f,0x03c,
    
    0x34,0x038,
    0x35,0x031,
    0x36,0x026,
    0x37,0x0e6,
    
    0x04,0x0dd,  //ADC/DAC 采樣速率12.288/(3*256) = 16KHz
    0x08,0x00c,
    0x20,0x138,
    0x21,0x138,
    0x2b,0x000,
    0x2c,0x000,
    0x00,0x157,
    0x01,0x157,
    0x05,0x000,
    0x06,0x000,
    0x07,0x042, //bit6=1, Enable master mode; bit[1:0]=10,I2S Format;bit[3:2]=00,16 bits
    0x18,0x004,
    0x30,0x000,
    
    0x02,0x163,//179,// //LOUT1 Volume
    0x03,0x163,//0x179,//ROUT1 Volume
    

    #ifdef DLOOPBACK  //不開LOOPBACK
    0x09,0x001,
    #endif

    0x22,0x100,  //Enable Left DAC to Left Output Mixer
    0x25,0x100,    //Enable Right DAC to Right Output Mixer 

    #ifdef ALOOPBACK   //先關掉內部路徑播放
    0x2d,0x080, //Left Input Boost Mixer to Left Output Mixer
    0x2e,0x080, //Right Input Boost Mixer to Right Output Mixer
    #endif
};
View Code

調用I2C函數進行初始化:

uint8_t WM8960_Set_Play_Recorde_Reg(void)
{
    uint8_t i = 0;
    uint8_t res = 0;
    
    res = WM8960_Write_Reg((uint8_t)wm8960_reg_master[0], wm8960_reg_master[1]);
    if(res != 0)    
    {
        return res;
    }

  Delay_ms(10);
    for(i=2; i<(sizeof(wm8960_reg_master)/sizeof(u16)); i+=2)
    {
        res =  WM8960_Write_Reg((uint8_t)wm8960_reg_master[i],wm8960_reg_master[i+1]);
    }

    return 0;
}
View Code

I2C發送函數,使用的是IO模擬:

void W8960_IIC_Init(void)
{                         
    RCC->AHB1ENR|=1<<1;    //使能PORTB時鍾             
    GPIO_Set(GPIOB,PIN6|PIN7,GPIO_MODE_OUT,GPIO_OTYPE_PP,GPIO_SPEED_50M,GPIO_PUPD_PU);//PB10/PB11設置 
    W8960_IIC_SCL=1;
    W8960_IIC_SDA=1;
    
//    GPIO_InitTypeDef GPIO_InitStructure;

//    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE);    /* 打開GPIO時鍾 */

//    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7;
//    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
//    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;      
//    GPIO_InitStructure.GPIO_OType = GPIO_OType_OD;      /* 開漏輸出 */
//    GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
//    GPIO_Init(GPIOB, &GPIO_InitStructure);
    
    W8960_IIC_Stop();
}
//產生IIC起始信號
void W8960_IIC_Start(void)
{
    W8960_SDA_OUT();     //sda線輸出
    W8960_IIC_SDA=1;            
    W8960_IIC_SCL=1;
    Delay_us(8);
     W8960_IIC_SDA=0;//START:when CLK is high,DATA change form high to low 
    Delay_us(8);
    W8960_IIC_SCL=0;//鉗住I2C總線,准備發送或接收數據 
}      
//產生IIC停止信號
void W8960_IIC_Stop(void)
{
    W8960_SDA_OUT();//sda線輸出
    W8960_IIC_SCL=0;
    W8960_IIC_SDA=0;//STOP:when CLK is high DATA change form low to high
     Delay_us(4);
    W8960_IIC_SCL=1; 
     Delay_us(4);
    W8960_IIC_SDA=1;//發送I2C總線結束信號
    Delay_us(4);                                   
}
//等待應答信號到來
//返回值:1,接收應答失敗
//        0,接收應答成功
uint8_t W8960_IIC_Wait_Ack(void)
{
    uint8_t ucErrTime=0;
    W8960_SDA_IN();      //SDA設置為輸入  
    W8960_IIC_SDA=1;Delay_us(5);       
    W8960_IIC_SCL=1;Delay_us(5);     
    while(W8960_READ_SDA)
    {
        ucErrTime++;
        if(ucErrTime>250)
        {
            W8960_IIC_Stop();
            return 1;
        }
    }
    W8960_IIC_SCL=0;//時鍾輸出0        
    return 0;  
} 
//產生ACK應答
void W8960_IIC_Ack(void)
{
    W8960_IIC_SCL=0;
    W8960_SDA_OUT();
    W8960_IIC_SDA=0;
    Delay_us(4);
    W8960_IIC_SCL=1;
    Delay_us(4);
    W8960_IIC_SCL=0;
}
//不產生ACK應答            
void W8960_IIC_NAck(void)
{
    W8960_IIC_SCL=0;
    W8960_SDA_OUT();
    W8960_IIC_SDA=1;
    Delay_us(2);
    W8960_IIC_SCL=1;
    Delay_us(2);
    W8960_IIC_SCL=0;
}

//IIC發送一個字節
//返回從機有無應答
//1,有應答
//0,無應答              
void W8960_IIC_Send_Byte(uint8_t txd)
{                        
    uint8_t t;   
        W8960_SDA_OUT();         
    W8960_IIC_SCL=0;//拉低時鍾開始數據傳輸
        Delay_us(1);
        //發送前7bit,SCL下跳后1us才輸出新SDA
    for(t=0;t<7;t++)
    {              
        W8960_IIC_SDA=(txd&0x80)>>7;
        txd<<=1;       
        Delay_us(4);   //
        W8960_IIC_SCL=1;
        Delay_us(5); 
        W8960_IIC_SCL=0;    
        Delay_us(1);
    }     
        //發送最后1bit,SCL下跳后無任何延時,后直接進入waitAck (SDA變輸入,防止短暫SDA互斥)        
    W8960_IIC_SDA=(txd&0x80)>>7;
        Delay_us(5);   //
        W8960_IIC_SCL=1;
        Delay_us(5); 
        W8960_IIC_SCL=0;    
} 

void W8960_I2C_WriteByte(uint8_t DevAddr, uint8_t DataAddr, uint8_t DataToWrite)
{
  W8960_IIC_Start();                                                 //Master發送起始信號
    W8960_IIC_Send_Byte(DevAddr);                                //Master發送從設備地址
    W8960_IIC_Wait_Ack();
    
//    if(I2C_Wait_ACK()) return I2C_TIMEOUT;//等待ACK超時錯誤
    W8960_IIC_Send_Byte(DataAddr);                            //發送數據地址
    W8960_IIC_Wait_Ack();
//    if(I2C_Wait_ACK()) return I2C_TIMEOUT;//等待ACK超時錯誤
    W8960_IIC_Send_Byte(DataToWrite);
    W8960_IIC_Wait_Ack();
    
    Delay_us(10);    
    W8960_IIC_Stop();    //發送停止信號
    Delay_us(10);
}

void W8960_I2C_WriteBurst(uint8_t DevAddr, uint8_t DataAddr, uint8_t* pData, uint32_t Num)
{
    uint32_t i = 0;
    
    W8960_IIC_Start();                                                    //Master發送起始信號
    W8960_IIC_Send_Byte(DevAddr);                                //Master發送從設備地址
    W8960_IIC_Wait_Ack();
    W8960_IIC_Send_Byte(DataAddr);                            //發送數據地址
    W8960_IIC_Wait_Ack();
    for(i = 0; i < Num; i++)
    {
        W8960_IIC_Send_Byte(*(pData+i));                        //發送數據
        W8960_IIC_Wait_Ack();
    }
    
    W8960_IIC_Stop();    //發送停止信號
    Delay_ms(1);
}


//讀1個字節,ack=1時,發送ACK,ack=0,發送nACK   
uint8_t W8960_I2C_Read8bit(uint8_t ack)
{
    unsigned char i,receive=0;
    W8960_SDA_IN();//SDA設置為輸入
    for(i=0;i<8;i++ )
    {
        W8960_IIC_SCL=0; 
        Delay_us(4);
        W8960_IIC_SCL=1;
        receive<<=1;
        if(W8960_READ_SDA)receive++;   
        Delay_us(4); 
    }                     
    if (!ack)
        W8960_IIC_NAck();//發送nACK
    else
        W8960_IIC_Ack(); //發送ACK   
    return receive;
}

void W8960_I2C_ReadBurst(uint8_t DevAddr, uint16_t DataAddr, uint8_t* pData, uint32_t Num)
{
    uint32_t i = 0;
    
    W8960_IIC_Start();                                                    //Master發送起始信號
    W8960_IIC_Send_Byte(DevAddr);                                //Master發送從設備地址
    W8960_IIC_Wait_Ack();
//    W8960_IIC_Send_Byte(DataAddr>>8);                            //發送數據地址
//    W8960_IIC_Wait_Ack();
    W8960_IIC_Send_Byte(DataAddr);                            //發送數據地址
    W8960_IIC_Wait_Ack();
    W8960_IIC_Stop();
    Delay_us(10);
    W8960_IIC_Start();                                                    //Master發送起始信號
    W8960_IIC_Send_Byte(DevAddr+1);                            //Master發送從設備讀地址
    W8960_IIC_Wait_Ack();
    
    for(i = 0; i < (Num-1); i++)
    {
        *(pData+i) = W8960_I2C_Read8bit(1);            //讀數據,ACK
    }
    *(pData+i) = W8960_I2C_Read8bit(0);                //讀數據,NACK
    
    W8960_IIC_Stop();                                                        //發送停止信號
}

uint8_t WM8960_Write_Reg(uint8_t reg, uint16_t dat)
{
    uint8_t tmp_H;
    uint8_t tmp_L;
  tmp_H =(uint8_t) (reg<<1)|((uint8_t) ((dat>>8)&0x0001)); //取高字節
    tmp_L =(uint8_t) (dat&0x00FF); //取低8字節
    
    W8960_IIC_Start();                                                    //Master發送起始信號
    W8960_IIC_Send_Byte(WM8960_IIC_ADDR);                                //Master發送從設備地址
    if(W8960_IIC_Wait_Ack()==1) {return 1;}; //等待超時返回1 failed
    W8960_IIC_Send_Byte(tmp_H);                        //發送數據
    if(W8960_IIC_Wait_Ack()==1) {return 1;}; //等待超時返回1 failed
    W8960_IIC_Send_Byte(tmp_L);                        //發送數據
    if(W8960_IIC_Wait_Ack()==1) {return 1;}; //等待超時返回1 failed
    W8960_IIC_Stop();    //發送停止信號
    return 0;
}
View Code

I2C發送函數,使用的是STM32庫函數:

 

i2c.h

#ifndef __I2C_H
#define __I2C_H

#include "stm32f4xx.h"


#define I2C_LIB                                                1
#define DCMI_TIMEOUT_MAX                          10000

#define DEVICE_WRITE_ADDRESS                    0x34
#define DEVICE_READ_ADDRESS                        0x35


/* Configure I2C1 pins: PB6->scl and PB7->sda */ 

#define Open_I2Cx                                               I2C1
#define Open_i2c_CLK                                    RCC_APB1Periph_I2C1

#define Open_i2c_SDA_PIN                                 GPIO_Pin_7
#define Open_i2c_SDA_GPIO_PORT                           GPIOB
#define Open_i2c_SDA_GPIO_CLK                            RCC_AHB1Periph_GPIOB
#define Open_i2c_SDA_SOURCE                              GPIO_PinSource7
#define Open_i2c_SDA_AF                                  GPIO_AF_I2C1

#define Open_i2c_SCL_PIN                                 GPIO_Pin_6
#define Open_i2c_SCL_GPIO_PORT                           GPIOB
#define Open_i2c_SCL_GPIO_CLK                            RCC_AHB1Periph_GPIOB
#define Open_i2c_SCL_SOURCE                              GPIO_PinSource6
#define Open_i2c_SCL_AF                                  GPIO_AF_I2C1

#define I2C_SPEED                                               60000    //WM8960 I2C clk must 60KHz
#define I2C_SLAVE_ADDRESS7                                      0xFE

void I2C_GPIO_Config(void);
uint8_t I2C_Write_Byte(uint8_t Reg, uint8_t Data);
#endif
View Code

i2c.c

#include "i2c.h"
#include "stm32f4xx_i2c.h"

void I2C_GPIO_Config(void)
{
    GPIO_InitTypeDef  GPIO_InitStructure; 
    I2C_InitTypeDef      i2c_InitStructure;
    
    RCC_AHB1PeriphClockCmd(Open_i2c_SDA_GPIO_CLK | Open_i2c_SCL_GPIO_CLK,ENABLE);
    RCC_APB1PeriphClockCmd(Open_i2c_CLK,ENABLE);
    
    GPIO_PinAFConfig(Open_i2c_SDA_GPIO_PORT, Open_i2c_SDA_SOURCE, Open_i2c_SDA_AF);
    GPIO_PinAFConfig(Open_i2c_SCL_GPIO_PORT, Open_i2c_SCL_SOURCE, Open_i2c_SCL_AF);    
    
    GPIO_InitStructure.GPIO_Pin =  Open_i2c_SDA_PIN | Open_i2c_SCL_PIN;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
    GPIO_InitStructure.GPIO_OType = GPIO_OType_OD;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz;
    GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
    GPIO_Init(GPIOB, &GPIO_InitStructure);
    
    I2C_DeInit(Open_I2Cx);
    i2c_InitStructure.I2C_Mode = I2C_Mode_I2C;
    i2c_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2;
    i2c_InitStructure.I2C_OwnAddress1 = I2C_SLAVE_ADDRESS7;
    i2c_InitStructure.I2C_Ack = I2C_Ack_Enable;
    i2c_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;
    i2c_InitStructure.I2C_ClockSpeed = I2C_SPEED;
    I2C_Init(Open_I2Cx, &i2c_InitStructure);
    I2C_Cmd(Open_I2Cx, ENABLE);
    I2C_AcknowledgeConfig(Open_I2Cx, ENABLE);    
}

uint8_t I2C_Write_Byte(uint8_t Reg, uint8_t Data)
{
    
  uint32_t timeout = DCMI_TIMEOUT_MAX;
  
  /* Generate the Start Condition */
  I2C_GenerateSTART(Open_I2Cx, ENABLE);

  /* Test on I2C2 EV5 and clear it */
  timeout = DCMI_TIMEOUT_MAX; /* Initialize timeout value */
  while(!I2C_CheckEvent(Open_I2Cx, I2C_EVENT_MASTER_MODE_SELECT))
  {
    /* If the timeout delay is exeeded, exit with error code */
    if ((timeout--) == 0) return 0xFF;
  }
   /*-----------------------------------------------------------------------------------*/
  /* Send DCMI selcted device slave Address for write */
  I2C_Send7bitAddress(Open_I2Cx, DEVICE_WRITE_ADDRESS, I2C_Direction_Transmitter);
 
  /* Test on I2C2 EV6 and clear it */
  timeout = DCMI_TIMEOUT_MAX; /* Initialize timeout value */
  while(!I2C_CheckEvent(Open_I2Cx, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED))
  {
    /* If the timeout delay is exeeded, exit with error code */
    if ((timeout--) == 0) return 0xFF;
  }
   /*-----------------------------------------------------------------------------------*/
  /* Send I2C2 location address LSB */
  I2C_SendData(Open_I2Cx, (uint8_t)(Reg));

  /* Test on I2C2 EV8 and clear it */
  timeout = DCMI_TIMEOUT_MAX; /* Initialize timeout value */
  while(!I2C_CheckEvent(Open_I2Cx, I2C_EVENT_MASTER_BYTE_TRANSMITTED))
  {
    /* If the timeout delay is exeeded, exit with error code */
    if ((timeout--) == 0) return 0xFF;
  }
   /*-----------------------------------------------------------------------------------*/
  /* Send Data */
  I2C_SendData(Open_I2Cx, Data);    

  /* Test on I2C2 EV8 and clear it */
  timeout = DCMI_TIMEOUT_MAX; /* Initialize timeout value */
  while(!I2C_CheckEvent(Open_I2Cx, I2C_EVENT_MASTER_BYTE_TRANSMITTED))
  {
    /* If the timeout delay is exeeded, exit with error code */
    if ((timeout--) == 0) return 0xFF;
  }  
   /*-----------------------------------------------------------------------------------*/
  /* Send I2C2 STOP Condition */
  I2C_GenerateSTOP(Open_I2Cx, ENABLE);
  
  /* If operation is OK, return 0 */
  return 0;
}
View Code

這里有個問題,I2C 時鍾頻率只能設置在60K,設置高點、低點,播放時就會有噪聲,讓人費解。

WM8960 I2C初始化函數 uint8_t WM8960_Set_Play_Recorde_Reg(void),注意這函數里有個數據格式轉換,可以查看WM8960 I2C 傳輸格式,

地址寄存器有效為是7位[bit15 : bit9],數據有效為是9位[bit9 : bit0],但是發送的時候,是按照一個字節8bit發送的,先發地址寄存器7位 [bit15 : bit9]還得帶上

數據位的最高bit8,構成[bit15:bit8]一個字節,所以數據得轉換一下:

temp = sizeof(wm8960_reg_master)/sizeof(u16);
    for(i=0 ; i<temp; i+=2)
    {
        WM8960_Reg_Config[i] = ((uint8_t)(wm8960_reg_master[i]<<1)) | ((uint8_t)((wm8960_reg_master[i+1]>>8)& 0x01));
        WM8960_Reg_Config[i+1] = (uint8_t)(wm8960_reg_master[i+1] & 0xff);
    }

 

 

 

#define REG_NUM 100
uint8_t WM8960_Reg_Config[REG_NUM] ={0};

void Data_Format_Convert(void)
{
    uint8_t i = 0;
    uint8_t temp = 0;
    temp = sizeof(wm8960_reg_master)/sizeof(u16);
    for(i=0 ; i<temp; i+=2)
    {
        WM8960_Reg_Config[i] = ((uint8_t)(wm8960_reg_master[i]<<1)) | ((uint8_t)((wm8960_reg_master[i+1]>>8)& 0x01));
        WM8960_Reg_Config[i+1] = (uint8_t)(wm8960_reg_master[i+1] & 0xff);
    }
}
uint8_t WM8960_Set_Play_Recorde_Reg(void)
{
    uint8_t i = 0;
    uint8_t res = 0;
    
#ifdef I2C_LIB
    Data_Format_Convert();//對WM890的數組寄存器數據進行轉換
    res = I2C_Write_Byte(WM8960_Reg_Config[0],WM8960_Reg_Config[1]);
    
    if(res != 0)    return res;
    
    Delay_ms(10);
    
    for(i=2; i<(sizeof(wm8960_reg_master)/sizeof(u16)); i+=2)
    {
        res = I2C_Write_Byte(WM8960_Reg_Config[i],WM8960_Reg_Config[i+1]);
    }
#else
    
    res = WM8960_Write_Reg((uint8_t)wm8960_reg_master[0], wm8960_reg_master[1]);
    if(res != 0)    
    {
        return res;
    }
    
  Delay_ms(10);
    
    for(i=2; i<(sizeof(wm8960_reg_master)/sizeof(u16)); i+=2)
    {
        res =  WM8960_Write_Reg((uint8_t)wm8960_reg_master[i],wm8960_reg_master[i+1]);
    }
#endif
    return res;
}
View Code

重點講解下I2S的DMA方式及注意事項:

(1)、首先是I2S 管腳定義:

/**
    * I2S×ÜÏß´«ÊäÒôƵÊý¾Ý¿ÚÏß
    * WM8960_LRC    -> PB12/I2S2_WS
    * WM8960_BCLK   -> PB13/I2S2_CK
    * WM8960_ADCDAT -> PB14/I2S2ext_SD
    * WM8960_DACDAT -> PB15/I2S2_SD
    * WM8960_MCLK   -> PC6/I2S2_MCK
    */    
    /* Enable GPIO clock */
void WM8960_I2S_GPIO_Config(void)
{
    GPIO_InitTypeDef GPIO_InitStructure;

    RCC_AHB1PeriphClockCmd(WM8960_LRC_GPIO_CLK|WM8960_BCLK_GPIO_CLK| \
                         WM8960_ADCDAT_GPIO_CLK|WM8960_DACDAT_GPIO_CLK| \
                           WM8960_MCLK_GPIO_CLK, ENABLE);

    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
    GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;

    GPIO_InitStructure.GPIO_Pin = WM8960_LRC_PIN;
    GPIO_Init(WM8960_LRC_PORT, &GPIO_InitStructure);

    GPIO_InitStructure.GPIO_Pin = WM8960_BCLK_PIN;
    GPIO_Init(WM8960_BCLK_PORT, &GPIO_InitStructure);

    GPIO_InitStructure.GPIO_Pin = WM8960_MCLK_PIN;
    GPIO_Init(WM8960_MCLK_PORT, &GPIO_InitStructure);

    GPIO_InitStructure.GPIO_Pin = WM8960_DACDAT_PIN;
    GPIO_Init(WM8960_DACDAT_PORT, &GPIO_InitStructure);

    GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
    GPIO_InitStructure.GPIO_Pin = WM8960_ADCDAT_PIN;
    GPIO_Init(WM8960_ADCDAT_PORT, &GPIO_InitStructure);

    /* Connect pins to I2S peripheral  */
    GPIO_PinAFConfig(WM8960_LRC_PORT,    WM8960_LRC_SOURCE,    WM8960_LRC_AF);
    GPIO_PinAFConfig(WM8960_BCLK_PORT,   WM8960_BCLK_SOURCE,   WM8960_BCLK_AF);
    GPIO_PinAFConfig(WM8960_ADCDAT_PORT, WM8960_ADCDAT_SOURCE, WM8960_ADCDAT_AF);
    GPIO_PinAFConfig(WM8960_DACDAT_PORT, WM8960_DACDAT_SOURCE, WM8960_DACDAT_AF);
    GPIO_PinAFConfig(WM8960_MCLK_PORT,   WM8960_MCLK_SOURCE,   WM8960_MCLK_AF);
}
View Code

(2)、配置I2S 發送,STM32做的從機,所以不需要配置MCLK,當然也不需要輸出:

void WM8960_I2Sx_Mode_Config(const uint16_t _usStandard,const uint16_t _usWordLen,const uint32_t _usAudioFreq)
{
    I2S_InitTypeDef I2S_InitStructure;
    
#if 0    //STM32作為從機不需要配置時鍾
    uint32_t n = 0;
    FlagStatus status = RESET;

/**
    *    For I2S mode, make sure that either:
    *        - I2S PLL is configured using the functions RCC_I2SCLKConfig(RCC_I2S2CLKSource_PLLI2S),
    *        RCC_PLLI2SCmd(ENABLE) and RCC_GetFlagStatus(RCC_FLAG_PLLI2SRDY).
    */
    RCC_I2SCLKConfig(RCC_I2S2CLKSource_PLLI2S);
    RCC_PLLI2SCmd(ENABLE);
    for (n = 0; n < 500; n++)
    {
        status = RCC_GetFlagStatus(RCC_FLAG_PLLI2SRDY);
        if (status == 1)break;
    }
#endif 
    /* 打開 I2S2 APB1 時鍾 */
    RCC_APB1PeriphClockCmd(WM8960_CLK, ENABLE);

    /* 復位 SPI2 外設到缺省狀態 */
    SPI_I2S_DeInit(WM8960_I2Sx_SPI);

    /* I2S2 外設配置 */
    /* 配置I2S工作模式 */
    I2S_InitStructure.I2S_Mode = I2S_Mode_SlaveTx;//I2S_Mode_MasterTx;        
    /* 接口標准 */
    I2S_InitStructure.I2S_Standard = _usStandard;            
    /* 數據格式,16bit */
    I2S_InitStructure.I2S_DataFormat = _usWordLen;            
    /* 主時鍾模式 */
    I2S_InitStructure.I2S_MCLKOutput = I2S_MCLKOutput_Disable;//I2S_MCLKOutput_Enable;    
    /* 音頻采樣頻率 */
    I2S_InitStructure.I2S_AudioFreq = _usAudioFreq;            
    I2S_InitStructure.I2S_CPOL = I2S_CPOL_Low;
    I2S_Init(WM8960_I2Sx_SPI, &I2S_InitStructure);
    
    /* 使能 SPI2/I2S2 外設 */
    I2S_Cmd(WM8960_I2Sx_SPI, ENABLE);
    SPI_I2S_DMACmd(WM8960_I2Sx_SPI,SPI_I2S_DMAReq_Tx,ENABLE);//SPI2 TX DMA請求使能.
}
View Code

(3)、配置I2S接收模式,配置雙緩沖模式

void WM8960_I2Sxext_Mode_Config(const uint16_t _usStandard, const uint16_t _usWordLen,const uint32_t _usAudioFreq)
{
    I2S_InitTypeDef I2Sext_InitStructure;

    /* I2S2 外設配置 */

    RCC_APB1PeriphClockCmd(RCC_APB1Periph_SPI2, ENABLE);
        
    /* 復位 SPI2 外設到缺省狀態 */
    SPI_I2S_DeInit(I2S2ext);
    I2Sext_InitStructure.I2S_Mode = I2S_Mode_SlaveTx;            /* 配置I2S工作模式 注意這里是SlaveTx 而不是Rx,容易誤導*/
    I2Sext_InitStructure.I2S_Standard = _usStandard;            /* 接口標准 */
    I2Sext_InitStructure.I2S_DataFormat = _usWordLen;            /* 數據格式,16bit */
    I2Sext_InitStructure.I2S_MCLKOutput = I2S_MCLKOutput_Disable;    /* 主時鍾模式 */
    I2Sext_InitStructure.I2S_AudioFreq = _usAudioFreq;            /* 音頻采樣頻率 */
    I2Sext_InitStructure.I2S_CPOL = I2S_CPOL_Low;
    
    I2S_Init(I2S2ext, &I2Sext_InitStructure);
    I2S_FullDuplexConfig(I2S2ext, &I2Sext_InitStructure);  //可以進入函數中看到,當I2S_Mode == I2S_Mode_SlaveTx時,選擇的是tmp = I2S_Mode_SlaveRx;
    
    /* 使能 SPI2/I2S2 外設 */
    I2S_Cmd(I2S2ext, ENABLE);
    SPI_I2S_DMACmd(I2S2ext,SPI_I2S_DMAReq_Rx,ENABLE);//SPI2 RX DMA請求使能.
}
View Code

這里注意下I2S模式選的是I2S_Mode_SlaceTx,你可能會覺得這個地方配置錯了。這地方我也是查詢了好久才找到。坑爹玩意

I2Sext_InitStructure.I2S_Mode = I2S_Mode_SlaveTx;            /* 配置I2S工作模式 注意這里是SlaveTx 而不是Rx,容易誤導*/

其實不是,進到void I2S_FullDuplexConfig(SPI_TypeDef* I2Sxext, I2S_InitTypeDef* I2S_InitStruct) 這個函數里頭,發現這代碼是這樣寫的,最終是temp=I2S_Mode_SlaveRx;

 /* Get the mode to be configured for the extended I2S */
  if ((I2S_InitStruct->I2S_Mode == I2S_Mode_MasterTx) || (I2S_InitStruct->I2S_Mode == I2S_Mode_SlaveTx))
  {
    tmp = I2S_Mode_SlaveRx;
  }
  else
  {
    if ((I2S_InitStruct->I2S_Mode == I2S_Mode_MasterRx) || (I2S_InitStruct->I2S_Mode == I2S_Mode_SlaveRx))
    {
      tmp = I2S_Mode_SlaveTx;
    }
  }

 (3)、配置DMA雙緩沖發送和發送完產生的中斷函數:

#define WM8960_I2Sx_DMA                       DMA1
#define WM8960_I2Sx_DMA_CLK                   RCC_AHB1Periph_DMA1
#define WM8960_I2Sx_TX_DMA_CHANNEL            DMA_Channel_0
#define WM8960_I2Sx_TX_DMA_STREAM             DMA1_Stream4
#define WM8960_I2Sx_TX_DMA_IT_TCIF            DMA_IT_TCIF4
#define WM8960_I2Sx_TX_DMA_STREAM_IRQn        DMA1_Stream4_IRQn 
#define WM8960_I2Sx_TX_DMA_STREAM_IRQFUN            DMA1_Stream4_IRQHandler
void WM8960_I2Sx_TX_DMA_Init(const uint16_t *buffer0,const uint16_t *buffer1,const uint32_t num)
{  
    NVIC_InitTypeDef   NVIC_InitStructure;
    DMA_InitTypeDef  DMA_InitStructure;
    
 
  RCC_AHB1PeriphClockCmd(WM8960_I2Sx_DMA_CLK,ENABLE);//DMA1時鍾使能 
    
    DMA_DeInit(WM8960_I2Sx_TX_DMA_STREAM);
    while (DMA_GetCmdStatus(WM8960_I2Sx_TX_DMA_STREAM) != DISABLE){}//等待DMA1_Stream4可配置 
        
    DMA_ClearITPendingBit(WM8960_I2Sx_TX_DMA_STREAM,DMA_IT_FEIF4|DMA_IT_DMEIF4|DMA_IT_TEIF4|DMA_IT_HTIF4|DMA_IT_TCIF4);//清空DMA1_Stream4上所有中斷標志

  /* 配置 DMA Stream */
  DMA_InitStructure.DMA_Channel = WM8960_I2Sx_TX_DMA_CHANNEL;  //通道0 SPIx_TX通道 
  DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&WM8960_I2Sx_SPI->DR;//外設地址為:(u32)&SPI2->DR
  DMA_InitStructure.DMA_Memory0BaseAddr = (uint32_t)buffer0;//DMA 存儲器0地址
  DMA_InitStructure.DMA_DIR = DMA_DIR_MemoryToPeripheral;//存儲器到外設模式
  DMA_InitStructure.DMA_BufferSize = num;//數據傳輸量 
  DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;//外設非增量模式
  DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;//存儲器增量模式
  DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;//外設數據長度:16位
  DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;//存儲器數據長度:16位 
  DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;// 使用循環模式 
  DMA_InitStructure.DMA_Priority = DMA_Priority_High;//高優先級
  DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable; //不使用FIFO模式        
  DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_1QuarterFull;
  DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single;//外設突發單次傳輸
  DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;//存儲器突發單次傳輸
  DMA_Init(WM8960_I2Sx_TX_DMA_STREAM, &DMA_InitStructure);//初始化DMA Stream
        
    DMA_DoubleBufferModeConfig(WM8960_I2Sx_TX_DMA_STREAM,(uint32_t)buffer0,DMA_Memory_0);//雙緩沖模式配置
    DMA_DoubleBufferModeConfig(WM8960_I2Sx_TX_DMA_STREAM,(uint32_t)buffer1,DMA_Memory_1);//雙緩沖模式配置

    DMA_DoubleBufferModeCmd(WM8960_I2Sx_TX_DMA_STREAM,ENABLE);//雙緩沖模式開啟

    DMA_ITConfig(WM8960_I2Sx_TX_DMA_STREAM,DMA_IT_TC,ENABLE);//開啟傳輸完成中斷

    DMA_Cmd(WM8960_I2Sx_TX_DMA_STREAM,DISABLE);
    NVIC_InitStructure.NVIC_IRQChannel = WM8960_I2Sx_TX_DMA_STREAM_IRQn; 
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;//搶占優先級1
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;//子優先級2
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;//使能外部中斷通道
    NVIC_Init(&NVIC_InitStructure);//配置
}

void WM8960_I2Sx_TX_DMA_STREAM_IRQFUN(void)
{      
    if(DMA_GetITStatus(WM8960_I2Sx_TX_DMA_STREAM,WM8960_I2Sx_TX_DMA_IT_TCIF)==SET)//DMA傳輸完成標志
    { 
        DMA_ClearITPendingBit(WM8960_I2Sx_TX_DMA_STREAM,WM8960_I2Sx_TX_DMA_IT_TCIF);//清DMA傳輸完成標准

        if(WM8960_I2Sx_TX_DMA_STREAM->CR&(1<<19)) //當前使用Memory1數據
        {
            bufflag=0;                       //可以將數據讀取到緩沖區0
        }
        else                               //當前使用Memory0數據
        {
            
            bufflag=1;                       //可以將數據讀取到緩沖區1
        }
        
        Isread_tx ++;    
    }                                                
} 
View Code

 (4)、配置DMA雙緩沖接收和接收中斷函數:

void WM8960_I2Sxext_RX_DMA_Init(const uint16_t *buffer0,const uint16_t *buffer1,const uint32_t num)
{  
    NVIC_InitTypeDef   NVIC_InitStructure1;
    DMA_InitTypeDef  DMA_InitStructure1;    
 
  RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA1,ENABLE);//DMA1時鍾使能 
    
    DMA_DeInit(DMA1_Stream3);
    while (DMA_GetCmdStatus(DMA1_Stream3) != DISABLE){}//等待DMA1_Stream3可配置 
        
    DMA_ClearITPendingBit(DMA1_Stream3,DMA_IT_FEIF3|DMA_IT_DMEIF3|DMA_IT_TEIF3|DMA_IT_HTIF3|DMA_IT_TCIF3);//清空DMA1_Stream3上所有中斷標志

  /* 配置 DMA Stream */
  DMA_InitStructure1.DMA_Channel = DMA_Channel_3;  //通道0 SPIx_TX通道 
  DMA_InitStructure1.DMA_PeripheralBaseAddr = (uint32_t)&WM8960_I2Sx_ext->DR;//外設地址為:(u32)&SPI2->DR
  DMA_InitStructure1.DMA_Memory0BaseAddr = (uint32_t)buffer0;//DMA 存儲器0地址
  DMA_InitStructure1.DMA_DIR = DMA_DIR_PeripheralToMemory;//外設到存儲器模式
  DMA_InitStructure1.DMA_BufferSize = num;//數據傳輸量 
  DMA_InitStructure1.DMA_PeripheralInc = DMA_PeripheralInc_Disable;//外設非增量模式
  DMA_InitStructure1.DMA_MemoryInc = DMA_MemoryInc_Enable;//存儲器增量模式
  DMA_InitStructure1.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;//外設數據長度:16位
  DMA_InitStructure1.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;//存儲器數據長度:16位 
  DMA_InitStructure1.DMA_Mode = DMA_Mode_Circular;// 使用循環模式 
  DMA_InitStructure1.DMA_Priority = DMA_Priority_VeryHigh;
  DMA_InitStructure1.DMA_FIFOMode = DMA_FIFOMode_Disable; //不使用FIFO模式        
  DMA_InitStructure1.DMA_FIFOThreshold = DMA_FIFOThreshold_1QuarterFull;
  DMA_InitStructure1.DMA_MemoryBurst = DMA_MemoryBurst_Single;//外設突發單次傳輸
  DMA_InitStructure1.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;//存儲器突發單次傳輸
  DMA_Init(DMA1_Stream3, &DMA_InitStructure1);//初始化DMA Stream
        
    DMA_DoubleBufferModeConfig(DMA1_Stream3,(uint32_t)buffer0,DMA_Memory_0);//雙緩沖模式配置
    DMA_DoubleBufferModeConfig(DMA1_Stream3,(uint32_t)buffer1,DMA_Memory_1);//雙緩沖模式配置

    DMA_DoubleBufferModeCmd(DMA1_Stream3,ENABLE);//雙緩沖模式開啟

    DMA_ITConfig(DMA1_Stream3,DMA_IT_TC,ENABLE);//開啟傳輸完成中斷

    DMA_Cmd(DMA1_Stream3,DISABLE);
    
  NVIC_InitStructure1.NVIC_IRQChannel = DMA1_Stream3_IRQn; 
  NVIC_InitStructure1.NVIC_IRQChannelPreemptionPriority = 0;//搶占優先級0
  NVIC_InitStructure1.NVIC_IRQChannelSubPriority = 0;//子優先級2
  NVIC_InitStructure1.NVIC_IRQChannelCmd = ENABLE;//使能外部中斷通道
  NVIC_Init(&NVIC_InitStructure1);//配置
}

void DMA1_Stream3_IRQHandler(void)
{
    if(DMA_GetITStatus(DMA1_Stream3,DMA_IT_TCIF3)==SET)
    {
        DMA_ClearITPendingBit(DMA1_Stream3,DMA_IT_TCIF3);

        if(DMA1_Stream3->CR&(1<<19)) //當前使用Memory1數據
        {
            bufflag=1;
        }
        else                                 //當前使用Memory0數據
        {
            bufflag=0;
        }
        
        Isread_rx++;                            // DMA傳輸完成標志
    }
}
View Code

 (5)、主函數中調用測試語音錄音和播放:

void WM8960_I2S_Play_Start(void)
{         
    DMA_Cmd(WM8960_I2Sx_TX_DMA_STREAM,ENABLE);//開啟DMA TX傳輸,開始播放     
}
void WM8960_I2S_Play_Stop(void)
{        
    DMA_Cmd(WM8960_I2Sx_TX_DMA_STREAM,DISABLE);//關閉DMA TX傳輸,結束播放 
}
void WM8960_I2Sxext_Recorde_Start(void)
{         
    DMA_Cmd(WM8960_I2Sxext_RX_DMA_STREAM,ENABLE);//開啟DMA RX傳輸,開始錄音
}

void WM8960_I2Sxext_Recorde_Stop(void)
{        
    DMA_Cmd(WM8960_I2Sxext_RX_DMA_STREAM,DISABLE);//關閉DMA RX傳輸,結束錄音
}
View Code

 

extern u8 Isread_tx;
extern u8 Isread_rx;

//注意兩個數組別定義太大,定義太大STM空間不夠,編譯器會報錯,這里一個數組存放了48630,兩個數組48630*2
extern uint16_t adudio_buffer0[]; //可以事先定義好一個數組buffer0,先存放一點數據進去,不錄音之前,也能播放一段聲音出來 extern uint16_t adudio_buffer1[]; //可以事先定義好一個數組buffer1,先存放一點數據進去,不錄音之前,也能播放一段聲音出來 extern uint16_t ADUDIO_BUFFER_SIZE; //uint16_t ADUDIO_BUFFER_SIZE = sizeof(adudio_buffer0)/sizeof(uint16_t); #define DMA_Point_to_Memory0 0 #define DMA_Point_to_Memory1 1 void Audio_Set(void) { WM8960_I2S_GPIO_Config(); //I2S IO配置 if(WM8960_Set_Play_Recorde_Reg())//對WM8960進行初始化 { printf("WM8960 Init Fail!!!\r\n"); } else printf("WM8960 Init Success!!!\r\n"); WM8960_I2Sx_Mode_Config(I2S_Standard_Phillips,I2S_DataFormat_16b,I2S_AudioFreq_16k);//音頻采樣速率,這里配置16K,收發要保持一致 WM8960_I2Sx_TX_DMA_Init(adudio_buffer1,adudio_buffer0,ADUDIO_BUFFER_SIZE);//注意數量大小就是一個緩沖區的大小,而不是兩個緩沖區大小之和,buffer1傳給memory0,buffer0傳給memory1 WM8960_I2Sxext_Mode_Config(I2S_Standard_Phillips,I2S_DataFormat_16b,I2S_AudioFreq_16k);//音頻采樣速率,這里配置16K,收發保持一致,否則播放出的音速就變了
//假如現在有一段錄音“1234”,再同一時間內,44K采集到的數據比16K采集的數據多,加上STM數組空間有限,如果用44K采集的話,可能采集到“12”就填滿了兩個數組,錄音播放的時候就只能聽到前半截.
//如果用16K去采集數據,“1234”都能存到兩個數組中,錄音播放就會比較完整。這里的44K好比就是無損音樂,數據大;16K就相當於MP3格式的音樂,數據少。常規聽起來感覺到不差異。
//這里就是要配置ADC/DAC采樣速率16K的原因。
WM8960_I2Sxext_RX_DMA_Init(adudio_buffer1,adudio_buffer0,ADUDIO_BUFFER_SIZE); //配置I2S DMA雙緩沖接收,buffer1傳給memory0,buffer0傳給memory1 }
void Audio_Play_Recorde(void) { Audio_Set(); while(1) { //播放音樂 if(KEYL==0)//按下左按鍵,進行播放 { Delay_ms(1000); WM8960_I2S_Play_Start();//開啟播放 } if(Isread_tx == 2) { WM8960_I2S_Play_Stop();//兩個數組的數據播放完后,停止播放 Isread_tx = 0;//標志清0 printf("Play Complete!!!\r\n"); printf("Please Recorde!!!\r\n"); } if(KEYR==0)//按下右鍵將兩個數組清0 { Delay_ms(1000); memset(adudio_buffer1,0,sizeof(uint16_t)*ADUDIO_BUFFER_SIZE); memset(adudio_buffer0,0,sizeof(uint16_t)*ADUDIO_BUFFER_SIZE); printf("Clear Buffer Complete!!!\r\n"); } //錄音 if(KEYM==0)//按下左鍵,開啟錄音 { Delay_ms(1000); WM8960_I2Sxext_Recorde_Start();//開始錄音 } if(Isread_rx == 2) { WM8960_I2Sxext_Recorde_Stop();//錄滿兩個數組后,停止錄音 Isread_rx = 0;//標志清0 printf("Recorde Complete!!!\r\n"); printf("Please Play!!!\r\n"); } // // if(bufflag==DMA_Point_to_Memory0) //說明Memory1中的數據可以拷貝 // { // rx_flag = 2; // memcpy(tx_buffer0,rx_buffer0,sizeof(u8)*BUFFER_SIZE); // } // if(bufflag==DMA_Point_to_Memory1)//說明Memory0中的數據可以拷貝 // { // rx_flag = 2; // memcpy(tx_buffer1,rx_buffer1,sizeof(u8)*BUFFER_SIZE); // } } }

 總結:

(1)、為什么STM32配置主機,WM8960作為從機,能播放,但是接收不了數據,所以始終進不了中斷函數,就無法錄音?

答:播放時,是STM32將數據發給WM8960,是主動發送數據,會有時鍾輸出,而錄音時,是STM32接收數據,

一直在那等待數據,示波器量MCLK 和Bitcllk沒有時鍾,雖然程序里配置使能MCLK時鍾輸出,但就是沒有。

后來想到SPI接收數據的場景,在接收數據時,得讓STM32發送任意字節,目的就是讓時鍾輸出。接收不了數據的問題估計就是出在這里了。

static uint16_t SPIx_Receive_byte(void)
{
    while(SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_TXE)==RESET);  //檢查緩沖區是否為空
    SPI_I2S_SendData(SPI2,Dummy_Byte);//發送任意字節,就是為了時鍾輸出
    
    while(SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_RXNE)==RESET);
    return SPI_I2S_ReceiveData(SPI2);
}

 我想STM32肯定可以做主機接收數據,只是現在沒調試好,如果在接收時,是通過STM32發送數據產生時鍾,那么WM8960會不會有雜音播放呢?

再一個采用的是DMA方式讀取,看不到類似SPI那種接收讀取的函數。又該如何配置呢? 這個問題可以好好研究。

(2)、在一個就是I2S 接收時的配置,雖然網上例子中,I2Sext_InitStructure.I2S_Mode = I2S_Mode_SlaveTx;  沒有重點說明,

一開始按照網上的資料配置,不能錄音,仔細檢查代碼,發現這個有疑問,明明是接收,怎么是配置為發送呢,讓我竊喜,肯定是這個地方出錯了,

於是改成了I2Sext_InitStructure.I2S_Mode = I2S_Mode_SlaveRx; 結果很失望,還是不能錄音,於是單步調試進入I2S_FullDuplexConfig(I2S2ext, &I2Sext_InitStructure);

函數中,發現里面有這一段代碼,原來如此,之前人家配置的I2S_Mode_SlaveTX是正確的。單步調試進入官方的庫函數,仔細查看函數里面做了哪些,這種方法是非常有效。

if ((I2S_InitStruct->I2S_Mode == I2S_Mode_MasterTx) || (I2S_InitStruct->I2S_Mode == I2S_Mode_SlaveTx))
  {
    tmp = I2S_Mode_SlaveRx;
  }
  else
  {
    if ((I2S_InitStruct->I2S_Mode == I2S_Mode_MasterRx) || (I2S_InitStruct->I2S_Mode == I2S_Mode_SlaveRx))
    {
      tmp = I2S_Mode_SlaveTx;
    }
  }

 還有個細節,在配置I2S接收工作模式時,I2S_Init要不要寫?"因為看到了I2S_FullDuplexConfig(I2S2ext, &I2Sext_InitStructure);

也是將I2Sext_InitStructure變量傳進去進行初始化,I2S_Init()是不是多余了,這個還真不是多余,得一定要配置,I2S_Init()是對I2S協議、

數據格式、主時鍾、音頻采樣、工作模式和空閑時的時鍾電平狀態進行初始化,而I2S_FullDuplexConfig函數僅僅是將I2S2ext擴展口設置為全雙工模式。

I2S_Init(I2S2ext, &I2Sext_InitStructure);
I2S_FullDuplexConfig(I2S2ext, &I2Sext_InitStructure);  //可以進入函數中看到,當I2S_Mode == I2S_Mode_SlaveTx時,選擇的是tmp = I2S_Mode_SlaveRx;

 (3)、其實到現在我還有個疑問?I2S2ext擴展口設置全雙工模式時使用,全雙工的無非就是發送時,一根數據線在發送,另一根數據線在接收,看下引腳對應

   * I2S總線傳輸音頻數據口線
    * WM8960_LRC    -> PB12/I2S2_WS  //映射到NSS引腳,即幀時鍾,用於切換左右聲道的數據,WS頻率等於音頻信號采樣率(fs),
                      //STM32發送時是要產生一個時鍾給WM8960,WM8960根據該時鍾區分左右聲道,接收時呢? 目前程序里面好像是不用關心,是真的嗎?

* WM8960_BCLK -> PB13/I2S2_CK //串行時鍾(映射到SPI_SCK引腳),即位時鍾,是主模式下的串行時鍾輸出以及從模式下的串行時鍾輸入。 * WM8960_ADCDAT -> PB14/I2S2ext_SD//擴展串行數據線(MISO),用於全雙工傳輸的數據接收 * WM8960_DACDAT -> PB15/I2S2_SD //串行數據線(映射MOSI),用於發送或接收兩個時分復用的數據通道上的數據(僅半雙工模式),如果是全雙工模式,該信號僅用於發送數據 * WM8960_MCLK -> PC6/I2S2_MCK //附加時鍾,給外設音頻模塊提供工作主時鍾

 目前程序里好像只用到了半雙工,收發都不是同時。

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM