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
  • 半双工通信:同一时刻只能发送或接收数据
  • 开漏输出:需要上拉电阻保证信号电平

电气特性:

参数标准模式快速模式高速模式
速率100kbps400kbps3.4Mbps
电压5V/3.3V5V/3.3V3.3V
电容400pF400pF100pF

二、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的pagesizesize

四、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 = &reg;
    
    /* 读数据 */
    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设备驱动程序开发》
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值