4. Zephyr I2C API概览:设备树绑定、I2C控制器配置、API函数详解
在驱动I2C触摸屏之前,必须掌握Zephyr RTOS中I2C子系统的核心编程模型。本章将深入剖析从设备树绑定到API调用的完整链路,帮助你在实战中准确配置控制器、解析设备树节点,并熟练使用 i2c_write、i2c_read 和 i2c_transfer 三大核心函数。
4.1 设备树绑定:I2C设备节点的声明与解析
Zephyr使用设备树(Devicetree)描述硬件拓扑。对于I2C触摸屏,需要在设备树中完成两件事:使能I2C控制器和声明触摸屏从设备节点。
4.1.1 I2C控制器节点配置
以STM32系列为例,I2C1控制器的设备树节点通常位于 soc.dtsi 中。开发者只需在板级 .dts 或 .overlay 文件中设置 status = "okay" 并指定引脚复用。
/* 板级设备树片段:使能I2C1并配置引脚 */
&i2c1 {
status = "okay";
clock-frequency = <I2C_BITRATE_FAST>; /* 400 kHz */
pinctrl-0 = <&i2c1_sda_pb7 &i2c1_scl_pb6>;
pinctrl-names = "default";
};
4.1.2 触摸屏从设备节点
在I2C控制器节点内部,添加触摸屏子节点,必须包含 compatible、reg(7位从机地址)和 label 属性。
/* 在i2c1节点内添加触摸屏 */
&i2c1 {
status = "okay";
clock-frequency = <I2C_BITRATE_FAST>;
touch_ft5336: ft5336@38 {
compatible = "focaltech,ft5336";
reg = <0x38>; /* 7位I2C地址 */
label = "TOUCH";
irq-gpios = <&gpioc 13 GPIO_ACTIVE_LOW>;
reset-gpios = <&gpioc 0 GPIO_ACTIVE_LOW>;
};
};
reg 属性值必须与触摸屏数据手册中的7位从机地址一致。地址错误将导致所有I2C通信失败。使用 i2c scan shell命令可验证地址是否正确。
4.1.3 设备树绑定宏与API映射
Zephyr提供 DT_NODELABEL 和 DT_INST 宏在C代码中引用设备树节点。推荐使用 DT_INST 系列宏实现驱动实例化。
| 宏 | 用途 | 示例 |
|---|---|---|
DT_NODELABEL(label) |
通过节点标签获取节点标识符 | DT_NODELABEL(touch_ft5336) |
DT_INST(idx, compat) |
按兼容字符串和实例索引获取节点 | DT_INST(0, focaltech_ft5336) |
DT_INST_REG_ADDR(idx) |
获取实例的I2C从机地址 | DT_INST_REG_ADDR(0) |
DT_INST_PROP(idx, label) |
获取实例的label属性值 | DT_INST_PROP(0, label) |
4.2 I2C控制器配置:初始化与参数设置
在应用层或驱动初始化阶段,需要获取I2C控制器设备指针并配置通信参数。Zephyr的I2C API通过 struct device 句柄操作控制器。
4.2.1 获取I2C控制器设备
使用 DEVICE_DT_GET 或 device_get_binding 获取控制器设备结构体指针。
#include <zephyr/kernel.h>
#include <zephyr/device.h>
#include <zephyr/drivers/i2c.h>
/* 方法1:通过设备树标签获取(推荐) */
#define I2C1_NODE DT_NODELABEL(i2c1)
const struct device *i2c_dev = DEVICE_DT_GET(I2C1_NODE);
/* 方法2:通过label字符串获取(不推荐,运行时开销) */
/* const struct device *i2c_dev = device_get_binding("I2C_1"); */
if (!device_is_ready(i2c_dev)) {
printk("I2C controller not ready\n");
return -ENODEV;
}
4.2.2 配置I2C通信参数
i2c_configure 函数用于设置控制器的工作模式、速度和寻址模式。触摸屏通常使用标准模式(100 kHz)或快速模式(400 kHz),7位寻址。
uint32_t i2c_cfg = I2C_SPEED_SET(I2C_SPEED_FAST) | I2C_MODE_CONTROLLER;
int ret = i2c_configure(i2c_dev, i2c_cfg);
if (ret != 0) {
printk("I2C configure failed: %d\n", ret);
return ret;
}
| 配置宏 | 值 | 说明 |
|---|---|---|
I2C_SPEED_STANDARD |
1 | 标准模式,100 kHz |
I2C_SPEED_FAST |
2 | 快速模式,400 kHz |
I2C_SPEED_FAST_PLUS |
3 | 快速+模式,1 MHz |
I2C_MODE_CONTROLLER |
0 | 控制器模式(主模式) |
I2C_MODE_TARGET |
1 | 目标模式(从模式,极少用于触摸屏) |
4.3 I2C API函数详解:i2c_write / i2c_read / i2c_transfer
Zephyr提供三级I2C API:简单读写、带寄存器地址的读写和组合传输。触摸屏驱动主要使用后两者。
4.3.1 i2c_write:向从设备发送数据
函数原型:
int i2c_write(const struct device *dev, const uint8_t *buf, uint32_t num_bytes, uint16_t addr);
- dev:I2C控制器设备指针。
- buf:待发送数据缓冲区。
- num_bytes:发送字节数。
- addr:从设备7位I2C地址(无需左移,API内部处理)。
- 返回值:0成功,负值错误码。
触摸屏应用示例——向寄存器0x01写入配置值0x02:
uint8_t cmd[2] = {0x01, 0x02};
int ret = i2c_write(i2c_dev, cmd, sizeof(cmd), TOUCH_I2C_ADDR);
if (ret != 0) {
printk("I2C write failed: %d\n", ret);
}
4.3.2 i2c_read:从从设备读取数据
函数原型:
int i2c_read(const struct device *dev, uint8_t *buf, uint32_t num_bytes, uint16_t addr);
注意:i2c_read 执行一次完整的I2C读传输,不包含前导的寄存器地址写入。对于需要先指定寄存器地址再读取数据的触摸屏,必须使用 i2c_write_read 或 i2c_transfer。
i2c_read 读取触摸屏坐标数据,而不先写入寄存器地址,将导致读取到错误的数据。
4.3.3 i2c_transfer:组合传输(核心函数)
函数原型:
int i2c_transfer(const struct device *dev, struct i2c_msg *msgs, uint8_t num_msgs, uint16_t addr);
i2c_transfer 支持在一次I2C总线操作中执行多个消息段(message),每个消息段可以是写或读,且支持重复起始条件(Restart)。这是触摸屏驱动中最常用的函数。
4.3.4 i2c_msg结构体与组合传输示例
struct i2c_msg {
uint8_t *buf; /* 数据缓冲区 */
uint32_t len; /* 数据长度 */
uint8_t flags; /* 消息标志 */
};
/* 常用标志 */
#define I2C_MSG_WRITE 0x00 /* 写消息 */
#define I2C_MSG_READ 0x01 /* 读消息 */
#define I2C_MSG_STOP 0x02 /* 传输结束后发送停止条件 */
#define I2C_MSG_RESTART 0x04 /* 发送重复起始条件 */
触摸屏读取坐标的典型组合传输:先写寄存器地址,再读数据。
uint8_t reg_addr = 0x02; /* 触摸点状态寄存器 */
uint8_t touch_data[6]; /* 读取6字节触摸数据 */
struct i2c_msg msgs[2];
/* 消息0:写寄存器地址 */
msgs[0].buf = ®_addr;
msgs[0].len = 1;
msgs[0].flags = I2C_MSG_WRITE;
/* 消息1:读触摸数据,使用重复起始,最后发停止 */
msgs[1].buf = touch_data;
msgs[1].len = sizeof(touch_data);
msgs[1].flags = I2C_MSG_READ | I2C_MSG_STOP;
int ret = i2c_transfer(i2c_dev, msgs, 2, TOUCH_I2C_ADDR);
if (ret == 0) {
/* 成功读取触摸数据,解析 touch_data */
}
4.4 三大API函数对比与选型指南
| 函数 | 适用场景 | 是否支持组合传输 | 触摸屏驱动使用频率 |
|---|---|---|---|
i2c_write |
仅向从设备发送数据,无需读取响应 | 否 | 低(配置寄存器时偶尔使用) |
i2c_read |
仅从从设备读取数据,无需前导地址 | 否 | 极低(几乎不用) |
i2c_transfer |
需要先写寄存器地址再读数据,或执行复杂I2C事务 | 是(支持多消息、重复起始) | 高(核心函数) |
i2c_transfer 实现所有I2C通信。即使只需要写一个寄存器,也可以构造单消息的 i2c_msg 数组调用 i2c_transfer,保持代码风格一致,便于后续扩展。
4.5 错误处理与调试技巧
I2C API返回负值错误码,常见错误码及排查方向:
- -EIO (负5):I2C总线通信错误。检查硬件连接、上拉电阻、从机地址是否正确。
- -ENXIO (负6):从设备无应答。确认设备是否上电、地址是否匹配、控制器是否使能。
- -EAGAIN (负11):总线忙,重试即可。可在驱动中增加重试机制。
- -EINVAL (负22):参数无效。检查
i2c_msg标志位、缓冲区长度是否合理。
/* 带重试的I2C传输封装 */
#define I2C_RETRY_CNT 3
static int i2c_transfer_with_retry(const struct device *dev,
struct i2c_msg *msgs,
uint8_t num_msgs,
uint16_t addr)
{
int ret;
for (int i = 0; i < I2C_RETRY_CNT; i++) {
ret = i2c_transfer(dev, msgs, num_msgs, addr);
if (ret == 0) {
return 0;
}
if (ret == -EAGAIN) {
k_sleep(K_MSEC(1));
continue;
}
/* 非重试错误,直接返回 */
break;
}
return ret;
}