21. I2C

21.1. Overview

  • This section mainly introduces the key driver interfaces and invocation methods of the I2C peripheral in HPM. For more details, please refer to the API documentation in hpm_i2c_drv.h and the relevant user manual.

  • Supports standard mode (100 Kb/s), fast mode (400 Kb/s), and enhanced fast mode (1 Mb/s).

  • Supports 7-bit and 10-bit addressing.

  • Supports master mode and slave mode.

  • Supports various interrupts and DMA transfers.

    • Transfer complete interrupt

    • Byte receive interrupt

    • Byte send interrupt

    • Start signal interrupt

    • Stop signal interrupt

    • Arbitration loss interrupt

    • Address match interrupt

    • FIFO half full/half empty interrupt

    • FIFO full interrupt

    • FIFO empty interrupt

  • Supports bus deadlock recovery. This feature is supported if the HPM_IP_FEATURE_I2C_SUPPORT_RESET macro is defined in the SOC’s hpm_soc_ip_feature.h.

  • Equipped with a 4-byte hardware FIFO buffer. The specific depth can be checked via the I2C_SOC_FIFO_SIZE macro in hpm_soc_feature.h or obtained using the i2c_get_fifo_size API interface in hpm_i2c_drv.h.

  • Maximum single transfer length is 4096 bytes. The specific length can be checked via the I2C_SOC_TRANSFER_COUNT_MAX macro in hpm_soc_feature.h.

21.2. I2C Initialization

  • It is necessary to ensure that the I2C clock source is enabled and the related I2C peripheral pins are initialized.

    • The clock_add_to_group function can be used to add the I2C clock source to the clock group, ensuring the I2C clock source is enabled.

  • Related structure description:

    • The i2c_config_t structure is used to configure I2C parameters

      /* I2C configuration structure */
      typedef struct {
          bool is_10bit_addressing;  /* Whether to use 10-bit addressing */
          uint8_t i2c_mode;          /* I2C speed mode */
      } i2c_config_t;
      
  • Related enumeration value description:

    • The i2c_mode_t enumeration is used to configure I2C speed mode

      /* I2C speed mode */
      typedef enum i2c_mode {
          i2c_mode_normal,      /* Standard mode 100 Kb/s */
          i2c_mode_fast,        /* Fast mode 400 Kb/s */
          i2c_mode_fast_plus,  /* Fast plus mode 1 Mb/s */
      } i2c_mode_t;
      

21.2.1. Master Mode Initialization

  • Initialization API function:

    hpm_stat_t i2c_init_master(I2C_Type *ptr, uint32_t src_clk_in_hz, i2c_config_t *config);
    
    • Parameter description:

      Parameter Name

      Type

      Description

      ptr

      I2C_Type*

      Pointer to the base address of the I2C controller

      src_clk_in_hz

      uint32_t

      Clock source frequency of the I2C controller (Hz)

      config

      i2c_config_t*

      Pointer to the I2C configuration structure

    • Return value:

      Return Value

      Description

      status_success

      Initialization successful

      status_i2c_not_supported

      I2C controller not supported

    • The clock source frequency of the I2C controller can be obtained using the clock_get_frequency API.

      • Example:

        uint32_t i2c_clk_freq = clock_get_frequency(clock_i2c0);
        
  • Example:

    • I2C0 uses standard mode, 100 Kb/s, 7-bit addressing

    /* I2C clock source enable and pin initialization (not shown here) */
    /* I2C0 initialization as master */
    
    i2c_config_t i2c_config;
    uint32_t i2c_clk_freq = clock_get_frequency(clock_i2c0);
    i2c_config.is_10bit_addressing = false;
    i2c_config.i2c_mode = i2c_mode_normal;
    hpm_stat_t status = i2c_init_master(HPM_I2C0, i2c_clk_freq, &i2c_config);
    

