5. I2S emulation
5.1. 概述
i2s_over_spi 组件提供了一套SPI模拟I2S的操作API,用于配置、初始化和读写I2S总线。支持多种I2S设备,并提供了阻塞和非阻塞模式下的数据传输功能。以下是该组件的主要特征:
多实例支持 - 支持多个SPI模拟I2S实例(如I2S0, I2S1等),每个实例都有独立的配置参数。 - 通过``hpm_i2s_over_spi_t``结构体管理每个I2S实例的配置信息,比如bclk位时钟,lrck声道时钟,mclk主时钟的模拟生成,I2S数据模拟生成等。
阻塞与非阻塞读写操作接口
仅支持主机。
提供了非阻塞式的读写函数。
提供了阻塞式的读写函数,支持超时机制,确保在长时间未响应的情况下能够及时返回错误状态。
5.2. 相关结构体介绍
结构体介绍
gptmr定时器配置结构体,这个结构体用于用于I2S时钟生成, 封装了生成I2S协议所需时钟信号的关键硬件资源,比如
bclk(位时钟):控制数据位的采样频率
lrck(帧时钟):决定左右声道的切换频率
mclk(主时钟):提供工作基准时钟
typedef struct { GPTMR_Type *ptr; /**< GPTMR硬件寄存器指针 */ clock_name_t clock_name; /**< 时钟源选择(如clock_mclk)*/ uint8_t channel; /**< 定时器通道编号(0或1)*/ } hpm_i2s_gptmr_context_t;
SPI从机上下文配置结构体,这个结构体用于I2S数据通道模拟,封装了通过SPI接口实现I2S数据传输所需的硬件资源
typedef struct { SPI_Type *ptr; /**< SPI硬件寄存器指针 */ clock_name_t clock_name; /**< 时钟源选择 */ uint16_t txdma_src; /**< TX DMA请求源编号 */ uint16_t rxdma_src; /**< RX DMA请求源编号 */ uint32_t cs_pin; /**< 片选引脚编号 */ void (*write_cs)(uint32_t cs_pin, uint8_t state); /**< 片选控制回调函数 */ } hpm_i2s_spi_context_t;
DMA上下文配置结构体,这个结构体用于I2S数据传输管理,封装了DMA链式传输所需的硬件资源,支持多段式连续传输
typedef struct { dma_resource_t *resource; /**< DMA通道资源指针 */ dma_linked_descriptor_t *descriptors; /**< 链式描述符数组指针 */ } hpm_i2s_dma_context_t;
I2S-over-SPI上下文结构体,集成管理通过SPI模拟I2S协议所需的全部硬件资源和状态信息。
typedef struct hpm_i2s_over_spi { /* 时钟生成模块 */ hpm_i2s_gptmr_context_t bclk; /**< 位时钟生成器(控制数据位速率)*/ hpm_i2s_gptmr_context_t lrck; /**< 声道时钟生成器(控制采样率)*/ hpm_i2s_gptmr_context_t mclk; /**< 主时钟生成器(驱动编解码器)*/ /* 数据传输模块 */ hpm_i2s_spi_context_t spi_slave;/**< SPI从机配置(承载I2S数据流)*/ hpm_i2s_dma_context_t tx_dma; /**< 发送DMA资源(管理音频输出)*/ hpm_i2s_dma_context_t rx_dma; /**< 接收DMA资源(管理音频输入)*/ /* 状态控制模块 */ bool i2s_rx; /**< 接收使能标志 */ i2s_rx_data_tc rx_callback; /**< 接收完成回调函数指针 */ hpm_i2s_gptmr_context_t transfer_time; /**< 传输计时器(记录传输耗时)*/ bool has_done; /**< 传输完成标志 */ void (*transfer_complete)(struct hpm_i2s_over_spi *i2s); /**< 全局传输完成回调 */ } hpm_i2s_over_spi_t;
5.3. API调用流程介绍
5.3.1. 定义I2S-over-SPI实例变量
定义一个
hpm_i2s_over_spi_t结构体的实例。例如,在samples/i2s_emulation示例中使用的i2s_device实例变量。
5.3.2. 初始化I2S-over-SPI实例变量
将所需的硬件资源,比如定时器,SPI,DMA等,复制到
hpm_i2s_over_spi_t结构体的成员变量中,使之进行绑定,建立I2S-over-SPI硬件模拟系统。举例: 在samples/i2s_emulation示例中,
i2s_emulation_configAPI就是对i2s_device进行硬件绑定操作, 绑定具体硬件资源到i2s_device实例变量中static void i2s_emulation_config(hpm_i2s_over_spi_t *i2s) { /*------------------------ 时钟系统配置 ------------------------*/ /* 主时钟生成器配置 */ i2s->mclk.ptr = BOARD_GPTMR_I2S_MCLK; // 使用GPTMR定时器生成MCLK i2s->mclk.channel = BOARD_GPTMR_I2S_MCLK_CHANNEL; // 使用通道0/1 i2s->mclk.clock_name = BOARD_GPTMR_I2S_MCLK_CLK_NAME; // 时钟源选择 /* 声道时钟配置(采样率控制)*/ i2s->lrck.ptr = BOARD_GPTMR_I2S_LRCK; // 使用GPTMR定时器生成LRCK i2s->lrck.channel = BOARD_GPTMR_I2S_LRCK_CHANNEL; // 通道配置 i2s->lrck.clock_name = BOARD_GPTMR_I2S_LRCK_CLK_NAME; /* 位时钟配置(数据位速率)*/ i2s->bclk.ptr = BOARD_GPTMR_I2S_BCLK; // 使用GPTMR定时器生成BCLK i2s->bclk.channel = BOARD_GPTMR_I2S_BLCK_CHANNEL; i2s->bclk.clock_name = BOARD_GPTMR_I2S_BLCK_CLK_NAME; /*------------------------ 传输计时器配置 ----------------------*/ i2s->transfer_time.ptr = BOARD_GPTMR_I2S_FINSH; // 使用GPTMR定时器作为传输计时器 i2s->transfer_time.channel = BOARD_GPTMR_I2S_FINSH_CHANNEL; i2s->transfer_time.clock_name = BOARD_GPTMR_I2S_FINSH_CLK_NAME; /* 使能传输完成中断,优先级设为5 */ intc_m_enable_irq_with_priority(BOARD_GPTMR_I2S_FINSH_IRQ, 5); /*------------------------ SPI数据通道配置 ---------------------*/ i2s->spi_slave.ptr = BOARD_APP_SPI_BASE; // 使用SPI作为数据通道 i2s->spi_slave.clock_name = BOARD_APP_SPI_CLK_NAME; i2s->spi_slave.cs_pin = I2S_OVER_SPI_CS_CONTROLLER; // 片选引脚编号 i2s->spi_slave.write_cs = board_write_spi_cs; // 片选控制函数指针 /* DMA请求源配置 */ i2s->spi_slave.rxdma_src = BOARD_APP_SPI_RX_DMA; // 接收DMA请求源 i2s->spi_slave.txdma_src = BOARD_APP_SPI_TX_DMA; // 发送DMA请求源 /*------------------------ DMA资源配置 ------------------------*/ i2s->rx_callback = rx_callback; // 注册接收完成回调 /* 接收DMA描述符数组(内存需8字节对齐)*/ i2s->rx_dma.descriptors = rx_descriptors; // 预分配描述符内存空间,需要链式传输 i2s->rx_dma.resource = &dma_resource_pools[0]; // 使用DMA通道0资源 /* 发送DMA配置*/ i2s->tx_dma.descriptors = NULL; // 发送描述符暂未使用,不需要链式传输 i2s->tx_dma.resource = &dma_resource_pools[1]; // 预留DMA通道1资源 /*------------------------ 状态初始化 -------------------------*/ i2s->has_done = false; // 传输完成标志复位 i2s->i2s_rx = false; // 初始化为发送模式 }
5.3.3. I2S-over-SPI初始化
调用
hpm_i2s_master_over_spi_initAPI 来初始化 I2S-over-SPI。在此过程中,i2s_device实例变量的参数会被赋值到API的``i2s``并生效。hpm_i2s_master_over_spi_initAPI原型:hpm_stat_t hpm_i2s_master_over_spi_init(hpm_i2s_over_spi_t *i2s)
参数说明
参数名
类型
描述
i2s
hpm_i2s_over_spi_t*
指向I2S-over-SPI上下文结构体的指针,包含I2S配置信息和状态
返回值:
status_success: 成功status_invalid_argument: 无效参数status_fail: 其他错误
举例: 如何使用
hpm_i2s_master_over_spi_init函数来初始化 I2S-over-SPIhpm_i2s_master_over_spi_init(&i2s_device);
5.3.4. I2S-over-SPI接收操作
5.3.4.1. 接收配置
调用
hpm_i2s_master_over_spi_rx_configAPI 配置I2S接收参数。配置I2S接收参数,包括协议类型、采样率、音频位深度、接收缓冲区等。
配置完成后,I2S-over-SPI会根据配置信息进行初始化和准备接收操作。
该API支持单缓存区和双缓冲区模式。
双缓冲模式下,I2S-over-SPI会使用两个缓冲区交替接收数据,确保数据的连续性和完整性。
双缓冲模式下,在上述的rx_callback回调函数中,需要判断当前接收的是哪个缓冲区的数据,然后进行相应的处理。
hpm_i2s_master_over_spi_rx_configAPI原型:hpm_stat_t hpm_i2s_master_over_spi_rx_config(hpm_i2s_over_spi_t *i2s, uint8_t protocol, uint32_t lrck_hz, uint32_t audio_depth, uint8_t *buffer0, uint8_t *buffer1, uint32_t size);
参数说明:
参数名
类型
描述
i2s
hpm_i2s_over_spi_t*
I2S-over-SPI上下文结构体指针
protocol
uint8_t
协议类型,当前不支持:
I2S_PROTOCOL_PCM和I2S_PROTOCOL_I2S_PHILIPSlrck_hz
uint32_t
声道时钟频率(采样率),单位Hz 比如 8000/48000
audio_depth
uint32_t
音频位深度,当前支持:(16bit)和(32bit)
buffer0
uint8_t*
接收缓冲区0指针
buffer1
uint8_t*
接收缓冲区1指针(双缓冲模式使用)
size
uint32_t
缓冲区大小(字节)
返回值:
返回值
描述
status_success
配置成功
status_invalid_argument
参数错误(包含以下情况):缓冲区指针无效,协议类型不支持,缓冲区大小超限
举例: 如何使用
hpm_i2s_master_over_spi_init函数来初始化 I2S-over-SPI
#define RX_SIZE_MAX (4096U)
/* 双缓冲配置 */
ATTR_PLACE_AT_NONCACHEABLE_WITH_ALIGNMENT(8) uint8_t rx_buffer0[RX_SIZE_MAX];
ATTR_PLACE_AT_NONCACHEABLE_WITH_ALIGNMENT(8) uint8_t rx_buffer1[RX_SIZE_MAX];
hpm_i2s_master_over_spi_rx_config(&i2s_device,
I2S_PROTOCOL_MSB_JUSTIFIED,
48000,
16,
rx_buffer0,
rx_buffer1,
RX_SIZE_MAX);
5.3.4.2. 启动接收
调用
hpm_i2s_master_over_spi_rx_startAPI 启动配置好的I2S接收流程启动I2S接收流程,开始接收数据。
该API会根据配置信息启动I2S-over-SPI的接收操作,开始接收数据。
接收完成后,会触发相应的回调函数(如
rx_callback)进行数据处理。
hpm_i2s_master_over_spi_rx_startAPI原型:hpm_stat_t hpm_i2s_master_over_spi_rx_start(hpm_i2s_over_spi_t *i2s, i2s_rx_data_tc callback);
参数说明:
参数名
类型
描述
i2s
hpm_i2s_over_spi_t*
I2S-over-SPI上下文结构体指针(需已通过rx_config配置)
callback
i2s_rx_data_tc
接收完成回调函数指针,当DMA完成一个缓冲区传输时触发
返回值:
返回值
描述
status_success
接收成功启动
status_invalid_argument
参数错误(包含以下情况):上下文指针为空, 回调函数指针为空
举例: 如何使用
hpm_i2s_master_over_spi_rx_start函数来启动 I2S-over-SPI 接收
/* 定义接收完成回调 */
void rx_done_callback(uint32_t buf_index) {
printf("Buffer %d received\n", buf_index);
/* 处理接收完成的数据缓冲区 buf_index为缓存索引,比如双缓存模式下,0代表缓存0,1代表缓存1 */
}
int main(void) {
/* 配置接收参数... */
hpm_i2s_master_over_spi_rx_config(&i2s_device, ...);
/* 启动接收并注册回调 */
hpm_stat_t result = hpm_i2s_master_over_spi_rx_start(&i2s_device, rx_done_callback);
if (result != status_success) {
printf("RX start failed: 0x%x\n", result);
}
/* TODO */
}
5.3.4.3. 停止接收
调用
hpm_i2s_master_over_spi_rx_stopAPI 停止正在进行的I2S接收流程。 - 该API会停止I2S-over-SPI的接收操作,停止接收数据。hpm_i2s_master_over_spi_rx_startAPI原型:hpm_stat_t hpm_i2s_master_over_spi_rx_stop(hpm_i2s_over_spi_t *i2s);
参数说明:
参数名
类型
描述
i2s
hpm_i2s_over_spi_t*
I2S-over-SPI上下文结构体指针
返回值:
返回值
描述
status_success
接收成功停止
status_invalid_argument
参数错误(上下文指针为空)
举例: 如何使用
hpm_i2s_master_over_spi_rx_stop函数来停止 I2S-over-SPI 接收
/* 停止接收流程 */
hpm_stat_t result = hpm_i2s_master_over_spi_rx_stop(&i2s_device);
if (result != status_success) {
printf("Failed to stop RX: 0x%x\n", result);
}
5.3.5. I2S-over-SPI发送操作
5.3.5.1. 启动发送,分为阻塞和非阻塞接口
5.3.5.1.1. 非阻塞发送
调用
hpm_i2s_master_over_spi_tx_buffer_nonblockingAPI 启动非阻塞式发送。 - 启动I2S发送流程,开始发送数据。 - 该API会根据配置信息启动I2S-over-SPI的发送操作,开始发送数据。hpm_i2s_master_over_spi_tx_buffer_nonblockingAPI原型:hpm_stat_t hpm_i2s_master_over_spi_tx_buffer_nonblocking(hpm_i2s_over_spi_t *i2s, uint8_t protocol, uint32_t lrck_hz, uint8_t audio_depth, uint8_t *data, uint32_t size);
参数说明:
参数名
类型
描述
i2s
hpm_i2s_over_spi_t*
I2S-over-SPI上下文结构体指针(需已初始化)
protocol
uint8_t
协议类型,当前不支持:
I2S_PROTOCOL_PCM和I2S_PROTOCOL_I2S_PHILIPSlrck_hz
uint32_t
声道时钟频率(采样率),单位Hz 比如8000/16000
audio_depth
uint8_t
音频位深度,当前支持:(16bit)和(32bit)
data
uint8_t*
发送数据缓冲区指针
size
uint32_t
数据长度(字节)
返回值:
返回值
描述
status_success
发送成功启动
status_invalid_argument
参数错误(包含以下情况):缓冲区指针无效,协议类型不支持,数据长度超限
举例: 如何使用
hpm_i2s_master_over_spi_tx_buffer_nonblocking函数来进行非阻塞发送/* 非阻塞发送16bit音频数据 */ ATTR_PLACE_AT_NONCACHEABLE uint8_t tx_buffer[TX_SIZE_MAX]; hpm_stat_t result = hpm_i2s_master_over_spi_tx_buffer_nonblocking(&i2s_device, I2S_PROTOCOL_MSB_JUSTIFIED, 16000, 16, tx_buffer, TX_SIZE_MAX); if (result != status_success) { printf("非阻塞发送启动失败: 0x%x\n", result); }
调用
hpm_i2s_master_over_spi_tx_is_busyAPI 检查I2S发送状态,当``hpm_i2s_master_over_spi_tx_buffer_nonblocking`` 发送完成后会返回true。hpm_i2s_master_over_spi_tx_is_busyAPI原型:bool hpm_i2s_master_over_spi_tx_is_busy(hpm_i2s_over_spi_t *i2s);
参数说明:
参数名
类型
描述
i2s
hpm_i2s_over_spi_t*
I2S-over-SPI上下文结构体指针
返回值:
返回值
描述
true
发送正在进行中
false
发送已完成或未启动
举例: 如何使用
hpm_i2s_master_over_spi_tx_is_busy函数来检查I2S发送状态/* 在非阻塞发送中轮询状态 */ while (hpm_i2s_master_over_spi_tx_is_busy(&i2s_device)) { }
5.3.5.1.2. 阻塞发送
调用
hpm_i2s_master_over_spi_tx_buffer_blockingAPI 执行阻塞式发送启动I2S发送流程,开始发送数据,会阻塞等待发送完成。
hpm_i2s_master_over_spi_tx_buffer_blockingAPI原型:hpm_stat_t hpm_i2s_master_over_spi_tx_buffer_blocking(hpm_i2s_over_spi_t *i2s, uint8_t protocol, uint32_t lrck_hz, uint8_t audio_depth, uint8_t *data, uint32_t size, uint32_t timeout);
参数说明:
参数名
类型
描述
i2s
hpm_i2s_over_spi_t*
I2S-over-SPI上下文结构体指针(需已初始化)
protocol
uint8_t
协议类型,当前不支持:
I2S_PROTOCOL_PCM和I2S_PROTOCOL_I2S_PHILIPSlrck_hz
uint32_t
声道时钟频率(采样率),单位Hz 比如8000/16000
audio_depth
uint8_t
音频位深度,当前支持:(16bit)和(32bit)
data
uint8_t*
发送数据缓冲区指针
size
uint32_t
数据长度(字节)
timeout
uint32_t
超时时间(单位为毫秒)。如果在这个时间内操作没有完成,函数将返回一个超时错误
返回值:
返回值
描述
status_success
发送成功完成
status_invalid_argument
参数错误(包含以下情况):缓冲区指针无效,协议类型不支持,数据长度超限
举例: 如何使用
hpm_i2s_master_over_spi_tx_buffer_blocking函数来阻塞发送数据。/* 阻塞发送16bit音频数据 */ uint8_t tx_buffer[TX_SIZE_MAX]; hpm_stat_t result = hpm_i2s_master_over_spi_tx_buffer_blocking(&i2s_device, I2S_PROTOCOL_MSB_JUSTIFIED, 16000, 16, tx_buffer, TX_SIZE_MAX, 1000000); if (result != status_success) { printf("阻塞发送失败: 0x%x\n", result); }
5.4. 注意
由于I2S_over_SPI组件使用了DMA管理器组件,DMA的通道等配置由DMA管理器分配,在使用DMA时分配的DMA通道避免与I2S_over_SPI组件使用的DMA通道冲突。
I2S_over_SPI组件使用的TX DMA通道可以在
i2s_device实例变量的成员变量中找到,比如i2s_device.tx_dma.resource。I2S_over_SPI组件使用的RX DMA通道可以在
i2s_device实例变量的成员变量中找到,比如i2s_device.rx_dma.resource。
举例 : 如何使用DMA通道资源
hpm_i2s_over_spi_t i2s_device; /* 初始I2S over SPI实例... 不做列举 */ /* 打印发送DMA通道资源占用的DMA实例以及DMA通道 */ printf("TX DMA instance: %d, TX DMA channel: %d\n", i2s_device.rx_dma.resource->dma_instance, i2s_device.rx_dma.resource->dma_channel); /* 改变TX DMA资源的中断优先级为1*/ dma_mgr_enable_dma_irq_with_priority(i2s_device.rx_dma.resource, 1); /* 获取接收DMA通道资源占用的DMA实例以及DMA通道 */ printf("RX DMA instance: %d, TX DMA channel: %d\n", i2s_device.rx_dma.resource->dma_instance, i2s_device.rx_dma.resource->dma_channel); /* 改变RX DMA资源的中断优先级为1*/ dma_mgr_enable_dma_irq_with_priority(i2s_device.rx_dma.resource, 1);