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 参考
以下 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 之前完成时钟源选择。
/* 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 的接收请求。
/* 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 与发送缓冲区。
/* 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 请求并使能两端。
/* 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。
/* 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);
/* 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);
/* 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,再统一使能。
/* 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. 每轮先发后收。 主机侧与从机侧各发一个数据,再从两条接收通道各取回一个数据,收发都在主循环里轮询完成。
//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++]);
//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 与两个扩展外设,再分别比对从机侧与主机侧收到的数据。
/* 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,处理函数名须与启动文件一致。
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 使能之前打开,使能端点后发送缓冲一空就立刻触发第一次发送中断。
/* 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 个数据后关闭自身,接收中断每次读走一个数据并让计数加一。
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++]);
}
}
}
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,比较时数据不等则再比一次高字节,以容忍低位差异。
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);
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. 主程序按三步切换并重置计数与缓冲。 每步之间复位发送与接收计数,第二步与第三步之间还把接收缓冲清零。
/* 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 个退出。
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。
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 主收并预写首个数据。 从机发送在使能后先写入一个数据,再使能主机接收,避免主机侧空等。
/* 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 后从声道重新开始。 |
用户手册 |