5.12 I2S 使用说明

本章说明 N32G41x 标准外设库中 I2S 驱动与示例的使用方法。I2S 与 SPI 共用 n32g41x_spi.h 与 n32g41x_spi.c,其中 4 个公开函数用于 I2S 初始化结构体的默认值填充、按采样频率计算分频并配置 I2S 模式与数据格式、I2S 使能以及 13 位 PCM 长帧旁路选择。示例覆盖 DMA 收发、中断收发与 SPI/I2S 模式切换三类。I2S 的音频标准、时钟发生器与寄存器定义请配合用户手册的“串行外设接口/内置音频总线(SPI/I2S)”章节阅读,芯片已知问题以勘误手册为准。

5.12.1 相关文件

相对路径 作用
firmware/n32g41x_std_periph_driver/inc/n32g41x_spi.h 定义 I2S_InitType 结构体与 I2S 模式、标准、数据格式、采样频率、时钟极性等宏,并在 /* I2S init */ 区段声明 4 个 I2S 函数
firmware/n32g41x_std_periph_driver/src/n32g41x_spi.c 实现上述 4 个函数:按采样频率与时钟源计算 I2SPREDIV 线性分频,写入 SPI_I2S_CFGR 的 I2S 模式、标准与数据格式字段,置位或清除 I2SEN 与 PCMBYPASS 位
projects/n32g41x_EVAL/examples/I2S/ I2S 示例根目录,含 I2S_DMA_Master、I2S_DMA_Slave、I2S_Interrupt_Master、I2S_Interrupt_Slave、SPI_I2S_Switch_Master、SPI_I2S_Switch_Slave 6 个示例
projects/n32g41x_EVAL/examples/I2S/I2S_DMA/I2S_DMA_Master/ 该示例的工程组成:MDK-ARM/、EWARM/、inc/、src/、readme.txt
projects/n32g41x_EVAL/bsp/ 各示例共用的板级支持文件:日志打印与毫秒延时函数

5.12.2 API 参考

搜索 API共 4 个函数

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

I2S 初始化

I2S_Init、I2S_InitStruct、I2S_Enable、I2S_Enable13BitPCMLongBypass

void I2S_Init(SPI_Module *SPIx, const I2S_InitType *I2S_InitParam)根据 I2S_InitStruct 中指定的参数初始化 SPIx 外设(I2S 模式)。
源码注释
name    I2S_Init.
fun     Initializes the SPIx peripheral according to the specified parameters in the I2S_InitStruct.
param   SPIx :
          - SPI1
param   I2S_InitParam :
          - 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
           - user defined
return  none
void I2S_InitStruct(I2S_InitType *I2S_StructInit)为 I2S_InitStruct 的每个成员填充默认值。
源码注释
name    I2S_InitStruct.
fun     Fills each I2S_InitStruct member with its default value.
param   I2S_StructInit :
          - 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 :
          - SPI1
param   Cmd :
          - ENABLE
          - DISABLE
return  none
void I2S_Enable13BitPCMLongBypass(SPI_Module *SPIx, FunctionalState Cmd)选择 13 位 PCM 长帧是否旁路。
源码注释
name    I2S_Enable13BitPCMLongBypass.
fun     pcm long for 13bit is bypass select.
param   SPIx (The input parameters must be the following values):
          - SPI1
param   Cmd (The input parameters must be the following values):
          - ENABLE
          - DISABLE
return  none

5.12.3 Demo 示例说明

本节说明 I2S 的 6 个示例:I2S_DMA_Master 与 I2S_DMA_Slave 用 DMA 收发数据,I2S_Interrupt_Master 与 I2S_Interrupt_Slave 用 TxE、RxNE 中断收发数据,SPI_I2S_Switch_Master 与 SPI_I2S_Switch_Slave 演示同一 SPI1 上 SPI 与 I2S 模式的切换;前两对与最后一对均为两块开发板对测。

5.12.3.1 I2S_DMA_Master DMA 主机发送

工程目录: projects/n32g41x_EVAL/examples/I2S/I2S_DMA/I2S_DMA_Master/

使能 DMA、GPIO 与 SPI1/I2S1 时钟→配置 PA4、PA5、PA7 为复用推挽→配置 DMA_CH3 搬运 32 个半字→使能 I2S 主机发送并等待 DMA 完成→串口打印发送完成

项目 源码解读
功能 I2S1 配置为主机发送,通过 DMA 发送 32 个 16 位数据,与 I2S_DMA_Slave 从机工程对测。
源码组成 src/main.c:main、RCC_Configuration、GPIO_Configuration、DMA_Configuration、I2S_Configuration;
inc/main.h:I2S 引脚、DMA 通道与时钟宏;
src/n32g41x_it.c、inc/n32g41x_it.h:中断服务函数;
readme.txt:功能与对测步骤;
MDK-ARM/、EWARM/:Keil 与 IAR 工程。
关键配置 时钟源 HSI+PLL,系统时钟 N32G412 为 80 MHz、N32G415 为 96 MHz;
I2S 时钟源为系统时钟,I2S_STD_PHILLIPS、I2S_DATA_FMT_16BITS_EXTENDED、I2S_MCLK_DISABLE、I2S_AUDIO_FREQ_48K、I2S_CLKPOL_LOW,模式为 I2S_MODE_MASTER_TX;
WS 为 PA4、CK 为 PA5、SD 为 PA7,复用 GPIO_AF0、GPIO_MODE_AF_PP、GPIO_NO_PULL;
发送用 DMA_CH3(DMA_REMAP_SPI1_TX),外设地址为 SPI1->DAT、半字传输、普通模式。
程序与数据路径 main 调用 log_init 后依次执行 RCC_Configuration、延时 1 s、GPIO_Configuration、DMA_Configuration、I2S_Configuration;
随后使能 DMA 发送请求、使能 DMA 通道与 I2S;
I2S_MASTER_Buffer_Tx 的数据经 DMA 写入 I2S 数据寄存器并从 SD 发出;
等待 DMA 传输完成、TE 置位与 BUSY 清零后打印完成信息。
主要 API SPI_I2S_DeInit、RCC_ConfigI2SClk、I2S_InitStruct、I2S_Init、I2S_Enable、SPI_I2S_EnableDma、SPI_I2S_GetStatus、DMA_Init、DMA_RequestRemap、DMA_EnableChannel、DMA_GetFlagStatus
验证方法 两块板 PA4、PA5、PA7 与 GND 一一对接,先复位从机再复位主机;
主机串口打印 I2S DMA Master TX transfer completed!,从机串口打印接收数据比对结果;
也可用逻辑分析仪观察 WS、CK、SD 波形。
使用提示 N32G41x 仅 SPI1 支持 I2S,SPI2 不支持;
I2S 时钟源配置为系统时钟,引脚与 SPI1 复用并选 AF0;
开发环境为 KEIL MDK-ARM V5.34 或 IAR EWARM 8.50.1,适用芯片 N32G412x8L7、N32G412xBL7、N32G415x8L7、N32G415xBL7。
核心代码解读与移植

示例先准备 DMA、GPIO 与 I2S 三路资源,再由 main 启动一次 DMA 发送。

数据路径: RCC_Configuration 打开时钟 → GPIO_Configuration 把 PA4、PA5、PA7 设为复用推挽 → DMA_Configuration 把 I2S_MASTER_Buffer_Tx 接到 SPI1->DAT → I2S_Configuration 写入 I2S_InitType 后调用 I2S_Init → main 使能 DMA 与 I2S → 串口输出完成信息

1. 打开 DMA、GPIO 与 SPI1/I2S1 时钟。 三个时钟缺一不可:DMA 时钟供 DMA_CH3 使用,GPIO 时钟供 PA4、PA5、PA7 的复用功能,I2S_MASTER_CLK 打开 SPI1 即 I2S1。移植到其它外设实例时要同步改这三处宏。

