I²C(Inter-Integrated Circuit),又称 TWI(Two Wire Interface),是一种使用两根信号线(SDA、SCL)的串行总线协议,由飞利浦公司(现恩智浦)于1982年开发。它常用于主从设备通信,广泛应用于嵌入式系统中的温湿度传感器、EEPROM、加速度计、RTC、触摸屏等外设连接。
参数 | 标准模式 | 快速模式 | 高速模式 |
---|---|---|---|
速率 | 100kbps | 400kbps | 3.4Mbps |
电压 | 5V/3.3V | 5V/3.3V | 3.3V |
电容 | 400pF | 400pF | 100pF |
Linux 的 I²C 系统采用分层设计,各层职责明确:
代表主设备(通常是 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
实现底层传输方法代表连接到总线上的外设,如 EEPROM、温度传感器等。
关键点:
struct i2c_client
表示位于drivers/i2c/i2c-core.c
,提供核心功能:
主要功能:
i2c_transfer
等核心传输函数内核中的虚拟总线类型,用于设备驱动匹配:
struct bus_type i2c_bus_type = {
.name = "i2c",
.match = i2c_device_match,
.probe = i2c_device_probe,
.remove = i2c_device_remove,
.shutdown = i2c_device_shutdown,
};
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 */
};
&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>;
};
};
属性 | 说明 |
---|---|
compatible |
驱动匹配字符串,格式"厂商,型号" |
reg |
设备I²C地址(7位格式) |
clock-frequency |
可选,指定总线频率 |
interrupts |
设备中断配置(如果支持) |
status |
"okay"启用,"disabled"禁用 |
设备特定属性 | 如EEPROM的pagesize 、size 等 |
#include
#include
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");
数据传输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;
}
# 查看所有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
#include
#include
#include
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;
}
常见问题及解决方法:
检查物理连接和上拉电阻(通常4.7kΩ)
确认设备地址正确(注意7位/8位格式)
用示波器或逻辑分析仪检查信号波形
降低时钟频率测试
检查电源稳定性
确认设备是否支持快速模式
# 内核调试信息
dmesg | grep i2c
# 详细调试(需要内核配置CONFIG_I2C_DEBUG_CORE)
echo 1 > /sys/module/i2c_core/parameters/debug
使用Saleae、PulseView等工具分析时序
检查START/STOP条件、ACK/NACK响应
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>;
};
};
};
/* 在驱动中设置 */
client->flags |= I2C_CLIENT_TEN;
/* 设备树中指定 */
eeprom@50 {
compatible = "atmel,24c512";
reg = <0x50 0x400>; /* 10位地址 */
};
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)
};
合并多次操作为一个传输
使用i2c_transfer而非多个i2c_smbus调用
根据设备能力选择最高可用频率
长距离布线需降低频率
对大块数据传输启用DMA
i2c@400a0000 {
dmas = <&sdma 25 4 0>, <&sdma 26 4 0>;
dma-names = "rx", "tx";
};
对于支持中断的设备,使用中断而非轮询
验证所有用户空间传入的参数
限制寄存器访问范围
使用互斥锁保护共享资源
static DEFINE_MUTEX(device_lock);
mutex_lock(&device_lock);
/* 临界区操作 */
mutex_unlock(&device_lock);
正确处理电源状态转换
保存/恢复设备状态
1、官方文档
Linux内核文档:Documentation/i2c/
I2C规范:NXP UM10204
2、工具
i2c-tools包(i2cdetect, i2cget, i2cset等)
逻辑分析仪(PulseView, Saleae)
3、开发板
Raspberry Pi I2C接口
BeagleBone I2C接口
4、书籍
《Linux设备驱动程序》
《精通Linux设备驱动程序开发》