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 参考
以下 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。移植到其它外设实例时要同步改这三处宏。
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 的宏里,换板时要一起改。
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 位格式一致。
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。从机侧的这组参数必须与主机完全一致才能对测成功。
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 传输完成标志,并确认发送缓冲为空且总线不忙,最后打印完成信息。
/* 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 时钟三者都要打开,只是宏名换成从机侧。换外设实例时这组宏要整体替换。
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 的宏。
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 才能逐项比对。
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,由外部主机提供,但采样频率、标准与数据格式仍需匹配。修改任何一项都要同时改主机工程。
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 一致。
/* 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 即可。
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。
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 并打开通道。优先级数值要与同工程内其它中断统一规划。
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 与时钟极性低,最后设为主机发送。
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 与索引变量的类型要保持一致。
/* 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)
;
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。
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 并打开通道,与主机工程对称。优先级数值要与同工程内其它中断统一规划。
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,采样频率与数据格式仍必须与主机匹配。修改任一项都要同时改主机工程。
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 比对并打印结果。预期数据必须与主机发送内容一致。
/* 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");
}
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。
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。
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。
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 的配置位,必须重新初始化。
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 输出状态,便于与从机日志对齐。步骤之间的延时是与从机切换模式同步的关键。
/* 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。
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。
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 对应。
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 的配置位。
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 接收缓冲区,避免残留数据影响第三次比对。三次结果都通过时打印总结果。
/* 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 相关条目。