src/main.csrc/main.c · RCC_Configuration
void RCC_Configuration(void)
{
    /* Enable DMA clock */
    RCC_EnableAHBPeriphClk(I2S_MASTER_DMA_CLK, ENABLE);

    /* Enable GPIO clock */
    RCC_EnableAHBPeriphClk(RCC_AHB_PERIPH_GPIO, ENABLE);

    /* Enable SPI1/I2S1 clock */
    RCC_EnableAPB2PeriphClk(I2S_MASTER_CLK, ENABLE);
}

换用别的 SPI 实例或 DMA 时钟时要同步改 I2S_MASTER_CLK 与 I2S_MASTER_DMA_CLK。

2. 把 WS、CK、SD 三个引脚配为复用推挽。 GPIO_InitStruct 先取默认值,再对 PA4 设置引脚、复用号、推挽模式与无上下拉;PA5、PA7 复用同一组结构体,只改 Pin 与 GPIO_Alternate。引脚号与复用号集中在 inc/main.h 的宏里,换板时要一起改。

src/main.csrc/main.c · GPIO_Configuration
    GPIO_InitType GPIO_InitStructure;

    GPIO_InitStruct(&GPIO_InitStructure);

    /* I2S_MASTER: WS(PA4) as AF_PP */
    GPIO_InitStructure.Pin            = I2S_MASTER_WS_PIN;
    GPIO_InitStructure.GPIO_Pull      = GPIO_NO_PULL;
    GPIO_InitStructure.GPIO_Alternate = I2S_MASTER_WS_AF;
    GPIO_InitStructure.GPIO_Mode      = GPIO_MODE_AF_PP;
    GPIO_InitPeripheral(I2S_MASTER_WS_GPIO, &GPIO_InitStructure);

改为其它引脚时要同步修改 inc/main.h 中的引脚号宏与 GPIO_AF0 复用号。

3. 配置 DMA_CH3 搬运发送数据。 外设地址指向 SPI1->DAT,存储器地址指向 I2S_MASTER_Buffer_Tx,方向为外设目的,数据宽度为半字,BufSize 为 32、普通模式只发一次;DMA_RequestRemap 把 DMA_CH3 映射到 SPI1 的发送请求。缓冲区大小与数据宽度要和 I2S 的 16 位格式一致。

src/main.csrc/main.c · DMA_Configuration
    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_InitStructure.BufSize        = BufferSize;
    DMA_InitStructure.PeriphInc      = DMA_PERIPH_INC_DISABLE;
    DMA_InitStructure.DMA_MemoryInc  = DMA_MEM_INC_ENABLE;
    DMA_InitStructure.PeriphDataSize = DMA_PERIPH_DATA_SIZE_HALFWORD;
    DMA_InitStructure.MemDataSize    = DMA_MemoryDataSize_HalfWord;
    DMA_InitStructure.CircularMode   = DMA_MODE_NORMAL;
    DMA_InitStructure.Priority       = DMA_PRIORITY_VERY_HIGH;
    DMA_InitStructure.Mem2Mem        = DMA_M2M_DISABLE;
    DMA_Init(I2S_MASTER_Tx_DMA_Channel, &DMA_InitStructure);
    DMA_RequestRemap(I2S_MASTER_Tx_DMA_Remap, DMA, I2S_MASTER_Tx_DMA_Channel, ENABLE);

缓冲区长度、数据宽度与 DMA_REMAP_SPI1_TX 映射要随通道和 I2S 数据格式一并核对。

4. 按采样频率配置 I2S 主机发送。 先 SPI_I2S_DeInit 复位 SPI1/I2S1,再把时钟源设为系统时钟;I2S_InitStruct 取默认值后设置 Phillips 标准、16 位扩展格式、关闭 MCLK、48 kHz 与时钟极性低,并用 RCC_GetClocksFreqValue 取得的系统时钟频率参与分频计算,最后设置为主机发送并调用 I2S_Init。从机侧的这组参数必须与主机完全一致才能对测成功。

src/main.csrc/main.c · I2S_Configuration
    I2S_InitType I2S_InitStructure;
    RCC_ClocksType RCC_Clocks;

    SPI_I2S_DeInit(I2S_MASTER);

    /* Configure I2S clock source as system clock */
    RCC_ConfigI2SClk(RCC_I2S_CLKSEL_SYS);

    /* I2S configuration */
    I2S_InitStruct(&I2S_InitStructure);
    I2S_InitStructure.Standard       = I2S_STD_PHILLIPS;
    I2S_InitStructure.DataFormat     = I2S_DATA_FMT_16BITS_EXTENDED;
    I2S_InitStructure.MCLKEnable     = I2S_MCLK_DISABLE;
    I2S_InitStructure.AudioFrequency = I2S_AUDIO_FREQ_48K;
    I2S_InitStructure.CLKPOL         = I2S_CLKPOL_LOW;

    /* Get system clock frequency for I2S clock calculation */
    RCC_GetClocksFreqValue(&RCC_Clocks);
    I2S_InitStructure.ClkSrcFrequency = RCC_Clocks.SysclkFreq;

    /* Configure I2S1 as Master TX */
    I2S_InitStructure.I2sMode = I2S_MODE_MASTER_TX;
    I2S_Init(I2S_MASTER, &I2S_InitStructure);

主从两端的标准、数据格式、采样频率与时钟极性必须相同,ClkSrcFrequency 要与实际系统时钟一致。

5. 使能 DMA 与 I2S,等待发送完成。 main 依次使能 I2S 的 DMA 发送请求、使能 DMA 通道、再使能 I2S,保证搬移请求在 I2S 工作前就绪;随后等待 DMA 传输完成标志,并确认发送缓冲为空且总线不忙,最后打印完成信息。

src/main.csrc/main.c · main
    /* Enable I2S DMA TX request */
    SPI_I2S_EnableDma(I2S_MASTER, SPI_I2S_DMA_TX, ENABLE);

    /* Enable DMA TX channel */
    DMA_EnableChannel(I2S_MASTER_Tx_DMA_Channel, ENABLE);

    /* Enable I2S */
    I2S_Enable(I2S_MASTER, ENABLE);

    /* Wait for DMA TX transfer complete */
    while (!DMA_GetFlagStatus(I2S_MASTER_Tx_DMA_FLAG, DMA))
        ;

    /* Wait until I2S TX buffer is empty and bus is not busy */
    while (SPI_I2S_GetStatus(I2S_MASTER, SPI_I2S_TE_FLAG) == RESET)
        ;
    while (SPI_I2S_GetStatus(I2S_MASTER, SPI_I2S_BUSY_FLAG) != RESET)
        ;

结束条件依赖 DMA_FLAG_TC3、SPI_I2S_TE_FLAG 与 SPI_I2S_BUSY_FLAG,换通道或换实例时要同步改这些标志宏。

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

5.12.3.2 I2S_DMA_Slave DMA 从机接收

工程目录: projects/n32g41x_EVAL/examples/I2S/I2S_DMA/I2S_DMA_Slave/

使能 DMA、GPIO 与 SPI1/I2S1 时钟→配置 PA4、PA5、PA7 为复用推挽→配置 DMA_CH2 接收 32 个半字→使能 I2S 从机接收并等待 DMA 完成→比对接收数据并串口输出结果

