I2C

1. I2C 驱动说明

1.1 简介

I2C(Inter Integrated Circuit)总线是一种半双工、双向二线制同步串行总线.
思澈平台I2C特性主要包括:

  • 支持10bit和7bit设备地址;

  • 支持轮询(polling)/中断(INT)/DMA三种模式;

  • 支持 I2C 工作速度: 100Kbps/400Kbps/1Mbps;

I2C 总线的工作过程,可以参考 I2C 总线设备 .

1.2 I2C 资源分布

思澈平台最多支持 7 个I2C实例,其中:4个在HCPU上,3个在 LCPU;
具体分布如下:

平台

HCPU

LCPU

52x

I2C1/I2C2/I2C3/I2C4

56x

I2C1/I2C2/I2C3/I2C4

I2C5/I2C6/I2C7

55x

I2C1/I2C2/I2C3

I2C4/I2C5/I2C6

58x

I2C1/I2C2/I2C3/I2C4

I2C5/I2C6/I2C7

I2C的总线名称依次为“i2c1”~“i2c7”;
I2C总线的配置信息还可以参考: i2c_config.h;

备注

HCPU可以使用LCPU的I2C,但LCPU只能使用LCPU的I2C

IO口灵活性存在差异

  • 55x/58x系列:I2C的IO口为固定分配,需参考芯片规格书确认具体IO对应的I2C,不可随意切换;

  • 52x/56x系列:灵活性更高,任何带有I2C_UART功能的IO,均可通过配置映射为 I2C;

规格对比参考图:

../../_images/I2C_IO.png

1.3 I2C 三种工作模式

I2C 支持询(polling)/中断(INT)/DMA三种模式;

模式

CPU占用率

代码复杂度

适用场景

轮询

高,CPU持续参与,需不断检查状态,占用CPU资源

低速、小数据量、简单应用;

中断

中,CPU无需持续查询状态,仅在事件发生时响应中断,减少CPU等待时间

中等数据量、实时性要求较高;

DMA

低,DMA控制器直接管理数据传输,CPU无需参与数据搬运

高速、大数据量、高吞吐量场景;

下面的流程图展示了三种模式的区别:

../../_images/i2c_mode.png

备注

DMA模式,仅作用于数据段,适合高吞吐,低CPU占有率,通讯过程中不需要CPU参与,被CPU打断的概率低;

1.4 I2C 总线接口

I2C打开、配置和通讯,相关接口如下:

函数

描述

rt_device_find()

根据 I2C 总线设备名称查找设备获取设备句柄

rt_i2c_open()

打开i2c设备

rt_i2c_close()

关闭i2c设备

rt_i2c_configure

配置i2c设备

rt_i2c_transfer()

传输数据,可以收,也可以发

rt_i2c_master_send()

发送连续数据

rt_i2c_master_recv

接收连续数据

rt_i2c_mem_read()

读取设备寄存器数据

rt_i2c_mem_write()

写入设备寄存器数据

备注

接口rt_i2c_mem_read()和rt_i2c_mem_write() 适合读写I2C设备的寄存器的数据;

I2C总线一般使用流程:
rt_i2c_open() -->rt_i2c_configure()–>rt_i2c_transfer()–>rt_i2c_close()

1.5 I2C的使用

I2C的使用需经过配置选型、硬件绑定、参数设置和驱动调用四个核心步骤,以下为详细说明:

1.5.2 配置 Pinmux(绑定 IO 与 I2C总线)

Pinmux 配置的核心作用是建立物理 IO 引脚与目标I2C总线的硬件映射关系,告知芯片“哪个 IO 负责I2C通讯”。只有完成绑定,I2C总线才能和I2C设备进行通讯。

  • 配置逻辑与示例 以“PA40 作为 I2C2_SCL(I2C1总线的时钟线),PA39作为 I2C1_SDA(I2C2总线的数据线)”为例,配置代码如下:

//函数:HAL_PIN_Set(引脚标识, 功能别名, 上下拉配置, 核心选择)
HAL_PIN_Set(
    PAD_PA39,       // 物理 IO 引脚(如 PA39)
    I2C2_SDA,       // I2C2的数据线
    PIN_NOPULL,     // 上下拉配置(外部有上拉时,配置NOPULL;外部没有上拉时,配置PULLUP)
    1               // 1:引脚位于HCPU;0:引脚位于LCPU
);
HAL_PIN_Set(
    PAD_PA40,       // 物理 IO 引脚(如 PA40)
    I2C2_SCL,       // I2C2的时钟线
    PIN_NOPULL,     // 上下拉配置(外部有上拉时,配置NOPULL;外部没有上拉时,配置PULLUP)
    1               // 1:引脚位于HCPU;0:引脚位于LCPU
);

上拉电阻的使用原则:

  1. 默认I2C使用外部上拉,以确保I2C的驱动能力;
    上拉电阻的阻值选择参考,如下:
    400k 推荐使用2.2kΩ
    100k/200k 可以使用4.7kΩ~10kΩ;

  2. 内部上拉属于弱上拉,当I2C 只使用内部上拉时,工作速度建议保持在100k;

1.5.3 配置应用的使用的I2C总线

在应用层配置要使用的I2C总线。
以“sensor使用I2C2总线”为例,配置界面如下:

../../_images/sensor_i2c.png

1.5.4 调用I2C总线驱动接口

通过RT-Thread接口完成I2C的初始化与控制;
参考代码:

// 查找I2C总线
(struct rt_i2c_bus_device *)rt_device_find("i2c2");
//打开I2C总线设备
rt_device_open(bus_handle, flag_open);
// 设置参数:时钟,超时,模式,地址
rt_i2c_configure(bus_handle, &configuration);
// I2C通讯
rt_i2c_transfer();

1.5.5 I2C总线挂载多个设备

I2C总线支持同时挂载多个I2C设备,通过地址来区分访问的设备。
示意图如下:

../../_images/i2c_multi_device.png

备注

I2C总线只需要一组上拉电阻即可,不用给每个I2C设备单独增加上拉电阻。

每个I2C设备访问I2C总线的方式相同,驱动层已经增加了I2C设备访问I2C总线的互斥功能,每个I2C设备访问I2C总线的时候一般只考虑各自驱动即可。

警告

同一个I2C总线上的I2C设备,如果存在I2C速率不同的情况,所有该I2C总线的I2C设备在通讯前务必执行rt_i2c_configure()配置匹配的I2C速率。

1.6 I2C 中断模式的配置

I2C 中断模式,可以在rt_device_open(bus_handle, flag_open)时,通过flag_open来区分.
轮询模式
flag_open = RT_DEVICE_FLAG_RDWR
中断模式
flag_open = RT_DEVICE_FLAG_RDWR | RT_DEVICE_FLAG_INT_RX | RT_DEVICE_FLAG_INT_TX
以下显示所有支持的flag

#define RT_DEVICE_FLAG_RDONLY           0x001           /**< read only */
#define RT_DEVICE_FLAG_WRONLY           0x002           /**< write only */
#define RT_DEVICE_FLAG_RDWR             0x003           /**< read and write */
#define RT_DEVICE_FLAG_INT_RX           0x100           /**< INT mode on Rx */
#define RT_DEVICE_FLAG_DMA_RX           0x200           /**< DMA mode on Rx */
#define RT_DEVICE_FLAG_INT_TX           0x400           /**< INT mode on Tx */
#define RT_DEVICE_FLAG_DMA_TX           0x800           /**< DMA mode on Tx */

备注

I2C总线使用轮询和中断方式时,I2C总线存在由于通讯过程中被打断时间过长而导致I2C设备异常,建议增加对I2C设备异常处理机制,如:判断连续多次读到的I2C数据都会异常数据时,复位I2C设备;

1.7 I2C DMA模式的配置

DMA模式配置包括: menuconfig使能I2C总线DMA, 配置dma_config,I2C 总线flag_open包含RT_DEVICE_FLAG_DMA_TX/RT_DEVICE_FLAG_DMA_RX.

1.7.2 配置dma_config

在dma_config.c中,配置DMA相关的宏