21.2.2. Slave Mode Initialization

  • Initialization API function:

    hpm_stat_t i2c_init_slave(I2C_Type *ptr, uint32_t src_clk_in_hz, i2c_config_t *config, const uint16_t slave_address);
    
    • Parameter description:

      Parameter Name

      Type

      Description

      ptr

      I2C_Type*

      Pointer to the base address of the I2C controller

      src_clk_in_hz

      uint32_t

      Clock source frequency of the I2C controller (Hz)

      config

      i2c_config_t*

      Pointer to the I2C configuration structure

      slave_addr

      uint8_t

      Slave address

    • Return value:

      Return Value

      Description

      status_success

      Initialization successful

      status_i2c_not_supported

      I2C controller not supported

    • The clock source frequency of the I2C controller can be obtained using the clock_get_frequency API.

  • Example:

    • I2C0 uses standard mode, 100 Kb/s, 7-bit addressing, slave address 0x10

      /* I2C clock source enable and pin initialization (not shown here) */
      /* I2C0 initialization as slave */
      i2c_config_t i2c_config;
      uint32_t i2c_clk_freq = clock_get_frequency(clock_i2c0);
      i2c_config.is_10bit_addressing = false;
      i2c_config.i2c_mode = i2c_mode_normal;
      hpm_stat_t status = i2c_init_slave(HPM_I2C0, i2c_clk_freq, &i2c_config, 0x10);
      

21.3. I2C Data Transmission

21.3.1. Polling Transfer