项目 源码解读
功能 I2S1 配置为从机接收,通过 DMA 接收 32 个 16 位数据,接收后与预期数据比对并输出结果,与 I2S_DMA_Master 主机工程对测。
源码组成 src/main.c:main、RCC_Configuration、GPIO_Configuration、DMA_Configuration、I2S_Configuration、Buffercmp;
inc/main.h:I2S 引脚、DMA 通道与时钟宏;
src/n32g41x_it.c、inc/n32g41x_it.h:中断服务函数;
readme.txt:功能与对测步骤;
MDK-ARM/、EWARM/:Keil 与 IAR 工程。
关键配置 时钟源 HSI+PLL,系统时钟 N32G412 为 80 MHz、N32G415 为 96 MHz;
I2S 时钟源为系统时钟,I2S_STD_PHILLIPS、I2S_DATA_FMT_16BITS_EXTENDED、I2S_MCLK_DISABLE、I2S_AUDIO_FREQ_48K、I2S_CLKPOL_LOW,模式为 I2S_MODE_SlAVE_RX;
WS 为 PA4、CK 为 PA5、SD 为 PA7,复用 GPIO_AF0、GPIO_MODE_AF_PP;
接收用 DMA_CH2(DMA_REMAP_SPI1_RX),外设地址为 SPI1->DAT、半字传输、普通模式。
程序与数据路径 main 调用 log_init 后依次执行 RCC_Configuration、GPIO_Configuration、DMA_Configuration、I2S_Configuration;
随后使能 DMA 接收请求、使能 DMA 通道与 I2S;
数据从 SD 进入 I2S 数据寄存器并由 DMA 存入 I2S_Buffer_Rx;
等待 DMA 传输完成后调用 Buffercmp 与 I2S_Expected_Buffer 比对并打印结果。
主要 API SPI_I2S_DeInit、RCC_ConfigI2SClk、I2S_InitStruct、I2S_Init、I2S_Enable、SPI_I2S_EnableDma、DMA_Init、DMA_RequestRemap、DMA_EnableChannel、DMA_GetFlagStatus
验证方法 两块板 PA4、PA5、PA7 与 GND 一一对接,先复位从机再复位主机;
从机串口打印 I2S DMA Slave RX completed, data match! Test PASS!! 表示数据正确,打印 Test FAIL!! 表示比对失败。
使用提示 从机必须先使能 I2S 与 DMA 并进入等待接收状态,主机延时 1 s 后才发送;
主从配置必须一致(Phillips、16 位扩展、48 kHz),N32G41x 仅 SPI1 支持 I2S。
核心代码解读与移植

示例先准备 DMA、GPIO 与 I2S 三路资源,再由 main 启动 DMA 接收并比对数据。

数据路径: RCC_Configuration 打开时钟 → GPIO_Configuration 配置 PA4、PA5、PA7 → DMA_Configuration 把 SPI1->DAT 接到 I2S_Buffer_Rx → I2S_Configuration 配置为从机接收 → main 使能 DMA 与 I2S → Buffercmp 比对后串口输出结果

1. 打开 DMA、GPIO 与 SPI1/I2S1 时钟。 与主机工程相同,DMA 时钟、GPIO 时钟与 SPI1 时钟三者都要打开,只是宏名换成从机侧。换外设实例时这组宏要整体替换。

src/main.csrc/main.c · RCC_Configuration
void RCC_Configuration(void)
{
    /* Enable DMA clock */
    RCC_EnableAHBPeriphClk(I2S_SLAVE_DMA_CLK, ENABLE);

    /* Enable GPIO clock */
    RCC_EnableAHBPeriphClk(RCC_AHB_PERIPH_GPIO, ENABLE);

    /* Enable SPI1/I2S1 clock */
    RCC_EnableAPB2PeriphClk(I2S_SLAVE_CLK, ENABLE);
}

I2S_SLAVE_CLK 与 I2S_SLAVE_DMA_CLK 要与所选外设实例和 DMA 时钟对应。

2. 把 WS、CK、SD 三个引脚配为复用推挽。 引脚与主机工程共用 PA4、PA5、PA7,配置方式一致;从机侧时钟与 WS 由外部主机提供,引脚仍按复用推挽配置。换引脚时同样要改 inc/main.h 的宏。

src/main.csrc/main.c · GPIO_Configuration
    GPIO_InitType GPIO_InitStructure;

    GPIO_InitStruct(&GPIO_InitStructure);

    /* I2S_SLAVE: WS(PA4) as AF_PP */
    GPIO_InitStructure.Pin            = I2S_SLAVE_WS_PIN;
    GPIO_InitStructure.GPIO_Pull      = GPIO_NO_PULL;
    GPIO_InitStructure.GPIO_Alternate = I2S_SLAVE_WS_AF;
    GPIO_InitStructure.GPIO_Mode      = GPIO_MODE_AF_PP;
    GPIO_InitPeripheral(I2S_SLAVE_WS_GPIO, &GPIO_InitStructure);

引脚号与 GPIO_AF0 复用号在 inc/main.h 中定义,改板时要同步修改。

3. 配置 DMA_CH2 搬运接收数据。 方向为外设源,外设地址仍指向 SPI1->DAT,存储器地址指向 I2S_Buffer_Rx,宽度为半字;DMA_RequestRemap 把 DMA_CH2 映射到 SPI1 的接收请求。收发两端的缓冲区长度必须同为 32 才能逐项比对。

src/main.csrc/main.c · DMA_Configuration
    DMA_InitStructure.PeriphAddr     = (uint32_t)&I2S_SLAVE->DAT;
    DMA_InitStructure.MemAddr        = (uint32_t)I2S_Buffer_Rx;
    DMA_InitStructure.Direction      = DMA_DIR_PERIPH_SRC;
    DMA_InitStructure.BufSize        = BufferSize;
    DMA_InitStructure.PeriphInc      = DMA_PERIPH_INC_DISABLE;
    DMA_InitStructure.DMA_MemoryInc  = DMA_MEM_INC_ENABLE;
    DMA_InitStructure.PeriphDataSize = DMA_PERIPH_DATA_SIZE_HALFWORD;
    DMA_InitStructure.MemDataSize    = DMA_MemoryDataSize_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, DMA, I2S_SLAVE_Rx_DMA_Channel, ENABLE);

接收缓冲区大小、数据宽度与 DMA_REMAP_SPI1_RX 映射要随通道和数据格式一并核对。

4. 按采样频率配置 I2S 从机接收。 参数与主机完全一致,只把模式改为 I2S_MODE_SlAVE_RX;从机不输出 CK 与 WS,由外部主机提供,但采样频率、标准与数据格式仍需匹配。修改任何一项都要同时改主机工程。

src/main.csrc/main.c · I2S_Configuration
    I2S_InitType I2S_InitStructure;
    RCC_ClocksType RCC_Clocks;

    SPI_I2S_DeInit(I2S_SLAVE);

    /* Configure I2S clock source as system clock */
    RCC_ConfigI2SClk(RCC_I2S_CLKSEL_SYS);

    /* I2S configuration - must match Master settings */
    I2S_InitStruct(&I2S_InitStructure);
    I2S_InitStructure.Standard       = I2S_STD_PHILLIPS;
    I2S_InitStructure.DataFormat     = I2S_DATA_FMT_16BITS_EXTENDED;
    I2S_InitStructure.MCLKEnable     = I2S_MCLK_DISABLE;
    I2S_InitStructure.AudioFrequency = I2S_AUDIO_FREQ_48K;
    I2S_InitStructure.CLKPOL         = I2S_CLKPOL_LOW;

    /* Get system clock frequency for I2S clock calculation */
    RCC_GetClocksFreqValue(&RCC_Clocks);
    I2S_InitStructure.ClkSrcFrequency = RCC_Clocks.SysclkFreq;

    /* Configure I2S1 as Slave RX */
    I2S_InitStructure.I2sMode = I2S_MODE_SlAVE_RX;
    I2S_Init(I2S_SLAVE, &I2S_InitStructure);

主从两端的标准、数据格式、采样频率与时钟极性必须成对修改。

5. 使能 DMA 与 I2S,接收后比对数据。 main 先使能 DMA 接收请求、使能 DMA 通道、再使能 I2S 并进入等待;DMA 传输完成后用 Buffercmp 与 I2S_Expected_Buffer 逐项比较,再按结果打印。I2S_Expected_Buffer 的内容必须与主机发送的 I2S_MASTER_Buffer_Tx 一致。

