5.17 I2S 使用说明

本章说明 N32H49x 标准外设库中 I2S 的驱动文件、11 个 I2S_ 前缀公开函数,以及 I2S_DMA、I2S_Fullduplex、I2S_Interrupt、SPI_I2S_Switch 共 4 个示例。I2S 与 SPI 共用 n32h49x_spi.h、n32h49x_spi.c,数据收发、DMA、中断与状态查询使用的 SPI_I2S_ 共用函数收录在 SPI 篇。音频协议、时钟发生器与开关流程请配合用户手册的「串行外设接口/内置音频总线(SPI/I2S)」章节阅读。

5.17.1 相关文件

相对路径 作用
firmware/n32h49x_std_periph_driver/inc/n32h49x_spi.h 定义 I2S_InitType、I2S_EXT_InitType 结构体与主从收发模式、音频标准、数据格式、采样率等宏,并声明 11 个 I2S_ 前缀函数;
同一头文件的其余 32 个函数归 SPI 篇
firmware/n32h49x_std_periph_driver/src/n32h49x_spi.c 实现上述 11 个函数:线性分频计算与配置寄存器写入、结构体默认值、I2S 与 I2SEXT 的初始化与使能、收发与标志/中断状态查询
projects/n32h49x_EVAL/examples/I2S/ I2S 示例根目录,含 I2S_DMA、I2S_Fullduplex、I2S_Interrupt、SPI_I2S_Switch 4 个示例
projects/n32h49x_EVAL/examples/I2S/I2S_DMA/ 该示例的工程目录组成:MDK-ARM/、EWARM/、inc/、src/、readme.txt
projects/n32h49x_EVAL/bsp/ 4 个示例共用的板级支持文件:串口日志打印函数

5.17.2 API 参考

搜索 API共 11 个函数

以下 11 个公开函数按 n32h49x_spi.h 的声明顺序分为 2 组;每个条目给出功能摘要与 n32h49x_spi.c 中该函数正上方的注释原文。

I2S 初始化与使能

I2S_InitStruct、I2S_Enable、I2S_Init

I2S_EXT 从端初始化、收发与状态

I2S_EXT_Init、I2S_EXT_Enable、I2S_EXT_TransmitData、I2S_EXT_ReceiveData、I2S_EXT_EnableDma、I2S_EXT_EnableInt、I2S_EXT_GetStatus、I2S_EXT_GetIntStatus

void I2S_InitStruct(I2S_InitType* I2S_InitStruct)为 I2S_InitStruct 的每个成员填入默认值。
源码注释
name    I2S_InitStruct.
fun     Fills each I2S_InitStruct member with its default value.
param   I2S_InitStruct :
          - I2sMode
          - Standard
          - DataFormat
          - MCLKEnable
          - AudioFrequency
          - CLKPOL
return  none
void I2S_Enable(SPI_Module* SPIx, FunctionalState Cmd)使能或禁用指定的 SPI 外设(I2S 模式)。
源码注释
name    I2S_Enable.
fun     Enables or disables the specified SPI peripheral (in I2S mode).
param   SPIx :
          - SPI2
          - SPI3
param   Cmd :
          - ENABLE
          - DISABLE
return  none
void I2S_Init(SPI_Module* SPIx,const I2S_InitType* I2S_InitStruct)根据 I2S_InitStruct 中指定的参数初始化 SPIx 外设。
源码注释
name    I2S_Init.
fun     Initializes the SPIx peripheral according to the specified parameters in the I2S_InitStruct.
param   SPIx :
          - SPI2
          - SPI3
param   I2S_InitStruct :
          - I2sMode
           - I2S_MODE_SlAVE_TX
           - I2S_MODE_SlAVE_RX
           - I2S_MODE_MASTER_TX
           - I2S_MODE_MASTER_RX
          - Standard
           - I2S_STD_PHILLIPS
           - I2S_STD_MSB_ALIGN
           - I2S_STD_LSB_ALIGN
           - I2S_STD_PCM_SHORTFRAME
           - I2S_STD_PCM_LONGFRAME
          - DataFormat
           - I2S_DATA_FMT_16BITS
           - I2S_DATA_FMT_16BITS_EXTENDED
           - I2S_DATA_FMT_24BITS
           - I2S_DATA_FMT_32BITS
          - MCLKEnable
           - I2S_MCLK_ENABLE
           - I2S_MCLK_DISABLE
          - AudioFrequency
           - I2S_AUDIO_FREQ_192K
           - I2S_AUDIO_FREQ_96K
           - I2S_AUDIO_FREQ_48K
           - I2S_AUDIO_FREQ_44K
           - I2S_AUDIO_FREQ_32K
           - I2S_AUDIO_FREQ_22K
           - I2S_AUDIO_FREQ_16K
           - I2S_AUDIO_FREQ_11K
           - I2S_AUDIO_FREQ_8K
           - I2S_AUDIO_FREQ_DEFAULT
          - CLKPOL
           - I2S_CLKPOL_LOW
           - I2S_CLKPOL_HIGH
          - ClkSrcFrequency
           - RCC_Clocks.SysclkFreq
           - RCC_Clocks.HclkFreq
           - RCC_Clocks.Pclk1Freq
           - RCC_Clocks.Pclk2Freq
           - RCC_Clocks.AdcPllClkFreq
           - RCC_Clocks.AdcHclkFreq
           - RCC_Clocks.extpllFreq
           - user defined
void I2S_EXT_Init(I2S_EXT_Module* I2Sx_EXT,const I2S_InitType* I2S_EXT_InitStruct)根据 I2S_EXT_InitStruct 中指定的参数初始化 I2Sx_EXT 外设。
源码注释
name    I2S_EXT_Init.
fun     Initializes the I2Sx_EXT peripheral according to the specified parameters in the I2S_EXT_InitStruct.
param   I2Sx_EXT :
          - I2S2_EXT
          - I2S3_EXT
