Skip to content

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   = &reg,
        },
        {
            .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_transferSMBus (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总线被占用检查是否有其他驱动占用同一总线

褚成志的笔记