src/main.csrc/main.c · main
    /* Enable I2S DMA RX request */
    SPI_I2S_EnableDma(I2S_SLAVE, SPI_I2S_DMA_RX, ENABLE);

    /* Enable DMA RX channel */
    DMA_EnableChannel(I2S_SLAVE_Rx_DMA_Channel, ENABLE);

    /* Enable I2S Slave - must be enabled before Master starts */
    I2S_Enable(I2S_SLAVE, ENABLE);

    log_info(" Waiting for Master to send data...\r\n");

    /* Wait for DMA RX transfer complete */
    while (!DMA_GetFlagStatus(I2S_SLAVE_Rx_DMA_FLAG, DMA))
        ;

    /* Check the correctness of received data */
    TransferStatus = Buffercmp(I2S_Buffer_Rx, I2S_Expected_Buffer, BufferSize);

    if (TransferStatus == PASSED)
    {
        log_info("\r\n I2S DMA Slave RX completed, data match! Test PASS!!\r\n");
    }
    else
    {
        log_info("\r\n I2S DMA Slave RX completed but data mismatch! Test FAIL!!\r\n");
    }

预期数据 I2S_Expected_Buffer、传输完成标志 DMA_FLAG_TC2 与缓冲区大小要随主机数据一并核对。

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

5.12.3.3 I2S_Interrupt_Master 中断主机发送

工程目录: projects/n32g41x_EVAL/examples/I2S/I2S_Interrupt/I2S_Interrupt_Master/

使能 GPIO 与 SPI1/I2S1 时钟→配置 PA4、PA5、PA7 为复用推挽→配置 SPI1 中断优先级→使能 I2S 主机发送与 TxE 中断→中断逐字发送 32 个数据后关闭中断→串口打印发送完成

项目 源码解读
功能 I2S1 配置为主机发送,由 TxE 中断逐个把 32 个 16 位数据写入数据寄存器,全部发完后在中断里关闭 TxE 中断,与 I2S_Interrupt_Slave 从机工程对测。
源码组成 src/main.c:main、RCC_Configuration、GPIO_Configuration、NVIC_Configuration、I2S_Configuration;
src/n32g41x_it.c:SPI1_IRQHandler 中断服务函数;
inc/main.h:I2S 引脚、中断号与时钟宏;
readme.txt:功能与对测步骤;
MDK-ARM/、EWARM/:Keil 与 IAR 工程。
关键配置 时钟源 HSI+PLL,系统时钟 N32G412 为 80 MHz、N32G415 为 96 MHz;
I2S 时钟源为系统时钟,I2S_STD_PHILLIPS、I2S_DATA_FMT_16BITS_EXTENDED、I2S_MCLK_DISABLE、I2S_AUDIO_FREQ_48K、I2S_CLKPOL_LOW,模式为 I2S_MODE_MASTER_TX;
WS 为 PA4、CK 为 PA5、SD 为 PA7,复用 GPIO_AF0、GPIO_MODE_AF_PP;
中断为 SPI1_IRQn,抢占优先级 1、子优先级 0,分组 NVIC_PriorityGroup_0。
程序与数据路径 main 调用 log_init 后执行 RCC_Configuration、延时 1 s、NVIC_Configuration、GPIO_Configuration、I2S_Configuration;
随后使能 TxE 中断与 I2S;
每次 TxE 中断把 I2S_Buffer_Tx[TxIdx++] 写入数据寄存器,TxIdx 到达 32 后在中断里关闭 TxE 中断;
主循环退出等待后确认缓冲为空、总线不忙并打印完成信息。
主要 API SPI_I2S_DeInit、RCC_ConfigI2SClk、I2S_InitStruct、I2S_Init、I2S_Enable、SPI_I2S_EnableInt、SPI_I2S_GetIntStatus、SPI_I2S_TransmitData、SPI_I2S_GetStatus、NVIC_Init
验证方法 两块板 PA4、PA5、PA7 与 GND 相连,同时上电或复位;
主机串口打印 I2S Interrupt TX transfer completed!!,从机串口打印 Test PASS!! 表示对测成功;
也可用逻辑分析仪观察 I2S 输出波形。
使用提示 主机在启动后延时 1 s 再发送,从机需先进入接收就绪;
两块板对侧摆放时连线应尽量短;
N32G41x 仅 SPI1 支持 I2S,引脚与 SPI1 复用并选 AF0。
核心代码解读与移植

示例先配置引脚与中断,再由 TxE 中断逐字发送数据。

数据路径: RCC_Configuration 打开时钟 → GPIO_Configuration 配置 PA4、PA5、PA7 → NVIC_Configuration 打开 SPI1 中断 → I2S_Configuration 配置为主机发送 → main 使能 TxE 中断与 I2S → SPI1_IRQHandler 逐个发送 I2S_Buffer_Tx → 串口输出

1. 打开 GPIO 与 SPI1/I2S1 时钟。 中断方式不需要 DMA 时钟,只需 GPIO 时钟与 SPI1 时钟。换实例时改 I2S_MASTER_CLK 与 RCC_AHB_PERIPH_GPIO 即可。

src/main.csrc/main.c · RCC_Configuration
void RCC_Configuration(void)
{
    /* Enable GPIO clock */
    RCC_EnableAHBPeriphClk(RCC_AHB_PERIPH_GPIO, ENABLE);

    /* Enable SPI1/I2S1 clock */
    RCC_EnableAPB2PeriphClk(I2S_MASTER_CLK, ENABLE);
}

这两个时钟宏要与所选 GPIO 端口和 SPI 实例对应。

2. 把 WS、CK、SD 三个引脚配为复用推挽。 与 DMA 示例相同的三针配置,PA5、PA7 复用 PA4 的推挽模式与无上下拉设置。引脚定义集中在 inc/main.h。

src/main.csrc/main.c · GPIO_Configuration
    GPIO_InitType GPIO_InitStructure;

    GPIO_InitStruct(&GPIO_InitStructure);

    /* I2S_MASTER: WS(PA4) as AF_PP */
    GPIO_InitStructure.Pin            = I2S_MASTER_WS_PIN;
    GPIO_InitStructure.GPIO_Pull      = GPIO_NO_PULL;
    GPIO_InitStructure.GPIO_Alternate = I2S_MASTER_WS_AF;
    GPIO_InitStructure.GPIO_Mode      = GPIO_MODE_AF_PP;
    GPIO_InitPeripheral(I2S_MASTER_WS_GPIO, &GPIO_InitStructure);

引脚号与复用号在 inc/main.h 中定义,改板时要一并修改。

3. 配置 SPI1/I2S1 的中断优先级。 优先级分组设为 NVIC_PriorityGroup_0,SPI1_IRQn 抢占优先级 1、子优先级 0 并打开通道。优先级数值要与同工程内其它中断统一规划。

src/main.csrc/main.c · NVIC_Configuration
    NVIC_InitType NVIC_InitStructure;

    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_0);

    /* SPI1/I2S1 IRQ channel configuration */
    NVIC_InitStructure.NVIC_IRQChannel                   = I2S_MASTER_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority        = 0;
    NVIC_InitStructure.NVIC_IRQChannelCmd                = ENABLE;
    NVIC_Init(&NVIC_InitStructure);

中断通道由 I2S_MASTER_IRQn 指定,换成其它 SPI 实例时要改这个宏。

4. 按采样频率配置 I2S 主机发送。 与 DMA 示例相同的初始化序列:复位外设、选系统时钟源、填充默认值后设置 Phillips 标准、16 位扩展格式、关闭 MCLK、48 kHz 与时钟极性低,最后设为主机发送。