param   I2S_InitStruct:
          - I2sMode
           - I2S_MODE_SlAVE_TX
           - I2S_MODE_SlAVE_RX
           - I2S_MODE_MASTER_TX
           - I2S_MODE_MASTER_RX
          - Standard
           - I2S_STD_PHILLIPS
           - I2S_STD_MSB_ALIGN
           - I2S_STD_LSB_ALIGN
           - I2S_STD_PCM_SHORTFRAME
           - I2S_STD_PCM_LONGFRAME
          - DataFormat
           - I2S_DATA_FMT_16BITS
           - I2S_DATA_FMT_16BITS_EXTENDED
           - I2S_DATA_FMT_24BITS
           - I2S_DATA_FMT_32BITS
          - CLKPOL
           - I2S_CLKPOL_LOW
           - I2S_CLKPOL_HIGH
return  none
void I2S_EXT_Enable(I2S_EXT_Module* I2Sx_EXT, FunctionalState Cmd)使能或禁用指定的 I2S EXT 外设。
源码注释
name    I2S_EXT_Enable.
fun     Enables or disables the specified I2S EXT peripheral.
param   I2Sx_EXT :
          - I2S2_EXT
          - I2S3_EXT
param   Cmd :
          - ENABLE
          - DISABLE
return  none
void I2S_EXT_TransmitData(I2S_EXT_Module* I2Sx, uint16_t Data)通过 I2Sx_EXT 外设发送一个数据。
源码注释
name    I2S_EXT_TransmitData.
fun     Transmits a Data through the I2Sx_EXT peripheral.
param   I2Sx :
                 - I2S2_EXT
                 - I2S3_EXT
param   Data            \* Data to be transmitted *\
return  none
uint16_t I2S_EXT_ReceiveData(const I2S_EXT_Module* I2Sx)返回 I2Sx_EXT 外设最近接收到的数据。
源码注释
name    I2S_EXT_ReceiveData.
fun     Returns the most recent received data by the I2Sx_EXT peripheral.
param   I2Sx :
                 - I2S2_EXT
                 - I2S3_EXT
return  The data in the I2S_DAT register.
void I2S_EXT_EnableDma(I2S_EXT_Module* I2Sx, uint16_t I2S_EXT_DMAReq, FunctionalState Cmd)使能或禁用 I2Sx 的 DMA 接口。
源码注释
name    I2S_EXT_EnableDma.
fun     Enables or disables the I2Sx DMA interface.
param   I2Sx :
                 - I2S2_EXT
                 - I2S3_EXT
param   I2S_EXT_DMAReq :
         - I2S_EXT_DMA_TX
         - I2S_EXT_DMA_RX
param   Cmd :
         - ENABLE
         - DISABLE
return  none
void I2S_EXT_EnableInt(I2S_EXT_Module* I2Sx, uint8_t I2S_EXT_IT, FunctionalState Cmd)使能或禁用指定的 I2S_EXT 中断。
源码注释
name    I2S_EXT_EnableInt.
fun     Enables or disables the specified I2S_EXT interrupts.
param   I2Sx :
                 - I2S2_EXT
                 - I2S3_EXT
param   I2S_EXT_IT :
         - I2S_EXT_INT_TEINTEN
         - I2S_EXT_INT_RNEINTEN
         - I2S_EXT_INT_ERRINTEN
param   Cmd :
         - ENABLE
         - DISABLE
return  none
FlagStatus I2S_EXT_GetStatus(const I2S_EXT_Module* I2Sx, uint16_t I2S_EXT_FLAG)检查指定的 I2S_EXT 标志位是否置位。
源码注释
name    I2S_EXT_GetStatus.
fun     Checks whether the specified I2S_EXT flag is set or not.
param   I2Sx :
                 - I2S2_EXT
                 - I2S3_EXT
param   I2S_EXT_FLAG :
                 - I2S_EXT_BUSY_FLAG
                 - I2S_EXT_OVER_FLAG
                 - I2S_EXT_UNDER_FLAG
                 - I2S_EXT_CHSIDE_FLAG
                 - I2S_EXT_TE_FLAG
                 - I2S_EXT_RNE_FLAG
return  The new state of I2S_EXT_FLAG (SET or RESET).
INTStatus I2S_EXT_GetIntStatus(const I2S_EXT_Module* I2Sx, uint8_t I2S_EXT_IT)检查指定的 I2S_EXT 中断是否发生。
源码注释
name    I2S_EXT_GetIntStatus.
fun     Checks whether the specified I2S_EXT interrupt has occurred or not.
param   I2Sx :
                 - I2S2_EXT
                 - I2S3_EXT
param   I2S_EXT_IT :
         - I2S_EXT_TE_FLAG
         - I2S_EXT_RNE_FLAG
         - I2S_EXT_BUSY_FLAG
         - I2S_EXT_OVER_FLAG
         - I2S_EXT_UNDER_FLAG
         - I2S_EXT_CHSIDE_FLAG
return  The new state of I2S_EXT_IT (SET or RESET).

5.17.3 Demo 示例说明

本节说明 I2S 的 4 个示例,都在单块评估板上用 I2S2 与 I2S3 对测:I2S_DMA 用 DMA 收发,I2S_Fullduplex 用 I2S 与 I2SEXT 组合成全双工,I2S_Interrupt 用 TE、RNE 中断收发,SPI_I2S_Switch 在 I2S 与 SPI 两种模式间切换。I2S2 固定使用 CK:PC7、WS:PC6、SD:PC9,I2S3 使用 CK:PC3、SD:PC1 与 WS:PC2,两两之间需用跳线连接。

5.17.3.1 I2S_DMA DMA 收发

工程目录: projects/n32h49x_EVAL/examples/I2S/I2S_DMA/

使能 GPIO、DMA 与两个 I2S 的时钟→配置从机接收与主机发送的 DMA 通道→配置两个 I2S 的音频参数→使能收发请求、DMA 通道与 I2S→等两条 DMA 完成→比对接收数据并打印结果

