• 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!!! "); } else printf("WM8960 Init Success!!! "); 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!!! "); printf("Please Recorde!!! "); } 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!!! "); } //录音 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!!! "); printf("Please Play!!! "); } // // 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 //附加时钟,给外设音频模块提供工作主时钟

     目前程序里好像只用到了半双工,收发都不是同时。

  • 相关阅读:
    Cox回归模型【生存分析】
    推荐系统整理
    推荐——基于python
    经纬度坐标数据处理——基于R
    横截面数据分类——基于R
    《利用python进行数据分析》NumPy基础:数组和矢量计算 学习笔记
    python模块hashlib & hmac
    python unittest+parameterized,单元测试框架+参数化
    使用docker安装mysql并连接
    用docker部署RabbitMQ环境
  • 原文地址:https://www.cnblogs.com/wen2376/p/12382557.html
Copyright © 2020-2023  润新知