src/main.csrc/main.c · I2S_Configuration
    I2S_InitType I2S_InitStructure;
    RCC_ClocksType RCC_Clocks;

    SPI_I2S_DeInit(I2S_MASTER);

    /* Configure I2S clock source as system clock */
    RCC_ConfigI2SClk(RCC_I2S_CLKSEL_SYS);

    /* I2S configuration */
    I2S_InitStruct(&I2S_InitStructure);
    I2S_InitStructure.Standard       = I2S_STD_PHILLIPS;
    I2S_InitStructure.DataFormat     = I2S_DATA_FMT_16BITS_EXTENDED;
    I2S_InitStructure.MCLKEnable     = I2S_MCLK_DISABLE;
    I2S_InitStructure.AudioFrequency = I2S_AUDIO_FREQ_48K;
    I2S_InitStructure.CLKPOL         = I2S_CLKPOL_LOW;

    /* Get system clock frequency for I2S clock calculation */
    RCC_GetClocksFreqValue(&RCC_Clocks);
    I2S_InitStructure.ClkSrcFrequency = RCC_Clocks.SysclkFreq;

    /* Configure I2S1 as Master TX */
    I2S_InitStructure.I2sMode = I2S_MODE_MASTER_TX;
    I2S_Init(I2S_MASTER, &I2S_InitStructure);

从机的标准、格式、采样频率与极性必须与主机一致。

5. 使能 TxE 中断与 I2S,并在中断中逐字发送。 main 使能 TxE 中断和 I2S 后等待 TxIdx 计满;中断每次读取 SPI_I2S_INT_TE 状态,未发完就写入下一个数据,发满 32 个后关闭 TxE 中断结束通信。BufferSize、I2S_Buffer_Tx 与索引变量的类型要保持一致。

src/main.csrc/main.c · main
    /* Enable the I2S1 TxE interrupt */
    SPI_I2S_EnableInt(I2S_MASTER, SPI_I2S_INT_TE, ENABLE);

    /* Enable I2S */
    I2S_Enable(I2S_MASTER, ENABLE);

    /* Wait for all data to be sent */
    while (TxIdx < BufferSize)
    {
    }

    /* Wait until I2S TX buffer is empty and bus is not busy */
    while (SPI_I2S_GetStatus(I2S_MASTER, SPI_I2S_TE_FLAG) == RESET)
        ;
    while (SPI_I2S_GetStatus(I2S_MASTER, SPI_I2S_BUSY_FLAG) != RESET)
        ;
src/n32g41x_it.csrc/n32g41x_it.c · SPI1_IRQHandler
    if (SPI_I2S_GetIntStatus(I2S_MASTER, SPI_I2S_INT_TE) != RESET)
    {
        if (TxIdx == BufferSize)
        {
            /* Disable the I2S1 TxE interrupt to end the communication */
            SPI_I2S_EnableInt(I2S_MASTER, SPI_I2S_INT_TE, DISABLE);
        }
        else
        {
            /* Send data from I2S1 */
            SPI_I2S_TransmitData(I2S_MASTER, I2S_Buffer_Tx[TxIdx++]);
        }
    }

中断服务函数名 SPI1_IRQHandler 与启动文件一致,换成其它实例时要改用对应向量名。

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

5.12.3.4 I2S_Interrupt_Slave 中断从机接收

工程目录: projects/n32g41x_EVAL/examples/I2S/I2S_Interrupt/I2S_Interrupt_Slave/

使能 GPIO 与 SPI1/I2S1 时钟→配置 PA4、PA5、PA7 为复用推挽→配置 SPI1 中断优先级→使能 I2S 从机接收与 RxNE 中断→中断接收 32 个数据→比对数据并串口输出结果

项目 源码解读
功能 I2S1 配置为从机接收,由 RxNE 中断逐个把数据存入 I2S_Buffer_Rx,收满 32 个后与预期数据比对,与 I2S_Interrupt_Master 主机工程对测。
源码组成 src/main.c:main、RCC_Configuration、GPIO_Configuration、NVIC_Configuration、I2S_Configuration、Buffercmp;
src/n32g41x_it.c:SPI1_IRQHandler 中断服务函数;
inc/main.h:I2S 引脚、中断号与时钟宏;
readme.txt:功能与对测步骤;
MDK-ARM/、EWARM/:Keil 与 IAR 工程。
关键配置 时钟源 HSI+PLL,系统时钟 N32G412 为 80 MHz、N32G415 为 96 MHz;
I2S 时钟源为系统时钟,I2S_STD_PHILLIPS、I2S_DATA_FMT_16BITS_EXTENDED、I2S_MCLK_DISABLE、I2S_AUDIO_FREQ_48K、I2S_CLKPOL_LOW,模式为 I2S_MODE_SlAVE_RX;
WS 为 PA4、CK 为 PA5、SD 为 PA7,复用 GPIO_AF0、GPIO_MODE_AF_PP;
中断为 SPI1_IRQn,抢占优先级 1、子优先级 0,分组 NVIC_PriorityGroup_0。
程序与数据路径 main 调用 log_init 后执行 RCC_Configuration、NVIC_Configuration、GPIO_Configuration、I2S_Configuration;
随后使能 RxNE 中断与 I2S 并等待 RxIdx 计满;
每次 RxNE 中断把数据寄存器读到 I2S_Buffer_Rx[RxIdx++];
收满后用 Buffercmp 与 I2S_Expected_Buffer 比对并打印结果。
主要 API SPI_I2S_DeInit、RCC_ConfigI2SClk、I2S_InitStruct、I2S_Init、I2S_Enable、SPI_I2S_EnableInt、SPI_I2S_GetIntStatus、SPI_I2S_ReceiveData、NVIC_Init
验证方法 两块板 PA4、PA5、PA7 与 GND 相连,同时上电或复位;
从机串口打印 I2S Slave RX transfer completed, data match! Test PASS!! 表示数据正确,打印 Test FAIL!! 表示比对失败。
使用提示 从机先使能 I2S 与 RxNE 中断并等待接收,主机延时 1 s 后发送;
主从配置必须一致(Phillips、16 位扩展、48 kHz);
两块板对侧摆放时连线应尽量短。
核心代码解读与移植

示例先配置引脚、中断与从机接收参数,再由 RxNE 中断逐字接收数据并比对。

数据路径: GPIO_Configuration 配置 PA4、PA5、PA7 → NVIC_Configuration 打开 SPI1 中断 → I2S_Configuration 配置为从机接收 → main 使能 RxNE 中断与 I2S → SPI1_IRQHandler 把数据存入 I2S_Buffer_Rx → Buffercmp 比对后串口输出

1. 把 WS、CK、SD 三个引脚配为复用推挽。 引脚与主机工程共用 PA4、PA5、PA7,从机侧由外部主机提供 CK 与 WS,引脚仍按复用推挽配置。引脚定义集中在 inc/main.h。

src/main.csrc/main.c · GPIO_Configuration
    GPIO_InitType GPIO_InitStructure;

    GPIO_InitStruct(&GPIO_InitStructure);

    /* I2S_SLAVE: WS(PA4), CK(PA5), SD(PA7) - AF mode, peripheral uses as input from Master */
    GPIO_InitStructure.Pin            = I2S_SLAVE_WS_PIN;
    GPIO_InitStructure.GPIO_Pull      = GPIO_NO_PULL;
    GPIO_InitStructure.GPIO_Alternate = I2S_SLAVE_WS_AF;
    GPIO_InitStructure.GPIO_Mode      = GPIO_MODE_AF_PP;
    GPIO_InitPeripheral(I2S_SLAVE_WS_GPIO, &GPIO_InitStructure);

引脚号与复用号在 inc/main.h 中定义,改板时要一并修改。

2. 配置 SPI1/I2S1 的中断优先级。 优先级分组设为 NVIC_PriorityGroup_0,SPI1_IRQn 抢占优先级 1、子优先级 0 并打开通道,与主机工程对称。优先级数值要与同工程内其它中断统一规划。

src/main.csrc/main.c · NVIC_Configuration
    NVIC_InitStructure.NVIC_IRQChannel                   = I2S_SLAVE_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority        = 0;
    NVIC_InitStructure.NVIC_IRQChannelCmd                = ENABLE;
    NVIC_Init(&NVIC_InitStructure);