项目 源码解读
功能 I2S3 作主机发送、I2S2 作从机接收,经 DMA2_CH2 与 DMA1_CH4 在单板上互传 32 个 16 位数据,收完后比对并打印结果。
源码组成 src/main.c:发送缓冲区 I2S_MASTER_Buffer_Tx、接收缓冲区 I2S_SLAVE_Buffer_Rx、main 与 GPIO_Configuration、DMA_Configuration、I2S_Configuration、Buffercmp;
src/n32h49x_it.c、inc/n32h49x_it.h:中断服务函数;
inc/main.h:SPI2/SPI3、引脚、复用与 DMA 宏;
readme.txt:功能、环境与接线说明;
MDK-ARM/、EWARM/:Keil 与 IAR 工程。
关键配置 I2S 为 I2S_STD_PHILLIPS、I2S_DATA_FMT_16BITS_EXTENDED、I2S_AUDIO_FREQ_48K、I2S_CLKPOL_LOW、I2S_MCLK_DISABLE,I2S3 主发送、I2S2 从接收,时钟源为系统时钟;
DMA 为半字宽度、外设地址不递增、存储器地址递增、普通模式、最高优先级,I2S3 发送用 DMA2_CH2、I2S2 接收用 DMA1_CH4;
引脚 I2S2 CK:PC7、WS:PC6、SD:PC9(AF13),I2S3 CK:PC3、SD:PC1、WS:PC2(AF2、AF1、AF12);
系统时钟为 HSI+PLL、240 MHz。
程序与数据路径 main 配置 GPIO、DMA 与 I2S 后使能两个 DMA 通道;
I2S_Configuration 内已打开收发 DMA 请求并使能 I2S2、I2S3;
DMA2_CH2 把 I2S_MASTER_Buffer_Tx 写入 SPI3->DAT,数据经 I2S3 发出、由 I2S2 接收后写入 I2S_SLAVE_Buffer_Rx;
main 等两条 DMA 完成标志后调用 Buffercmp 比对并打印结果。
主要 API I2S_InitStruct、I2S_Init、I2S_Enable、SPI_I2S_DeInit、SPI_I2S_EnableDma、DMA_Init、DMA_RequestRemap、DMA_EnableChannel、DMA_GetFlagStatus、RCC_ConfigI2S2Clk、RCC_ConfigI2S3Clk
验证方法 按 readme 用跳线把 I2S2 与 I2S3 的 WS、SCK、SD 对应相连;
下载复位运行后 TransferStatus 为 PASSED,串口打印 Test PASS!! 表示收发数据一致。
使用提示 须用跳线在同一块板上连接 I2S2 与 I2S3;
两个 I2S 都以系统时钟为源,须在初始化前配置 I2S 时钟源。
基于 N32H497ZGL7_STB 评估板;
开发环境为 KEIL MDK-ARM 5.34 或 IAR EWARM 8.50.1。
核心代码解读与移植

示例在单块板上把 I2S3 配成主发送、I2S2 配成从接收,用两条 DMA 通道把发送缓冲区的数据搬到接收缓冲区。

数据路径: I2S_MASTER_Buffer_Tx → DMA2_CH2 → SPI3->DAT → I2S3 移位寄存器 → PC1(SD)、PC3(CK)、PC2(WS)→ I2S2 移位寄存器 → SPI2->DAT → DMA1_CH4 → I2S_SLAVE_Buffer_Rx → Buffercmp

1. 先选时钟源再初始化 I2S。 两个 I2S 都以系统时钟为源,须在 I2S_Init 之前完成时钟源选择。

src/main.csrc/main.c · I2S_Configuration
    /* CLK source must be configured before I2S initialization */
    RCC_ConfigI2S2Clk(RCC_I2S2_CLKSRC_SYSCLK);  /* I2S2(I2S_SLAVE) clk source select */
    RCC_ConfigI2S3Clk(RCC_I2S3_CLKSRC_SYSCLK);  /* I2S3(I2S_MASTER) clk source select */

改用其它时钟源时,ClkSrcFrequency 要写入所选源的实际频率。

2. 配置从机接收的 DMA 通道。 方向为外设到存储器,从 SPI2->DAT 读半字写入接收缓冲区,并映射到 I2S2 的接收请求。

src/main.csrc/main.c · DMA_Configuration
    /* I2S_SLAVE_Rx_DMA_Channel configuration ---------------------------------------------*/
    DMA_DeInit(I2S_SLAVE_Rx_DMA_Channel);
    DMA_StructInit(&DMA_InitStructure);
    DMA_InitStructure.PeriphAddr     = (uint32_t)&I2S_SLAVE->DAT;
    DMA_InitStructure.MemAddr        = (uint32_t)I2S_SLAVE_Buffer_Rx;
    DMA_InitStructure.Direction      = DMA_DIR_PERIPH_SRC;
    DMA_InitStructure.BufSize        = BufferSize;
    DMA_InitStructure.PeriphInc      = DMA_PERIPH_INC_DISABLE;
    DMA_InitStructure.MemoryInc      = DMA_MEM_INC_ENABLE;
    DMA_InitStructure.PeriphDataSize = DMA_PERIPH_DATA_WIDTH_HALFWORD;;
    DMA_InitStructure.MemDataSize    = DMA_MEM_DATA_WIDTH_HALFWORD;
    DMA_InitStructure.CircularMode   = DMA_MODE_NORMAL;
    DMA_InitStructure.Priority       = DMA_PRIORITY_VERY_HIGH;
    DMA_InitStructure.Mem2Mem        = DMA_M2M_DISABLE;
    DMA_Init(I2S_SLAVE_Rx_DMA_Channel, &DMA_InitStructure);
    DMA_RequestRemap(I2S_SLAVE_Rx_DMA_Remap, I2S_SLAVE_Rx_DMA_Channel, ENABLE);

发送与接收用不同通道,通道号、完成标志与重映射都由 inc/main.h 的宏给出。数据宽度为半字,与 16 位扩展格式每帧一次数据寄存器访问相对应。

3. 配置主机发送的 DMA 通道。 复用同一结构体,只把方向改成存储器到外设、地址换成 SPI3->DAT 与发送缓冲区。

