21. I2C

21.1. 概述

  • 主要介绍先楫I2C外设的主要驱动接口说明和调用方法,更多内容请参考 hpm_i2c_drv.h 的API说明以及相关用户手册。

  • 支持标准模式(100Kb/s)、快速模式(400Kb/s)、增强快速模式(1Mb/s)。

  • 支持7位地址和10位地址。

  • 支持主机模式和从机模式。

  • 支持各类中断以及DMA传输。

    • 传输完成中断

    • 字节接收中断

    • 字节发送中断

    • 开始信号中断

    • 停止信号中断

    • 仲裁丢失中断

    • 地址命中中断

    • FIFO半满/半空中断

    • FIFO满中断

    • FIFO空中断

  • 支持总线死锁恢复。此项支持需要看SOC的 hpm_soc_ip_feature.h 是否定义 HPM_IP_FEATURE_I2C_SUPPORT_RESET

  • 具有4字节硬件FIFO缓冲。具体深度可查看 hpm_soc_feature.hI2C_SOC_FIFO_SIZE 宏定义或者使用 hpm_i2c_drv.hi2c_get_fifo_size API接口获取

  • 单次传输支持最大长度为4096字节。具体长度可查看 hpm_soc_feature.hI2C_SOC_TRANSFER_COUNT_MAX 宏定义

21.2. I2C初始化

  • 需要确保I2C的时钟源已经开启,并且初始化了相关I2C外设引脚。

    • 可使用`clock_add_to_group` 函数用于将I2C时钟源添加到时钟组中,从而确保I2C时钟源已经开启。

  • 相关结构体介绍:

    • i2c_config_t 结构体用于配置I2C的相关参数

      /* I2C配置结构体 */
      typedef struct {
          bool is_10bit_addressing;  /* 是否使用10位地址 */
          uint8_t i2c_mode;          /* I2C速度模式 */
      } i2c_config_t;
      
  • 相关枚举值介绍:

    • i2c_mode_t 枚举用于配置I2C的速度模式

    /* I2C速度模式 */
    typedef enum i2c_mode {
        i2c_mode_normal,      /* 标准模式 100Kb/s */
        i2c_mode_fast,        /* 快速模式 400Kb/s */
        i2c_mode_fast_plus,  /* 增强快速模式 1Mb/s */
    } i2c_mode_t;
    

21.2.1. 主机模式初始化

  • 初始化函数API:

    hpm_stat_t i2c_init_master(I2C_Type *ptr, uint32_t src_clk_in_hz, i2c_config_t *config);
    
    • 参数说明:

      参数名

      类型

      描述

      ptr

      I2C_Type*

      指向I2C控制器基地址的指针

      src_clk_in_hz

      uint32_t

      I2C控制器的时钟源频率(Hz)

      config

      i2c_config_t*

      指向I2C配置结构体的指针

    • 返回值:

      返回值

      描述

      status_success

      初始化成功

      status_i2c_not_supported

      I2C控制器不支持

    • I2C控制器的时钟源频率可使用 clock_get_frequency API获取。

      • 示例:

        uint32_t i2c_clk_freq = clock_get_frequency(clock_i2c0);
        
  • 示例:

    • I2C0使用标准模式,100Kb/s,7位地址

    /* I20C时钟源开启,相关引脚初始化,不做举例... */
    /* I2C0初始化,作为主机。 */
    
    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. 从机模式初始化

  • 初始化函数API:

    hpm_stat_t i2c_init_slave(I2C_Type *ptr, uint32_t src_clk_in_hz, i2c_config_t *config, const uint16_t slave_address);
    
    • 参数说明:

      参数名

      类型

      描述

      ptr

      I2C_Type*

      指向I2C控制器基地址的指针

      src_clk_in_hz

      uint32_t

      I2C控制器的时钟源频率(Hz)

      config

      i2c_config_t*

      指向I2C配置结构体的指针

      slave_addr

      uint8_t

      从机地址

    • 返回值:

      返回值

      描述

      status_success

      初始化成功

      status_i2c_not_supported

      I2C控制器不支持

    • I2C控制器的时钟源频率可使用 clock_get_frequency API获取。

  • 示例:

    • I2C0使用标准模式,100Kb/s,7位地址,从机地址为0x10

      /* I20C时钟源开启,相关引脚初始化,不做举例... */
      /* I2C0初始化,作为从机。 */
      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数据传输