中断通道由 I2S_SLAVE_IRQn 指定,换成其它 SPI 实例时要改这个宏。

3. 按采样频率配置 I2S 从机接收。 参数与主机完全一致,只把模式改为 I2S_MODE_SlAVE_RX;从机不产生 CK 与 WS,采样频率与数据格式仍必须与主机匹配。修改任一项都要同时改主机工程。

src/main.csrc/main.c · I2S_Configuration
    I2S_InitStruct(&I2S_InitStructure);
    I2S_InitStructure.Standard       = I2S_STD_PHILLIPS;
    I2S_InitStructure.DataFormat     = I2S_DATA_FMT_16BITS_EXTENDED;
    I2S_InitStructure.MCLKEnable     = I2S_MCLK_DISABLE;
    I2S_InitStructure.AudioFrequency = I2S_AUDIO_FREQ_48K;
    I2S_InitStructure.CLKPOL         = I2S_CLKPOL_LOW;

主从两端的标准、数据格式、采样频率与时钟极性必须成对修改。

4. 使能 RxNE 中断与 I2S,并在中断中接收与比对。 main 使能 RxNE 中断和 I2S 后等待 RxIdx 计满;中断每次读取 SPI_I2S_INT_RNE 状态并把数据寄存器存入 I2S_Buffer_Rx;收满 32 个后用 Buffercmp 与 I2S_Expected_Buffer 比对并打印结果。预期数据必须与主机发送内容一致。

src/main.csrc/main.c · main
    /* Enable the I2S1 RxNE interrupt */
    SPI_I2S_EnableInt(I2S_SLAVE, SPI_I2S_INT_RNE, ENABLE);

    /* Enable I2S Slave before Master starts */
    I2S_Enable(I2S_SLAVE, ENABLE);

    /* Wait for all data to be received */
    while (RxIdx < BufferSize)
    {
    }

    TransferStatus = Buffercmp(I2S_Buffer_Rx, I2S_Expected_Buffer, BufferSize);

    if (TransferStatus == PASSED)
    {
        log_info("\r\n I2S Slave RX transfer completed, data match! Test PASS!!\r\n");
    }
    else
    {
        log_info("\r\n I2S Slave RX completed but data mismatch! Test FAIL!!\r\n");
    }
src/n32g41x_it.csrc/n32g41x_it.c · SPI1_IRQHandler
    if (SPI_I2S_GetIntStatus(I2S_SLAVE, SPI_I2S_INT_RNE) != RESET)
    {
        I2S_Buffer_Rx[RxIdx++] = SPI_I2S_ReceiveData(I2S_SLAVE);
    }

接收缓冲区 I2S_Buffer_Rx、预期数据 I2S_Expected_Buffer 与缓冲区大小要随主机数据一并核对。

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

5.12.3.5 SPI_I2S_Switch_Master 模式切换主机

工程目录: projects/n32g41x_EVAL/examples/I2S/SPI_I2S_Switch/SPI_I2S_Switch_Master/

使能 GPIO 与 SPI1 时钟→配置 PA4、PA5、PA7 为复用推挽→I2S 主机模式轮询发送 32 个数据→切到 SPI 主机单线发送 32 个数据→再切回 I2S 主机发送→串口分步打印状态

项目 源码解读
功能 在同一 SPI1 上按三步切换:先用 I2S 主机发送、再切为 SPI 主机单线发送、最后切回 I2S 主机发送,每步各发 32 个 16 位数据,与 SPI_I2S_Switch_Slave 从机工程对测。
源码组成 src/main.c:main、RCC_Configuration、GPIO_Configuration、I2S_Master_TX、SPI_Master_TX;
inc/main.h:引脚与时钟宏;
src/n32g41x_it.c、inc/n32g41x_it.h:中断服务函数;
readme.txt:功能与对测步骤;
MDK-ARM/、EWARM/:Keil 与 IAR 工程。
关键配置 时钟源 HSI+PLL,系统时钟 N32G412 为 80 MHz、N32G415 为 96 MHz;
I2S 模式为 I2S_MODE_MASTER_TX,I2S_STD_PHILLIPS、I2S_DATA_FMT_16BITS_EXTENDED、I2S_MCLK_DISABLE、I2S_AUDIO_FREQ_48K、I2S_CLKPOL_LOW;
SPI 模式为 SPI_DIR_SINGLELINE_TX、SPI_MODE_MASTER、SPI_DATA_SIZE_16BITS、SPI_CLKPOL_LOW、SPI_CLKPHA_SECOND_EDGE、SPI_NSS_SOFT、SPI_BR_PRESCALER_32、SPI_FB_MSB;
引脚 NSS/WS 为 PA4、SCK/CK 为 PA5、MOSI/SD 为 PA7,复用 AF0。
程序与数据路径 main 调用 log_init 后执行 RCC_Configuration、延时 1 s、GPIO_Configuration;
随后调用 I2S_Master_TX 以 I2S 发送,延时 100 ms 后调用 SPI_Master_TX 以 SPI 单线发送,再延时 100 ms 后再次调用 I2S_Master_TX;
两步切换均由 SPI_I2S_DeInit 复位 SPI1 后重新初始化。
主要 API SPI_I2S_DeInit、RCC_ConfigI2SClk、I2S_InitStruct、I2S_Init、I2S_Enable、SPI_InitStruct、SPI_Init、SPI_Enable、SPI_I2S_TransmitData、SPI_I2S_GetStatus
验证方法 两块板 PA4、PA5、PA7 与 GND 直连,先上电从机再上电主机;
主机串口依次打印 Step 1: I2S TX completed!、Step 2: SPI TX completed!、Step 3: I2S TX completed! 与 All steps done!,从机串口打印各步接收比对结果。
使用提示 主机启动延时 1 s 确保从机就绪,每步之间延时 100 ms 让从机完成模式切换;
两块板使用相同的 SPI1 引脚(PA4、PA5、PA7)飞线直连;
N32G41x 仅 SPI1 支持 I2S。
核心代码解读与移植

示例一次配置好引脚,然后按 I2S、SPI、I2S 三步在同一 SPI1 上发送。

数据路径: RCC_Configuration 打开时钟 → GPIO_Configuration 配置 PA4、PA5、PA7 → I2S_Master_TX 以 I2S 主机轮询发送 → SPI_Master_TX 切到 SPI 主机单线发送 → I2S_Master_TX 再切回 I2S 发送 → 串口分步输出

1. 打开 GPIO 与 SPI1 时钟。 I2S 与 SPI 共用 SPI1 的时钟,所以这里只开 GPIO 时钟与 SPI1 时钟。换实例时改 SPI_I2S_MASTER_CLK。

src/main.csrc/main.c · RCC_Configuration
void RCC_Configuration(void)
{
    /* Enable GPIO clock */
    RCC_EnableAHBPeriphClk(RCC_AHB_PERIPH_GPIO, ENABLE);

    /* Enable SPI1 clock */
    RCC_EnableAPB2PeriphClk(SPI_I2S_MASTER_CLK, ENABLE);
}

时钟宏要与所选 GPIO 端口和 SPI1 实例对应。

2. 把 NSS/WS、SCK/CK、MOSI/SD 三个引脚配为复用推挽。 三个引脚在 SPI 与 I2S 两种模式下复用同一组 AF0,因此只需配置一次。引脚定义集中在 inc/main.h。

src/main.csrc/main.c · GPIO_Configuration
    GPIO_InitType GPIO_InitStructure;

    GPIO_InitStruct(&GPIO_InitStructure);

    /* SPI1/I2S1: NSS/WS(PA4) as AF_PP */
    GPIO_InitStructure.Pin            = SPI_I2S_NSS_WS_PIN;
    GPIO_InitStructure.GPIO_Pull      = GPIO_NO_PULL;
    GPIO_InitStructure.GPIO_Alternate = SPI_I2S_NSS_WS_AF;
    GPIO_InitStructure.GPIO_Mode      = GPIO_MODE_AF_PP;
    GPIO_InitPeripheral(SPI_I2S_NSS_WS_GPIO, &GPIO_InitStructure);

