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_config API就是对 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_init API 来初始化 I2S-over-SPI。在此过程中,i2s_device 实例变量的参数会被赋值到API的``i2s``并生效。

  • hpm_i2s_master_over_spi_init API原型:

    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-SPI

    hpm_i2s_master_over_spi_init(&i2s_device);
    

5.3.4. I2S-over-SPI接收操作

5.3.4.1. 接收配置

  • 调用 hpm_i2s_master_over_spi_rx_config API 配置I2S接收参数。

    • 配置I2S接收参数,包括协议类型、采样率、音频位深度、接收缓冲区等。

    • 配置完成后,I2S-over-SPI会根据配置信息进行初始化和准备接收操作。

    • 该API支持单缓存区和双缓冲区模式。

    • 双缓冲模式下,I2S-over-SPI会使用两个缓冲区交替接收数据,确保数据的连续性和完整性。

    • 双缓冲模式下,在上述的rx_callback回调函数中,需要判断当前接收的是哪个缓冲区的数据,然后进行相应的处理。

  • hpm_i2s_master_over_spi_rx_config API原型:

    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_PCMI2S_PROTOCOL_I2S_PHILIPS

      lrck_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_start API 启动配置好的I2S接收流程

    • 启动I2S接收流程,开始接收数据。

    • 该API会根据配置信息启动I2S-over-SPI的接收操作,开始接收数据。

    • 接收完成后,会触发相应的回调函数(如 rx_callback)进行数据处理。

  • hpm_i2s_master_over_spi_rx_start API原型:

    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_stop API 停止正在进行的I2S接收流程。 - 该API会停止I2S-over-SPI的接收操作,停止接收数据。

  • hpm_i2s_master_over_spi_rx_start API原型:

    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_nonblocking API 启动非阻塞式发送。 - 启动I2S发送流程,开始发送数据。 - 该API会根据配置信息启动I2S-over-SPI的发送操作,开始发送数据。

    • hpm_i2s_master_over_spi_tx_buffer_nonblocking API原型:

      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_PCMI2S_PROTOCOL_I2S_PHILIPS

      lrck_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_busy API 检查I2S发送状态,当``hpm_i2s_master_over_spi_tx_buffer_nonblocking`` 发送完成后会返回true。

    • hpm_i2s_master_over_spi_tx_is_busy API原型:

      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_blocking API 执行阻塞式发送

    • 启动I2S发送流程,开始发送数据,会阻塞等待发送完成。

  • hpm_i2s_master_over_spi_tx_buffer_blocking API原型:

    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_PCMI2S_PROTOCOL_I2S_PHILIPS

      lrck_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);