src/main.csrc/main.c · DMA_Configuration
    /* I2S_MASTER_Tx_DMA_Channel configuration ---------------------------------------------*/
    DMA_DeInit(I2S_MASTER_Tx_DMA_Channel);
    DMA_InitStructure.PeriphAddr = (uint32_t)&I2S_MASTER->DAT;
    DMA_InitStructure.MemAddr    = (uint32_t)I2S_MASTER_Buffer_Tx;
    DMA_InitStructure.Direction  = DMA_DIR_PERIPH_DST;
    DMA_Init(I2S_MASTER_Tx_DMA_Channel, &DMA_InitStructure);
    DMA_RequestRemap(I2S_MASTER_Tx_DMA_Remap, I2S_MASTER_Tx_DMA_Channel, ENABLE);

4. 初始化并使能两个 I2S。 I2S2 配成从接收、I2S3 配成主发送,随后打开收发 DMA 请求并使能两端。

src/main.csrc/main.c · I2S_Configuration
    /* I2S2 configuration */
    I2S_InitStructure.I2sMode = I2S_MODE_SlAVE_RX;
    I2S_Init(SPI2, &I2S_InitStructure);

    /* I2S3 configuration */
    I2S_InitStructure.I2sMode = I2S_MODE_MASTER_TX;
    I2S_Init(SPI3, &I2S_InitStructure);

    /* Enable I2S_SLAVE Rx request */
    SPI_I2S_EnableDma(I2S_SLAVE, SPI_I2S_DMA_RX, ENABLE);
    /* Enable I2S_MASTER Tx request */
    SPI_I2S_EnableDma(I2S_MASTER, SPI_I2S_DMA_TX, ENABLE);

    /* Enable the I2S2 */
    I2S_Enable(I2S_SLAVE, ENABLE);

    /* Enable the I2S3 */
    I2S_Enable(I2S_MASTER, ENABLE);

DMA 请求要先于 I2S 使能打开,否则首批数据可能来不及搬运。

对应源码: src/main.c、inc/main.h

5.17.3.2 I2S_Fullduplex I2S 与 I2SEXT 全双工

工程目录: projects/n32h49x_EVAL/examples/I2S/I2S_Fullduplex/

配置 GPIO 并释放调试引脚→按全双工组合初始化 I2S2、I2S2_EXT、I2S3、I2S3_EXT→使能扩展外设与两个 I2S→轮询收发主机与从机各 32 个数据→关闭两个 I2S 与扩展外设→分别比对两条链路并打印结果

项目 源码解读
功能 I2S3 主发送配 I2S2 从接收、I2S2_EXT 从发送配 I2S3_EXT 从接收,在同一块板上组成全双工,两端各发 32 个 16 位数据并互相校验。
源码组成 src/main.c:主机与从机的发送缓冲区 Master_Buffer_Tx、Slave_Buffer_Tx,接收缓冲区 Master_Buffer_Rx、Slave_Buffer_Rx,main 与 GPIO_Configuration、I2S_Configuration、Buffercmp;
src/n32h49x_it.c、inc/n32h49x_it.h:中断服务函数;
inc/main.h:SPI2/SPI3、引脚、复用与 AUX_SD 宏;
readme.txt:功能、环境与接线说明;
MDK-ARM/、EWARM/:Keil 与 IAR 工程。
关键配置 I2S 为 I2S_STD_PHILLIPS、I2S_DATA_FMT_16BITS_EXTENDED、I2S_AUDIO_FREQ_48K、I2S_CLKPOL_LOW、I2S_MCLK_DISABLE,时钟源为系统时钟;
I2S3 主发送配 I2S3_EXT 从接收,I2S2 从接收配 I2S2_EXT 从发送;
引脚 I2S2 CK:PC7、WS:PC6、SD:PC9 与 AUX_SD:PC2,I2S3 CK:PC3、SD:PC1、WS:PA4 与 AUX_SD:PB4;
系统时钟为 HSI+PLL、240 MHz。
程序与数据路径 main 配置 GPIO 与 I2S 后进入循环;
每轮先等 I2S3 发送缓冲空并写出主机数据,再等 I2S2_EXT 发送缓冲空并写出从机数据;
随后等 I2S2 接收非空读出从机收到的数据,等 I2S3_EXT 接收非空读出主机收到的数据,收满 32 个退出循环;
关闭 I2S 与扩展外设后,用 Buffercmp 分别比对两条链路并打印结果。
主要 API I2S_InitStruct、I2S_Init、I2S_EXT_Init、I2S_Enable、I2S_EXT_Enable、SPI_I2S_DeInit、SPI_I2S_TransmitData、SPI_I2S_ReceiveData、I2S_EXT_TransmitData、I2S_EXT_ReceiveData、SPI_I2S_GetStatus、I2S_EXT_GetStatus、RCC_ConfigI2S2Clk、RCC_ConfigI2S3Clk
验证方法 用跳线把 I2S2 与 I2S3 的 WS、CK 及两路数据线对应相连;
下载复位运行后 TransferStatus1 与 TransferStatus2 均为 PASSED,串口打印 Test PASS!!。
使用提示 I2S3 的 WS 在 PA4、I2S3_EXT 的 AUX_SD 在 PB4,须连接两路数据线;
PB4 为默认调试引脚,示例把 SWJ 重映射为仅 SWD 后才可作扩展数据线。
基于 N32H497ZGL7_STB 评估板;
开发环境为 KEIL MDK-ARM 5.34 或 IAR EWARM 8.50.1。
核心代码解读与移植

示例把 I2S 与其扩展外设组合成两对收发,用轮询在单板上完成全双工对测。

数据路径: Master_Buffer_Tx → SPI3->DAT → I2S3(主发)→ I2S2(从收)→ SPI2->DAT → Slave_Buffer_Rx;同时 Slave_Buffer_Tx → I2S2_EXT->DAT → I2S2_EXT(从发)→ I2S3_EXT(从收)→ I2S3_EXT->DAT → Master_Buffer_Rx

1. 释放调试引脚并配置引脚复用。 I2S3_EXT 的数据线落在默认调试引脚 PB4,须先把 SWJ 重映射为仅 SWD。

