4. SPI component

4.1. 概述

  • SPI 组件提供了一套相比驱动层更高级的API,用于配置、初始化和操作SPI总线。支持多种SPI设备,并提供了阻塞和非阻塞模式下的数据传输功能。以下是该组件的主要特征:

    • 多实例支持

      • 支持多个SPI实例(如SPI0, SPI1等),每个实例都有独立的配置参数。

    • 阻塞与非阻塞读写操作接口

      • 支持主从机。

      • 提供了非阻塞式的读写函数。

      • 提供了阻塞式的读写函数,支持超时机制,确保在长时间未响应的情况下能够及时返回错误状态。

4.2. 相关枚举和结构体介绍

  • 枚举介绍

    • SPI工作模式枚举类型,这个枚举定义了两种不同的SPI工作模式选项,分别是主机模式和从机模式。该枚举在 hpm_spi_drv.h 中定义。

      typedef enum {
      spi_master_mode = 0,
      spi_slave_mode
      } spi_mode_selection_t;
      
    • SPI数据传输格式枚举类型,支持三种数据格式:单线、双线、四线三种格式。该枚举在 hpm_spi_drv.h 中定义。

      typedef enum {
          spi_single_io_mode = 0,
          spi_dual_io_mode,
          spi_quad_io_mode,
      } spi_data_phase_format_t;
      
    • SPI CPOL枚举类型,这个枚举定义了SPI SCLK在空闲状态下的电平。分别空闲低电平和空闲高电平。该枚举在 hpm_spi_drv.h 中定义。

      typedef enum {
          spi_sclk_low_idle = 0,
          spi_sclk_high_idle
      } spi_sclk_idle_state_t;
      
    • SPI CPHA枚举类型,这个枚举用于指定SPI SCLK采样是在奇数边沿还是偶数边沿进行。分别奇数边沿和偶数边沿进行。该枚举在 hpm_spi_drv.h 中定义。

      typedef enum {
          spi_sclk_sampling_odd_clk_edges = 0,
          spi_sclk_sampling_even_clk_edges
      } spi_sclk_sampling_clk_edges_t;
      
    • SPI BIT order枚举类型,这个枚举用于指定SPI通信中的位顺序。 spi_msb_first 表示最高有效位优先,而 spi_lsb_first 表示最低有效位优先。该枚举在 hpm_spi_drv.h 中定义。

      typedef enum {
          spi_msb_first = 0,
          spi_lsb_first,
      } spi_shift_direction_t;
      
  • 结构体介绍

    • 初始化配置结构体,用于配置SPI的设置。它包括SPI操作模式、数据线模式、时钟极性、时钟相位、数据移位方向、数据长度(以位为单位)和数据合并模式等参数。

      typedef struct {
          spi_mode_selection_t mode;               /*!< SPI工作模式 */
          spi_data_phase_format_t io_mode;         /*!< SPI数据线模式 */
          spi_sclk_idle_state_t clk_polarity;      /*!< 时钟极性 (CPOL) */
          spi_sclk_sampling_clk_edges_t clk_phase; /*!< 时钟相位 (CPHA) */
          spi_shift_direction_t direction;         /*!< 数据移位方向 (MSB或LSB) */
          uint8_t data_len;                        /*!< 单位是位 (1~32位) */
          bool data_merge;                         /*!< 数据合并模式 仅支持 data_len'为 8 */
      } spi_initialize_config_t;
      

4.3. API调用流程介绍

4.3.1. 定义SPI初始化变量

  • 定义一个 spi_initialize_config_t 结构体的变量。例如,在示例中使用的 init_config 变量。

4.3.2. 缺省SPI初始化变量

  • 使用 hpm_spi_get_default_init_config API 对 init_config 变量赋值到API的 spi_initialize_config_t 参数中,进行初始化。

    • hpm_spi_get_default_init_config API原型:

      void hpm_spi_get_default_init_config(spi_initialize_config_t *config);
      
    • 此函数将各个成员设置为默认值:

      • 主机工作模式

      • 单线数据线模式

      • 时钟极性为低电平空闲

      • 时钟相位为在奇数边沿采样

      • 数据长度为8位

      • 不进行数据合并

      • 数据传输方向为MSB优先

