4. SPI Component

4.1. Overview

  • The SPI component provides a higher-level set of APIs compared to the driver layer for configuring, initializing, and operating the SPI bus. It supports multiple SPI devices and offers data transmission functionalities in both blocking and non-blocking modes. Below are the key features of this component:

    • Multi-instance support

      • Supports multiple SPI instances (such as SPI0, SPI1, etc.), each with independent configuration parameters.

    • Blocking and non-blocking read/write operation interfaces

      • Supports both master and slave modes.

      • Provides non-blocking read/write functions.

      • Provides blocking read/write functions with timeout mechanisms to ensure timely error status returns in case of long unresponsiveness.

4.2. Introduction to Relevant Enums and Structs

  • Enum introduction

    • SPI working mode enum type: This enum defines two different SPI working mode options, namely master mode and slave mode. This enum is defined in hpm_spi_drv.h.

      typedef enum {
          spi_master_mode = 0,
          spi_slave_mode
      } spi_mode_selection_t;
      
    • SPI data transfer format enum type: Supports three data formats—single-wire, dual-wire, and quad-wire. This enum is defined in 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 enum type: This enum defines the idle state level of the SPI SCLK. Specifically, it defines low idle and high idle states. This enum is defined in hpm_spi_drv.h.

      typedef enum {
          spi_sclk_low_idle = 0,
          spi_sclk_high_idle
      } spi_sclk_idle_state_t;
      
    • SPI CPHA enum type: This enum specifies whether the SPI SCLK sampling occurs on odd or even clock edges. Specifically, it defines odd clock edges and even clock edges. This enum is defined in 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 enum type: This enum specifies the bit order in SPI communication. spi_msb_first indicates most significant bit first, while spi_lsb_first indicates least significant bit first. This enum is defined in hpm_spi_drv.h.

      typedef enum {
          spi_msb_first = 0,
          spi_lsb_first,
      } spi_shift_direction_t;
      
  • Struct introduction

    • Initialization configuration struct, used for configuring settings of the SPI. It includes parameters such as SPI operation mode, data line mode, clock polarity, clock phase, data shift direction, data length (in bits), and data merging mode.

      typedef struct {
          spi_mode_selection_t mode;               /*!< SPI working mode */
          spi_data_phase_format_t io_mode;         /*!< SPI data line mode */
          spi_sclk_idle_state_t clk_polarity;      /*!< Clock polarity (CPOL) */
          spi_sclk_sampling_clk_edges_t clk_phase; /*!< Clock phase (CPHA) */
          spi_shift_direction_t direction;         /*!< Data shift direction (MSB or LSB) */
          uint8_t data_len;                        /*!< Length in bits (1~32 bits) */
          bool data_merge;                         /*!< Data merging mode, only supported when data_len is 8 */
      } spi_initialize_config_t;
      

4.3. API Call Flow Introduction

4.3.1. Define SPI Initialization Variable

  • Define a variable of the spi_initialize_config_t struct. For example, the init_config variable used in the sample.

4.3.2. Default SPI Initialization Variable

  • Use the hpm_spi_get_default_init_config API to assign default values to the init_config variable’s parameters within the API’s spi_initialize_config_t .

    • Prototype of the hpm_spi_get_default_init_config API:

      void hpm_spi_get_default_init_config(spi_initialize_config_t *config);
      
    • This function sets each member to default values:

      • Master operation mode

      • Single-wire data line mode

      • Clock polarity low idle

      • Clock phase sampling on odd clock edges

      • Data length of 8 bits

      • No data merging

      • Data transfer direction MSB first

