39

灵动微电子 · 2023年07月07日

灵动微课堂 |基于MM32F0163D7P的I2S接口的音乐播放器实验

I2S(Inter-IC Sound)总线,又称集成电路内置音频总线,是飞利浦公司为数字音频设备之间的音频数据传输而制定的一种总线标准。采用了独立的导线传输时钟与数据信号的设计,通过将数据和时钟信号分离,避免了因时差诱发的失真。

MM32F0160系列的I2S 接口有以下主要特征:

  • 半双工通信(仅发送器或接收器)
  • 主操作或从操作
  • 9 位可配置线性预分频器,以达到精确的音频采样频率(8KHz~192KHz)
  • 数据帧格式可配置为 16 位、24 位或 32 位
  • 数据包帧固定为 16 位(16 位有效数据)或 32 位(16 位、24 位、32 位有效数据)
  • 可配置时钟极性(稳定状态)
  • 发送模式下具有下溢标志(仅从机),接收模式下具有上溢标志(主/从机)和发送/接收模式下的帧错误标志(仅从机)
  • 用于传输和接收的 32 位寄存器为两个声道分时复用
  • 数据方向始终是 MSB 优先
  • 支持 I2S 协议:

    • 飞利浦标准
    • MSB 对齐标准(MSB 位向左对齐)
    • LSB 对齐标准(LSB 位向右对齐)
    • PCM 标准(具有短帧同步模式、长帧同步模式的两种方式)
  • 利用 DMA 请求传输数据(32 位宽)
  • 可配置 MCLK 时钟输出来驱动外部音频组件,其比率固定在 256×Fs(其中 Fs 为音频采样频率)

1. MM32的I2S总线简述

MM32F0160的I2S总线与SPI总线复用即SPI_I2S串行外设(串行外设接口与集成电路内置音频总线)。

I2S总线接口与SPI总线接口引脚复用关系如下:

SD:串行数据(映射在MOSI引脚上),用于发送或接收两次多路数据通道(仅在半双工模式下)。

WS:声道选择(映射在NSS引脚上),是主模式控制数据的输出信号,或从模式的输入。

CK:串行时钟(映射在SCK引脚上),是主模式串行时钟的输出,或从模式串行时钟的输入。

MCK:可选的串行时钟(映射在MISO引脚上),用于驱动外部音频组件(仅当外部音频设备需要时钟输入时使用,由主模式提供)。

2. SPI_I2S功能框图简介

image.png
图1 SPI_I2S功能框图

如上图1所示为SPI_I2S外设的功能框图,SPI_I2S通过“总线接口逻辑”挂载在APB和DMA总线上,TXREG和RXREG寄存器、主模式控制单元和从模式控制单元、主从选择控制、收发控制逻辑以及时钟生成及控制单元,8Byte的发送缓冲和8Byte的接收缓冲构成,时钟控制单元由Spbrg和Pclk提供时钟。

3. SPI_I2S外设的I2S时钟预分频器

I2SCLK时钟由系统APB时钟提供,I2S模块的预分频器电路结构如下图2所示:

image.png
图2 SPI_I2S时钟预分频器电路结构图

如上图2所示,当MCKOE位为‘0’时芯片不需要输出MCK时钟,预分频器直接将I2SCLK分频到CK;当MCKOE位为‘1’时芯片会输出MCK时钟,预分频器将I2SCLK分频后得到MCK,然后再经过分频处理才得到CK(分频倍数由CHLEN选择为 4或8)。

音频采样率一般常用 192KHz,96 KHz,48 KHz,44.1 KHz,32 KHz,22.05 KHz,16 KHz,11.025KHz,8 KHz。因此可根据 I2S 时钟分频器的电路功能式样,配置寄存器 I2SCFGR中的I2SDIV[8:0]、MCKOE和CHLEN 位来得到期望的音频采样率。

I2S 传输数据时,比特率计算公式如下表1所示(CK 输出一个时钟周期对应传输 1 比特数据,因此比特率 = CK频率FCK)。

image.png
表1

音频采样率(Fs)和 I2S 比特率的关系由如下的公式定义:

Fs = I2S 比特率/(通道长度×通道数)= FCK /(通道长度×通道数)

注:通道长度,即数据包帧长度,可配置为16位或32位;通道数为左右声道,值固定为2。

综上所述,根据I2SDIV[8: 0]、MCKOE和CHLEN位的配置情况, 得到音频采样率与FI2SCLK(APB时钟频率)的关系如下表2所示:

image.png
表2

4. SPI_I2S外设的I2S接口的音乐播放器

工作原理介绍

基于I2S接口的音乐播放器工作原理框图如下图3所示:

image.png
图3 I2S接口的音乐播放器原理框图

如上图3所示为I2S接口的音乐播放器工作原理框图:

MM32F0160作为Host MCU其SPI1接口用于驱动25WQ80存储器用于写入和读取存储的音频文件。

MCU端I2S2(SPI2_I2S2)接口工作在从机模式,MCLK不输出时钟。通过PWM输出12MHz的REF_Clock给NAU88C22音频编解码芯片MCLK脚,NAU88C22内部PLL合成稳定的12.288MHz作为内部IMLCK主时钟。NAU88C22 BCLK输出bit clock时钟到MCU端I2S2_CK作为音频采样时钟。I2S2_WS接口即FS用于分时切换左右声道。I2S2_SD接口即DACIN输出从25WQ80存储器读取的音频信号流DAC Stream传输给NAU88C22音频编解码芯片。

MCU端I2C_SDA和I2C_SCL接口用于设置NAU88C22工作模式和参数。NAU88C22 DAC输出可以选择从Speaker PA输出推喇叭或者从Earphone PA耳机接口输出推动耳机。

5. I2S接口的GPIO初始化

I2S接口GPIO的初始化代码如下所示:

void I2S2_GPIO_Init(void)
{    
    GPIO_InitTypeDef GPIO_InitStruct;
    
    RCC_GPIO_ClockCmd(GPIOB, ENABLE); 
    
    GPIO_StructInit(&GPIO_InitStruct);
    /* PB12 I2S2_WS */    
    GPIO_InitStruct.GPIO_Pin   = GPIO_Pin_12;    
    GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;    
    GPIO_InitStruct.GPIO_Mode  = GPIO_Mode_AF_PP;    
    GPIO_Init(GPIOB, &GPIO_InitStruct); 

    /* PB13 I2S2_CK */    
    GPIO_InitStruct.GPIO_Pin   = GPIO_Pin_13;    
    GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;    
    GPIO_InitStruct.GPIO_Mode  = GPIO_Mode_AF_PP;    
    GPIO_Init(GPIOB, &GPIO_InitStruct); 

    /* PB14 I2S2_MCLK */    
    GPIO_InitStruct.GPIO_Pin   = GPIO_Pin_14;    
    GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;    
    GPIO_InitStruct.GPIO_Mode  = GPIO_Mode_IPU;    
    GPIO_Init(GPIOB, &GPIO_InitStruct); 

    /* PB15 I2S2_SD */    
    GPIO_InitStruct.GPIO_Pin   = GPIO_Pin_15;    
    GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;    
    GPIO_InitStruct.GPIO_Mode  = GPIO_Mode_AF_PP;    
    GPIO_Init(GPIOB, &GPIO_InitStruct);

    /* PB12 AF I2S2_WS */    
    GPIO_PinAFConfig(GPIOB, GPIO_PinSource12, GPIO_AF_0); 

    /* PB12 AF I2S2_CK */    
    GPIO_PinAFConfig(GPIOB, GPIO_PinSource13, GPIO_AF_0);

    /* PB12 AF I2S2_MCLK */    
    GPIO_PinAFConfig(GPIOB, GPIO_PinSource14, GPIO_AF_0); 

    /* PB12 AF I2S2_SD */    
    GPIO_PinAFConfig(GPIOB, GPIO_PinSource15, GPIO_AF_0);
}

6. I2S从机的初始化

