title: I2C驱动开发 tags: [Linux驱动, I2C, 嵌入式, 总线驱动, 设备树] created: 2026-09-16 updated: 2026-09-17
关联知识: [[STM32学习笔记/07-I2C通信与EEPROM 24C02]] | [[03-Linux驱动开发核心/07-platform驱动模型]] | [[04-Linux总线与接口驱动/02-SPI驱动开发]]
⚠️ 来源说明:本节不属于《I.MX6U嵌入式Linux驱动开发指南》第六十一章内容(I2C 物理协议在裸机篇第二十六章讲解),为扩展知识。
I2C(Inter-Integrated Circuit)是Philips公司开发的两线式串行通信总线,广泛用于连接低速外设。
硬件特性:
速率模式:
| 模式 | 速率 | 说明 |
|---|---|---|
| 标准模式 | 100KHz | 最基本的I2C通信速率 |
| 快速模式 | 400KHz | 常用的高速模式 |
| 高速模式 | 3.4MHz | 特定应用场景 |
| 超高速 | 5MHz | 最新规范 |
I2C通信由主机产生起始和停止条件来控制总线:
起始条件(S):SCL为高电平时,SDA产生下降沿
停止条件(P):SCL为高电平时,SDA产生上升沿
时序图:
___ ___
SDA / \_______________________________/ \___
| | |
| S | 数据传输 | P |
SCL \___/‾‾‾\___/‾‾‾\___/‾‾‾\___/‾‾‾\___/‾‾‾\___
START SCL时钟同步 STOP
I2C数据传输遵循严格的格式:
起始位 → 7位从机地址 + R/W位 → ACK → 数据字节 → ACK → ... → 停止位
详细流程:
数据有效性规则:
7位寻址(常用):
[A6:A0] + R/W10位寻址:
11110[A9:A8] + R/W,第二字节 [A7:A0]保留地址:
| 地址 | 用途 |
|---|---|
| 0x00 | 广播呼叫 |
| 0x01~0x07 | 保留 |
| 0x78~0x7F | 保留 |
Linux I2C子系统采用分层分离思想,分为三层:
graph TD
A[用户空间] -->|read/write/ioctl| B[I2C设备驱动<br/>i2c_driver]
B -->|i2c_transfer| C[I2C核心层<br/>i2c-core]
C -->|调用master_xfer| D[I2C总线驱动<br/>i2c_adapter]
D -->|寄存器操作| E[I2C控制器硬件<br/>SOC I2C外设]
E -->|SDA/SCL| F[I2C从设备<br/>AP3216C/AT24C02]
style A fill:#e1f5fe
style B fill:#f3e5f5
style C fill:#e8f5e9
style D fill:#fff3e0
style E fill:#fce4ec
style F fill:#f5f5f5
各层职责:
| 层级 | 组件 | 职责 | 开发者 |
|---|---|---|---|
| 设备驱动层 | i2c_driver |
实现具体设备的读写逻辑 | 应用开发者 |
| 核心层 | i2c-core |
提供通用API,管理设备/驱动匹配 | 内核维护者 |
| 总线驱动层 | i2c_adapter |
控制I2C控制器硬件 | SoC厂商 |
关键理解:I2C总线驱动(适配器驱动)由SoC厂商编写(如NXP为IMX6ULL编写
i2c-imx.c),用户只需编写I2C设备驱动。
表示SOC的I2C控制器,定义在include/linux/i2c.h:
struct i2c_adapter {
struct module *owner;
unsigned int class;
const struct i2c_algorithm *algo; /* 总线访问算法 */
void *algo_data;
struct rt_mutex bus_lock;
int timeout; /* 超时时间,单位jiffies */
int retries;
struct device dev; /* 适配器设备 */
int nr; /* 总线号 */
char name[48];
struct completion dev_released;
struct mutex userspace_clients_lock;
struct list_head userspace_clients;
struct i2c_bus_recovery_info *bus_recovery_info;
const struct i2c_adapter_quirks *quirks;
};
关键成员说明:
algo:指向i2c_algorithm,包含实际的传输函数master_xfernr:适配器编号,对应/dev/i2c-Xbus_lock:总线锁,保证同一时刻只有一个master操作总线struct i2c_algorithm {
int (*master_xfer)(struct i2c_adapter *adap,
struct i2c_msg *msgs,
int num); /* I2C传输函数 */
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_transfer最终调用此函数functionality:返回适配器支持的协议标志,如I2C_FUNC_I2C描述一个挂载在I2C总线上的设备:
struct i2c_client {
unsigned short flags; /* 标志 */
unsigned short addr; /* 7位芯片地址,存储在低7位 */
char name[I2C_NAME_SIZE]; /* 设备名字 */
struct i2c_adapter *adapter; /* 对应的适配器 */
struct device dev; /* 设备结构体 */
int irq; /* 中断号 */
struct list_head detected;
};
重要:addr字段只存储7位地址值(不含R/W位),例如AP3216C的地址0x1E直接存储为0x1E。
类似platform_driver,是设备驱动的核心结构体:
struct i2c_driver {
unsigned int class;
int (*probe)(struct i2c_client *,
const struct i2c_device_id *); /* 匹配成功后执行 */
int (*remove)(struct i2c_client *); /* 移除时执行 */
void (*shutdown)(struct i2c_client *);
void (*alert)(struct i2c_client *, unsigned int data);
int (*command)(struct i2c_client *client,
unsigned int cmd, void *arg);
struct device_driver driver; /* 驱动结构体 */
const struct i2c_device_id *id_table; /* 传统匹配ID表 */
int (*detect)(struct i2c_client *,
struct i2c_board_info *);
const unsigned short *address_list;
struct list_head clients;
};
描述一次I2C数据传输的基本单元:
struct i2c_msg {
__u16 addr; /* 从机地址 */
__u16 flags; /* 标志 */
__u16 len; /* 消息长度 */
__u8 *buf; /* 消息数据缓冲区 */
};
flags常用值:
| 标志 | 值 | 说明 |
|---|---|---|
I2C_M_RD |
0x0001 | 读数据 |
I2C_M_TEN |
0x0010 | 10位地址模式 |
I2C_M_STOP |
0x8000 | 传输后发送停止条件 |
I2C_M_NOSTART |
0x4000 | 不发送起始条件 |
I2C_M_REV_DIR_ADDR |
0x2000 | 反转读写方向 |
I2C_M_IGNORE_NAK |
0x1000 | 忽略 NAK |
I2C_M_NO_RD_ACK |
0x0800 | 读操作不发送 ACK |
I2C_M_RECV_LEN |
0x0400 | 首字节为长度 |
以上 8 个标志与《I.MX6U嵌入式Linux驱动开发指南》示例代码 61.3.2.1 中
i2c_msg结构体内的定义一致。
在未使用设备树时,内核使用 i2c_board_info 描述一个具体的 I2C 设备(原书示例代码 61.3.1.1):
struct i2c_board_info {
char type[I2C_NAME_SIZE]; /* I2C 设备名字 */
unsigned short flags; /* 标志 */
unsigned short addr; /* I2C 器件地址 */
void *platform_data;
struct dev_archdata *archdata;
struct device_node *of_node;
struct fwnode_handle *fwnode;
int irq;
};
其中 type 和 addr 必须设置。原书用 I2C_BOARD_INFO 宏来初始化(示例代码 61.3.1.3):
#define I2C_BOARD_INFO(dev_type, dev_addr) \
.type = dev_type, .addr = (dev_addr)
/* 原书示例:OV2640 摄像头,器件地址 0x30 */
static struct i2c_board_info mx27_3ds_i2c_camera = {
I2C_BOARD_INFO("ov2640", 0x30),
};
采用设备树后不再使用 i2c_board_info,改为在 I2C 控制器节点下创建子节点。
I2C总线的匹配由i2c_bus_type驱动,定义在drivers/i2c/i2c-core.c:
struct bus_type i2c_bus_type = {
.name = "i2c",
.match = i2c_device_match,
.probe = i2c_device_probe,
.remove = i2c_device_remove,
.shutdown = i2c_device_shutdown,
};
匹配函数i2c_device_match的执行顺序:
graph TD
A[i2c_device_match] --> B{of_driver_match_device?}
B -->|是| C[设备树匹配成功]
B -->|否| D{acpi_driver_match_device?}
D -->|是| E[ACPI匹配成功]
D -->|否| F{driver->id_table?}
F -->|是| G[i2c_match_id]
F -->|否| H[匹配失败]
G --> I[比较name字段]
I -->|匹配| C
I -->|不匹配| H
匹配方式优先级:
compatible属性(优先)i2c_device_id的name字段I2C设备作为I2C总线节点的子节点,格式如下:
&i2c1 {
clock-frequency = <100000>; /* I2C频率100KHz */
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_i2c1>;
status = "okay";
设备名@器件地址 {
compatible = "厂商,设备名";
reg = <器件地址>;
/* 其他属性 */
};
};
| 属性 | 说明 | 示例 |
|---|---|---|
reg |
I2C从设备7位地址 | reg = <0x1e>; |
compatible |
匹配驱动的兼容字符串 | "alientek,ap3216c" |
clock-frequency |
I2C总线频率 | <100000> (100KHz) |
status |
设备状态 | "okay" / "disabled" |
IMX6ULL I2C1控制器节点(来自imx6ull.dtsi):
i2c1: i2c@021a0000 {
#address-cells = <1>;
#size-cells = <0>;
compatible = "fsl,imx6ul-i2c", "fsl,imx21-i2c";
reg = <0x021a0000 0x4000>;
interrupts = <GIC_SPI 36 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&clks IMX6UL_CLK_I2C1>;
status = "disabled";
};
AP3216C设备子节点(修改后):
&i2c1 {
clock-frequency = <100000>;
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_i2c1>;
status = "okay";
ap3216c@1e {
compatible = "alientek,ap3216c";
reg = <0x1e>;
};
};
重要:
imx6ull-alientek-emmc.dts中&i2c1节点默认带有 NXP 官方 EVK 的两个子节点mag3110@0e(磁力计,compatible 为"fsl,mag3110")和fxls8471@1e(加速度计,compatible 为"fsl,fxls8471")。正点原子 I.MX6U-ALPHA 开发板没有这两个器件,必须先将它们删除,再添加ap3216c@1e子节点(原书示例代码 61.5.1.3)。否则fxls8471@1e会与 AP3216C 争夺同一地址 0x1e。
pinctrl配置:
pinctrl_i2c1: i2c1grp {
fsl,pins = <
MX6UL_PAD_UART4_TX_DATA__I2C1_SCL 0x4001b8b0
MX6UL_PAD_UART4_RX_DATA__I2C1_SDA 0x4001b8b0
>;
};
编译设备树后,检查I2C设备是否正确注册:
# 查看所有I2C设备
ls /sys/bus/i2c/devices/
# 输出示例:0-001e i2c-0 i2c-1
# 查看设备名
cat /sys/bus/i2c/devices/0-001e/name
# 输出:ap3216c
| 函数 | 说明 |
|---|---|
i2c_add_driver(driver) |
注册I2C设备驱动(常用) |
i2c_del_driver(driver) |
注销I2C设备驱动 |
i2c_register_driver(owner, driver) |
注册驱动(底层函数) |
i2c_add_adapter(adapter) |
注册适配器(动态总线号) |
i2c_add_numbered_adapter(adap) |
注册适配器(静态总线号) |
i2c_del_adapter(adap) |
注销适配器 |
函数原型(原书第 61.1.1/61.1.2 节):
int i2c_register_driver(struct module *owner, struct i2c_driver *driver); /* 0成功,负值失败 */
void i2c_del_driver(struct i2c_driver *driver); /* 无返回值 */
int i2c_add_adapter(struct i2c_adapter *adapter); /* 动态总线号,0成功,负值失败 */
int i2c_add_numbered_adapter(struct i2c_adapter *adap); /* 静态总线号,0成功,负值失败 */
void i2c_del_adapter(struct i2c_adapter *adap); /* 无返回值 */
i2c_add_driver是宏定义,等价于:
#define i2c_add_driver(driver) \
i2c_register_driver(THIS_MODULE, driver)
int i2c_transfer(struct i2c_adapter *adap,
struct i2c_msg *msgs,
int num);
参数说明:
| 参数 | 说明 |
|---|---|
adap |
I2C适配器,通过client->adapter获取 |
msgs |
I2C消息数组 |
num |
消息数量 |
| 返回值 | 负值失败,非负值为发送的消息数 |
读操作示例(构建2条消息):
struct i2c_msg msg[2];
struct i2c_client *client = dev->private_data;
/* msg[0]: 发送要读取的寄存器地址 */
msg[0].addr = client->addr;
msg[0].flags = 0; /* 写标志 */
msg[0].buf = ® /* 寄存器地址 */
msg[0].len = 1;
/* msg[1]: 读取寄存器数据 */
msg[1].addr = client->addr;
msg[1].flags = I2C_M_RD; /* 读标志 */
msg[1].buf = val; /* 数据缓冲区 */
msg[1].len = len; /* 数据长度 */
ret = i2c_transfer(client->adapter, msg, 2);
写操作示例(构建1条消息):
struct i2c_msg msg;
u8 b[256];
b[0] = reg; /* 寄存器首地址 */
memcpy(&b[1], buf, len); /* 拷贝写入数据 */
msg.addr = client->addr;
msg.flags = 0; /* 写标志 */
msg.buf = b;
msg.len = len + 1; /* 地址 + 数据 */
ret = i2c_transfer(client->adapter, &msg, 1);
/* 发送数据 */
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);
| 参数 | 说明 |
|---|---|
client |
I2C设备客户端 |
buf |
数据缓冲区 |
count |
数据字节数(< 64KB) |
| 返回值 | 负值失败,非负值为传输字节数 |
i2c_master_send/recv内部调用i2c_transfer,只适用于简单的单次读写,复杂场景建议直接使用i2c_transfer。
⚠️ 来源说明:本节 API 列表不属于《I.MX6U嵌入式Linux驱动开发指南》第六十一章内容,为扩展知识(原书仅提到
i2c_algorithm.smbus_xfer)。
/* 读取单个字节数据 */
s32 i2c_smbus_read_byte_data(struct i2c_client *client, u8 command);
/* 写入单个字节数据 */
s32 i2c_smbus_write_byte_data(struct i2c_client *client,
u8 command, u8 value);
/* 读取2字节数据 */
s32 i2c_smbus_read_word_data(struct i2c_client *client, u8 command);
/* 写入2字节数据 */
s32 i2c_smbus_write_word_data(struct i2c_client *client,
u8 command, u16 value);
/* 读取块数据 */
s32 i2c_smbus_read_block_data(struct i2c_client *client,
u8 command, u8 *values);
/* 读取I2C数据 */
s32 i2c_smbus_read_i2c_block_data(struct i2c_client *client,
u8 command, u8 length, u8 *values);
简单单字节读写 → i2c_smbus_read/write_byte_data
简单收发 → i2c_master_send/recv
多寄存器读写 → i2c_transfer(推荐,最灵活)
SMBus兼容设备 → SMBus系列API
/* ap3216creg.h - AP3216C寄存器地址 */
#ifndef AP3216C_H
#define AP3216C_H
#define AP3216C_SYSTEMCONG 0x00 /* 配置寄存器 */
#define AP3216C_INTSTATUS 0x01 /* 中断状态寄存器 */
#define AP3216C_INTCLEAR 0x02 /* 中断清除寄存器 */
#define AP3216C_IRDATALOW 0x0A /* IR数据低字节 */
#define AP3216C_IRDATAHIGH 0x0B /* IR数据高字节 */
#define AP3216C_ALSDATALOW 0x0C /* ALS数据低字节 */
#define AP3216C_ALSDATAHIGH 0x0D /* ALS数据高字节 */
#define AP3216C_PSDATALOW 0x0E /* PS数据低字节 */
#define AP3216C_PSDATAHIGH 0x0F /* PS数据高字节 */
#endif
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of_gpio.h>
#include <linux/semaphore.h>
#include <linux/timer.h>
#include <linux/i2c.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include "ap3216creg.h"
#define AP3216C_CNT 1
#define AP3216C_NAME "ap3216c"
/* 设备结构体 */
struct ap3216c_dev {
dev_t devid;
struct cdev cdev;
struct class *class;
struct device *device;
struct device_node *nd;
int major;
void *private_data; /* 保存i2c_client指针 */
unsigned short ir, als, ps; /* 三合一传感器数据 */
};
static struct ap3216c_dev ap3216cdev;
/*
* ap3216c_read_regs - 从AP3216C读取多个寄存器数据
* @dev: AP3216C设备
* @reg: 要读取的寄存器首地址
* @val: 读取到的数据
* @len: 要读取的数据长度
* 返回: 0成功,负值失败
*/
static int ap3216c_read_regs(struct ap3216c_dev *dev, u8 reg,
void *val, int len)
{
int ret;
struct i2c_msg msg[2];
struct i2c_client *client = (struct i2c_client *)dev->private_data;
/* msg[0]: 发送要读取的寄存器首地址 */
msg[0].addr = client->addr;
msg[0].flags = 0; /* 写标志 */
msg[0].buf = ®
msg[0].len = 1;
/* msg[1]: 读取数据 */
msg[1].addr = client->addr;
msg[1].flags = I2C_M_RD; /* 读标志 */
msg[1].buf = val;
msg[1].len = len;
ret = i2c_transfer(client->adapter, msg, 2);
if (ret == 2)
ret = 0;
else {
printk("i2c rd failed=%d reg=%06x len=%d\n", ret, reg, len);
ret = -EREMOTEIO;
}
return ret;
}
/*
* ap3216c_write_regs - 向AP3216C多个寄存器写入数据
* @dev: AP3216C设备
* @reg: 要写入的寄存器首地址
* @buf: 要写入的数据缓冲区
* @len: 要写入的数据长度
* 返回: 0成功,负值失败
*/
static s32 ap3216c_write_regs(struct ap3216c_dev *dev, u8 reg,
u8 *buf, u8 len)
{
u8 b[256];
struct i2c_msg msg;
struct i2c_client *client = (struct i2c_client *)dev->private_data;
b[0] = reg; /* 寄存器首地址 */
memcpy(&b[1], buf, len); /* 拷贝写入数据 */
msg.addr = client->addr;
msg.flags = 0; /* 写标志 */
msg.buf = b;
msg.len = len + 1; /* 地址 + 数据 */
return i2c_transfer(client->adapter, &msg, 1);
}
/*
* ap3216c_read_reg - 读取AP3216C单个寄存器值
*/
static unsigned char ap3216c_read_reg(struct ap3216c_dev *dev, u8 reg)
{
u8 data = 0;
ap3216c_read_regs(dev, reg, &data, 1);
return data;
#if 0
/* 也可以用 SMBus API 读取单个寄存器,效果等价 */
struct i2c_client *client = (struct i2c_client *)dev->private_data;
return i2c_smbus_read_byte_data(client, reg);
#endif
}
/*
* ap3216c_write_reg - 向AP3216C指定寄存器写入值
*/
static void ap3216c_write_reg(struct ap3216c_dev *dev, u8 reg, u8 data)
{
u8 buf = 0;
buf = data;
ap3216c_write_regs(dev, reg, &buf, 1);
}
/*
* ap3216c_readdata - 读取AP3216C的IR/ALS/PS原始数据
* 同时打开ALS和IR+PS时,两次读取间隔需大于112.5ms
*/
void ap3216c_readdata(struct ap3216c_dev *dev)
{
unsigned char i = 0;
unsigned char buf[6];
/* 循环读取6字节传感器数据 */
for (i = 0; i < 6; i++)
buf[i] = ap3216c_read_reg(dev, AP3216C_IRDATALOW + i);
if (buf[0] & 0x80) /* IR_OF位为1,数据无效 */
dev->ir = 0;
else
dev->ir = ((unsigned short)buf[1] << 2) | (buf[0] & 0x03);
dev->als = ((unsigned short)buf[3] << 8) | buf[2];
if (buf[4] & 0x40) /* PS数据无效标志 */
dev->ps = 0;
else
dev->ps = ((unsigned short)(buf[5] & 0x3F) << 4) |
(buf[4] & 0x0F);
}
/* 字符设备操作函数 */
static int ap3216c_open(struct inode *inode, struct file *filp)
{
filp->private_data = &ap3216cdev;
/* 初始化AP3216C: 软复位 */
ap3216c_write_reg(&ap3216cdev, AP3216C_SYSTEMCONG, 0x04);
mdelay(50); /* 复位至少需要10ms */
/* 启动ALS+IR+PS */
ap3216c_write_reg(&ap3216cdev, AP3216C_SYSTEMCONG, 0x03);
return 0;
}
static ssize_t ap3216c_read(struct file *filp, char __user *buf,
size_t cnt, loff_t *off)
{
short data[3];
long err = 0;
struct ap3216c_dev *dev = (struct ap3216c_dev *)filp->private_data;
ap3216c_readdata(dev);
data[0] = dev->ir;
data[1] = dev->als;
data[2] = dev->ps;
err = copy_to_user(buf, data, sizeof(data));
return 0;
}
static int ap3216c_release(struct inode *inode, struct file *filp)
{
return 0;
}
/* 文件操作结构体 */
static const struct file_operations ap3216c_ops = {
.owner = THIS_MODULE,
.open = ap3216c_open,
.read = ap3216c_read,
.release = ap3216c_release,
};
/* I2C设备匹配表 */
static const struct i2c_device_id ap3216c_id[] = {
{"alientek,ap3216c", 0},
{}
};
/* 设备树匹配表 */
static const struct of_device_id ap3216c_of_match[] = {
{ .compatible = "alientek,ap3216c" },
{ /* Sentinel */ }
};
/* I2C驱动结构体 */
static struct i2c_driver ap3216c_driver = {
.probe = ap3216c_probe,
.remove = ap3216c_remove,
.driver = {
.owner = THIS_MODULE,
.name = "ap3216c",
.of_match_table = ap3216c_of_match,
},
.id_table = ap3216c_id,
};
/*
* ap3216c_probe - 驱动与设备匹配成功后执行
* 完成字符设备注册和私有数据初始化
*/
static int ap3216c_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
/* 1. 构建设备号 */
if (ap3216cdev.major) {
ap3216cdev.devid = MKDEV(ap3216cdev.major, 0);
register_chrdev_region(ap3216cdev.devid, AP3216C_CNT,
AP3216C_NAME);
} else {
alloc_chrdev_region(&ap3216cdev.devid, 0, AP3216C_CNT,
AP3216C_NAME);
ap3216cdev.major = MAJOR(ap3216cdev.devid);
}
/* 2. 注册字符设备 */
cdev_init(&ap3216cdev.cdev, &ap3216c_ops);
cdev_add(&ap3216cdev.cdev, ap3216cdev.devid, AP3216C_CNT);
/* 3. 创建设备类 */
ap3216cdev.class = class_create(THIS_MODULE, AP3216C_NAME);
if (IS_ERR(ap3216cdev.class)) {
return PTR_ERR(ap3216cdev.class);
}
/* 4. 创建设备节点 */
ap3216cdev.device = device_create(ap3216cdev.class, NULL,
ap3216cdev.devid, NULL,
AP3216C_NAME);
if (IS_ERR(ap3216cdev.device)) {
return PTR_ERR(ap3216cdev.device);
}
/* 5. 保存i2c_client指针 */
ap3216cdev.private_data = client;
return 0;
}
/*
* ap3216c_remove - 移除I2C设备时执行
* 注销字符设备和设备节点
*/
static int ap3216c_remove(struct i2c_client *client)
{
cdev_del(&ap3216cdev.cdev);
unregister_chrdev_region(ap3216cdev.devid, AP3216C_CNT);
device_destroy(ap3216cdev.class, ap3216cdev.devid);
class_destroy(ap3216cdev.class);
return 0;
}
/* 驱动入口 */
static int __init ap3216c_init(void)
{
int ret = 0;
ret = i2c_add_driver(&ap3216c_driver);
return ret;
}
/* 驱动出口 */
static void __exit ap3216c_exit(void)
{
i2c_del_driver(&ap3216c_driver);
}
module_init(ap3216c_init);
module_exit(ap3216c_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("ALIENTEK");
probe函数执行流程:
ap3216c_probe()
├── register_chrdev_region() // 静态分配设备号
├── cdev_init() // 初始化cdev
├── cdev_add() // 注册字符设备
├── class_create() // 创建设备类
├── device_create() // 创建设备节点 /dev/ap3216c
└── private_data = client // 保存i2c_client指针
数据读取流程:
用户read() → ap3216c_read() → ap3216c_readdata()
└── 循环6次调用ap3216c_read_reg()
└── ap3216c_read_regs()
└── i2c_transfer(client->adapter, msg, 2)
└── i2c_imx_xfer() // SOC适配器传输函数
├── i2c_imx_start()
├── i2c_imx_write() // 写寄存器地址
├── i2c_imx_read() // 读数据
└── i2c_imx_stop()
module_i2c_driver宏:可替代手写module_init/exit,简化驱动注册:
module_i2c_driver(ap3216c_driver);
# 扫描I2C总线上的设备(显示所有响应的地址)
i2cdetect -y 0
# 输出示例:
# 0 1 2 3 4 5 6 7 8 9 a b c d e f
# 00: -- -- -- -- -- -- -- --
# 10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- 1e --
# 读取设备寄存器值
i2cget -y 0 0x1e 0x00
# 读取地址0x1e设备的0x00寄存器
# 写入设备寄存器
i2cset -y 0 0x1e 0x00 0x03
# 向地址0x1e设备的0x00寄存器写入0x03
# 转储设备所有寄存器
i2cdump -y 0 0x1e
参数说明:
| 参数 | 说明 |
|---|---|
-y |
跳过交互确认 |
0 |
I2C总线号(对应i2c-0) |
0x1e |
设备7位地址 |
0x00 |
寄存器地址 |
# 1. 编译驱动模块
make -j32
# 2. 编译测试APP
arm-linux-gnueabihf-gcc ap3216cApp.c -o ap3216cApp
# 3. 拷贝文件到开发板
scp ap3216c.ko ap3216cApp root@192.168.1.200:/lib/modules/4.1.15/
# 4. 开发板上加载驱动
depmod # 首次加载需执行
modprobe ap3216c.ko # 加载驱动模块
# 5. 运行测试
./ap3216cApp /dev/ap3216c
# 输出示例:ir = 285, als = 1024, ps = 512
# 6. 卸载驱动
rmmod ap3216c
| 问题 | 可能原因 | 解决方法 |
|---|---|---|
i2c_transfer失败 |
设备地址错误 | 用i2cdetect扫描确认地址 |
| 读取数据全0 | 设备未正确初始化 | 检查probe函数,确认设备树配置 |
| 读取数据全0xFF | SDA/SCL被拉低 | 检查上拉电阻和IO复用配置 |
| 模块加载失败 | compatible不匹配 | 检查设备树compatible与驱动匹配表 |
| 设备节点不存在 | 设备树status不正确 | 确认status = "okay" |
⚠️ 来源说明:本节不属于《I.MX6U嵌入式Linux驱动开发指南》内容,为扩展知识。
| 特性 | IMX6ULL | STM32F4 | RK3568 |
|---|---|---|---|
| I2C控制器 | I2C1/I2C2/I2C3 | I2C1~I2C4 | I2C0~I2C4 |
| 适配器驱动 | i2c-imx.c |
i2c-stm32.c |
i2c-rk3x.c |
| 匹配属性 | "fsl,imx6ul-i2c" |
"st,stm32f4-i2c" |
"rockchip,rk3x-i2c" |
| 最大速率 | 400KHz | 400KHz | 400KHz |
| DMA支持 | 支持 | 支持 | 支持 |
| 寄存器基地址 | 0x021A0000 | 多组 | 多组 |
| 设备树写法 | 相同 | 相同 | 相同 |
Linux I2C框架: STM32 HAL框架: 裸机开发:
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ 用户空间 │ │ 应用层 │ │ 应用层 │
│ read/write │ │ HAL_I2C_* │ │ 直接寄存器 │
├──────────────┤ ├──────────────┤ ├──────────────┤
│ I2C设备驱动 │ │ HAL层 │ │ 寄存器操作 │
│ i2c_driver │ │ I2C Handle │ │ 直接配置 │
├──────────────┤ ├──────────────┤ ├──────────────┤
│ I2C核心层 │ │ LL层/注册器 │ │ 硬件 │
│ i2c-core │ │ 直接操作 │ │ │
├──────────────┤ ├──────────────┤ └──────────────┘
│ I2C适配器 │ │ I2C外设 │
│ i2c_adapter │ │ STM32 I2C │
├──────────────┤ └──────────────┘
│ I2C控制器 │
│ SOC硬件 │
└──────────────┘
核心差异:
⚠️ 来源说明:本节不属于《I.MX6U嵌入式Linux驱动开发指南》内容,为扩展知识。
考察点:I2C架构理解
参考答案: Linux I2C驱动分为三层:
i2c_driver):针对具体I2C设备编写,实现设备的读写逻辑,注册字符设备提供用户空间接口i2c-core):提供与硬件无关的通用API,管理设备与驱动的匹配,维护I2C总线i2c_bus_typei2c_adapter):由SoC厂商编写,控制I2C控制器硬件,提供master_xfer传输函数这种分层实现了驱动与设备的分离,与platform总线思想一致。
i2c_transfer函数的参数msgs如何构建?读操作和写操作有什么区别?考察点:I2C数据传输机制
参考答案:
i2c_transfer参数msgs是i2c_msg数组,每条消息包含从机地址、标志位、缓冲区和长度。
i2c_msg,flags=0,buf包含寄存器地址+写入数据i2c_msg,msg[0].flags=0(写寄存器地址),msg[1].flags=I2C_M_RD(读数据)读操作需要两条消息是因为I2C协议要求先写寄存器地址再读数据,且每次方向切换需要重新发送起始条件。
考察点:设备驱动匹配机制
参考答案: 三种匹配方式,按优先级:
compatible属性与of_device_id表中的compatible字段i2c_device_id的name字段匹配函数i2c_device_match按上述顺序依次尝试,一旦匹配成功即停止。
考察点:驱动模型理解
参考答案: I2C适配器(控制器)是SOC的片上外设,其寄存器地址、中断号、时钟等资源都通过设备树描述,天然符合platform总线的设备/驱动分离模型。
NXP的i2c-imx.c就是一个标准的platform驱动,通过of_device_id匹配设备树节点,在probe中获取IO资源、初始化i2c_adapter、设置i2c_algorithm,最后调用i2c_add_numbered_adapter注册适配器。
这体现了Linux驱动的统一思想:所有SOC外设驱动都用platform模型,I2C/SPI等总线只是其上层抽象。
考察点:调试能力
参考答案: 使用内核自带的I2C调试工具:
i2cdetect -y <bus>:扫描总线上的设备地址i2cget -y <bus> <addr> <reg>:读取指定寄存器i2cset -y <bus> <addr> <reg> <val>:写入指定寄存器i2cdump -y <bus> <addr>:转储所有寄存器这些工具直接通过/dev/i2c-X设备节点操作I2C控制器,不需要编写驱动。前提是I2C适配器驱动已加载(SoC启动时自动加载)。通过读写关键寄存器可以验证硬件连接和基本通信是否正常。
代码来源: 正点原子 I.MX6U驱动开发指南 第六十一章, 例程 21_iic 内容来源: 《I.MX6U嵌入式Linux驱动开发指南》第六十一章 Linux I2C驱动实验 最后更新: 2026-09-17