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.