当前位置: 首页 > news >正文

Linux I2C 子系统

I2C 与 SMBus 协议对比

SMBus 是 I2C 的简化与约束版本,在实际设备中更常被支持,Linux 也建议优先使用 SMBus,若硬件不支持,也可通过 I2C 协议软件模拟。

关键差异

特性I2C 协议SMBus 协议
VDD 极限值高达 12V1.8V ~ 5V
最小时钟频率无限制10KHz
Clock Stretching时长无限制最大时间有限制
地址回应无强制回应要求强制回应,用于感知设备状态
数据传输格式仅定义传输方式,格式由设备自定义明确多种数据格式
REPEATED START 支持支持,写读操作间可直接发新 START支持
低功耗版本无特定低功耗版本定义有 SMBus Low Power Version

总结

因多数设备实现 SMBus,Linux 建议优先使用。即使 I2C 控制器无 SMBus 硬件支持,也可通过软件模拟 SMBus 协议。
在这里插入图片描述

Linux I2C 子系统核心结构体

i2c_adapter:I2C 控制器抽象

代表物理 I2C 控制器(适配器),是 CPU 与 I2C 总线的硬件接口抽象。

struct i2c_adapter {struct module *owner;unsigned int class;          // 适配器支持的设备类型(可选)const struct i2c_algorithm *algo; // 指向通信算法结构体void *algo_data;struct rt_mutex bus_lock;int timeout;                 // 超时时间int retries;                 // 重试次数struct device dev;           // 设备模型相关,用于设备管理int nr;                      // I2C 总线编号(如 i2c-0、i2c-1)char name[48];               // 适配器名称// 其他成员...
};

作用

  • 管理一条 I2C 总线,是 I2C 通信的硬件载体。
  • 通过 algo 关联 i2c_algorithm,提供硬件相关操作接口。
  • 向上层(I2C 核心)提供总线访问能力,使设备驱动能与总线上设备通信。
    使用场景:每个 I2C 控制器需注册一个 i2c_adapter 实例到内核,如嵌入式系统有 3 个 I2C 控制器,就会注册 i2c-0i2c-1i2c-2 三个实例。

i2c_algorithm:通信算法抽象

定义 I2C 适配器实现 I2C 协议的底层操作(起始、数据收发、停止等),是适配器与硬件交互的“驱动逻辑”。

struct i2c_algorithm {int (*master_xfer)(struct i2c_adapter *adap, struct i2c_msg *msgs, int num);int (*smbus_xfer)(struct i2c_adapter *adap, u16 addr, unsigned short flags, char read_write, u8 command, int size, union i2c_smbus_data *data);u32 (*functionality)(struct i2c_adapter *);// 其他成员...
};

核心函数

  • master_xfer:核心函数,实现 I2C 主模式下的消息传输(发送/接收数据)。
  • smbus_xfer:用于 SMBus 协议的传输(可选)。
  • functionality:返回适配器支持的功能(如是否支持 10 位地址、SMBus 协议等)。
    使用场景:每个 i2c_adapter 必须绑定一个 i2c_algorithm 才能实现实际 I2C 通信,不同芯片 I2C 控制器的 i2c_algorithm 实现不同,但都通过 master_xfer 提供统一接口。

i2c_client:I2C 从设备抽象

代表 I2C 总线上的具体从设备(如传感器、EEPROM 等),是设备驱动与硬件设备的桥梁。
addr:表示 I2C 从设备的地址(通常是 7 位地址,10 位地址需要通过flags中的I2C_CLIENT_TEN标志位来标识 )。
name:设备的名称,用于标识设备类型。
adapter:指向该设备所连接的i2c_adapter(I2C 适配器),通过它来和硬件总线交互。
dev:内嵌的struct device结构体,用于设备模型管理,涉及电源管理、热插拔等功能。
driver:指向绑定到该设备的i2c_driver,当设备和驱动匹配成功后,会设置此指针。