src/main.csrc/main.c · GPIO_Configuration
    /* GPIOB, GPIOA and GPIOC clocks enable */
    RCC_EnableAHB1PeriphClk(RCC_AHB_PERIPHEN_GPIOA | RCC_AHB_PERIPHEN_GPIOB  | RCC_AHB_PERIPHEN_GPIOC, ENABLE);
    /* Enable peripheral clocks ------------------------------------------------*/
    /* AFIO clocks enable */
    RCC_EnableAPB2PeriphClk(RCC_APB2_PERIPHEN_AFIO, ENABLE);

    GPIO_ConfigPinRemap(GPIOB_PORT_SOURCE, GPIO_PIN_SOURCE4,  GPIO_RMP_SWJ_SWD);
src/main.csrc/main.c · GPIO_Configuration
    /* Configure I2S2_EXT pins: AUX_SD:PC2---------------------------------*/
    GPIO_InitStructure.Pin            =  I2S_SLAVE_PIN_AUX_SD;
    GPIO_InitStructure.GPIO_Alternate = I2S_SLAVE_AF_AUX_SD;
    GPIO_InitPeripheral(I2S_SLAVE_GPIO_AUX_SD, &GPIO_InitStructure);
src/main.csrc/main.c · GPIO_Configuration
    /* Configure I2S3_EXT pins: AUX_SD -------------------------------------------*/
    GPIO_InitStructure.Pin       = GPIO_PIN_4;
    GPIO_InitStructure.GPIO_Alternate = GPIO_AF15;
    GPIO_InitPeripheral(GPIOB, &GPIO_InitStructure);

I2S2、I2S3 的 CK、WS、SD 与两路 AUX_SD 都按复用推挽配置,引脚宏在 inc/main.h 中给出。

2. 按全双工组合初始化 I2S 与扩展外设。 同一组音频参数依次写入 I2S2、I2S2_EXT、I2S3、I2S3_EXT,再统一使能。

src/main.csrc/main.c · I2S_Configuration
    /* I2S3 Master Transmitter to I2S2 Slave Receiver communication -----------*/
    /* I2S2 configuration */
    I2S_InitStructure.I2sMode = I2S_MODE_SlAVE_RX;
    I2S_Init(SPI2, &I2S_InitStructure);

    /* I2S2_EXT configuration */
    I2S_InitStructure.I2sMode = I2S_MODE_SlAVE_TX;
    I2S_EXT_Init(I2S2_EXT, &I2S_InitStructure);

    /* I2S3 configuration */
    I2S_InitStructure.I2sMode = I2S_MODE_MASTER_TX;
    I2S_Init(SPI3, &I2S_InitStructure);

    /* I2S3_EXT configuration */
    I2S_InitStructure.I2sMode = I2S_MODE_SlAVE_RX;
    I2S_EXT_Init(I2S3_EXT, &I2S_InitStructure);

    /* Enable the I2S3_EXT */
    I2S_EXT_Enable(I2S3_EXT, ENABLE);
    /* Enable the I2S2_EXT */
    I2S_EXT_Enable(I2S2_EXT, ENABLE);

    /* Enable the I2S3 */
    I2S_Enable(SPI3, ENABLE);
    /* Enable the I2S2 */
    I2S_Enable(SPI2, ENABLE);

3. 每轮先发后收。 主机侧与从机侧各发一个数据,再从两条接收通道各取回一个数据,收发都在主循环里轮询完成。

src/main.csrc/main.c · main
        //master send
        /* Wait the Tx buffer to be empty */
        while (SPI_I2S_GetStatus(SPI3, SPI_I2S_TE_FLAG) == RESET)
        {
        }

        /* Send a data from I2S3 */
        SPI_I2S_TransmitData(SPI3, Master_Buffer_Tx[TxIdx]);

        //slave send
        /* Wait the Tx buffer to be empty */
        while (I2S_EXT_GetStatus(I2S2_EXT, I2S_EXT_TE_FLAG) == RESET)
        {
        }

        /* Send a data from I2S2_EXT */
        I2S_EXT_TransmitData(I2S2_EXT, Slave_Buffer_Tx[TxIdx++]);
src/main.csrc/main.c · main
        //slave recv
        /* Wait the Rx buffer to be full */
        while (SPI_I2S_GetStatus(SPI2, SPI_I2S_RNE_FLAG) == RESET)
        {
        }

        /* Store the I2S2 received data in the relative data table */
        Slave_Buffer_Rx[RxIdx] = SPI_I2S_ReceiveData(SPI2);

        //master recv
        /* Wait the Rx buffer to be full */
        while (I2S_EXT_GetStatus(I2S3_EXT, I2S_EXT_RNE_FLAG) == RESET)
        {
        }

        /* Store the I2S2 received data in the relative data table */
        Master_Buffer_Rx[RxIdx++] = I2S_EXT_ReceiveData(I2S3_EXT);

轮询以发送缓冲空、接收缓冲非空为条件,因此主循环的节奏由两端的数据流共同决定。

4. 关闭外设并比对两条链路。 收发结束后先关闭两个 I2S 与两个扩展外设,再分别比对从机侧与主机侧收到的数据。

src/main.csrc/main.c · main
    /* Disable the I2S3 */
    I2S_Enable(SPI3, DISABLE);
    /* Disable the I2S2 */
    I2S_Enable(SPI2, DISABLE);

    /* Enable the I2S3_EXT */
    I2S_EXT_Enable(I2S3_EXT, DISABLE);
    /* Enable the I2S2_EXT */
    I2S_EXT_Enable(I2S2_EXT, DISABLE);

    TransferStatus1 = Buffercmp((uint16_t*)Slave_Buffer_Rx, Master_Buffer_Tx, BufferSize);

对应源码: src/main.c、inc/main.h

5.17.3.3 I2S_Interrupt 中断收发

工程目录: projects/n32h49x_EVAL/examples/I2S/I2S_Interrupt/

配置 SPI3 与 SPI2 的中断优先级→配置引脚→第一阶段按 16 位、48K 初始化并使能收发中断→中断里逐项收发,收满 32 个后比对→清零缓冲后第二阶段换成 24 位、16K 再传一轮→用掩码比较并打印结果