I2S从机的初始化步骤如下所示:

  1. 配置SPI_I2S_GCTL.SPIEN位为‘1’,开启模块使能;
  2. 配置SPI_I2S_GCTL.MODE位为‘0’,使模块功能为从模式;
  3. 配置寄存器SPI_I2S_I2SCFGR中的 I2SDIV[8:0]、 DATLEN 和 CHLEN 位,以符合希望得到的音频采样频率及数据包帧格式;
  4. 配置SPI_I2S_I2SCFGR.SPI_I2S位为‘1’,使能 I2S 传输功能;
  5. 配置寄存器SPI_I2S_I2SCFGR中的I2SSTD[1: 0]、 PCMSYNC 位,选择I2S传输时使用的通信标准;
  6. 配置SPI_I2S_GCTL.DMAMODE 位为‘1’,以启用 DMA 传输;
  7. 开启半双工传输许可, 即配置寄存器SPI_I2S_GCTL 中的 TXEN 或 RXEN 位为‘1’ (TXEN、 RXEN不可同时配置为‘1’)。

注意:从模式下发送时,在检测到WS的边沿之前,需要对寄存器SPI_I2S_TXREG进行1次数据写入操作;而且,从模式下接收时,在配置RXEN位为‘1’之前,需要一直维持WS输入信号在高电平。

I2S从机的初始化代码如下所示:

void I2S2_Slave_Init(void)
{
    RCC_APB1PeriphClockCmd(RCC_APB1ENR_SPI2, ENABLE); /* Enable SPI2_I2S Clock */

    SPI2->CCTL &= ~SPI_CCTL_LSBFE;  /* MSB first enable */
    SPI2->CCTL &= ~SPI_CCTL_CPHA;   /* Clock phase select start second clock */    
    SPI2->CCTL |= SPI_CCTL_CPHASEL; /* CPHA polarity select start second clock */
    SPI2->CCTL |= SPI_CCTL_SPILEN;  /* SPI character length 8bit data */
    SPI2->CCTL |= SPI_CCTL_CPOL;    /* Clock polarity select high */
    SPI2->CCTL |= SPI_CCTL_TXEDGE;  /* Transmit data edge for i2s bus */

    SPI2->I2SCFGR &= ~SPI_I2SCFGR_MCKOE; /* I2S master clock output disable */  
    SPI2->I2SCFGR &= ~SPI_I2SCFGR_CHLEN; /* Vocal tract length 16bit */    
    SPI2->I2SCFGR |= SPI_I2SCFGR_DATLEN_32; /* Audio data width 32 */
    SPI2->I2SCFGR |= SPI_I2SCFGR_I2SSTD_Philips; /* I2S STD Philips */
    SPI2->I2SCFGR |= SPI_I2SCFGR_SPI_I2S; /* SPI/I2S module function selection */

    SPI2->GCTL &= ~SPI_GCTL_MODE;           /* I2S Slave mode */
    SPI2->GCTL |= SPI_GCTL_DW_8_32;         /* double-word data select signal */
    SPI2->GCTL |= SPI_GCTL_DMAMODE;         /* DMA access mode enable */
    SPI2->GCTL |= SPI_GCTL_TXEN;            /* I2S Transmit enable */
    SPI2->GCTL |= SPI_GCTL_INTEN;           /* SPI_I2S interrupt enable */
    SPI2->GCTL |= SPI_GCTL_SPIEN;           /* Enable I2S */    
}

7. I2S发送DAC音频信号流

MM32F0163D7P的I2S2发送DAC音频信号流使用DMA中断发送,代码接口如下所示:

void I2S2_TxData_DMA_Interrupt(uint8_t *Buffer, uint8_t datasize)
{    
    DMA_InitTypeDef  DMA_InitStruct;
    NVIC_InitTypeDef NVIC_InitStruct;

    RCC_DMA_ClockCmd(DMA1, ENABLE);/* Enable DMA1 Clock */

    DMA_DeInit(DMA1_Channel5);

    DMA_StructInit(&DMA_InitStruct);                            DMA_InitStruct.DMA_PeripheralBaseAddr = (uint32_t)&(SPI2->TXREG);   /* SPI2_I2S2 BaseAddr */    
    DMA_InitStruct.DMA_MemoryBaseAddr     = (uint32_t)(Buffer);         /* Memory buffer for music data*/    
    DMA_InitStruct.DMA_DIR                = DMA_DIR_PeripheralDST;    
    DMA_InitStruct.DMA_BufferSize         = datasize;                   /* Left and Right channel audio buffer size */    
    DMA_InitStruct.DMA_PeripheralInc      = DMA_PeripheralInc_Disable;    
    DMA_InitStruct.DMA_MemoryInc          = DMA_MemoryInc_Enable;       /* memory increment */    
    DMA_InitStruct.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord; /* half word transfer */    
    DMA_InitStruct.DMA_MemoryDataSize     = DMA_MemoryDataSize_HalfWord;    /* half word transfer */    
    DMA_InitStruct.DMA_Mode               = DMA_Mode_Normal;                /* Normal mode */    
    DMA_InitStruct.DMA_Priority           = DMA_Priority_Medium;            /* DMA Priority Medium */    
    DMA_InitStruct.DMA_M2M                = DMA_M2M_Disable;                /* Disable memory to memory transfer */    
    DMA_InitStruct.DMA_Auto_reload        = DMA_Auto_Reload_Enable;         /* Enable auto reload */    
    DMA_Init(DMA1_Channel5, &DMA_InitStruct);    
    DMA_ClearFlag(DMA1_FLAG_TC5);   /* Clear transfer complete flag */    
    DMA_ITConfig(DMA1_Channel5, DMA_IT_TC, ENABLE); /* Enable DMA Channel5 SPI2_I2S2 DMA transfer complete interrupt */     NVIC_InitStruct.NVIC_IRQChannel = DMA1_Channel4_7_IRQn; /* Set SPI2_I2S2 DMA Channel NVIC intterrupt priority */    
    NVIC_InitStruct.NVIC_IRQChannelPriority = 0x01;    NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;            /* Enable NVIC IRQChannel */    
    NVIC_Init(&NVIC_InitStruct); 

    DMA_Cmd(DMA1_Channel5, ENABLE); /* Enable SPI2_I2S2 DMA Channel5 */    
    SPI_DMACmd(SPI1, ENABLE);       /* Enable SPI_I2S */   

    while (0 == I2S2_TX_DMA_InterruptFlag)      
    {    
    }
}

I2S的DMA中断处理函数:

void DMA1_Channel4_7_IRQHandler(void)
{    
    if(SET == DMA_GetITStatus(DMA1_IT_TC5))    
    {        
    /* I2S2 DMA interrupt transfer flag */        
    I2S2_TX_DMA_InterruptFlag = 1;

    /* Clear transfer complete interrupt flag */        
    DMA_ClearITPendingBit(DMA1_IT_TC5); 

    /* Enable SPI2_I2S2 DMA Channel5 */        
    DMA_Cmd(DMA1_Channel5, ENABLE);    
    }
}

8. MCU输出12MHz的PWM给NAU88C22音频编解码芯片

配置MM32F0163D7P TIM1输出12MHz的PWM给NAU88C22合成12.228MHz时钟给NAU88C22的IMCLK和MCU的I2S,可参考MM32F0160_Samples中的TIM1 PWM输出例程。

9. SPI1接口的GPIO驱动25WQ80存储器

SPI1接口的GPIO初始化代码如下所示:

void SPI1_GPIO_Init(void)
{
    GPIO_InitTypeDef GPIO_InitStruct;

    RCC_GPIO_ClockCmd(GPIOA, ENABLE);
    RCC_GPIO_ClockCmd(GPIOB, ENABLE);

    GPIO_PinAFConfig(GPIOA, GPIO_PinSource15, GPIO_AF_0);  /* PA15 AF SPI1_CS */
    GPIO_PinAFConfig(GPIOB, GPIO_PinSource3, GPIO_AF_0);   /* PB3 AF SPI1_SCK */
    GPIO_PinAFConfig(GPIOB, GPIO_PinSource4, GPIO_AF_0);   /* PB5 AF SPI1_MOSI */
    GPIO_PinAFConfig(GPIOB, GPIO_PinSource5, GPIO_AF_0);   /* PB4 AF SPI1_MISO */

    GPIO_StructInit(&GPIO_InitStruct);
    GPIO_InitStruct.GPIO_Pin   = GPIO_Pin_15;               /* PA15 SPI1_CS */
    GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_InitStruct.GPIO_Mode  = GPIO_Mode_AF_PP;
    GPIO_Init(GPIOA, &GPIO_InitStruct);

    GPIO_InitStruct.GPIO_Pin   = GPIO_Pin_3;                /* PB3 SPI1_CS */
    GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_InitStruct.GPIO_Mode  = GPIO_Mode_AF_PP;
    GPIO_Init(GPIOB, &GPIO_InitStruct);

    GPIO_InitStruct.GPIO_Pin   = GPIO_Pin_5;                /* PB5 SPI1_CS */
    GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_InitStruct.GPIO_Mode  = GPIO_Mode_AF_PP;
    GPIO_Init(GPIOB, &GPIO_InitStruct);

    GPIO_InitStruct.GPIO_Pin   = GPIO_Pin_4;                /* PB4 SPI1_CS */
    GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_InitStruct.GPIO_Mode  = GPIO_Mode_IPU;
    GPIO_Init(GPIOB, &GPIO_InitStruct);
}

SPI1接口驱动25WQ80存储器,存储待读取的音频信号,经过SPI1读取后通过I2S2接口传输音频信号流到NAU88C22音频编解码芯片解码播放音乐,其初始化代码如下所示:

void SPI1_NVIC_Config(uint16_t spi_baud_div)
{    
    SPI_InitTypeDef SPI_InitStruct;    
    NVIC_InitTypeDef NVIC_InitStruct;

    /* SPI1 NVIC Priority Config */    
    NVIC_InitStruct.NVIC_IRQChannel = SPI1_IRQn;    
    NVIC_InitStruct.NVIC_IRQChannelPriority = 0x01;    
    NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;    
    NVIC_Init(&NVIC_InitStruct);

    RCC_SPI_ClockCmd(SPI1, ENABLE);     /* Enable SPI1 Clock */  
    SPI_DeInit(SPI1);    
    SPI_StructInit(&SPI_InitStruct);    
    SPI_InitStruct.SPI_Mode = SPI_Mode_Master;  /* SPI master mode */    
    SPI_InitStruct.SPI_DataSize  = SPI_DataSize_8b;    
    SPI_InitStruct.SPI_DataWidth = 8;    
    SPI_InitStruct.SPI_CPOL = SPI_CPOL_Low;     /* The clock is low in idle state. */    
    SPI_InitStruct.SPI_CPHA = SPI_CPHA_1Edge;   /* Data sampling starts from the first clock edge */    
    SPI_InitStruct.SPI_NSS  = SPI_NSS_Soft; 

    /* SPI data edge adjust in fast speed mode */    
    SPI_InitStruct.SPI_BaudRatePrescaler = (SPI_BaudRatePrescaler_TypeDef)spi_baud_div    ;    
    SPI_InitStruct.SPI_FirstBit = SPI_FirstBit_MSB; /* Data transfers start from MSB */    
    SPI_Init(SPI1, &SPI_InitStruct); 

    if(SPI_InitStruct.SPI_BaudRatePrescaler <= 8)    
    {        
    exSPI_DataEdgeAdjust(SPI1, SPI_DataEdgeAdjust_FAST);    
    }    
    /* Enable Receive available data interrupt and transmitter empty interrupt */    
    SPI_ITConfig(SPI1, SPI_IT_RX | SPI_IT_TXEPT, ENABLE);

    SPI_BiDirectionalLineConfig(SPI1, SPI_Direction_Rx);    /* Receive enable */    
    SPI_BiDirectionalLineConfig(SPI1, SPI_Direction_Tx);    /* Transmit enable */    
    SPI_Cmd(SPI1, ENABLE);                                  /* Enable SPI1 */
}