4.3.3. SPI初始化

  • 调用 hpm_spi_initialize API 来初始化 SPI。在此过程中, init_config 变量的参数会被赋值到API的 config 并生效。

  • hpm_spi_initialize API原型:

    hpm_stat_t hpm_spi_initialize(SPI_Type *ptr, spi_initialize_config_t *config);
    
  • 举例 : 实例化SPI0,设置SPI0为主机模式,四线数据线,CPOL为高电平空闲,CPHA为在奇数边沿采样,数据传输方向为MSB优先,数据单位长度为16位。

    init_config.mode = spi_master_mode;
    init_config.io_mode = spi_quad_io_mode;
    init_config.clk_polarity = spi_sclk_high_idle;
    init_config.clk_phase = spi_sclk_sampling_odd_clk_edges;
    init_config.data_len = 16;
    init_config.direction = spi_msb_first;
    hpm_spi_initialize(HPM_SPI0, &init_config);
    
  • 举例: 实例化SPI1,设置SPI1为从机模式,单线数据线,CPOL为低电平空闲,CPHA为在偶数边沿采样,数据传输方向为LSB优先,数据单位长度为8位。

    init_config.mode = spi_slave_mode;
    init_config.io_mode = spi_dual_io_mode;
    init_config.clk_polarity = spi_sclk_low_idle;
    init_config.clk_phase = spi_sclk_sampling_even_clk_edges;
    init_config.data_len = 8;
    init_config.direction = spi_lsb_first;
    hpm_spi_initialize(HPM_SPI1, &init_config);
    

4.3.4. SPI主机设置SCLK频率

  • 从机模式下不需要此操作

  • 调用 hpm_spi_set_sclk_frequency API 来设置SPI的SCLK频率。

  • hpm_spi_set_sclk_frequency API原型:

    hpm_stat_t hpm_spi_set_sclk_frequency(SPI_Type *ptr, uint32_t freq)
    
    • 该函数用于设置指定SPI的SCLK频率。该函数通过选择合适的时钟源和分频系数来确保SPI时钟频率尽可能接近用户请求的频率。

      • 参数说明

        参数名

        类型

        描述

        ptr

        SPI_Type *

        指向SPI模块寄存器结构体的指针

        freq

        uint32_t

        请求的目标SPI时钟频率(单位:Hz)

      • 返回值

        • status_success: 成功设置SPI时钟频率。

        • status_invalid_argument: 提供的参数无效(例如,配置对象为NULL)

  • 举例 : 如何使用 hpm_spi_set_sclk_frequency 函数来设置SPI1主机 SCLK频率为1Mhz

    uint32_t desired_freq = 1000000; /* 目标频率为1MHz */
    hpm_stat_t result = hpm_spi_set_sclk_frequency(HPM_SPI1, desired_freq);
    if (result == status_success) {
        printf("SPI SCLK frequency set successfully.\n");
    } else {
        printf("Failed to set SPI SCLK frequency.\n");
    }
    