21.3.1.1. Master Mode

  • Provides read/write APIs without address operations, with address operations, and a custom sequence transfer API.

    • Read/Write APIs without address operations:

      hpm_stat_t i2c_master_write(I2C_Type *ptr, const uint16_t device_address, uint8_t *buf, const uint32_t size);
      hpm_stat_t i2c_master_read(I2C_Type *ptr, const uint16_t device_address, uint8_t *buf, const uint32_t size);
      
      • Parameter Description: The parameters for read/write APIs are consistent, as follows:

        Parameter Name

        Type

        Description

        ptr

        I2C_Type*

        Pointer to the base address of the I2C controller

        device_address

        uint16_t

        Device address

        buf

        uint8_t*

        Data buffer pointer

        size

        uint32_t

        Data length

      • Return Value:

        Return Value

        Description

        status_success

        Transfer successful

        status_invalid_argument

        Invalid parameter

        status_timeout

        Transfer timeout

        status_i2c_transmit_not_completed

        Transfer not completed

        status_i2c_no_addr_hit

        Address not hit (no device with this address on the bus)

        status_fail

        Transfer failed

      • Note:

        • This API can be used to scan for the existence of a specific slave device address. The buf parameter can be set to NULL, and the size parameter can be set to 0.

          • Example:

            /* Scan if I2C0 has a device with address 0x10 */
            uint8_t buf[1];
            hpm_stat_t status = i2c_master_read(HPM_I2C0, 0x10, NULL, 0);
            if (status == status_success) {
                /* Device with address 0x10 exists */
                printf("HPM_I2C0 has device address 0x10.\n");
            }
            
      • Example:

        • Master writes 10 bytes of data to the device with address 0x10

          uint8_t buf[10];
          hpm_stat_t status = i2c_master_write(HPM_I2C0, 0x10, buf, 10);
          if (status == status_success) {
              printf("HPM_I2C0 write 10 bytes to device address 0x10 success.\n");
          }
          
        • Master reads 10 bytes of data from the device with address 0x10

          uint8_t buf[10];
          hpm_stat_t status = i2c_master_read(HPM_I2C0, 0x10, buf, 10);
          if (status == status_success) {
              printf("HPM_I2C0 read 10 bytes from device address 0x10 success.\n");
          }
          
    • Read/Write APIs with address operations:

      hpm_stat_t i2c_master_address_write(I2C_Type *ptr, const uint16_t device_address, uint8_t *addr, uint32_t addr_size_in_byte, uint8_t *buf, const uint32_t size_in_byte);
      hpm_stat_t i2c_master_address_read(I2C_Type *ptr, const uint16_t device_address, uint8_t *addr, uint32_t addr_size_in_byte, uint8_t *buf, const uint32_t size_in_byte);
      
      • Parameter Description: The parameters for read/write APIs are consistent, as follows:

        Parameter Name

        Type

        Description

        ptr

        I2C_Type*

        Pointer to the base address of the I2C controller

        device_address

        uint16_t

        Device address

        addr

        uint8_t*

        Address buffer pointer

        addr_size_in_byte

        uint32_t

        Address length

        buf

        uint8_t*

        Data buffer pointer

        size_in_byte

        uint32_t

        Data length

      • Return Value:

        Return Value

        Description

        status_success

        Transfer successful

        status_invalid_argument

        Invalid parameter

        status_timeout

        Transfer timeout

        status_i2c_transmit_not_completed

        Transfer not completed

        status_i2c_no_addr_hit

        Address not hit (no device with this address on the bus)

        status_fail

        Transfer failed

      • Example:

        • Master writes 10 bytes of data to the register at address 0x01 of the device with address 0x10

          uint8_t addr[1] = {0x01};
          uint8_t buf[10];
          hpm_stat_t status = i2c_master_address_write(HPM_I2C0, 0x10, addr, 1, buf, 10);
          if (status == status_success) {
              printf("HPM_I2C0 write 10 bytes to device address 0x10 success.\n");
          }
          
        • Master reads 10 bytes of data from the register at address 0x02 of the device with address 0x10

          uint8_t addr[1] = {0x02};
          uint8_t buf[10];
          hpm_stat_t status = i2c_master_address_read(HPM_I2C0, 0x10, addr, 1, buf, 10);
          if (status == status_success) {
              printf("HPM_I2C0 read 10 bytes from device address 0x10 success.\n");
          }
          
      • Note: The address length and data length must not exceed I2C_SOC_TRANSFER_COUNT_MAX bytes.

    • Custom sequence transfer API:

      hpm_stat_t i2c_master_transfer(I2C_Type *ptr, const uint16_t device_address, uint8_t *buf, const uint32_t size,  uint16_t flags);
      
      • Parameter Description:

        Parameter Name

        Type

        Description

        ptr

        I2C_Type*

        Pointer to the base address of the I2C controller

        device_address

        uint16_t

        Device address

        buf

        uint8_t*

        Data buffer pointer

        size

        uint32_t

        Data length

        flags

        uint16_t

        Transfer flags

      • Return Value:

        Return Value

        Description

        status_success

        Transfer successful

        status_invalid_argument

        Invalid parameter

        status_timeout

        Transfer timeout

        status_i2c_transmit_not_completed

        Transfer not completed

        status_i2c_no_addr_hit

        Address not hit (no device with this address on the bus)

        status_fail

        Transfer failed

      • Flags: The related macro definitions can be found in hpm_i2c_drv.h

        Flag

        Description

        Notes

        I2C_WR

        Write data

        Cannot be set with I2C_RD

        I2C_RD

        Read data

        Cannot be set with I2C_WR

        I2C_ADDR_10BIT

        10-bit address

        None

        I2C_NO_START

        No start signal

        None

        I2C_NO_ADDRESS

        No address

        None

        I2C_NO_READ_ACK

        No acknowledgment

        None

        I2C_NO_STOP

        No stop signal

        None

        I2C_WRITE_CHECK_ACK

        Check acknowledgment during write

        None

      • Example:

        • In some sensors’ repeated Start operations, a command must be written first, followed by reading data. The custom sequence transfer API can be used in this case.

          /* Write command */
          uint8_t cmd = 0x01;
          /* Write data, do not send stop signal to keep the bus occupied */
          hpm_stat_t status = i2c_master_transfer(HPM_I2C0, 0x10, &cmd, 1, I2C_WR | I2C_NO_STOP);
          if (status == status_success) {
              printf("HPM_I2C0 write 1 byte to device address 0x10 success.\n");
          }
          /* Read data */
          uint8_t buf[10];
          status = i2c_master_transfer(HPM_I2C0, 0x10, buf, 10, I2C_RD);
          if (status == status_success) {
              printf("HPM_I2C0 read 10 bytes from device address 0x10 success.\n");
          }
          

21.3.1.2. Slave Mode

  • Read and Write APIs

    hpm_stat_t i2c_slave_write(I2C_Type *ptr, uint8_t *buf, const uint32_t size);
    hpm_stat_t i2c_slave_read(I2C_Type *ptr, uint8_t *buf, const uint32_t size);
    
    • Parameter Description: The parameters for the read and write APIs are the same, as described below:

      Parameter Name

      Type

      Description

      ptr

      I2C_Type*

      Pointer to the base address of the I2C controller

      buf

      uint8_t*

      Data buffer pointer

      size

      uint32_t

      Data length

    • Return Value:

      Return Value

      Description

      status_success

      Transmission succeeded

      status_invalid_argument

      Invalid argument

      status_timeout

      Transmission timeout

      status_i2c_transmit_not_completed

      Transmission not completed

      status_fail

      Transmission failed

    • Examples:

      • Slave writes 10 bytes of data to the host

        uint8_t buf[10];
        
        /* Wait for host to write data */
        do {
            stat = i2c_slave_write(TEST_I2C, data_buff, TEST_TRANSFER_DATA_IN_BYTE);
        } while (stat == status_fail);
        
      • Slave reads 10 bytes of data from the host

        uint8_t buf[10];
        
        /* Wait for host to read data */
        do {
            stat = i2c_slave_read(TEST_I2C, data_buff, TEST_TRANSFER_DATA_IN_BYTE);
        } while (stat == status_fail);
        