引脚号与复用号在 inc/main.h 中定义,改板时要一并修改。

3. 配置 I2S 主机发送并轮询发送数据。 I2S_Master_TX 先复位外设、选系统时钟源,再填充 I2S 参数并调用 I2S_Init,随后 I2S_Enable 使能 I2S,在循环里等待 SPI_I2S_TE_FLAG 后逐个写入数据。这里的数据来自 I2S_Buffer_Tx。

src/main.csrc/main.c · I2S_Master_TX
    I2S_InitStruct(&I2S_InitStructure);
    I2S_InitStructure.Standard       = I2S_STD_PHILLIPS;
    I2S_InitStructure.DataFormat     = I2S_DATA_FMT_16BITS_EXTENDED;
    I2S_InitStructure.MCLKEnable     = I2S_MCLK_DISABLE;
    I2S_InitStructure.AudioFrequency = I2S_AUDIO_FREQ_48K;
    I2S_InitStructure.CLKPOL         = I2S_CLKPOL_LOW;

    RCC_GetClocksFreqValue(&RCC_Clocks);
    I2S_InitStructure.ClkSrcFrequency = RCC_Clocks.SysclkFreq;

    I2S_InitStructure.I2sMode = I2S_MODE_MASTER_TX;
    I2S_Init(SPI_I2S_MASTER, &I2S_InitStructure);

    /* Enable I2S */
    I2S_Enable(SPI_I2S_MASTER, ENABLE);

I2S 侧的标准、数据格式、采样频率与极性和从机的 I2S 配置必须一致。

4. 切换到 SPI 主机单线发送。 SPI_Master_TX 先 SPI_I2S_DeInit 复位同一 SPI1,再按 SPI_InitType 配置为单线发送、16 位、软件 NSS,使能后轮询发送 SPI_Buffer_Tx,发送完关闭 SPI。切模式时不能沿用 I2S 的配置位,必须重新初始化。

src/main.csrc/main.c · SPI_Master_TX
    SPI_InitStruct(&SPI_InitStructure);

    /* SPI1 master: single-line TX, 16-bit */
    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_32;
    SPI_InitStructure.FirstBit     = SPI_FB_MSB;
    SPI_InitStructure.CRCPoly     = 7;
    SPI_Init(SPI_I2S_MASTER, &SPI_InitStructure);

SPI 侧的方向、位宽、极性、相位与波特率预分频要和从机的 SPI 配置对应。

5. 由 main 按步骤调用并打印状态。 main 依次调用 I2S_Master_TX、SPI_Master_TX、I2S_Master_TX,中间各延时 100 ms;每一步发送前后用 log_info 输出状态,便于与从机日志对齐。步骤之间的延时是与从机切换模式同步的关键。

src/main.csrc/main.c · main
    /* Step 1: I2S mode - Master TX */
    log_info("\r\n Step 1: I2S Master TX...\r\n");
    I2S_Master_TX();
    log_info(" Step 1: I2S TX completed!\r\n");

    /* Delay for slave to switch mode */
    SysTick_Delay_Ms(100);

    /* Step 2: Switch to SPI mode - Master TX */
    log_info("\r\n Step 2: SPI Master TX...\r\n");
    SPI_Master_TX();
    log_info(" Step 2: SPI TX completed!\r\n");

    /* Delay for slave to switch mode */
    SysTick_Delay_Ms(100);

    /* Step 3: Switch back to I2S mode - Master TX */
    log_info("\r\n Step 3: I2S Master TX (again)...\r\n");
    I2S_Master_TX();
    log_info(" Step 3: I2S TX completed!\r\n");

步骤数量、每步延时与发送缓冲区要和从机的接收流程一一对应。

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

5.12.3.6 SPI_I2S_Switch_Slave 模式切换从机

工程目录: projects/n32g41x_EVAL/examples/I2S/SPI_I2S_Switch/SPI_I2S_Switch_Slave/

使能 GPIO 与 SPI1 时钟→配置 PA4、PA5、PA7 为复用推挽→I2S 从机模式轮询接收并比对→切到 SPI 从机双线只读接收并比对→再切回 I2S 从机接收并比对→串口分步打印结果

项目 源码解读
功能 在同一 SPI1 上按三步切换:先以 I2S 从机接收、再切为 SPI 从机双线只读接收、最后切回 I2S 从机接收,每步各收 32 个 16 位数据并与预期数据比对,与 SPI_I2S_Switch_Master 主机工程对测。
源码组成 src/main.c:main、RCC_Configuration、GPIO_Configuration、I2S_Slave_RX、SPI_Slave_RX、Buffercmp;
inc/main.h:引脚与时钟宏;
src/n32g41x_it.c、inc/n32g41x_it.h:中断服务函数;
readme.txt:功能与对测步骤;
MDK-ARM/、EWARM/:Keil 与 IAR 工程。
关键配置 时钟源 HSI+PLL,系统时钟 N32G412 为 80 MHz、N32G415 为 96 MHz;
I2S 模式为 I2S_MODE_SlAVE_RX,I2S_STD_PHILLIPS、I2S_DATA_FMT_16BITS_EXTENDED、I2S_MCLK_DISABLE、I2S_AUDIO_FREQ_48K、I2S_CLKPOL_LOW;
SPI 模式为 SPI_DIR_DOUBLELINE_RONLY、SPI_MODE_SLAVE、SPI_DATA_SIZE_16BITS、SPI_CLKPOL_LOW、SPI_CLKPHA_SECOND_EDGE、SPI_NSS_SOFT、SPI_BR_PRESCALER_32、SPI_FB_MSB;
引脚 NSS/WS 为 PA4、SCK/CK 为 PA5、MOSI/SD 为 PA7,复用 AF0。
程序与数据路径 main 调用 log_init 后执行 RCC_Configuration、GPIO_Configuration;
随后调用 I2S_Slave_RX 以 I2S 接收并比对,再调用 SPI_Slave_RX 以 SPI 双线只读接收并比对,清空接收缓冲区后再次调用 I2S_Slave_RX;
三步比较结果分别存入 TransferStatus1、TransferStatus2、TransferStatus3,全部通过时打印 All Tests PASS!!。
主要 API SPI_I2S_DeInit、RCC_ConfigI2SClk、I2S_InitStruct、I2S_Init、I2S_Enable、SPI_InitStruct、SPI_Init、SPI_Enable、SPI_I2S_ReceiveData、SPI_I2S_GetStatus
验证方法 两块板 PA4、PA5、PA7 与 GND 直连,先上电从机再上电主机;
从机串口依次打印 Step 1: I2S RX PASS!!、Step 2: SPI RX PASS!!、Step 3: I2S RX PASS!!,全部通过时打印 All Tests PASS!!。
使用提示 从机无延时、始终先于主机就绪,由主机的 1 s 启动延时保证时序;
SPI 从机使用双线只读模式接收主机 MOSI 数据;
两块板使用相同的 SPI1 引脚飞线直连。
核心代码解读与移植

示例一次配置好引脚,然后按 I2S、SPI、I2S 三步在同一 SPI1 上接收并比对。

数据路径: RCC_Configuration 打开时钟 → GPIO_Configuration 配置 PA4、PA5、PA7 → I2S_Slave_RX 以 I2S 从机轮询接收 → SPI_Slave_RX 切到 SPI 从机双线只读接收 → I2S_Slave_RX 再切回 I2S 接收 → Buffercmp 比对后串口输出

1. 打开 GPIO 与 SPI1 时钟。 I2S 与 SPI 共用 SPI1 时钟,因此只开 GPIO 时钟与 SPI1 时钟,与主机工程对称。换实例时改 SPI_I2S_SLAVE_CLK。