项目 源码解读
功能 以发送缓冲空中断驱动 I2S3 主发送、接收缓冲非空中断驱动 I2S2 从接收,先在 16 位、48K 下传 32 个数据,再重配为 24 位、16K 传一轮,两次分别比对并打印结果。
源码组成 src/main.c:发送缓冲区 I2S3_Buffer_Tx、接收缓冲区 I2S2_Buffer_Rx、main 与 GPIO_Configuration、NVIC_Configuration、I2S_Configuration_1、I2S_Configuration_2、Buffercmp、Buffercmp24bits;
src/n32h49x_it.c:SPI3_I2S3_IRQHandler 与 SPI2_I2S2_IRQHandler;
inc/main.h:SPI2/SPI3、引脚与复用宏;
inc/n32h49x_it.h:中断函数声明;
readme.txt:功能、环境与现象说明;
MDK-ARM/、EWARM/:Keil 与 IAR 工程。
关键配置 第一阶段为 I2S_DATA_FMT_16BITS_EXTENDED、I2S_AUDIO_FREQ_48K,第二阶段为 I2S_DATA_FMT_24BITS、I2S_AUDIO_FREQ_16K,均为 I2S_STD_PHILLIPS、I2S_CLKPOL_LOW、I2S_MCLK_DISABLE;
NVIC 优先级分组 NVIC_PriorityGroup_0,SPI3_I2S3_IRQn 抢占优先级 1、子优先级 0,SPI2_I2S2_IRQn 抢占优先级 0;
中断源为 SPI_I2S_INT_TE 与 SPI_I2S_INT_RNE;
系统时钟为 HSI+PLL、240 MHz。
程序与数据路径 main 配置 NVIC、GPIO 与第一阶段 I2S 后等待 RxIdx 达到 32;
每次发送中断用 SPI_I2S_TransmitData 写出下一个主机数据,写满 32 个后关闭该中断;
每次接收中断用 SPI_I2S_ReceiveData 存入接收缓冲区;
第一阶段比对后清零缓冲并重配为 24 位、16K 再传一轮,用 Buffercmp24bits 校验。
主要 API I2S_InitStruct、I2S_Init、I2S_Enable、SPI_I2S_DeInit、SPI_I2S_EnableInt、SPI_I2S_GetIntStatus、SPI_I2S_TransmitData、SPI_I2S_ReceiveData、NVIC_PriorityGroupConfig、NVIC_Init、RCC_ConfigI2S2Clk、RCC_ConfigI2S3Clk
验证方法 用跳线连接 I2S2 与 I2S3 的 WS、CK、SD;
下载复位运行后 TransferStatus1 与 TransferStatus2 均为 PASSED,串口打印 Test PASS!!。
使用提示 24 位数据的第二阶段发送与接收各访问两次数据寄存器,比较时允许只匹配高字节;
两个外设共用 SPI 与 I2S 的中断向量,处理函数名须与启动文件一致。
基于 N32H497ZGL7_STB 评估板;
开发环境为 KEIL MDK-ARM 5.34 或 IAR EWARM 8.50.1。
核心代码解读与移植

示例用收发中断各驱动一端,主循环只等计数,并演示 16 位与 24 位两种数据格式下的中断收发。

数据路径: I2S3_Buffer_Tx[TxIdx] → 发送中断 → SPI3_I2S3_IRQHandler 写 SPI3->DAT → I2S3 → I2S2 → 接收中断 → SPI2_I2S2_IRQHandler 读 SPI2->DAT → I2S2_Buffer_Rx[RxIdx] → Buffercmp 或 Buffercmp24bits

1. 配置两个中断向量。 优先级分组取 0,主机侧抢占优先级为 1、从机侧为 0,处理函数名须与启动文件一致。

src/main.csrc/main.c · NVIC_Configuration
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_0);

    /* SPI3 IRQ Channel configuration */
    NVIC_InitStructure.NVIC_IRQChannel                   = SPI3_I2S3_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority        = 0;
    NVIC_InitStructure.NVIC_IRQChannelCmd                = ENABLE;
    NVIC_Init(&NVIC_InitStructure);

    /* SPI2 IRQ channel configuration */
    NVIC_InitStructure.NVIC_IRQChannel                   = SPI2_I2S2_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
    NVIC_Init(&NVIC_InitStructure);

2. 先开中断再使能外设。 收发两个中断都在 I2S 使能之前打开,使能端点后发送缓冲一空就立刻触发第一次发送中断。

src/main.csrc/main.c · I2S_Configuration_1
    /* Enable the I2S3 TxE interrupt */
    SPI_I2S_EnableInt(SPI3, SPI_I2S_INT_TE, ENABLE);

    /* Enable the I2S2 RxNE interrupt */
    SPI_I2S_EnableInt(SPI2, SPI_I2S_INT_RNE, ENABLE);

    /* Enable the I2S2 */
    I2S_Enable(SPI2, ENABLE);

    /* Enable the I2S3 */
    I2S_Enable(SPI3, ENABLE);

3. 发送侧写满后关中断,接收侧逐项存入缓冲。 发送中断在写出第 32 个数据后关闭自身,接收中断每次读走一个数据并让计数加一。

src/n32h49x_it.csrc/n32h49x_it.c · SPI3_I2S3_IRQHandler
void SPI3_I2S3_IRQHandler(void)
{
    /* Check the interrupt source */
    if (SPI_I2S_GetIntStatus(SPI3, SPI_I2S_INT_TE) == SET)
    {
        /* Check the end of buffer transfer */
        if (TxIdx == 32)
        {
            /* Disable the I2S3 TXE interrupt to end the communication */
            SPI_I2S_EnableInt(SPI3, SPI_I2S_INT_TE, DISABLE);
        }
        else
        {
            /* Send a data from I2S3 */
            SPI_I2S_TransmitData(SPI3, I2S3_Buffer_Tx[TxIdx++]);
        }
    }


}
src/n32h49x_it.csrc/n32h49x_it.c · SPI2_I2S2_IRQHandler
void SPI2_I2S2_IRQHandler(void)
{
    /* Check the interrupt source */
    if (SPI_I2S_GetIntStatus(SPI2, SPI_I2S_INT_RNE) == SET)
    {
        /* Store the I2S2 received data in the relative data table */
        I2S2_Buffer_Rx[RxIdx++] = SPI_I2S_ReceiveData(SPI2);
    }
}

