• Linux内核文档:
    • Documentation\i2c\instantiating-devices.rst
    • Documentation\i2c\writing-clients.rst
  • Linux内核驱动程序示例:
    • Linux-5.4/drivers/misc/eeprom/at24.c

待完善

无需编写驱动直接访问设备\_I2C-Tools介绍 

I2C-Tools:

// 列出当前的I2C Adapter(或称为I2C Bus、I2C Controller)
i2cdetect -l

// 打印某个I2C Adapter的Functionalities, I2CBUS为0、1、2等整数
i2cdetect -F I2CBUS

// 看看有哪些I2C设备, I2CBUS为0、1、2等整数
i2cdetect -y -a I2CBUS

……

一、 I2C 协议核心时序

I2C 通信由基本的操作序列构成:

  • 写操作: [S] [Slave Addr + W] [A] [Reg Addr] [A] [Data] [A] ... [P]
  • 读操作: [S] [Slave Addr + W] [A] [Reg Addr] [A] [Sr] [Slave Addr + R] [A] [Data] [A] ... [Data] [N] [P]
  • S: 起始信号 (Start Condition)
  • Sr: 重复起始信号 (Repeated Start)
  • Slave Addr: 7 位从机地址
  • W/R: 读写位 (0为写, 1为读)
  • A/N: 应答/非应答信号 (ACK/NACK)
  • P: 停止信号 (Stop Condition)

二、 驱动模型:设备与驱动的分离

Linux 的核心思想是将“硬件的描述”与“硬件的驱动程序”分离开

  • I2C Device (设备描述):
    • 信息来源: 设备树 (Device Tree) /struct i2c_board_info
    • 核心属性:
    • compatible: “我是谁?” (e.g., "lite-on,ap3216c") - 这是驱动匹配的关键
    • reg: “我的地址是多少?” (e.g., <0x1e>) - I2C 从机地址。
    • 挂载点: 它在设备树中的位置决定了它连接到哪个 I2C 控制器 (e.g., &i2c1)。

image

如何确认地址:

image

 

image

image

 写四复位,写三开启 ALS PS IR模块

 ALS 配置寄存器地址(八位)(不用设置)

image

 

  • I2C Driver (驱动程序):
    • 核心结构体: struct i2c_driver
    • 核心成员:
    • .driver.of_match_table: “我能驱动谁?” - 一个 of_device_id 数组,包含了它所支持设备的 compatible 字符串列表。
    • .probe: 当设备与驱动成功匹配时,内核调用的初始化函数
    • .remove: 当设备被移除或驱动卸载时,内核调用的清理函数
    • .id_table: (可选,用于非设备树系统和模块自动加载)

三、 驱动程序的生命周期与核心函数

  • 注册 (init):
    • 驱动模块加载时,调用 i2c_add_driver() i2c_driver 结构体注册到内核的 I2C 核心子系统中。
  • 匹配与探测 (Probe):
    • I2C 核心将设备树中的设备与所有已注册的驱动进行 compatible 字符串匹配。
    • 匹配成功后,内核创建 i2c_client 结构体,并调用驱动的 probe 函数。
    • probe 函数的核心职责:
    • 获取设备句柄: 保存内核传入的 i2c_client 指针,它是后续所有 I2C 通信的凭证。
    • 硬件初始化: 通过 I2C 通信,对设备芯片进行复位、配置等初始化操作。
    • 申请资源: 如申请中断、GPIO 等。
    • 向上层提供接口: 创建字符设备节点 (/dev/xxx),这是驱动与用户空间交互的桥梁。
    • register_chrdev(): 注册一个主设备号,并关联 file_operations 结构体。
    • class_create() / device_create(): 让 udev 自动在 /dev 目录下创建设备文件。
  • 交互 (file_operations):
    • 当用户程序 open, read, write, ioctl 设备文件时,内核通过 file_operations 表,调用驱动中对应的实现函数。
    • 这些函数是驱动的“业务逻辑”:
    • open: 初始化设备,准备开始通信。
    • read: 从 i2c_client 读取数据,并通过 copy_to_user 返回给用户。
    • write: 从用户空间 copy_from_user 接收数据,解析后通过 i2c_client 写入设备。
    • close (release): 关闭设备,释放资源。
  • 移除 (remove):
    • 当设备断开或驱动卸载时,内核调用 remove 函数。
    • 职责: probe 函数的反向操作。释放所有申请的资源(销毁字符设备、注销中断、关闭硬件等)。
  • 卸载 (exit):
    • 驱动模块卸载时,调用 i2c_del_driver() 将驱动从 I2C 核心中注销。

框架

image

 设备之间发送和接收消息都是通过 SMBus,而不是使用单独的控制线,这样可以节省设备的管脚数,SMBus协议之后再说

四、 重要结构体精解

  • i2c_adapter:
    • 代表一个 I2C 控制器 (I2C Bus/Controller),比如 i2c-0, i2c-1。它是 I2C 通信的物理通道
    • 它内部包含一个 i2c_algorithm,定义了如何在该总线上产生起始/停止信号、收发字节等底层硬件操作。驱动开发者通常不直接操作它
  • i2c_client:
    • 代表一个挂载在 i2c_adapter 上的从设备 (I2C Slave Device)
    • 绑定了一个 i2c_adapter 和一个从机地址。
    • 这是驱动开发者最常使用的“句柄”。所有 I2C 通信函数,如 i2c_smbus_read_byte_data,第一个参数都是 i2c_client
  • i2c_msg:
    • 用于构建原始的、更复杂的 I2C 传输序列(比如一次读操作中的先写后读)。
    • 当你需要进行 smbus 接口无法满足的复杂通信时,就需要手动填充 i2c_msg 数组,然后调用 i2c_transfer()

完善:

init:添加一个driver

static int __init i2c_driver_ap3216c_init(void)
{
  printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
  return i2c_add_driver(&i2c_ap3216c_driver);
}

probe函数:

static int ap3216c_probe(struct i2c_client *client)
{
  printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
  ap3216c_client = client;
  
  /* register_chrdev */
  major = register_chrdev(0, "ap3216c", &ap3216c_ops);

  ap3216c_class = class_create(THIS_MODULE, "ap3216c_class"); //device_class
  device_create(ap3216c_class, NULL, MKDEV(major, 0), NULL, "ap3216c"); /* /dev/ap3216c */

  return 0;
}

1.注册字符类设备,设备号,名字

2.创建设备类

3.创建节点

file_operation

open:

static int ap3216c_open (struct inode *node, struct file *file)
{
  i2c_smbus_write_byte_data(ap3216c_client, 0, 0x4);
  /* delay for reset */
  mdelay(20);
  i2c_smbus_write_byte_data(ap3216c_client, 0, 0x3);
  mdelay(250);
  return 0;
}

向寄存器复位

读写:

u16 i2c_smbus_read_word_data(const struct i2c_client *client, u8 command);

i2c_smbus_write_word_data(const struct i2c_client *client, u8 command, u16 value);

读数据需要用到 copy_to_user

 

image

 

GPIO模拟I2C:以后再说

(以后再说)

阅读 Linux-5.4/drivers/misc/eeprom/at24.c