src/main.csrc/main.c · RCC_Configuration
void RCC_Configuration(void)
{
    /* Enable GPIO clock */
    RCC_EnableAHBPeriphClk(RCC_AHB_PERIPH_GPIO, ENABLE);

    /* Enable SPI1 clock */
    RCC_EnableAPB2PeriphClk(SPI_I2S_SLAVE_CLK, ENABLE);
}

时钟宏要与所选 GPIO 端口和 SPI1 实例对应。

2. 把 NSS/WS、SCK/CK、MOSI/SD 三个引脚配为复用推挽。 三个引脚在 SPI 与 I2S 两种模式下复用同一组 AF0,只需配置一次。引脚定义集中在 inc/main.h。

src/main.csrc/main.c · GPIO_Configuration
    GPIO_InitType GPIO_InitStructure;

    GPIO_InitStruct(&GPIO_InitStructure);

    /* SPI1/I2S1: NSS/WS(PA4) as AF_PP */
    GPIO_InitStructure.Pin            = SPI_I2S_NSS_WS_PIN;
    GPIO_InitStructure.GPIO_Pull      = GPIO_NO_PULL;
    GPIO_InitStructure.GPIO_Alternate = SPI_I2S_NSS_WS_AF;
    GPIO_InitStructure.GPIO_Mode      = GPIO_MODE_AF_PP;
    GPIO_InitPeripheral(SPI_I2S_NSS_WS_GPIO, &GPIO_InitStructure);

引脚号与复用号在 inc/main.h 中定义,改板时要一并修改。

3. 配置 I2S 从机接收并轮询读数据。 I2S_Slave_RX 先复位外设,再填充与主机一致的 I2S 参数并调用 I2S_Init,随后 I2S_Enable 使能 I2S,在循环里等待 SPI_I2S_RNE_FLAG 后逐个读入接收缓冲区。这里的数据与主机发送的 I2S_Buffer_Tx 对应。

src/main.csrc/main.c · I2S_Slave_RX
    I2S_InitStruct(&I2S_InitStructure);
    I2S_InitStructure.Standard       = I2S_STD_PHILLIPS;
    I2S_InitStructure.DataFormat     = I2S_DATA_FMT_16BITS_EXTENDED;
    I2S_InitStructure.MCLKEnable     = I2S_MCLK_DISABLE;
    I2S_InitStructure.AudioFrequency = I2S_AUDIO_FREQ_48K;
    I2S_InitStructure.CLKPOL         = I2S_CLKPOL_LOW;

    RCC_GetClocksFreqValue(&RCC_Clocks);
    I2S_InitStructure.ClkSrcFrequency = RCC_Clocks.SysclkFreq;

    I2S_InitStructure.I2sMode = I2S_MODE_SlAVE_RX;
    I2S_Init(SPI_I2S_SLAVE, &I2S_InitStructure);

    /* Enable I2S Slave - must be enabled before Master starts */
    I2S_Enable(SPI_I2S_SLAVE, ENABLE);

I2S 侧的标准、数据格式、采样频率与极性和主机的 I2S 配置必须一致。

4. 切换到 SPI 从机双线只读接收。 SPI_Slave_RX 先 SPI_I2S_DeInit 复位同一 SPI1,再配置为双线只读、16 位、软件 NSS,使能后轮询把收到的数据写入 SPI_Buffer_Rx,接收完关闭 SPI。切模式时必须重新初始化,不能沿用 I2S 的配置位。

src/main.csrc/main.c · SPI_Slave_RX
    SPI_InitStruct(&SPI_InitStructure);

    /* SPI1 slave: double-line RX only, 16-bit */
    SPI_InitStructure.DataDirection = SPI_DIR_DOUBLELINE_RONLY;
    SPI_InitStructure.SpiMode      = SPI_MODE_SLAVE;
    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_32;
    SPI_InitStructure.FirstBit     = SPI_FB_MSB;
    SPI_InitStructure.CRCPoly     = 7;
    SPI_Init(SPI_I2S_SLAVE, &SPI_InitStructure);

SPI 侧的方向、位宽、极性、相位与波特率预分频要和主机的 SPI 配置对应。

5. 由 main 按步骤接收、比对并打印结果。 main 依次调用 I2S_Slave_RX、SPI_Slave_RX、I2S_Slave_RX,每次收完用 Buffercmp 与对应的预期缓冲区比较;第二次切换前要先清空 I2S 接收缓冲区,避免残留数据影响第三次比对。三次结果都通过时打印总结果。

src/main.csrc/main.c · main
    /* Step 2: Switch to SPI mode - Slave RX */
    log_info("\r\n Step 2: Waiting for SPI Master TX...\r\n");
    SPI_Slave_RX();

    TransferStatus2 = Buffercmp((uint16_t*)SPI_Buffer_Rx, SPI_Expected_Buffer, BufferSize);
    if (TransferStatus2 == PASSED)
        log_info(" Step 2: SPI RX PASS!!\r\n");
    else
        log_info(" Step 2: SPI RX FAIL!!\r\n");

    /* Clear I2S RX buffer for step 3 */
    {
        uint8_t i;
        for (i = 0; i < BufferSize; i++)
            I2S_Buffer_Rx[i] = 0;
    }

    /* Step 3: Switch back to I2S mode - Slave RX */
    log_info("\r\n Step 3: Waiting for I2S Master TX (again)...\r\n");
    I2S_Slave_RX((uint16_t*)I2S_Buffer_Rx);

    TransferStatus3 = Buffercmp((uint16_t*)I2S_Buffer_Rx, I2S_Expected_Buffer, BufferSize);
    if (TransferStatus3 == PASSED)
        log_info(" Step 3: I2S RX PASS!!\r\n");

步骤数量、清缓冲区的位置与两个预期缓冲区 I2S_Expected_Buffer、SPI_Expected_Buffer 要和主机发送流程一一对应。

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

5.12.4 注意与排障

场景 检查与建议 来源
I2S 只能单向收发 用户手册写明 I2S 只支持半双工,不支持全双工;
同一时刻只能发送或接收,需要双向音频时应使用两个 I2S 实例或分开收发。
用户手册
采样频率不准或音频失真 采样频率由 SPI_I2S_PREDIV 的 LDIV[9:0] 与 ODDEVEN 决定,并与 I2SxCLK 时钟源、是否输出 MCLK、声道长度相关;
按分频公式或配置表取值,例如 SYSCLK 为 72 MHz、输出 MCLK、目标 192 kHz 时 LDIV 取 1 的误差可达 26.76%,应改选表中误差更小的组合。
用户手册
发送时出现下溢 用户手册写明主、从发送若在下一个数据所需时钟边沿到达前未写入新数据,SPI_STS 的 UNDER 标志会置 1;
此时应关闭 I2S 并重新从左声道开始发送。
用户手册
接收时出现上溢 用户手册写明未及时读出前一帧又收到新数据时 SPI_STS 的 OVER 标志置 1;
读 SPI_DAT 会清除 RNE,处理上溢时要按手册顺序读寄存器清除标志,并保证在下一帧前把数据取走。
用户手册
关闭 I2S 破坏最后一次传输 用户手册要求按数据长度、声道长度与音频标准选择关闭顺序:先等待 RNE,再软件延时一个(16 位扩展到 32 位且为 LSB 对齐时需 17 个)I2S 时钟周期,最后清 SPI_I2S_CFGR 的 I2SEN;
连续传输时应先等待 TE 与 BUSY。
用户手册
出错后声道标志不可信 用户手册写明从发送下溢或接收上溢后 SPI_STS 的 CHSIDE 标志值无效,恢复通信前需要先关闭再重新打开 I2S。 用户手册

N32G412、N32G415 系列勘误手册 V1.0.0(2026.6.30)的勘误列表只列出一行 A 版芯片条目,勘误链接列为空,未列出 I2S 相关条目。

国民技术开发套件文档中心

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

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