21.3.1. polling传输

21.3.1.1. 主机模式

  • 提供了不带地址操作的读写API,带地址操作的读写API,以及自定义序列传输的API。

    • 不带地址操作的读写API:

      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);
      
      • 参数说明:读写API的参数一致,具体如下:

        参数名

        类型

        描述

        ptr

        I2C_Type*

        指向I2C控制器基地址的指针

        device_address

        uint16_t

        设备地址

        buf

        uint8_t*

        数据缓冲区指针

        size

        uint32_t

        数据长度

      • 返回值:

        返回值

        描述

        status_success

        传输成功

        status_invalid_argument

        无效参数

        status_timeout

        传输超时

        status_i2c_transmit_not_completed

        传输未完成

        status_i2c_no_addr_hit

        未命中地址(总线上并无此从机设备地址)

        status_fail

        传输失败

      • 提示:

        • 此API可用于扫描是否存在指定的从机设备地址。 buf 参数可设置为NULL, size 参数可设置为0。

          • 示例:

            /* 扫描I2C0是否存在从机地址为0x10的设备 */
            uint8_t buf[1];
            hpm_stat_t status = i2c_master_read(HPM_I2C0, 0x10, NULL, 0);
            if (status == status_success) {
                /* 存在从机地址为0x10的设备 */
                printf("HPM_I2C0 has device address 0x10.\n");
            }
            
      • 示例:

        • 主机向从机地址为0x10的设备写入10个字节的数据

          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");
          }
          
        • 主机从从机地址为0x10的设备读取10个字节的数据

          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");
          }
          
    • 带地址操作的读写API:

      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);
      
      • 参数说明:读写API的参数一致,具体如下:

        参数名

        类型

        描述

        ptr

        I2C_Type*

        指向I2C控制器基地址的指针

        device_address

        uint16_t

        设备地址

        addr

        uint8_t*

        地址缓冲区指针

        addr_size_in_byte

        uint32_t

        地址长度

        buf

        uint8_t*

        数据缓冲区指针

        size_in_byte

        uint32_t

        数据长度

      • 返回值:

        返回值

        描述

        status_success

        传输成功

        status_invalid_argument

        无效参数

        status_timeout

        传输超时

        status_i2c_transmit_not_completed

        传输未完成

        status_i2c_no_addr_hit

        未命中地址(总线上并无此从机设备地址)

        status_fail

        传输失败

      • 示例:

        • 主机向从机地址为0x10的设备的地址为0x01的寄存器写入10个字节的数据

          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");
          }
          
        • 主机从从机地址为0x10的设备的地址为0x02的寄存器读取10个字节的数据

          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");
          }
          
      • 注意:地址长度和数据长度不能超过 I2C_SOC_TRANSFER_COUNT_MAX 字节。

    • 自定义序列传输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);
      
      • 参数说明:

        参数名

        类型

        描述

        ptr

        I2C_Type*

        指向I2C控制器基地址的指针

        device_address

        uint16_t

        设备地址

        buf

        uint8_t*

        数据缓冲区指针

        size

        uint32_t

        数据长度

        flags

        uint16_t

        传输标志位

      • 返回值:

        返回值

        描述

        status_success

        传输成功

        status_invalid_argument

        无效参数

        status_timeout

        传输超时

        status_i2c_transmit_not_completed

        传输未完成

        status_i2c_no_addr_hit

        未命中地址(总线上并无此从机设备地址)

        status_fail

        传输失败

      • flags 传输标志位:相关宏定义可在 hpm_i2c_drv.h 中查看

        标志位

        描述

        备注

        I2C_WR

        写入数据

        此标志位不可与 I2C_RD 同时设置

        I2C_RD

        读取数据

        此标志位不可与 I2C_WR 同时设置

        I2C_ADDR_10BIT

        10位地址

        I2C_NO_START

        无开始信号

        I2C_NO_ADDRESS

        无地址

        I2C_NO_READ_ACK

        无应答

        I2C_NO_STOP

        无停止信号

        I2C_WRITE_CHECK_ACK

        写入数据时检查应答

      • 示例:

        • 在一些传感器中的repeated Start操作中,需要先写入一个命令,然后再读取数据,此时可以使用自定义序列传输API。

          /* 写入命令 */
          uint8_t cmd = 0x01;
          /* 写入数据, 不发stop信号,保持总线占用 */
          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");
          }
          /* 读取数据 */
          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. 从机模式

  • 读写API

    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);
    
    • 参数说明:读写API的参数一致,具体如下:

      参数名

      类型

      描述

      ptr

      I2C_Type*

      指向I2C控制器基地址的指针

      buf

      uint8_t*

      数据缓冲区指针

      size

      uint32_t

      数据长度

    • 返回值:

      返回值

      描述

      status_success

      传输成功

      status_invalid_argument

      无效参数

      status_timeout

      传输超时

      status_i2c_transmit_not_completed

      传输未完成

      status_fail

      传输失败

    • 示例:

      • 从机向主机写入10个字节的数据

        uint8_t buf[10];
        
        /* 等待主机写入数据 */
        do {
            stat = i2c_slave_write(TEST_I2C, data_buff, TEST_TRANSFER_DATA_IN_BYTE);
        } while (stat == status_fail);
        
      • 从机从主机读取10个字节的数据

        uint8_t buf[10];
        
        /* 等待主机读取数据 */
        do {
            stat = i2c_slave_read(TEST_I2C, data_buff, TEST_TRANSFER_DATA_IN_BYTE);
        } while (stat == status_fail);
        