struct i2c_client {unsigned short addr;         // 设备的 I2C 从地址(7 位或 10 位)char name[I2C_NAME_SIZE];    // 设备名称struct i2c_adapter *adapter; // 指向设备连接的 I2C 适配器struct device dev;           // 内核设备模型相关,用于电源、热插拔等管理struct i2c_driver *driver;   // 指向绑定的设备驱动int irq;                     // 设备中断号// 其他成员...
};

作用

  • 描述 I2C 从设备,记录硬件信息(I2C 地址、挂载总线等)。
  • 通过内核设备模型,将自身与 i2c_driver(设备驱动)绑定,实现驱动对设备的控制。
  • 提供通信入口,设备驱动通过其关联的 i2c_adapter 与硬件设备进行 I2C 通信。
    生命周期
  • 创建注册:常由设备树解析(i2c_new_device())或用户空间通过 sysfs 动态创建。
  • 驱动绑定i2c_driver 注册时,内核根据设备名或 ID 表,将 i2c_client 与匹配的 i2c_driver 绑定。
  • 通信控制:绑定后,驱动通过 i2c_transfer() 等接口,借助关联的 i2c_adapter 与硬件通信。
  • 注销:设备移除时,i2c_client 被注销,释放资源。

测试

在用户态生成

示例:

// 在I2C BUS0下创建i2c_client
# echo ap3216c 0x1e > /sys/bus/i2c/devices/i2c-0/new_device// 删除i2c_client
# echo 0x1e > /sys/bus/i2c/devices/i2c-0/delete_device
编写代码
  • i2c_new_device
  • i2c_new_probed_device
  • i2c_register_board_info
    • 内核没有EXPORT_SYMBOL(i2c_register_board_info)
    • 使用这个函数的驱动必须编进内核里去
使用设备树生成

在某个I2C控制器的节点下,添加如下代码:

		ap3216c@1e {compatible = "lite-on,ap3216c";reg = <0x1e>;};

i2c_msg:I2C 传输消息

表示一次 I2C 传输的消息单元,包含地址、方向、数据等信息。

struct i2c_msg {__u16 addr;                  // 从设备地址__u16 flags;                 // 传输标志,如 I2C_M_RD 表示读__u16 len;                   // 数据长度__u8 *buf;                   // 数据缓冲区
};

关键标志I2C_M_RD,bit 0 为该值时表示读操作,否则为写操作。一个 i2c_msg 要么读,要么写。

i2c_driver

probe:当 I2C 驱动与 I2C 设备匹配成功后调用,用于设备的初始化工作,比如申请设备资源、注册设备文件操作接口等。
remove:在设备移除时调用,用于释放设备相关资源。
of_match_table:设备树匹配表,用于通过设备树进行驱动和设备的匹配。
acpi_match_table:ACPI 匹配表,用于通过 ACPI 进行驱动和设备的匹配。
id_table:定义了该驱动支持的设备 ID 列表,用于传统的 ID 匹配方式。
driver:内嵌的struct device_driver结构体,提供驱动的通用属性和操作。
i2c_driver表明能支持哪些设备:

  • 使用of_match_table来判断
    • 设备树中,某个I2C控制器节点下可以创建I2C设备的节点
      • 如果I2C设备节点的compatible属性跟of_match_table的某项兼容,则匹配成功
    • i2c_client.name跟某个of_match_table[i].compatible值相同,则匹配成功
  • 使用id_table来判断
    • i2c_client.name跟某个id_table[i].name值相同,则匹配成功

i2c_driver跟i2c_client匹配成功后,就调用i2c_driver.probe函数。

I2C 数据传输核心流程与代码

在这里插入图片描述

内核数据传输函数:i2c_transfer

用于在 I2C 总线上传输一个或多个 i2c_msg,是内核中 I2C 数据传输的核心接口。

int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)
{int ret;// 检查参数有效性if (adap == NULL || msgs == NULL || num <= 0)return -EINVAL;// 调用适配器关联的 algorithm 中的 master_xfer 函数进行传输if (adap->algo->master_xfer) {ret = adap->algo->master_xfer(adap, msgs, num);} else {ret = -ENOSYS;}return ret;
}

