STM32和WM8960 I2S 利用DMA双缓冲音频播放和录音(二)

前面简单讲解了WM8960语音芯片工作方式,WM8960做master,之前参数配置ADC/DAC采样速率的是44.1K,有点问题,现在改为16K,

参数配置如下:

//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模拟或用库函数

重点讲解下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);
}
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双缓冲发送和发送完产生的中断函数:

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);//开启传输完成中断

    SPI_I2S_DMACmd(WM8960_I2Sx_SPI,SPI_I2S_DMAReq_Tx,ENABLE);//SPI2 TX DMA请求使能.
    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)、主函数中调用测试语音录音和播放:

extern u8 Isread_tx;
extern u8 Isread_rx;

extern uint16_t adudio_buffer0[];
extern uint16_t adudio_buffer1[];
extern uint16_t    ADUDIO_BUFFER_SIZE;

#define DMA_Point_to_Memory0 0
#define DMA_Point_to_Memory1 1

void Audio_Set(void)
{
    
    WM8960_I2S_GPIO_Config();
    if(WM8960_Set_Play_Recorde_Reg())
    {
        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);
    WM8960_I2Sx_TX_DMA_Init(adudio_buffer1,adudio_buffer0,ADUDIO_BUFFER_SIZE);//注意数量大小就是一个缓冲区的大小,而不是两个缓冲区大小之和    
    WM8960_I2Sxext_Mode_Config(I2S_Standard_Phillips,I2S_DataFormat_16b,I2S_AudioFreq_16k);
    WM8960_I2Sxext_RX_DMA_Init(adudio_buffer1,adudio_buffer0,ADUDIO_BUFFER_SIZE);    
}

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;
            printf("Play Complete!!!\r\n");
            printf("Please Recorde!!!\r\n");

        }
        if(KEYR==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;
            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);
//        }        
    }
}

猜你喜欢

转载自www.cnblogs.com/wen2376/p/12382557.html