21.3.2. 启动DMA传输

  • 此部分不负责DMA传输的相关参数(DMA通道,DMA源地址和设备地址等等设置),只负责I2C DMA传输的启动相关设置。

  • 具体可参考 I2C示例 中的DMA示例。

  • 每次传输完成后,需要清除 CMPL 标志位,否则会影响下一次DMA传输。可使用 i2c_clear_status API清除 CMPL 标志位。

  • 需要DMA收发的API,可以查看I2C组件文档 i2c_components

21.3.2.1. 主机模式

  • 读写DMA启动API:

    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);
    
    • 参数说明:读写API的参数一致,具体如下:

      参数名

      类型

      描述

      i2c_ptr

      I2C_Type*

      指向I2C控制器基地址的指针

      device_address

      uint16_t

      设备地址

      size

      uint32_t

      数据长度

    • 返回值:

      返回值

      描述

      status_success

      设置成功

      status_fail

      设置失败

      status_invalid_argument

      无效参数

      status_timeout

      设置超时

    • 注意:每次DMA传输前,需要先调用此API设置DMA传输参数。具体可参考 I2C示例 中的DMA示例。

    • 示例:

      • 主机向从机地址为0x10的设备写入10个字节的数据

        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);
        }
        /* 配置DMA的通道,DMA源地址和设备地址等等,启动DMA传输,不做举例,参考 `I2C示例`的dma相关 */
        
      • 主机从从机地址为0x10的设备读取10个字节的数据

        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);
        }
        /* 配置DMA的通道,DMA源地址和设备地址等等,启动DMA传输,不做举例,参考 `I2C示例`的dma相关 */
        