作用:APP 或驱动通过 i2c_adapter,以 i2c_msg 为数据单元,与 i2c_client 对应的从设备传输数据。

I2C 设备驱动开发核心步骤与代码

以一个简单的 I2C 传感器驱动为例,展示核心步骤。
在这里插入图片描述

定义设备 ID 表

用于匹配 i2c_clienti2c_driver

static const struct i2c_device_id sensor_id_table[] = {{"sensor_demo", 0},{},
};
MODULE_DEVICE_TABLE(i2c, sensor_id_table);
初始化 I2C 设备(probe 函数与 i2c_client 处理)

probe 函数在驱动与设备匹配时调用,完成设备初始化。

static int sensor_probe(struct i2c_client *client, const struct i2c_device_id *id)
{int ret;// 检查设备是否支持if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {dev_err(&client->dev, "adapter does not support I2C\n");return -EOPNOTSUPP;}// 可在这里进行设备初始化操作,如读取设备 ID 等// 示例:读取设备某个寄存器(假设设备 ID 寄存器地址为 0x00)u8 dev_id;ret = i2c_smbus_read_byte_data(client, 0x00);if (ret < 0) {dev_err(&client->dev, "failed to read device ID\n");return ret;}dev_id = ret;dev_info(&client->dev, "sensor device ID: 0x%x\n", dev_id);// 后续可进行设备相关资源申请、中断注册等操作return 0;
}
I2C 读写函数编写

实现对从设备寄存器的读写操作。

