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;
规格对比参考图:
1.3 I2C 三种工作模式¶
I2C 支持询(polling)/中断(INT)/DMA三种模式;
模式 |
CPU占用率 |
代码复杂度 |
适用场景 |
|---|---|---|---|
轮询 |
高,CPU持续参与,需不断检查状态,占用CPU资源 |
低 |
低速、小数据量、简单应用; |
中断 |
中,CPU无需持续查询状态,仅在事件发生时响应中断,减少CPU等待时间 |
低 |
中等数据量、实时性要求较高; |
DMA |
低,DMA控制器直接管理数据传输,CPU无需参与数据搬运 |
高 |
高速、大数据量、高吞吐量场景; |
下面的流程图展示了三种模式的区别:
备注
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
);
上拉电阻的使用原则:
默认I2C使用外部上拉,以确保I2C的驱动能力;
上拉电阻的阻值选择参考,如下:
400k 推荐使用2.2kΩ;
100k/200k 可以使用4.7kΩ~10kΩ;内部上拉属于弱上拉,当I2C 只使用内部上拉时,工作速度建议保持在100k;
1.5.3 配置应用的使用的I2C总线¶
在应用层配置要使用的I2C总线。
以“sensor使用I2C2总线”为例,配置界面如下:
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设备,通过地址来区分访问的设备。
示意图如下:
备注
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:20 和 i2c_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;
配置界面示例如下:
2.1.2 配置 Pinmux(绑定 IO 与 I2C总线)¶
参考原理图,得知:I2C SDA–> PA39 SCL–>PA40,存在外部上拉电阻2.2k;
配置代码如下:
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;
}
...
}