4.3.5. DMA配置

  • 若使用非阻塞读写接口,需要进行这一步配置

  • 通过 hpm_spi_dma_mgr_install_callback API 进行 DMA 初始化并注册回调函数。在使用非阻塞(non-blocking)API 进行数据传输时,必须调用此函数。如果不需要回调函数,可以传递 NULL 作为回调参数。

  • 用于为SPI模块的DMA传输配置回调函数。该函数允许用户为发送(TX)和接收(RX)DMA通道分别设置完成回调函数,以便在DMA传输完成后执行特定的操作。

  • hpm_spi_dma_mgr_install_callback API原型:

    hpm_stat_t hpm_spi_dma_mgr_install_callback(SPI_Type *ptr, spi_dma_complete_cb tx_complete, spi_dma_complete_cb rx_complete)
    
    • 参数说明

      参数名

      类型

      描述

      ptr

      SPI_Type*

      指向SPI模块寄存器结构体的指针

      tx_complete

      spi_dma_complete_cb

      用于TX DMA传输完成的回调函数

      rx_complete

      spi_dma_complete_cb

      用于RX DMA传输完成的回调函数

    • 返回值:

      • status_success : 成功

      • status_invalid_argument : 无效参数

  • 举例 : 如何使用 hpm_spi_dma_mgr_install_callback 函数来注册回调函数

    /**
    * TX DMA传输完成回调函数示例。
    */
    void tx_dma_complete_callback(void *context) {
        printf("TX DMA transfer completed.\n");
    }
    
    /**
    * RX DMA传输完成回调函数示例。
    */
    void rx_dma_complete_callback(void *context) {
        printf("RX DMA transfer completed.\n");
    }
    int main(void) {
        /* 初始化SPI... 不做列举 */
    
        /* 注册DMA传输完成回调 */
        hpm_stat_t result = hpm_spi_dma_mgr_install_callback(spi_ptr, tx_dma_complete_callback, rx_dma_complete_callback);
    
        if (result == status_success) {
            /* 成功安装DMA传输完成回调函数 */
            printf("DMA callback functions installed successfully.\n");
        } else {
            /* 安装DMA传输完成回调函数失败 */
            printf("Failed to install DMA callback functions.\n");
        }
    
        /* TODO */
    }
    
  • 提示:

    • 如果需要使用自定义的DMA回调函数,可以使用 hpm_spi_rx_dma_mgr_install_custom_callbackhpm_spi_tx_dma_mgr_install_custom_callback API。该API直接注册到DMA管理器,使用用户自定义的回调。

4.3.6. 读写操作,支持主从机模式