指定寄存器读(单字节)
static int sensor_read_reg(struct i2c_client *client, u8 reg, u8 *val)
{int ret;*val = i2c_smbus_read_byte_data(client, reg);if (*val < 0) {dev_err(&client->dev, "failed to read reg 0x%x\n", reg);ret = -EIO;} else {ret = 0;}return ret;
}
指定寄存器写(单字节)
static int sensor_write_reg(struct i2c_client *client, u8 reg, u8 val)
{int ret;ret = i2c_smbus_write_byte_data(client, reg, val);if (ret < 0) {dev_err(&client->dev, "failed to write reg 0x%x val 0x%x\n", reg, val);ret = -EIO;}return ret;
}
读取从设备多个寄存器数据
static int sensor_read_multi_regs(struct i2c_client *client, u8 reg, u8 *buf, int len)
{struct i2c_msg msgs[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 = len;msgs[1].buf = buf;// 传输消息ret = i2c_transfer(client->adapter, msgs, 2);if (ret != 2) {dev_err(&client->dev, "i2c transfer failed, ret = %d\n", ret);ret = -EIO;} else {ret = 0;}return ret;
}
向从设备多个寄存器写入数据
static int sensor_write_multi_regs(struct i2c_client *client, u8 reg, u8 *buf, int len)
{struct i2c_msg msgs[1];u8 *tmp_buf;int ret, i;tmp_buf = kmalloc(len + 1, GFP_KERNEL);if (!tmp_buf)return -ENOMEM;tmp_buf[0] = reg;for (i = 0; i < len; i++) {tmp_buf[i + 1] = buf[i];}// 消息:写寄存器地址和多个数据msgs[0].addr = client->addr;msgs[0].flags = 0; // 写操作msgs[0].len = len + 1;msgs[0].buf = tmp_buf;// 传输消息ret = i2c_transfer(client->adapter, msgs, 1);if (ret != 1) {dev_err(&client->dev, "i2c transfer failed, ret = %d\n", ret);ret = -EIO;} else {ret = 0;}kfree(tmp_buf);return ret;
}
设备操作函数编写

定义字符设备等的操作函数,供应用层调用。

static struct file_operations sensor_fops = {.owner = THIS_MODULE,.read = sensor_read,.write = sensor_write,.open = sensor_open,.release = sensor_release,// 其他操作函数...
};
static struct miscdevice sensor_miscdev = {.minor = MISC_DYNAMIC_MINOR,.name = "sensor_demo",.fops = &sensor_fops,
};
// 在 probe 函数中注册 misc 设备
static int sensor_probe(struct i2c_client *client, const struct i2c_device_id *id)
{// ... 其他初始化操作 ...misc_register(&sensor_miscdev);return 0;
}
// 在 remove 函数中注销 misc 设备
static int sensor_remove(struct i2c_client *client)
{misc_deregister(&sensor_miscdev);// ... 释放资源等操作 ...return 0;
}
定义 i2c_driver 并注册

将驱动与设备匹配逻辑、proberemove 等函数关联,并注册到内核。

static struct i2c_driver sensor_driver = {.driver = {.name = "sensor_demo",.owner = THIS_MODULE,},.probe = sensor_probe,.remove = sensor_remove,.id_table = sensor_id_table,
};
static int __init sensor_init(void)
{return i2c_add_driver(&sensor_driver);
}
static void __exit sensor_exit(void)
{i2c_del_driver(&sensor_driver);
}
module_init(sensor_init);
module_exit(sensor_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("I2C Sensor Demo Driver");

怎么使用I2C-GPIO

设置设备树,在里面添加一个节点即可:

  • compatible = "i2c-gpio";
  • 使用pinctrl把 SDA、SCL所涉及引脚配置为GPIO、开极
    • 可选
  • 指定SDA、SCL所用的GPIO
  • 指定频率(2种方法):
    • i2c-gpio,delay-us = <5>; /* ~100 kHz */
    • clock-frequency = <400000>;
  • #address-cells = <1>;
  • #size-cells = <0>;
  • i2c-gpio,sda-open-drain:
    • 它表示其他驱动、其他系统已经把SDA设置为open drain了
    • 在驱动里不需要在设置为open drain
    • 如果需要驱动代码自己去设置SDA为open drain,就不要提供这个属性
  • i2c-gpio,scl-open-drain:
    • 它表示其他驱动、其他系统已经把SCL设置为open drain了
    • 在驱动里不需要在设置为open drain
    • 如果需要驱动代码自己去设置SCL为open drain,就不要提供这个属性
编写设备树
i2c_gpio_100ask {compatible = "i2c-gpio";gpios = <&gpio4 20 0 /* sda */&gpio4 21 0 /* scl */>;i2c-gpio,delay-us = <5>;	/* ~100 kHz */#address-cells = <1>;#size-cells = <0>;
};

把上述代码,放入dts的根节点下面。

http://www.dtcms.com/a/454527.html

相关文章:

  • 中国建设银行官网站预约纪念币网站关键词优化到首页后怎么做
  • 专业营销型网站建设费用货源网
  • Windows 11基本操作
  • 建设行业个人信息网站哔哩哔哩免费安装
  • 广州网站app制作公司c 网站做微信收款功能
  • 扒完网站代码之后怎么做模板安卓app开发需要学什么
  • 上海800做网站桂林昨晚发生的新闻
  • 【软件安装】
  • 正宗营销型网站建设用自己的手机做网站
  • 网站域名备案与不备案的区别可以免费看国外短视频app
  • 网站标题的设置方法北京建王环境发展有限公司
  • 什么是电子商务网站的建设做博客网站需要工具吗
  • 做电影种子下载网站违法吗东莞网站(建设信科网络)
  • 网站建设后怎么写银川seo公司
  • 湛江网站搜索引擎推广外贸流程询盘
  • 12. Pandas 数据合并与拼接(concat 与 merge)
  • 23ICPC澳门站补题
  • 怎样做淘宝的导购网站推广宣传片制作网站
  • 51zwd做网站淘宝网中国站电脑版登录
  • 快速搭建网站 开源软件开发工程师多少钱一个月
  • vue知识点-列表渲染+key
  • 花茶网站模板装修全包
  • discuz企业网站模板陕西住房城乡建设网站
  • 哪些网站可以做海报网站建设需要摊销几年
  • Mac怎么搭建网站开发环境微信公众号怎么进行网站建设
  • 深圳网站设计服务公如何创建个人博客wordpress
  • 国外网站网站wordpress技术教程
  • 网站建设 的公司哪家好提供app开发公司报价
  • 做网站自动上传文章网络技术推广服务
  • 网站建设与开发定制怎么做局域网网站