21.3.2. Initiate DMA Transfer

  • This section does not handle parameters related to DMA transfer (such as DMA channel, DMA source address, and device address settings), but focuses solely on initiating I2C DMA transfers.

  • For more details, refer to the DMA example in I2C Examples.

  • After each transfer, the CMPL flag must be cleared; otherwise, it will affect the next DMA transfer. The i2c_clear_status API can be used to clear the CMPL flag.

  • For APIs requiring DMA transmission and reception, please consult the I2C component documentation i2c_components.

21.3.2.1. Master Mode

  • Read and Write DMA Initiation APIs:

    hpm_stat_t i2c_master_start_dma_write(I2C_Type *i2c_ptr, const uint16_t device_address, uint32_t size);
    hpm_stat_t i2c_master_start_dma_read(I2C_Type *i2c_ptr, const uint16_t device_address, uint32_t size);
    
    • Parameter Description: The parameters for read and write APIs are identical, as detailed below:

      Parameter Name

      Type

      Description

      i2c_ptr

      I2C_Type*

      Pointer to the base address of the I2C controller

      device_address

      uint16_t

      Device address

      size

      uint32_t

      Data length

    • Return Value:

      Return Value

      Description

      status_success

      Configuration succeeded

      status_fail

      Configuration failed

      status_invalid_argument

      Invalid argument

      status_timeout

      Configuration timeout

    • Note: Before each DMA transfer, this API must be called to configure DMA transfer parameters. Refer to the DMA example in I2C Examples.

    • Example:

      • Master writes 10 bytes of data to a slave device with address 0x10

        uint8_t buf[10];
        hpm_stat_t status = i2c_master_start_dma_write(HPM_I2C0, 0x10, 10);
        if (status == status_success) {
            printf("HPM_I2C0 dma write config success.\n");
        } else {
            assert(0);
        }
        /* Configure DMA channel, DMA source address, etc., start DMA transfer. See `I2C Examples` for DMA configuration */
        
      • Master reads 10 bytes of data from a slave device with address 0x10

        uint8_t buf[10];
        hpm_stat_t status = i2c_master_start_dma_read(HPM_I2C0, 0x10, 10);
        if (status == status_success) {
            printf("HPM_I2C0 dma read config success.\n");
        } else {
            assert(0);
        }
        /* Configure DMA channel, DMA source address, etc., start DMA transfer. See `I2C Examples` for DMA configuration */
        