SPI1读写25WQ80存储器函数接口请参考MM32F0160_Samples中的SPI_FLASH_Interrupt例程。

10. I2C接口的初始化

I2C1主机模式驱动NAU88C22音频编解码芯片用于发送指令控制NAU88C22音频编解码芯片工作。I2C1接口的GPIO初始化代码如下所示:

void I2C1_GPIO_Init(void)
{    
    GPIO_InitTypeDef GPIO_InitStruct;

    RCC_GPIO_ClockCmd(GPIOB, ENABLE);

    GPIO_PinAFConfig(GPIOB, GPIO_PinSource10, GPIO_AF_1);  /* PB10 AF I2C1_SCL */    
    GPIO_PinAFConfig(GPIOB, GPIO_PinSource11, GPIO_AF_1);  /* PB10 AF I2C1_SDA */

    GPIO_StructInit(&GPIO_InitStruct);    
    GPIO_InitStruct.GPIO_Pin = GPIO_Pin_10;         /* PB10 I2C1_SCL */    
    GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;

    /* Keep the bus free which means SCK & SDA is high */ 
    GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_OD;    
    GPIO_Init(GPIOB, &GPIO_InitStruct);

    GPIO_InitStruct.GPIO_Pin = GPIO_Pin_11;        /* PB11 I2C1_SDA */    
    GPIO_Init(GPIOB, &GPIO_InitStruct);
}

I2C1接口作为主机驱动NAU88C22音频编解码芯片工作,其初始化代码如下所示:

void I2C_Master_Mode_Init(I2C_TypeDef *I2Cx, uint32_t I2C_speed)
{    
    I2C_InitTypeDef I2C_InitStruct;    
    RCC_I2C_ClockCmd(I2C1, ENABLE);      /* Enable I2C clock */

    I2C_StructInit(&I2C_InitStruct);    
    I2C_InitStruct.Mode = I2C_CR_MASTER;    /* Configure I2C as master mode */    
    I2C_InitStruct.OwnAddress = 0; 

    if (I2C_speed > 200000)                /* more than 200K */{        
        I2C_InitStruct.Speed = I2C_CR_SPEED_FAST;   /* I2C fast speed mode */    
    }    
    else
    {        
    I2C_InitStruct.Speed = I2C_CR_SPEED_STD;    /* I2C standard speed mode */    
    }

    I2C_InitStruct.ClockSpeed = I2C_speed;          /* I2C Speed */ 

    I2C_Init(I2Cx, &I2C_InitStruct);    
    I2C_Cmd(I2Cx, ENABLE);
}

I2C1设置从机地址代码如下所示:

void I2C_Set_DeviceAddr(I2C_TypeDef *I2Cx, uint8_t deviceaddr)
{    
    /* Disable I2C */    
    I2C_Cmd(I2Cx, DISABLE);

    /* Set the device address */    
    I2C_Send7bitAddress(I2Cx, deviceaddr, I2C_Direction_Transmitter);

    /* Enable I2C */    
    I2C_Cmd(I2Cx, ENABLE);
}

I2C1的读写数据和命令的操作参考LibSamples_MM32F0160 I2C例程。

11. 编译I2S程序烧录烧录到开发板中播放音乐

编译MM32F0163D7P的I2S2程序并烧录到开发板中实现音乐播放功能。

实验1:播放三角波测试文件实测输出波形

image.png

实验2:播放48KHz/24BIT立体声WAVE格式音乐文件实测输出波形

image.png

作者:灵动MM32
文章来源:灵动MM32MCU

推荐阅读

更多MM32F5系列资料请关注灵动MM32 MCU专栏。如想进行MM32相关芯片技术交流,请添加极术小姐姐微信(id:aijishu20)加入微信群。
推荐阅读
关注数
6144
内容数
276
灵动MM32 MCU相关技术知识,欢迎关注~
目录
极术微信服务号
关注极术微信号
实时接收点赞提醒和评论通知
安谋科技学堂公众号
关注安谋科技学堂
实时获取安谋科技及 Arm 教学资源
安谋科技招聘公众号
关注安谋科技招聘
实时获取安谋科技中国职位信息