4.3.6.1. 阻塞式读写操作,分为全双工读写操作和半双工读写操作

  • 全双工读写操作 :用于阻塞同时读写操作。它会一直等待直到数据传输完成或超时,为 hpm_spi_transmit_receive_blocking API

  • hpm_spi_transmit_receive_blocking API

    • hpm_spi_transmit_receive_blocking API函数原型:

      hpm_stat_t hpm_spi_transmit_receive_blocking(SPI_Type *ptr, uint8_t *wbuff, uint8_t *rbuff, uint32_t size, uint32_t timeout)
      
      • 参数说明

        参数名

        类型

        描述

        ptr

        SPI_Type*

        指向SPI模块寄存器结构体的指针

        wbuff

        uint8_t*

        指向要发送的数据缓冲区的指针

        rbuff

        uint8_t*

        指向接收数据的缓冲区的指针

        size

        uint32_t

        要传输的数据大小(以字节为单位)

        timeout

        uint32_t

        超时时间(单位为毫秒)。如果在这个时间内操作没有完成,函数将返回一个超时错误

      • 返回值:

        • status_success : 成功完成数据传输

        • status_invalid_argument : 提供的参数无效

        • status_timeout : 如果在指定的timeout时间内未能完成操作

      • 举例 : 一个SPI1主设备或者从设备,通过SPI总线同时收发一些数据

        uint8_t tx_buffer[4] = {0x01, 0x02, 0x03, 0x04};  /* 发送缓冲区 */
        uint8_t rx_buffer[4];  /* 接收缓冲区 */
        uint32_t transfer_size = 4;  /* 要传输的数据大小 */
        uint32_t timeout_ms = 1000;  /* 超时时间为1秒 */
        
        /* 初始化SPI... 不做列举 */
        
        hpm_stat_t result = hpm_spi_transmit_receive_blocking(HPM_SPI1, tx_buffer, rx_buffer, transfer_size, timeout_ms);
        if (result == status_success) {
            /* 成功完成数据传输 */
            printf("Data transfer completed successfully.\n");
            printf("Received data: ");
            for (uint32_t i = 0; i < transfer_size; i++) {
                printf("%02X ", rx_buffer[i]);
            }
            printf("\n");
        } else {
            /* 数据传输失败 */
            printf("Failed to complete data transfer.\n");
        }
        
  • 半双工读写操作 :用于阻塞半双工读写操作。它会一直等待直到数据传输完成或超时,为 hpm_spi_transmit_blocking API 和 hpm_spi_receive_blocking API

  • hpm_spi_transmit_blocking API

    • 用于通过SPI接口进行数据的发送。该函数在阻塞模式下工作,即直到数据传输完成或超时才会返回。

    • hpm_spi_transmit_blocking API函数原型:

      hpm_stat_t hpm_spi_transmit_blocking(SPI_Type *ptr, uint8_t *buff, uint32_t size, uint32_t timeout)
      
      • 参数说明

        参数名

        类型

        描述

        ptr

        SPI_Type*

        指向SPI模块寄存器结构体的指针

        buff

        uint8_t*

        指向要发送的数据缓冲区的指针(注意:根据描述,类型应为指向 uint8_t 的指针,因此类型应该是 uint8_t* 而不是 uint8_t

        size

        uint32_t

        要传输的数据大小(以字节为单位)

        timeout

        uint32_t

        超时时间(单位为毫秒)。如果在这个时间内操作没有完成,函数将返回一个超时错误

      • 返回值:

        • status_success : 成功完成数据传输

        • status_invalid_argument : 提供的参数无效

        • status_timeout : 如果在指定的timeout时间内未能完成操作

      • 举例 : 一个SPI1主设备或者从设备,通过SPI总线发送一些数据

      uint8_t tx_buffer[4] = {0x01, 0x02, 0x03, 0x04};  /* 发送缓冲区 */
      uint32_t transfer_size = 4;  /* 要传输的数据大小 */
      uint32_t timeout_ms = 1000;  /* 超时时间为1秒 */
      
      /* 初始化SPI... 不做列举 */
      
      hpm_stat_t result = hpm_spi_transmit_blocking(HPM_SPI1, tx_buffer, transfer_size, timeout_ms);
      if (result == status_success) {
      /* 成功完成数据传输 */
          printf("Data transmission completed successfully.\n");
      } else {
          /* 数据传输失败 */
          printf("Failed to complete data transmission.\n");
      }
      
  • hpm_spi_receive_blocking API

    • 用于通过SPI接口进行数据的接收。该函数在阻塞模式下工作,即直到数据传输完成或超时才会返回。

    • hpm_spi_receive_blocking API函数原型:

      hpm_stat_t hpm_spi_receive_blocking(SPI_Type *ptr, uint8_t *buff, uint32_t size, uint32_t timeout)
      
      • 参数说明

        参数名

        类型

        描述

        ptr

        SPI_Type*

        指向SPI模块寄存器结构体的指针

        buff

        uint8_t*

        指向接收数据缓冲区的指针(注意:根据描述,类型应为指向 uint8_t 的指针)

        size

        uint32_t

        要接收的数据大小(以字节为单位)

        timeout

        uint32_t

        超时时间(单位为毫秒)。如果在这个时间内操作没有完成,函数将返回一个超时错误

      • 返回值:

        • status_success : 成功完成数据接收

        • status_invalid_argument : 提供的参数无效

        • status_timeout : 如果在指定的timeout时间内未能完成操作

      • 举例 : 一个SPI1主设备或者从设备,通过SPI总线接收一些数据

      uint8_t rx_buffer[4];  /* 接收缓冲区 */
      uint32_t transfer_size = 4;  /* 要传输的数据大小 */
      uint32_t timeout_ms = 1000;  /* 超时时间为1秒 */
      
      /* 初始化SPI... 不做列举 */
      hpm_stat_t result = hpm_spi_receive_blocking(HPM_SPI1, rx_buffer, transfer_size, timeout_ms);
      if (result == status_success) {
          /* 成功完成数据接收 */
          printf("Data reception completed successfully.\n");
          printf("Received data: ");
          for (uint32_t i = 0; i < transfer_size; i++) {
              printf("%02X ", rx_buffer[i]);
          }
          printf("\n");
      } else {
          /* 数据接收失败 */
          printf("Failed to complete data reception.\n");
      }
      

4.3.6.2. 非阻塞式读写操作,分为全双工读写操作和半双工读写操作

  • 全双工读写操作 :用于非阻塞同时读写操作。它允许在操作完成之前继续执行其他任务,而不需要等待操作完成,为 hpm_spi_transmit_receive_nonblocking API

  • hpm_spi_transmit_receive_nonblocking API

    • hpm_spi_transmit_receive_nonblocking API函数原型:

      hpm_stat_t hpm_spi_transmit_receive_nonblocking(SPI_Type *ptr, uint8_t *wbuff, uint8_t *rbuff, uint32_t size);
      
      • 参数说明

        参数名

        类型

        描述

        ptr

        SPI_Type*

        指向SPI模块寄存器结构体的指针

        wbuff

        uint8_t*

        指向要发送的数据缓冲区的指针(注意:根据描述,类型应为指向 uint8_t 的指针)

        rbuff

        uint8_t*

        指向接收数据的缓冲区的指针

        size

        uint32_t

        要传输的数据大小(以字节为单位)

      • 返回值:

        • status_success : 成功配置了非阻塞数据传输

        • status_invalid_argument : 提供的参数无效

      • 举例 : 一个SPI1主设备或者从设备,通过SPI总线同时收发一些数据

      uint8_t tx_buffer[4] = {0x01, 0x02, 0x03, 0x04};  /* 发送缓冲区 */
      uint8_t rx_buffer[4];  /* 接收缓冲区 */
      uint32_t transfer_size = 4;  /* 要传输的数据大小 */
      uint32_t timeout_ms = 1000;  /* 超时时间为1秒 */
      
      /* 初始化SPI... 不做列举 */
      
      hpm_stat_t result = hpm_spi_transmit_receive_nonblocking(spi_ptr, tx_buffer, rx_buffer, transfer_size);
      if (result == status_success) {
          /* 成功配置非阻塞传输/接收 */
          printf("Non-blocking data transmission and reception configured successfully.\n");
      } else {
          /* 配置非阻塞传输/接收失败 */
          printf("Failed to configure non-blocking data transmission and reception.\n");
      }
      
      /* TODO 由于是非阻塞操作,函数会立即返回,允许程序继续执行其他任务。比如等待此次传输完成 */
      
  • 半双工读写操作 :用于非阻塞半双工读写操作,为 hpm_spi_transmit_nonblocking API 和 hpm_spi_receive_nonblocking API

  • hpm_spi_transmit_nonblocking API

    • 用于通过SPI接口进行数据的非阻塞发送。该函数不会等待传输完成,而是立即返回,允许在后台处理数据传输。

    • hpm_spi_transmit_nonblocking API函数原型:

      hpm_stat_t hpm_spi_transmit_nonblocking(SPI_Type *ptr, uint8_t *buff, uint32_t size)
      
      • 参数说明

        参数名

        类型

        描述

        ptr

        SPI_Type*

        指向SPI模块寄存器结构体的指针

        buff

        uint8_t*

        指向要发送的数据缓冲区的指针(注意:类型应为指向 uint8_t 的指针)

        size

        uint32_t

        要传输的数据大小(以字节为单位)

      • 返回值:

        • status_success : 成功完成数据传输

        • status_invalid_argument : 提供的参数无效

      • 举例 : 一个SPI1主设备或者从设备,通过SPI总线发送一些数据

      uint8_t tx_buffer[4] = {0x01, 0x02, 0x03, 0x04};  /* 发送缓冲区 */
      uint32_t transfer_size = 4;  /* 要传输的数据大小 */
      uint32_t timeout_ms = 1000;  /* 超时时间为1秒 */
      
      /* 初始化SPI... 不做列举 */
      
      hpm_stat_t result = hpm_spi_transmit_nonblocking(HPM_SPI1, tx_buffer, transfer_size, timeout_ms);
      if (result == status_success) {
      /* 成功配置非阻塞传输 */
          printf("Non-blocking data transmission configured successfully.\n");
      } else {
          /* 配置非阻塞传输失败 */
          printf("Failed to configure non-blocking data transmission.\n");
      }
      /* TODO 由于是非阻塞操作,函数会立即返回,允许程序继续执行其他任务。比如等待此次传输完成 */
      
  • hpm_spi_receive_nonblocking API

    • 用于通过SPI接口进行数据的非阻塞接收。该函数不会等待接收完成,而是立即返回,允许在后台处理数据接收

    • hpm_spi_receive_nonblocking API函数原型:

      hpm_stat_t hpm_spi_receive_nonblocking(SPI_Type *ptr, uint8_t *buff, uint32_t size)
      
      • 参数说明

        参数名

        类型

        描述

        ptr

        SPI_Type*

        指向SPI模块寄存器结构体的指针

        buff

        uint8_t*

        指向接收数据缓冲区的指针(注意:类型应为指向 uint8_t 的指针)

        size

        uint32_t

        要接收的数据大小(以字节为单位)

      • 返回值:

        • status_success : 成功配置了非阻塞接收

        • status_invalid_argument : 提供的参数无效

      • 举例 : 一个SPI1主设备或者从设备,通过SPI总线接收一些数据

      uint8_t rx_buffer[4];  /* 接收缓冲区 */
      uint32_t transfer_size = 4;  /* 要传输的数据大小 */
      uint32_t timeout_ms = 1000;  /* 超时时间为1秒 */
      
      /* 初始化SPI... 不做列举 */
      hpm_stat_t result = hpm_spi_receive_nonblocking(spi_ptr, rx_buffer, transfer_size);
      if (result == status_success) {
          /* 成功配置非阻塞接收 */
          printf("Non-blocking data reception configured successfully.\n");
      } else {
          /* 配置非阻塞接收失败 */
          printf("Failed to configure non-blocking data reception.\n");
      }
      /* TODO 由于是非阻塞操作,函数会立即返回,允许程序继续执行其他任务。比如等待此次传输完成 */
      

Note

  • 由于SPI组件使用了DMA管理器组件,DMA的通道等配置由DMA管理器分配,在使用DMA时分配的DMA通道避免与SPI组件使用的DMA通道冲突。

  • SPI组件使用的发送DMA通道可以调用 hpm_spi_get_tx_dma_resource API 获取SPI发送的DMA通道资源。

    • hpm_spi_get_tx_dma_resource API函数原型:

      dma_resource_t *hpm_spi_get_tx_dma_resource(SPI_Type *ptr)
      
  • SPI组件使用的接收DMA通道可以调用 hpm_spi_get_rx_dma_resource API 获取SPI发送的DMA通道资源。

    • hpm_spi_get_rx_dma_resource API函数原型:

      dma_resource_t *hpm_spi_get_rx_dma_resource(SPI_Type *ptr)
      
  • 举例如何使用 hpm_spi_get_tx_dma_resource 和 hpm_spi_get_rx_dma_resource 函数获取DMA通道资源以及获取到DMA通道资源后如何使用DMA通道资源
    /* 初始化SPI... 不做列举 */
    /* 获取发送DMA通道资源 */
    dma_resource_t *tx_dma_resource = hpm_spi_get_tx_dma_resource(HPM_SPI1);
    if (tx_dma_resource != NULL) {
        /* 成功获取发送DMA通道资源 */
        printf("TX DMA channel resource obtained successfully.\n");
        /* 打印获取到的发送DMA通道资源占用的DMA实例以及DMA通道 */
        printf("TX DMA instance: %d, TX DMA channel: %d\n", tx_dma_resource->dma_instance, tx_dma_resource->dma_channel);
        /* 改变TX DMA资源的中断优先级为1*/
        dma_mgr_enable_dma_irq_with_priority(tx_dma_resource, 1);
    }
    /* 获取接收DMA通道资源 */
    dma_resource_t *rx_dma_resource = hpm_spi_get_rx_dma_resource(HPM_SPI1);
    if (rx_dma_resource != NULL) {
        /* 成功获取接收DMA通道资源 */
        printf("RX DMA channel resource obtained successfully.\n");
        /* 获取接收DMA通道资源占用的DMA实例以及DMA通道 */
        printf("RX DMA instance: %d, TX DMA channel: %d\n", rx_dma_resource->dma_instance, rx_dma_resource->dma_channel);
        /* 改变RX DMA资源的中断优先级为1*/
        dma_mgr_enable_dma_irq_with_priority(rx_dma_resource, 1);
    }