I2C 子系统
I2C 协议回顾
I2C(Inter-Integrated Circuit)是两线制串行总线:SCL(时钟)+ SDA(数据),支持多主多从,7 位或 10 位寻址。
Master Slave (0x48)
│ │
├── START │
├── 地址帧 [0x48 | W] ────►│ ACK
├── 寄存器地址 [0x00] ────►│ ACK
├── 数据 [0x12] ────►│ ACK
├── STOP │Linux I2C 子系统架构
用户空间
│ /dev/i2c-0(i2c-dev)
▼
┌─────────────────────────────────┐
│ I2C 核心层 │
│ i2c_transfer() / smbus_*() │
└──────┬──────────────────────────┘
│
┌──────▼──────────────────────────┐
│ I2C 总线驱动(Adapter) │
│ i2c-bcm2835 / i2c-designware │
└──────┬──────────────────────────┘
│ SCL/SDA
硬件 I2C 控制器
│
I2C 从设备(传感器、EEPROM…)编写 I2C 设备驱动
以温度传感器 LM75 为例:
c
#include <linux/module.h>
#include <linux/i2c.h>
#include <linux/of.h>
struct lm75_priv {
struct i2c_client *client;
int last_temp;
};
/* 读取温度寄存器 */
static int lm75_read_temp(struct lm75_priv *priv)
{
struct i2c_client *client = priv->client;
u8 reg = 0x00; /* 温度寄存器地址 */
u8 buf[2];
struct i2c_msg msgs[] = {
{
.addr = client->addr,
.flags = 0, /* 写 */
.len = 1,
.buf = ®,
},
{
.addr = client->addr,
.flags = I2C_M_RD, /* 读 */
.len = 2,
.buf = buf,
},
};
int ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs));
if (ret != ARRAY_SIZE(msgs))
return ret < 0 ? ret : -EIO;
/* LM75 温度格式:高 9 位,0.5°C 分辨率 */
s16 raw = (buf[0] << 8) | buf[1];
priv->last_temp = (raw >> 7) * 500; /* 单位:毫摄氏度 */
return 0;
}
/* SMBus 接口(更简洁,适合标准寄存器读写) */
static int lm75_read_smbus(struct i2c_client *client)
{
/* 读单字节寄存器 */
int val = i2c_smbus_read_byte_data(client, 0x00);
if (val < 0)
return val;
/* 读 16 位寄存器 */
val = i2c_smbus_read_word_data(client, 0x00);
/* 写寄存器 */
i2c_smbus_write_byte_data(client, 0x01, 0x60);
return 0;
}
static int lm75_probe(struct i2c_client *client)
{
struct lm75_priv *priv;
/* 检查 SMBus 功能 */
if (!i2c_check_functionality(client->adapter,
I2C_FUNC_SMBUS_BYTE_DATA)) {
dev_err(&client->dev, "SMBus byte data not supported\n");
return -EOPNOTSUPP;
}
priv = devm_kzalloc(&client->dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
priv->client = client;
i2c_set_clientdata(client, priv);
dev_info(&client->dev, "LM75 at address 0x%02x\n", client->addr);
return 0;
}
static void lm75_remove(struct i2c_client *client)
{
/* devm 资源自动释放 */
}
static const struct of_device_id lm75_of_match[] = {
{ .compatible = "national,lm75" },
{ .compatible = "ti,tmp75" },
{ }
};
MODULE_DEVICE_TABLE(of, lm75_of_match);
static const struct i2c_device_id lm75_id[] = {
{ "lm75", 0 },
{ "tmp75", 0 },
{ }
};
MODULE_DEVICE_TABLE(i2c, lm75_id);
static struct i2c_driver lm75_driver = {
.driver = {
.name = "lm75",
.of_match_table = lm75_of_match,
},
.probe = lm75_probe,
.remove = lm75_remove,
.id_table = lm75_id,
};
module_i2c_driver(lm75_driver);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("LM75 Temperature Sensor Driver");设备树节点
c
&i2c1 {
clock-frequency = <400000>; /* 400 kHz Fast Mode */
status = "okay";
lm75: temperature-sensor@48 {
compatible = "national,lm75";
reg = <0x48>; /* I2C 地址 */
};
eeprom@50 {
compatible = "atmel,24c32";
reg = <0x50>;
pagesize = <32>;
};
};i2c_transfer vs SMBus
| 特性 | i2c_transfer | SMBus (i2c_smbus_*) |
|---|---|---|
| 灵活性 | 高,支持任意消息序列 | 低,固定事务类型 |
| 兼容性 | 需要真正的 I2C 适配器 | 兼容 SMBus 适配器 |
| 代码量 | 较多 | 简洁 |
| 适用场景 | 复杂协议、非标准设备 | 标准寄存器读写 |
用户空间直接访问
bash
# 列出 I2C 总线
i2cdetect -l
# 扫描总线上的设备
i2cdetect -y 1
# 读取寄存器
i2cget -y 1 0x48 0x00 w
# 写寄存器
i2cset -y 1 0x48 0x01 0x60常见问题
| 问题 | 原因 | 解决 |
|---|---|---|
probe 未调用 | 地址冲突或 DTS 未启用 | i2cdetect 确认设备存在,检查 status = "okay" |
i2c_transfer 返回 -EREMOTEIO | 从设备无应答(NACK) | 检查地址、上拉电阻、电源 |
| 数据错误 | 时序问题 | 降低 clock-frequency,用逻辑分析仪抓波形 |
-EBUSY | 总线被占用 | 检查是否有其他驱动占用同一总线 |