4.3.3. SPI Initialization

  • Call the hpm_spi_initialize API to initialize the SPI. During this process, the parameters of the init_config variable are assigned and take effect in the API’s config parameter.

  • Prototype of the hpm_spi_initialize API:

    hpm_stat_t hpm_spi_initialize(SPI_Type *ptr, spi_initialize_config_t *config);
    
  • Example : Instantiate SPI0, set SPI0 to master mode, quad-wire data line, CPOL high idle, CPHA sampling on odd clock edges, data transfer direction MSB first, data unit length of 16 bits.

    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);
    
  • Example : Instantiate SPI1, set SPI1 to slave mode, single-wire data line, CPOL low idle, CPHA sampling on even clock edges, data transfer direction LSB first, data unit length of 8 bits.

    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. Set SPI Master SCLK Frequency

  • Not required in slave mode.

  • Call the hpm_spi_set_sclk_frequency API to set the SPI’s SCLK frequency.

  • Prototype of the hpm_spi_set_sclk_frequency API:

    hpm_stat_t hpm_spi_set_sclk_frequency(SPI_Type *ptr, uint32_t freq)
    
    • This function sets the specified SPI’s SCLK frequency by selecting appropriate clock sources and division factors to ensure the SPI clock frequency is as close as possible to the requested frequency.

    • Parameter Description

      Parameter Name

      Type

      Description

      ptr

      SPI_Type *

      Pointer to the SPI module register structure

      freq

      uint32_t

      Target SPI clock frequency (unit: Hz)

    • Return Values

      • status_success: Successfully set the SPI clock frequency.

      • status_invalid_argument: Invalid argument provided (e.g., configuration object is NULL)

  • Example : How to use the hpm_spi_set_sclk_frequency function to set the SPI1 master SCLK frequency to 1 MHz.

    uint32_t desired_freq = 1000000; /* Desired frequency is 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 Configuration

  • If using non-blocking read/write interfaces, this step is necessary.

  • Use the hpm_spi_dma_mgr_install_callback API for DMA initialization and to register callback functions. This function must be called when using non-blocking APIs for data transmission. If no callback function is needed, NULL can be passed as the callback parameter.

  • It is used to configure callback functions for DMA transfers in the SPI module. This function allows users to set completion callback functions for both TX and RX DMA channels separately to perform specific operations upon completion of DMA transfers.

  • Prototype of the 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)
    
    • Parameter Description

      Parameter Name

      Type

      Description

      ptr

      SPI_Type*

      Pointer to the SPI module register structure

      tx_complete

      spi_dma_complete_cb

      Callback function for TX DMA transfer completion

      rx_complete

      spi_dma_complete_cb

      Callback function for RX DMA transfer completion

    • Return Values:

      • status_success : Success

      • status_invalid_argument : Invalid argument

  • Example : How to use the hpm_spi_dma_mgr_install_callback function to register callback functions.

    /**
    * Example TX DMA transfer completion callback function.
    */
    void tx_dma_complete_callback(void *context) {
        printf("TX DMA transfer completed.\n");
    }
    
    /**
    * Example RX DMA transfer completion callback function.
    */
    void rx_dma_complete_callback(void *context) {
        printf("RX DMA transfer completed.\n");
    }
    int main(void) {
        /* Initialize SPI... Not listed */
    
        /* Register DMA transfer completion callbacks */
        hpm_stat_t result = hpm_spi_dma_mgr_install_callback(spi_ptr, tx_dma_complete_callback, rx_dma_complete_callback);
    
        if (result == status_success) {
            /* Successfully installed DMA transfer completion callback functions */
            printf("DMA callback functions installed successfully.\n");
        } else {
            /* Failed to install DMA transfer completion callback functions */
            printf("Failed to install DMA callback functions.\n");
        }
    
        /* TODO */
    }
    
  • Hint:

    • If you need to use a custom DMA callback function, you can use the hpm_spi_rx_dma_mgr_install_custom_callback and hpm_spi_tx_dma_mgr_install_custom_callback API. This API registers directly to the DMA manager and uses a user-defined callback.

4.3.6. Read/Write Operations Supporting Master and Slave Modes

