Linux I²C 总线开发
一、什么是 I²C?
I²C(Inter-Integrated Circuit),又称 TWI(Two Wire Interface),是一种使用两根信号线(SDA、SCL)的串行总线协议,由飞利浦公司(现恩智浦)于1982年开发。它常用于主从设备通信,广泛应用于嵌入式系统中的温湿度传感器、EEPROM、加速度计、RTC、触摸屏等外设连接。
主要特点:
- 两线制:仅需SDA(数据线)和SCL(时钟线)两根信号线
- 主从架构:主设备控制总线时序,从设备响应主设备请求
- 地址寻址:支持7位(标准)和10位(扩展)地址格式
- 多主多从:支持多个主设备和多个从设备共享同一总线
- 速率灵活:
- 标准模式:100kbps
- 快速模式:400kbps
- 高速模式:3.4Mbps
- 超快模式:5Mbps
- 半双工通信:同一时刻只能发送或接收数据
- 开漏输出:需要上拉电阻保证信号电平
电气特性:
参数 | 标准模式 | 快速模式 | 高速模式 |
---|---|---|---|
速率 | 100kbps | 400kbps | 3.4Mbps |
电压 | 5V/3.3V | 5V/3.3V | 3.3V |
电容 | 400pF | 400pF | 100pF |
二、Linux 下的 I²C 架构
Linux 的 I²C 系统采用分层设计,各层职责明确:
1. I²C Adapter(适配器/控制器)
代表主设备(通常是 CPU 的 I²C 控制器),由 SoC 平台驱动实现,负责控制实际 I²C 硬件。
关键点:
- 对应内核目录:
drivers/i2c/busses/
- 常见平台驱动:
i2c-qcom-geni.c
(Qualcomm平台)i2c-imx.c
(NXP i.MX系列)i2c-rk3x.c
(Rockchip平台)
- 通过
struct i2c_adapter
表示 - 提供
i2c_algorithm
实现底层传输方法
2. I²C Client(客户端设备)
代表连接到总线上的外设,如 EEPROM、温度传感器等。
关键点:
- 每个Client有唯一7位或10位地址
- 通过
struct i2c_client
表示 - 驱动开发者只需关注协议层通信
- 典型设备类型:
- 存储设备(EEPROM、FRAM)
- 传感器(温度、湿度、加速度)
- 实时时钟(RTC)
- IO扩展器
3. I²C Core(核心层)
位于drivers/i2c/i2c-core.c
,提供核心功能:
主要功能:
- 驱动注册/注销API
- 设备匹配与绑定
- 提供用户空间访问接口
- 实现
i2c_transfer
等核心传输函数 - 管理适配器和客户端设备
4. I²C 总线类型(Bus)
内核中的虚拟总线类型,用于设备驱动匹配:
struct bus_type i2c_bus_type = {
.name = "i2c",
.match = i2c_device_match,
.probe = i2c_device_probe,
.remove = i2c_device_remove,
.shutdown = i2c_device_shutdown,
};
三、设备树配置详解
1. 控制器节点配置
i2c1: i2c@400a0000 {
compatible = "nxp,imx21-i2c";
reg = <0x400a0000 0x4000>;
interrupts = <0 20 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&clks IMX6UL_CLK_I2C1>;
#address-cells = <1>;
#size-cells = <0>;
status = "okay";
/* 可选的DMA配置 */
dmas = <&sdma 25 4 0>, <&sdma 26 4 0>;
dma-names = "rx", "tx";
/* 可选的时钟配置 */
clock-frequency = <100000>; /* 100kHz */
};
2. 设备节点配置
&i2c1 {
status = "okay";
clock-frequency = <400000>; /* 400kHz */
/* 温度传感器 */
temp_sensor: lm75@48 {
compatible = "national,lm75";
reg = <0x48>;
vs-supply = <®_3v3>;
resolution = <9>; /* 9-bit resolution */
};
/* EEPROM设备 */
eeprom: at24@50 {
compatible = "atmel,24c256";
reg = <0x50>;
pagesize = <64>;
size = <32768>; /* 32KB */
address-width = <16>;
};
/* IO扩展器 */
gpio_exp: pca9555@20 {
compatible = "nxp,pca9555";
reg = <0x20>;
gpio-controller;
#gpio-cells = <2>;
interrupt-parent = <&gpio1>;
interrupts = <5 IRQ_TYPE_EDGE_FALLING>;
};
};
3. 重要属性说明
属性 | 说明 |
---|---|
compatible | 驱动匹配字符串,格式"厂商,型号" |
reg | 设备I²C地址(7位格式) |
clock-frequency | 可选,指定总线频率 |
interrupts | 设备中断配置(如果支持) |
status | "okay"启用,"disabled"禁用 |
设备特定属性 | 如EEPROM的pagesize 、size 等 |
四、I²C驱动开发实战
1. 驱动框架
#include <linux/i2c.h>
#include <linux/module.h>
static int sample_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
struct device *dev = &client->dev;
/* 初始化设备 */
dev_info(dev, "Probing device at address 0x%02x\n", client->addr);
/* 验证设备存在 */
if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
dev_err(dev, "I2C functionality not supported\n");
return -ENODEV;
}
/* 设备特定初始化... */
return 0;
}
static int sample_remove(struct i2c_client *client)
{
/* 清理资源 */
return 0;
}
static const struct of_device_id sample_of_match[] = {
{ .compatible = "vendor,sample-device" },
{ }
};
MODULE_DEVICE_TABLE(of, sample_of_match);
static const struct i2c_device_id sample_id[] = {
{ "sample-device", 0 },
{ }
};
MODULE_DEVICE_TABLE(i2c, sample_id);
static struct i2c_driver sample_driver = {
.driver = {
.name = "sample-i2c-driver",
.of_match_table = sample_of_match,
},
.probe = sample_probe,
.remove = sample_remove,
.id_table = sample_id,
};
module_i2c_driver(sample_driver);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("Sample I2C Device Driver");
2. 常用API函数
数据传输API:
/* 简单读写 */
int i2c_smbus_read_byte_data(const struct i2c_client *client, u8 command);
int i2c_smbus_write_byte_data(const struct i2c_client *client, u8 command, u8 value);
/* 通用传输 */
int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num);
/* 封装函数 */
int i2c_master_send(const struct i2c_client *client, const char *buf, int count);
int i2c_master_recv(const struct i2c_client *client, char *buf, int count);
典型传输示例:
/* 读取16位寄存器 */
static int read_reg16(struct i2c_client *client, u8 reg, u16 *value)
{
struct i2c_msg msgs[2];
u8 buf[2];
int ret;
/* 写寄存器地址 */
msgs[0].addr = client->addr;
msgs[0].flags = 0;
msgs[0].len = 1;
msgs[0].buf = ®
/* 读数据 */
msgs[1].addr = client->addr;
msgs[1].flags = I2C_M_RD;
msgs[1].len = 2;
msgs[1].buf = buf;
ret = i2c_transfer(client->adapter, msgs, 2);
if (ret == 2) {
*value = (buf[0] << 8) | buf[1];
return 0;
}
return ret < 0 ? ret : -EIO;
}
五、用户空间访问方法
1. 通过sysfs接口
# 查看所有I2C总线
ls /sys/bus/i2c/devices/
# 扫描总线上的设备
i2cdetect -y 1 # 扫描I2C-1总线
# 读写示例
i2cget -y 1 0x50 # 从地址0x50读取1字节
i2cset -y 1 0x50 0x00 0x12 # 向寄存器0x00写入0x12
2. 通过/dev接口
#include <linux/i2c-dev.h>
#include <fcntl.h>
#include <unistd.h>
int main() {
int file;
char filename[20];
unsigned char buf[10];
snprintf(filename, sizeof(filename), "/dev/i2c-%d", 1);
file = open(filename, O_RDWR);
if (file < 0) {
perror("Failed to open I2C device");
return 1;
}
if (ioctl(file, I2C_SLAVE, 0x50) < 0) {
perror("Failed to set slave address");
close(file);
return 1;
}
/* 读取操作 */
if (read(file, buf, 1) != 1) {
perror("Failed to read from I2C device");
}
close(file);
return 0;
}
六、调试与故障排除
常见问题及解决方法:
1、设备无响应
检查物理连接和上拉电阻(通常4.7kΩ)
确认设备地址正确(注意7位/8位格式)
用示波器或逻辑分析仪检查信号波形
2、传输错误
降低时钟频率测试
检查电源稳定性
确认设备是否支持快速模式
3、调试工具
# 内核调试信息
dmesg | grep i2c
# 详细调试(需要内核配置CONFIG_I2C_DEBUG_CORE)
echo 1 > /sys/module/i2c_core/parameters/debug
4、逻辑分析仪抓包
使用Saleae、PulseView等工具分析时序
检查START/STOP条件、ACK/NACK响应
七、高级主题
1. I2C多路复用器
i2c-mux@70 {
compatible = "nxp,pca9548";
reg = <0x70>;
#address-cells = <1>;
#size-cells = <0>;
i2c@0 {
#address-cells = <1>;
#size-cells = <0>;
reg = <0>;
sensor@48 {
compatible = "ti,tmp102";
reg = <0x48>;
};
};
i2c@1 {
#address-cells = <1>;
#size-cells = <0>;
reg = <1>;
eeprom@50 {
compatible = "atmel,24c02";
reg = <0x50>;
};
};
};
2. 10位地址支持
/* 在驱动中设置 */
client->flags |= I2C_CLIENT_TEN;
/* 设备树中指定 */
eeprom@50 {
compatible = "atmel,24c512";
reg = <0x50 0x400>; /* 10位地址 */
};
3. 电源管理
static int sample_suspend(struct device *dev)
{
struct i2c_client *client = to_i2c_client(dev);
/* 保存状态或进入低功耗模式 */
return 0;
}
static int sample_resume(struct device *dev)
{
struct i2c_client *client = to_i2c_client(dev);
/* 恢复状态 */
return 0;
}
static const struct dev_pm_ops sample_pm_ops = {
SET_SYSTEM_SLEEP_PM_OPS(sample_suspend, sample_resume)
};
八、性能优化建议
1、减少总线占用时间
合并多次操作为一个传输
使用i2c_transfer而非多个i2c_smbus调用
2、合理设置时钟频率
根据设备能力选择最高可用频率
长距离布线需降低频率
3、DMA传输
对大块数据传输启用DMA
i2c@400a0000 {
dmas = <&sdma 25 4 0>, <&sdma 26 4 0>;
dma-names = "rx", "tx";
};
4、中断驱动
对于支持中断的设备,使用中断而非轮询
九、安全注意事项
1、输入验证
验证所有用户空间传入的参数
限制寄存器访问范围
2、 并发控制
使用互斥锁保护共享资源
static DEFINE_MUTEX(device_lock);
mutex_lock(&device_lock);
/* 临界区操作 */
mutex_unlock(&device_lock);
3、电源管理
正确处理电源状态转换
保存/恢复设备状态
十、参考资源
1、官方文档
Linux内核文档:Documentation/i2c/
I2C规范:NXP UM10204
2、工具
i2c-tools包(i2cdetect, i2cget, i2cset等)
逻辑分析仪(PulseView, Saleae)
3、开发板
Raspberry Pi I2C接口
BeagleBone I2C接口
4、书籍
《Linux设备驱动程序》
《精通Linux设备驱动程序开发》