发送中断靠计数判断结束,接收中断不做边界判断,靠主循环等待 RxIdx 到 32。

4. 第二阶段换成 24 位并允许掩码比较。 第二阶段改用 24 位、16K,比较时数据不等则再比一次高字节,以容忍低位差异。

src/main.csrc/main.c · I2S_Configuration_2
    I2S_InitStructure.DataFormat     = I2S_DATA_FMT_24BITS;
    I2S_InitStructure.MCLKEnable     = I2S_MCLK_DISABLE;
    I2S_InitStructure.AudioFrequency = I2S_AUDIO_FREQ_16K;
    I2S_InitStructure.CLKPOL         = I2S_CLKPOL_LOW;

    /* I2S Clock source is System clock: Get System Clock frequency */
    RCC_GetClocksFreqValue(&RCC_Clocks);
src/main.csrc/main.c · Buffercmp24bits
    while (BufferLength--)
    {
        if (*pBuffer1 != *pBuffer2)
        {
            if (*pBuffer1 != (*pBuffer2 & 0xFF00))
            {
                return FAILED;
            }
        }

        pBuffer1++;
        pBuffer2++;
    }

24 位数据每帧需访问两次数据寄存器,收发两侧的读写次数要与数据格式匹配才能对齐。

对应源码: src/main.c、src/n32h49x_it.c

5.17.3.4 SPI_I2S_Switch I2S 与 SPI 切换

工程目录: projects/n32h49x_EVAL/examples/I2S/SPI_I2S_Switch/

使能两个外设时钟并配置引脚→第一步 I2S3 主发、I2S2 从收 32 个数据→复位后第二步切到 SPI 主发、从收→清零缓冲后第三步改成 I2S3 从发、I2S2 主收→三步各自比对并打印结果

项目 源码解读
功能 在同一对 SPI2 与 SPI3 上依次用 I2S 主发从收、SPI 单线主发双线从收、I2S 从发主收三种方式各传 32 个 16 位数据,每步比对并打印结果。
源码组成 src/main.c:主机与从机两组发送缓冲区 I2S3_Buffer_Tx、SPI3_Buffer_Tx,接收缓冲区 I2S2_Buffer_Rx、SPI2_Buffer_Rx,main 与 GPIO_Configuration、SPI_Configuration、两套 I2S_Configuration、三个收发函数、Buffercmp;
src/n32h49x_it.c、inc/n32h49x_it.h:中断服务函数;
inc/main.h:SPI2/SPI3、引脚与复用宏;
readme.txt:功能、环境与现象说明;
MDK-ARM/、EWARM/:Keil 与 IAR 工程。
关键配置 I2S 为 I2S_STD_PHILLIPS、I2S_DATA_FMT_16BITS_EXTENDED、I2S_AUDIO_FREQ_48K、I2S_CLKPOL_LOW、I2S_MCLK_DISABLE,第一步 I2S3 主发送配 I2S2 从接收、第三步 I2S3 从发送配 I2S2 主接收;
SPI 为 SPI3 单线主机发送 SPI_DIR_SINGLELINE_TX、SPI2 双线从机只读 SPI_DIR_DOUBLELINE_RONLY、16 位、SPI_CLKPHA_SECOND_EDGE、软件 NSS、SPI_BR_PRESCALER_4、MSB 先行;
引脚 I2S2 CK:PC7、WS:PC6、SD:PC9,I2S3 CK:PC3、SD:PC1、WS:PC2;
系统时钟为 HSI+PLL、240 MHz。
程序与数据路径 main 使能两个外设时钟并配置 GPIO 后依次调用三个收发函数;
I2S3_TransmitData_funtion 让 I2S3 主发送、I2S2 从接收,轮询收发 32 个数据后关闭两端;
SPI_TransmitData_funtion 复位后按 SPI 主从重新初始化,同样轮询收发 32 个数据;
I2S2_TransmitData_funtion 改成 I2S3 从发送、I2S2 主接收再传一轮,三步分别比对打印。
主要 API I2S_InitStruct、I2S_Init、I2S_Enable、SPI_I2S_DeInit、SPI_InitStruct、SPI_Init、SPI_Enable、SPI_I2S_TransmitData、SPI_I2S_ReceiveData、SPI_I2S_GetStatus、RCC_ConfigI2S2Clk、RCC_ConfigI2S3Clk
验证方法 用跳线连接 I2S2 与 I2S3 的 WS、CK、SD;
下载复位运行后 TransferStatus1、TransferStatus2、TransferStatus3 均为 PASSED,串口依次打印 1 Test PASS!!、2 Test PASS!!、3 Test PASS!!。
使用提示 每次切换前都调用 SPI_I2S_DeInit 复位,避免上一种模式的配置残留;
第三步进入前先清零 I2S2_Buffer_Rx,避免第一步的残留数据被当成通过。
基于 N32H497ZGL7_STB 与 N32H492ZGL7_STB 评估板;
开发环境为 KEIL MDK-ARM 5.34 或 IAR EWARM 8.50.1。
核心代码解读与移植

示例在同一个外设上依次用 I2S 与 SPI 两种模式收发,靠复位与重新初始化切换模式。

数据路径: 第一步 I2S3_Buffer_Tx → SPI3->DAT → I2S3(主发)→ I2S2(从收)→ SPI2->DAT → I2S2_Buffer_Rx;第二步 SPI3_Buffer_Tx → SPI3->DAT → SPI3(主发)→ SPI2(从收)→ SPI2->DAT → SPI2_Buffer_Rx;第三步 I2S3_Buffer_Tx → SPI3->DAT → I2S3(从发)→ I2S2(主收)→ SPI2->DAT → I2S2_Buffer_Rx

1. 主程序按三步切换并重置计数与缓冲。 每步之间复位发送与接收计数,第二步与第三步之间还把接收缓冲清零。