21.3.2.2. 从机模式

  • 启动DMA传输API:

    hpm_stat_t i2c_slave_dma_transfer(I2C_Type *i2c_ptr, uint32_t size);
    
    • 参数说明:

      参数名

      类型

      描述

      i2c_ptr

      I2C_Type*

      指向I2C控制器基地址的指针

      size

      uint32_t

      数据长度

    • 返回值:

      返回值

      描述

      status_success

      设置成功

      status_fail

      设置失败

      status_invalid_argument

      无效参数

      status_timeout

      设置超时

    • 注意:

      • 配置DMA参数后,需要先调用此API设置DMA传输参数。具体可参考 I2C示例 中的DMA示例。

      • 等待地址命中,再读取传输方向,若是读取方向,则DMA配置比如源地址为I2C数据寄存器地址等,若是写入方向,则DMA配置比如源地址为I2C数据寄存器地址等。

      • 等待地址命中可以使用地址命中中断,也可以使用轮询方式等待地址命中。

      • 轮询方式可以使用 i2c_get_status API获取地址命中状态。

      • 读取传输方向可以使用 i2c_is_writingi2c_is_reading API获取传输方向。

    • 示例:

      • 从机轮询向主机写入10个字节的数据

      uint8_t buf[10];
      /* 等待地址命中 */
      while (!(i2c_get_status(HPM_I2C0) & I2C_STATUS_ADDRHIT_MASK)) {
      }
      /* 读取方向 */
      if (i2c_is_writing(HPM_I2C0)) {
          /* 在从机模式,为发送写入方向 */
          /* 配置DMA的通道,DMA源地址和设备地址等等,启动DMA传输,不做举例,参考 `I2C示例`的dma相关 */
      } 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);
      }
      /* 等待I2C传输完成 */
      do {
          status = i2c_get_status(ptr);
      } while (!(status & I2C_STATUS_CMPL_MASK));
      /* 清除CMPL标志位 避免影响下一次传输 */
      i2c_clear_status(ptr, status);
      
      • 从机轮询方式从主机读取10个字节的数据

      uint8_t buf[10];
      /* 等待地址命中 */
      while (!(i2c_get_status(HPM_I2C0) & I2C_STATUS_ADDRHIT_MASK)) {
      }
      /* 读取方向 */
      if (i2c_is_reading(HPM_I2C0)) {
          /* 在从机模式,为接收读取方向 */
          /* 配置DMA的通道,DMA源地址和设备地址等等,启动DMA传输,不做举例,参考 `I2C示例`的dma相关 */
      } 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);
      }
      /* 等待I2C传输完成 */
      do {
          status = i2c_get_status(ptr);
      } while (!(status & I2C_STATUS_CMPL_MASK));
      /* 清除CMPL标志位 避免影响下一次传输 */
      i2c_clear_status(ptr, status);
      

21.4. I2C中断

  • 参考 I2C示例 中的interrupt示例。

  • 需要打开plic控制器对应的I2C中断,使用 intc_m_enable_irq API接口打开I2C中断。

  • 需要在plic控制器中配置I2C中断的优先级,使用 intc_m_enable_irq_with_priority API接口配置I2C中断的优先级。

  • 读取I2C中断使能,可以使用 i2c_get_irq_setting API接口读取I2C中断使能。

  • 每次对应的中断事件发生,需要在中断处理函数中调用 i2c_clear_status API接口清除中断标志。

  • 每次对应的中断事件发生,可以在中断处理函数中调用 i2c_get_status API接口获取中断标志。

  • I2C外设支持以下中断,可以从 hpm_i2c_drv.h 宏定义中查看。

    中断

    描述

    I2C_EVENT_TRANSACTION_COMPLETE

    传输完成

    I2C_EVENT_BYTE_RECEIVED

    接收字节

    I2C_EVENT_BYTE_TRANSMIT

    发送字节

    I2C_EVENT_START_CONDITION

    起始条件

    I2C_EVENT_STOP_CONDITION

    停止条件

    I2C_EVENT_LOSS_ARBITRATION

    仲裁丢失

    I2C_EVENT_ADDRESS_HIT

    地址命中

    I2C_EVENT_FIFO_HALF

    FIFO半满

    I2C_EVENT_FIFO_FULL

    FIFO满

    I2C_EVENT_FIFO_EMPTY

    FIFO空

  • 相关API接口:

    • 使能I2C中断:

      hpm_stat_t i2c_enable_irq(I2C_Type *ptr, uint32_t mask);
      
      • 参数说明:

        参数名

        类型

        描述

        ptr

        I2C_Type*

        指向I2C控制器基地址的指针

        mask

        uint32_t

        中断使能掩码,对应的中断掩码可以从 上述中断表格查看。

    • 禁能I2C中断:

      hpm_stat_t i2c_disable_irq(I2C_Type *ptr, uint32_t mask);
      
      • 参数说明:

        参数名

        类型

        描述

        ptr

        I2C_Type*

        指向I2C控制器基地址的指针

        mask

        uint32_t

        中断使能掩码,对应的中断掩码可以从 上述中断表格查看。

21.5. I2C示例