#define I2C2_DMA_IRQHandler            DMAC1_CH3_IRQHandler     /*DMA中断回调*/
#define I2C2_DMA_IRQ_PRIO              1                        /*DMA中断优先级*/
#define I2C2_DMA_INSTANCE              DMA1_Channel5            /*DMA中断通道实例*/
#define I2C2_DMA_IRQ                   DMAC1_CH5_IRQn           /*DMA中断*/
#define I2C2_DMA_REQUEST               DMA_REQUEST_23           /*I2C2的DMA设备序号,序号唯一*/

下图展示了所有平台的I2C总线的DMA_REQUEST(悬空部分表格表示对应平台没有对应的I2C总线或I2C总线不支持DMA)

平台

52x

55x

56x

58x

I2C1

DMAC1_22

DMAC1_22

DMAC1_22

DMAC1_22

I2C2

DMAC1_23

DMAC1_23

DMAC1_23

DMAC1_23

I2C3

DMAC1_24

DMAC1_17

DMAC1_24

DMAC1_24

I2C4

DMAC1_3

DMAC1_3

I2C5

DMAC2_21

DMAC2_21

DMAC3_21

I2C6

DMAC2_22

DMAC2_22

DMAC3_22

I2C7

DMAC2_23

DMAC2_23

DMAC3_23

1.7.3 配置dma_configI2C 总线flag_open包含RT_DEVICE_FLAG_DMA_TX/RT_DEVICE_FLAG_DMA_RX

执行rt_device_open(bus_handle, flag_open)时
flag_open = RT_DEVICE_FLAG_RDWR | RT_DEVICE_FLAG_DMA_RX | RT_DEVICE_FLAG_DMA_TX

1.8 I2C 异常处理

I2C 通讯异常的时候,会打印带错误码的log.

[1745800] drv.i2c: bus err:1, xfer:0/1, i2c_stat:20, i2c_errcode=10
[1745807] drv.i2c: reset and send 9 clks

错误log打印场景:i2c驱动复位,并且尝试发送了9个时钟信号复位设备后依然异常.
根据错误log中的i2c_stat和i2c_errcode来定位错误原因.
i2c_stat 类型如下:

typedef enum
{
    HAL_I2C_STATE_RESET             = 0x00U,   /*!< Peripheral is not yet Initialized         */
    HAL_I2C_STATE_READY             = 0x20U,   /*!< Peripheral Initialized and ready for use  */
    HAL_I2C_STATE_BUSY              = 0x24U,   /*!< An internal process is ongoing            */
    HAL_I2C_STATE_BUSY_TX           = 0x21U,   /*!< Data Transmission process is ongoing      */
    HAL_I2C_STATE_BUSY_RX           = 0x22U,   /*!< Data Reception process is ongoing         */
    HAL_I2C_STATE_LISTEN            = 0x28U,   /*!< Address Listen Mode is ongoing            */
    HAL_I2C_STATE_BUSY_TX_LISTEN    = 0x29U,   /*!< Address Listen Mode and Data Transmission
                                                 process is ongoing                         */
    HAL_I2C_STATE_BUSY_RX_LISTEN    = 0x2AU,   /*!< Address Listen Mode and Data Reception
                                                 process is ongoing                         */
    HAL_I2C_STATE_ABORT             = 0x60U,   /*!< Abort user request ongoing                */
    HAL_I2C_STATE_TIMEOUT           = 0xA0U,   /*!< Timeout state                             */
    HAL_I2C_STATE_ERROR             = 0xE0U    /*!< Error                                     */

} HAL_I2C_StateTypeDef;

i2c_errcode 错误类型如下:

#define HAL_I2C_ERROR_NONE      (0x00000000U)    /*!< No error              */
#define HAL_I2C_ERROR_BERR      (0x00000001U)    /*!< BERR error            */
#define HAL_I2C_ERROR_ARLO      (0x00000002U)    /*!< ARLO error            */
#define HAL_I2C_ERROR_AF        (0x00000004U)    /*!< ACKF error            */
#define HAL_I2C_ERROR_OVR       (0x00000008U)    /*!< OVR error             */
#define HAL_I2C_ERROR_DMA       (0x00000010U)    /*!< DMA transfer error    */
#define HAL_I2C_ERROR_TIMEOUT   (0x00000020U)    /*!< Timeout error         */
#define HAL_I2C_ERROR_SIZE      (0x00000040U)    /*!< Size Management error */
#define HAL_I2C_ERROR_DMA_PARAM (0x00000080U)    /*!< DMA Parameter Error   */
#if (USE_HAL_I2C_REGISTER_CALLBACKS == 1)
#define HAL_I2C_ERROR_INVALID_CALLBACK  (0x00000100U)    /*!< Invalid Callback error */
#endif /* USE_HAL_I2C_REGISTER_CALLBACKS */
#define HAL_I2C_ERROR_INVALID_PARAM     (0x00000200U)    /*!< Invalid Parameters error  */

错误log中:i2c_stat:20i2c_errcode=10 叠加后判断:

i2c总线驱动准备就绪,在进行DMA传输过程中出现了错误. 

2 I2C 应用案例

2.1 sensor 使用I2C

sensor 使用的I2C特性:工作速度400kbps、外部上拉电阻2.2k、DMA模式;
I2C总线:I2C2 DMA1_Channel3;
sensor 配置i2c的代码位置:solution2.0\sdk\customer\peripherals\sensor\acc\stk8321\stk8321.c;

2.1.1 menuconfig配置I2C总线

在系统配置界面(menuconfig)中,勾选sensor 用到的I2C总线I2C2 以及DMA;
配置界面示例如下:

../../_images/sensor_i2c2_dma.png

2.1.2 配置 Pinmux(绑定 IO 与 I2C总线)

参考原理图,得知:I2C SDA–> PA39 SCL–>PA40,存在外部上拉电阻2.2k;

../../_images/sensor_i2c_sch.png

配置代码如下:

HAL_PIN_Set(PAD_PA39, I2C2_SDA, PIN_NOPULL, 1);
HAL_PIN_Set(PAD_PA40, I2C2_SCL, PIN_NOPULL, 1);

2.1.4 sensor驱动代码中使用i2c

skt8321.c中展示了如何在sensor驱动中使用i2c.
i2c open 及 config

static int stk8321_i2c_init(void)
{
    rt_uint16_t flag_open = RT_DEVICE_FLAG_RDWR
#ifdef STK8321_USE_DMA
                            | RT_DEVICE_FLAG_DMA_RX | RT_DEVICE_FLAG_DMA_TX
#endif
                            ;
    /*find  i2c2 */
    gs_content.bus_handle = (struct rt_i2c_bus_device *)rt_device_find(GS_BUS_NAME);

    /*open  i2c2 */
    if (gs_content.bus_handle)
    {

        rt_device_open((rt_device_t)gs_content.bus_handle, flag_open);
    }
    /*config  i2c2 */
    {
        struct rt_i2c_configuration configuration =
        {
            .mode = 0,
            .addr = 0,
            .timeout = 200,
            .max_hz = 400000,
        };

        rt_i2c_configure(gs_content.bus_handle, &configuration);
    }

}

i2c read

static int32_t stk8321_i2c_read(GS_CONT_T *ctx, uint8_t reg, uint8_t *data, uint16_t len)
{
    rt_i2c_mem_read(handle->bus_handle, GS_I2C_ADDR, reg, 8, data, len);

}

i2c write

static int32_t stk8321_i2c_read(GS_CONT_T *ctx, uint8_t reg, uint8_t *data, uint16_t len)
{
...
        struct rt_i2c_msg msgs;

        value[0] = reg;
        value[1] = *data;

        msgs.addr  = GS_I2C_ADDR;   /* slave address */
        msgs.flags = RT_I2C_WR;        /* write flag */
        msgs.buf   = &value[0];             /* Send data pointer */
        msgs.len   = sizeof(value);

        if (rt_i2c_transfer(handle->bus_handle, &msgs, 1) == 1)
        {
            res = RT_EOK;
        } 
...
}