src/main.csrc/main.c · main
    /* SPI3 and SPI2 clocks enable */
    RCC_EnableAPB1PeriphClk(RCC_APB1_PERIPHEN_SPI3 | RCC_APB1_PERIPHEN_SPI2, ENABLE);

    log_init();
    log_info("\r\n This is SPI I2S switch demo!!\r\n");
    /* GPIO configuration ------------------------------------------------------*/
    GPIO_Configuration();

    I2S3_TransmitData_funtion();

    /* Reset TxIdx, RxIdx indexes */
    TxIdx = 0;
    RxIdx = 0;

    SPI_TransmitData_funtion();

    /* Reset TxIdx, RxIdx indexes and receive table values */
    for (TxIdx = 0; TxIdx < BufferSize; TxIdx++)
    {
        I2S2_Buffer_Rx[TxIdx] = 0;
    }

    TxIdx = 0;
    RxIdx = 0;

    I2S2_TransmitData_funtion();

2. 第一步以 I2S3 主发、轮询收发。 发送缓冲空就写出下一个数据,接收缓冲非空就取回一个,收满 32 个退出。

src/main.csrc/main.c · I2S3_TransmitData_funtion
    while (RxIdx < BufferSize)
    {
        /* Wait the Tx buffer to be empty */
        while (SPI_I2S_GetStatus(SPI3, SPI_I2S_TE_FLAG) == RESET)
        {
        }

        /* Send a data from I2S3 */
        SPI_I2S_TransmitData(SPI3, I2S3_Buffer_Tx[TxIdx++]);

        /* Wait the Rx buffer to be full */
        while (SPI_I2S_GetStatus(SPI2, SPI_I2S_RNE_FLAG) == RESET)
        {
        }

        /* Store the I2S2 received data in the relative data table */
        I2S2_Buffer_Rx[RxIdx++] = SPI_I2S_ReceiveData(SPI2);
    }

3. 第二步切到 SPI 主从模式。 先复位两个外设,SPI3 配成单线主机发送、SPI2 配成双线从机只读,再使能 SPI。

src/main.csrc/main.c · SPI_Configuration
    SPI_I2S_DeInit(SPI3);
    SPI_I2S_DeInit(SPI2);

    SPI_InitStruct(&SPI_InitStructure);
    SPI_InitStructure.DataDirection = SPI_DIR_SINGLELINE_TX;
    SPI_InitStructure.SpiMode       = SPI_MODE_MASTER;
    SPI_InitStructure.DataLen       = SPI_DATA_SIZE_16BITS;
    SPI_InitStructure.CLKPOL        = SPI_CLKPOL_LOW;
    SPI_InitStructure.CLKPHA        = SPI_CLKPHA_SECOND_EDGE;
    SPI_InitStructure.NSS           = SPI_NSS_SOFT;
    SPI_InitStructure.BaudRatePres  = SPI_BR_PRESCALER_4;
    SPI_InitStructure.FirstBit      = SPI_FB_MSB;
    SPI_InitStructure.CRCPoly       = 7;
    SPI_Init(SPI3, &SPI_InitStructure);

    /* SPI2 configuration ------------------------------------------------------*/
    SPI_InitStructure.DataDirection = SPI_DIR_DOUBLELINE_RONLY;
    SPI_InitStructure.SpiMode       = SPI_MODE_SLAVE;
    SPI_Init(SPI2, &SPI_InitStructure);

    /* Enable SPI2 */
    SPI_Enable(SPI2, ENABLE);
    /* Enable SPI3 */
    SPI_Enable(SPI3, ENABLE);

4. 第三步改成 I2S3 从发、I2S2 主收并预写首个数据。 从机发送在使能后先写入一个数据,再使能主机接收,避免主机侧空等。

src/main.csrc/main.c · I2S_Configuration_2
    /* I2S3 configuration */
    I2S_InitStructure.I2sMode = I2S_MODE_SlAVE_TX;
    I2S_Init(SPI3, &I2S_InitStructure);

    /* I2S2 configuration */
    I2S_InitStructure.I2sMode = I2S_MODE_MASTER_RX;
    I2S_Init(SPI2, &I2S_InitStructure);



    /* Enable the I2S3 */
    I2S_Enable(SPI3, ENABLE);

切换到 SPI 模式后 SPI_Init 会写回 SPI 配置,从 I2S 模式切回时则要重新配置音频参数,两种模式互不影响。

对应源码: src/main.c、inc/main.h

5.17.4 注意与排障

场景 检查与建议 来源
I2S 线性分频取值受限 时钟发生器的线性分频寄存器禁止把分频系数写入 0 或 1;
数据长度位不允许配置为 11。
配置采样率时让换算出的分频系数落在允许范围内,否则音频时钟不正确。
用户手册
帧长与通道长度只在 I2S 关闭时可改 通道长度、数据长度、音频标准、I2S 配置与时钟极性只能在 I2S 关闭时配置;
通道长度位仅在数据长度为 16 位时有效,其余数据格式下帧长固定为 32 位。
用户手册
扩展 I2S(I2SEXT)用法限制 扩展 I2S 只能作从端,且配置位只能取两个从端取值;
扩展外设与对应的 I2S 共用位时钟与声道选择,仅数据线独立。
用户手册
关闭 I2S 的顺序 发送方向要等发送缓冲空、总线不忙后再清 I2S 使能位;
接收方向要按数据格式与音频标准读到指定个数后再关闭,从接收可在任意时刻关闭,但停机前等总线不忙;
主接收模式下总线忙标志始终为低,不能用作结束判据。
用户手册
24 位或 32 位数据的读写次数 16 位数据(含 16 位扩展成 32 位帧)每帧读写一次数据寄存器;
24 位与 32 位数据每帧需读写两次数据寄存器,用 DMA 搬运时长度按半字计算。
用户手册
从端上溢或下溢 从接收未及时读出旧数据又收到新数据会上溢,需按序读数据寄存器与状态寄存器清除;
从发送未及时写入数据会下溢,此时声道状态失效,须关闭 I2S 后从声道重新开始。
用户手册
国民技术开发套件文档中心

按芯片系列在线查阅国民技术 MCU 开发套件的芯片资料、SDK、驱动 API 与 Demo 示例说明。

© 2026 国民技术股份有限公司 版权所有粤ICP备11069485号本站使用不含 Cookie 的匿名访问统计