4.3.6.1. Blocking Read/Write Operations, Divided into Full-Duplex and Half-Duplex Operations

  • Full-Duplex Read/Write Operation : Used for blocking simultaneous read/write operations. It waits until data transmission is complete or a timeout occurs, provided by the hpm_spi_transmit_receive_blocking API.

  • hpm_spi_transmit_receive_blocking API

    • Prototype of the 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)
      
      • Parameter Description

        Parameter Name

        Type

        Description

        ptr

        SPI_Type*

        Pointer to the SPI module register structure

        wbuff

        uint8_t*

        Pointer to the buffer containing data to send

        rbuff

        uint8_t*

        Pointer to the buffer to receive data

        size

        uint32_t

        Size of data to transmit (in bytes)

        timeout

        uint32_t

        Timeout in milliseconds. If the operation does not complete within this time, the function will return a timeout error

      • Return Values:

        • status_success : Successfully completed data transmission

        • status_invalid_argument : Invalid argument provided

        • status_timeout : If the operation did not complete within the specified timeout

      • Example : An SPI1 master or slave device simultaneously sending and receiving some data over the SPI bus.

      uint8_t tx_buffer[4] = {0x01, 0x02, 0x03, 0x04};  /* Transmit buffer */
      uint8_t rx_buffer[4];  /* Receive buffer */
      uint32_t transfer_size = 4;  /* Data size to transfer */
      uint32_t timeout_ms = 1000;  /* Timeout of 1 second */
      
      /* Initialize SPI... Not listed */
      
      hpm_stat_t result = hpm_spi_transmit_receive_blocking(HPM_SPI1, tx_buffer, rx_buffer, transfer_size, timeout_ms);
      if (result == status_success) {
          /* Successfully completed data transmission */
          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 {
          /* Data transmission failed */
          printf("Failed to complete data transfer.\n");
      }
      
  • Half-Duplex Read/Write Operation : Used for blocking half-duplex read/write operations. It waits until data transmission is complete or a timeout occurs, provided by the hpm_spi_transmit_blocking and hpm_spi_receive_blocking APIs.

  • hpm_spi_transmit_blocking API

    • Used for sending data through the SPI interface. The function works in blocking mode, meaning it waits until data transmission completes or times out.

    • Prototype of the hpm_spi_transmit_blocking API:

      hpm_stat_t hpm_spi_transmit_blocking(SPI_Type *ptr, uint8_t *buff, uint32_t size, uint32_t timeout)
      
      • Parameter Description

        Parameter Name

        Type

        Description

        ptr

        SPI_Type*

        Pointer to the SPI module register structure

        buff

        uint8_t*

        Pointer to the buffer containing data to send

        size

        uint32_t

        Size of data to transmit (in bytes)

        timeout

        uint32_t

        Timeout in milliseconds. If the operation does not complete within this time, the function will return a timeout error

      • Return Values:

        • status_success : Successfully completed data transmission

        • status_invalid_argument : Invalid argument provided

        • status_timeout : If the operation did not complete within the specified timeout

      • Example : An SPI1 master or slave device sending some data over the SPI bus.

      uint8_t tx_buffer[4] = {0x01, 0x02, 0x03, 0x04};  /* Transmit buffer */
      uint32_t transfer_size = 4;  /* Data size to transfer */
      uint32_t timeout_ms = 1000;  /* Timeout of 1 second */
      
      /* Initialize SPI... Not listed */
      
      hpm_stat_t result = hpm_spi_transmit_blocking(HPM_SPI1, tx_buffer, transfer_size, timeout_ms);
      if (result == status_success) {
          /* Successfully completed data transmission */
          printf("Data transmission completed successfully.\n");
      } else {
          /* Data transmission failed */
          printf("Failed to complete data transmission.\n");
      }
      
  • hpm_spi_receive_blocking API

    • Used for receiving data through the SPI interface. The function works in blocking mode, meaning it waits until data reception completes or times out.

    • Prototype of the hpm_spi_receive_blocking API:

      hpm_stat_t hpm_spi_receive_blocking(SPI_Type *ptr, uint8_t *buff, uint32_t size, uint32_t timeout)
      
      • Parameter Description

        Parameter Name

        Type

        Description

        ptr

        SPI_Type*

        Pointer to the SPI module register structure

        buff

        uint8_t*

        Pointer to the buffer to receive data

        size

        uint32_t

        Size of data to receive (in bytes)

        timeout

        uint32_t

        Timeout in milliseconds. If the operation does not complete within this time, the function will return a timeout error

      • Return Values:

        • status_success : Successfully completed data reception

        • status_invalid_argument : Invalid argument provided

        • status_timeout : If the operation did not complete within the specified timeout

      • Example : An SPI1 master or slave device receiving some data over the SPI bus.

      uint8_t rx_buffer[4];  /* Receive buffer */
      uint32_t transfer_size = 4;  /* Data size to transfer */
      uint32_t timeout_ms = 1000;  /* Timeout of 1 second */
      
      /* Initialize SPI... Not listed */
      hpm_stat_t result = hpm_spi_receive_blocking(HPM_SPI1, rx_buffer, transfer_size, timeout_ms);
      if (result == status_success) {
          /* Successfully completed data reception */
          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 {
          /* Data reception failed */
          printf("Failed to complete data reception.\n");
      }
      

4.3.6.2. Non-blocking Read/Write Operations, Divided into Full-Duplex and Half-Duplex Operations

  • Full-Duplex Read/Write Operation : Used for non-blocking simultaneous read/write operations. It allows other tasks to continue executing without waiting for the operation to complete, provided by the hpm_spi_transmit_receive_nonblocking API.

  • hpm_spi_transmit_receive_nonblocking API

    • Prototype of the 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);
      
      • Parameter Description

        Parameter Name

        Type

        Description

        ptr

        SPI_Type*

        Pointer to the SPI module register structure

        wbuff

        uint8_t*

        Pointer to the buffer containing data to send

        rbuff

        uint8_t*

        Pointer to the buffer to receive data

        size

        uint32_t

        Size of data to transfer (in bytes)

      • Return Values:

        • status_success : Successfully configured non-blocking data transmission/reception

        • status_invalid_argument : Invalid argument provided

      • Example : An SPI1 master or slave device simultaneously sending and receiving some data over the SPI bus.

      uint8_t tx_buffer[4] = {0x01, 0x02, 0x03, 0x04};  /* Transmit buffer */
      uint8_t rx_buffer[4];  /* Receive buffer */
      uint32_t transfer_size = 4;  /* Data size to transfer */
      uint32_t timeout_ms = 1000;  /* Timeout of 1 second */
      
      /* Initialize SPI... Not listed */
      
      hpm_stat_t result = hpm_spi_transmit_receive_nonblocking(spi_ptr, tx_buffer, rx_buffer, transfer_size);
      if (result == status_success) {
          /* Successfully configured non-blocking transmission/reception */
          printf("Non-blocking data transmission and reception configured successfully.\n");
      } else {
          /* Failed to configure non-blocking transmission/reception */
          printf("Failed to configure non-blocking data transmission and reception.\n");
      }
      
      /* TODO Since it's a non-blocking operation, the function returns immediately, allowing the program to continue executing other tasks. For example, wait for this transmission to complete */
      
  • Half-Duplex Read/Write Operation : Used for non-blocking half-duplex read/write operations, provided by the hpm_spi_transmit_nonblocking and hpm_spi_receive_nonblocking APIs.

  • hpm_spi_transmit_nonblocking API

    • Used for non-blocking data transmission through the SPI interface. The function does not wait for the transmission to complete but returns immediately, allowing background processing of data transmission.

    • Prototype of the hpm_spi_transmit_nonblocking API:

      hpm_stat_t hpm_spi_transmit_nonblocking(SPI_Type *ptr, uint8_t *buff, uint32_t size)
      
      • Parameter Description

        Parameter Name

        Type

        Description

        ptr

        SPI_Type*

        Pointer to the SPI module register structure

        buff

        uint8_t*

        Pointer to the buffer containing data to send

        size

        uint32_t

        Size of data to transmit (in bytes)

      • Return Values:

        • status_success : Successfully completed data transmission

        • status_invalid_argument : Invalid argument provided

      • Example : An SPI1 master or slave device sending some data over the SPI bus.

      uint8_t tx_buffer[4] = {0x01, 0x02, 0x03, 0x04};  /* Transmit buffer */
      uint32_t transfer_size = 4;  /* Data size to transfer */
      uint32_t timeout_ms = 1000;  /* Timeout of 1 second */
      
      /* Initialize SPI... Not listed */
      
      hpm_stat_t result = hpm_spi_transmit_nonblocking(HPM_SPI1, tx_buffer, transfer_size);
      if (result == status_success) {
          /* Successfully configured non-blocking transmission */
          printf("Non-blocking data transmission configured successfully.\n");
      } else {
          /* Failed to configure non-blocking transmission */
          printf("Failed to configure non-blocking data transmission.\n");
      }
      /* TODO Since it's a non-blocking operation, the function returns immediately, allowing the program to continue executing other tasks. For example, wait for this transmission to complete */
      
  • hpm_spi_receive_nonblocking API

    • Used for non-blocking data reception through the SPI interface. The function does not wait for the reception to complete but returns immediately, allowing background processing of data reception.

    • Prototype of the hpm_spi_receive_nonblocking API:

      hpm_stat_t hpm_spi_receive_nonblocking(SPI_Type *ptr, uint8_t *buff, uint32_t size)
      
      • Parameter Description

        Parameter Name

        Type

        Description

        ptr

        SPI_Type*

        Pointer to the SPI module register structure

        buff

        uint8_t*

        Pointer to the buffer to receive data

        size

        uint32_t

        Size of data to receive (in bytes)

      • Return Values:

        • status_success : Successfully configured non-blocking reception

        • status_invalid_argument : Invalid argument provided

      • Example : An SPI1 master or slave device receiving some data over the SPI bus.

      uint8_t rx_buffer[4];  /* Receive buffer */
      uint32_t transfer_size = 4;  /* Data size to transfer */
      uint32_t timeout_ms = 1000;  /* Timeout of 1 second */
      
      /* Initialize SPI... Not listed */
      hpm_stat_t result = hpm_spi_receive_nonblocking(spi_ptr, rx_buffer, transfer_size);
      if (result == status_success) {
          /* Successfully configured non-blocking reception */
          printf("Non-blocking data reception configured successfully.\n");
      } else {
          /* Failed to configure non-blocking reception */
          printf("Failed to configure non-blocking data reception.\n");
      }
      /* TODO Since it's a non-blocking operation, the function returns immediately, allowing the program to continue executing other tasks. For example, wait for this transmission to complete */
      

Note

  • As the SPI component uses the DMA manager component, configurations such as DMA channels are allocated by the DMA manager. When using DMA, ensure that the allocated DMA channels do not conflict with those used by the SPI component.

  • The transmit DMA channel used by the SPI component can be obtained by calling the hpm_spi_get_tx_dma_resource API to get the SPI transmit DMA channel resources.

    • Prototype of the hpm_spi_get_tx_dma_resource API:

      dma_resource_t *hpm_spi_get_tx_dma_resource(SPI_Type *ptr)
      
  • The receive DMA channel used by the SPI component can be obtained by calling the hpm_spi_get_rx_dma_resource API to get the SPI receive DMA channel resources.

    • Prototype of the hpm_spi_get_rx_dma_resource API:

      dma_resource_t *hpm_spi_get_rx_dma_resource(SPI_Type *ptr)
      
  • Example: How to Use hpm_spi_get_tx_dma_resource and hpm_spi_get_rx_dma_resource Functions to Obtain DMA Channel Resources and Utilize Them for Data Transfer

    /* Initialize SPI... details omitted */
    /* Get transmit DMA channel resource */
    dma_resource_t *tx_dma_resource = hpm_spi_get_tx_dma_resource(HPM_SPI1);
    if (tx_dma_resource != NULL) {
        /* Successfully obtained transmit DMA channel resource */
        printf("TX DMA channel resource obtained successfully.\n");
        /* Print the DMA instance and channel used by the transmit DMA resource */
        printf("TX DMA instance: %d, TX DMA channel: %d\n", tx_dma_resource->dma_instance, tx_dma_resource->dma_channel);
        /* Change the TX DMA resource's interrupt priority to 1 */
        dma_mgr_enable_dma_irq_with_priority(tx_dma_resource, 1);
    }
    /* Get receive DMA channel resource */
    dma_resource_t *rx_dma_resource = hpm_spi_get_rx_dma_resource(HPM_SPI1);
    if (rx_dma_resource != NULL) {
        /* Successfully obtained receive DMA channel resource */
        printf("RX DMA channel resource obtained successfully.\n");
        /* Get the DMA instance and channel used by the receive DMA resource */
        printf("RX DMA instance: %d, TX DMA channel: %d\n", rx_dma_resource->dma_instance, rx_dma_resource->dma_channel);
        /* Change the RX DMA resource's interrupt priority to 1 */
        dma_mgr_enable_dma_irq_with_priority(rx_dma_resource, 1);
    }