21.3.2.2. Slave Mode

  • Initiate DMA Transfer API:

    hpm_stat_t i2c_slave_dma_transfer(I2C_Type *i2c_ptr, uint32_t size);
    
    • Parameter Description:

      Parameter Name

      Type

      Description

      i2c_ptr

      I2C_Type*

      Pointer to the base address of the I2C controller

      size

      uint32_t

      Data length

    • Return Value:

      Return Value

      Description

      status_success

      Configuration succeeded

      status_fail

      Configuration failed

      status_invalid_argument

      Invalid argument

      status_timeout

      Configuration timeout

    • Notes:

      • After configuring DMA parameters, use this API to set up DMA transfer parameters. Refer to the DMA example in I2C Examples.

      • Wait for address match before reading the transfer direction. If it’s a read direction, configure DMA such as setting the source address to the I2C data register address, etc. Similarly for write direction.

      • Address match can be waited upon using an address match interrupt or by polling.

      • Polling can be done using the i2c_get_status API to get the address match status.

      • The transfer direction can be checked using the i2c_is_writing and i2c_is_reading APIs.

    • Example:

      • Slave polls to write 10 bytes of data to the master

        uint8_t buf[10];
        /* Wait for address match */
        while (!(i2c_get_status(HPM_I2C0) & I2C_STATUS_ADDRHIT_MASK)) {
        }
        /* Check writing direction */
        if (i2c_is_writing(HPM_I2C0)) {
            /* In slave mode, prepare for writing direction */
            /* Configure DMA channel, DMA source address, etc., start DMA transfer. See `I2C Examples` for DMA configuration */
        } else {
            assert(0);
        }
        hpm_stat_t status = i2c_slave_dma_transfer(HPM_I2C0, 10);
        if (status == status_success) {
            printf("i2c slave dma transfer success.\n");
        } else {
            printf("i2c slave dma transfer failed.\n");
            assert(0);
        }
        /* Wait for I2C transfer completion */
        do {
            status = i2c_get_status(ptr);
        } while (!(status & I2C_STATUS_CMPL_MASK));
        /* Clear CMPL flag to avoid affecting the next transfer */
        i2c_clear_status(ptr, status);
        
      • Slave polls to read 10 bytes of data from the master

      uint8_t buf[10];
      /* Wait for address match */
      while (!(i2c_get_status(HPM_I2C0) & I2C_STATUS_ADDRHIT_MASK)) {
      }
      /* Check reading direction */
      if (i2c_is_reading(HPM_I2C0)) {
          /* In slave mode, prepare for reading direction */
          /* Configure DMA channel, DMA source address, etc., start DMA transfer. See `I2C Examples` for DMA configuration */
      } else {
          assert(0);
      }
      hpm_stat_t status = i2c_slave_dma_transfer(HPM_I2C0, 10);
      if (status == status_success) {
          printf("i2c slave dma transfer success.\n");
      } else {
          printf("i2c slave dma transfer failed.\n");
          assert(0);
      }
      /* Wait for I2C transfer completion */
      do {
          status = i2c_get_status(ptr);
      } while (!(status & I2C_STATUS_CMPL_MASK));
      /* Clear CMPL flag to avoid affecting the next transfer */
      i2c_clear_status(ptr, status);
      

21.4. I2C Interrupt

  • Refer to the interrupt example in the I2C Examples.

  • The corresponding I2C interrupt in the PLIC controller must be enabled using the intc_m_enable_irq API.

  • The priority of the I2C interrupt must be configured in the PLIC controller using the intc_m_enable_irq_with_priority API.

  • To read the I2C interrupt enable settings, use the i2c_get_irq_setting API.

  • Each time an interrupt event occurs, call the i2c_get_status API in the interrupt handler to check the interrupt status.

  • Each time an interrupt event occurs, call the i2c_clear_status API in the interrupt handler to clear the interrupt flag.

  • The I2C peripheral supports the following interrupts, which can be viewed in the macro definitions from hpm_i2c_drv.h.

    Interrupt

    Description

    I2C_EVENT_TRANSACTION_COMPLETE

    Transaction complete

    I2C_EVENT_BYTE_RECEIVED

    Byte received

    I2C_EVENT_BYTE_TRANSMIT

    Byte transmitted

    I2C_EVENT_START_CONDITION

    Start condition

    I2C_EVENT_STOP_CONDITION

    Stop condition

    I2C_EVENT_LOSS_ARBITRATION

    Arbitration lost

    I2C_EVENT_ADDRESS_HIT

    Address match detected

    I2C_EVENT_FIFO_HALF

    FIFO half full/empty

    I2C_EVENT_FIFO_FULL

    FIFO full

    I2C_EVENT_FIFO_EMPTY

    FIFO empty

  • Related APIs:

    • Enable I2C Interrupt:

      hpm_stat_t i2c_enable_irq(I2C_Type *ptr, uint32_t mask);
      
      • Parameter Description:

        Parameter Name

        Type

        Description

        ptr

        I2C_Type*

        Pointer to the base address of the I2C controller

        mask

        uint32_t

        Interrupt enable mask. Supported masks can be found in the above interrupt table.

    • Disable I2C Interrupt:

      hpm_stat_t i2c_disable_irq(I2C_Type *ptr, uint32_t mask);
      
      • Parameter Description:

        Parameter Name

        Type

        Description

        ptr

        I2C_Type*

        Pointer to the base address of the I2C controller

        mask

        uint32_t

        Interrupt enable mask. Supported masks can be found in the above interrupt table.

21.5. I2C Samples