Linux 内核提供了 i2c-dev 驱动,允许用户空间程序通过 /dev/i2c-X 字符设备节点直接访问 I2C 总线上的从设备。这对于简单的传感器读取、EEPROM 读写或设备调试非常方便,无需编写专用的内核驱动。
1. 确认环境
首先,确保内核已加载 i2c-dev 模块,并且可以看到对应的设备节点。
bash
# 查看 I2C 设备节点
ls /dev/i2c*
# 输出示例: /dev/i2c-0 /dev/i2c-1 /dev/i2c-2 ...1
2
3
2
3
如果看不到节点,可能需要手动加载模块:
bash
modprobe i2c-dev1
2. 编程步骤
在用户空间操作 I2C 设备主要遵循以下步骤:
- 打开设备:
open对应的 I2C 总线节点(如/dev/i2c-1)。 - 设置从机地址:使用
ioctl的I2C_SLAVE或I2C_SLAVE_FORCE命令。 - 数据传输:
- 简单读写:使用
read/write系统调用。 - SMBus 协议:使用
i2c_smbus_*系列函数(推荐,兼容性更好)。 - 原始 I2C 消息:使用
ioctl的I2C_RDWR命令(最灵活,支持混合读写)。
- 简单读写:使用
- 关闭设备:
close。
3. 示例代码:读取寄存器
以下是一个简单的 C 语言示例,演示如何读取一个 I2C 设备的寄存器。
3.1 使用 read/write 接口
这种方式适用于简单的“写地址-读数据”模式,但不如 SMBus 接口标准。
c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <linux/i2c-dev.h>
#define I2C_DEV_PATH "/dev/i2c-1"
#define SLAVE_ADDR 0x50 // 示例:EEPROM 地址
int main() {
int fd;
char buf[10];
// 1. 打开 I2C 总线
if ((fd = open(I2C_DEV_PATH, O_RDWR)) < 0) {
perror("Failed to open i2c bus");
exit(1);
}
// 2. 设置从机地址
if (ioctl(fd, I2C_SLAVE, SLAVE_ADDR) < 0) {
perror("Failed to set slave address");
close(fd);
exit(1);
}
// 3. 写寄存器地址 (例如读取 0x10 地址的数据)
buf[0] = 0x10;
if (write(fd, buf, 1) != 1) {
perror("Failed to write register address");
close(fd);
exit(1);
}
// 4. 读取数据
if (read(fd, buf, 1) != 1) {
perror("Failed to read data");
close(fd);
exit(1);
}
printf("Read data: 0x%02x\n", (unsigned char)buf[0]);
close(fd);
return 0;
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
3.2 使用 ioctl I2C_RDWR (推荐)
I2C_RDWR 允许在一个系统调用中执行多次传输(例如:先写寄存器地址,紧接着读取数据,中间不释放总线),这对于多主设备环境或对时序敏感的设备更安全。
c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <linux/i2c.h>
#include <linux/i2c-dev.h>
#define I2C_DEV_PATH "/dev/i2c-1"
#define SLAVE_ADDR 0x50
int main() {
int fd;
struct i2c_rdwr_ioctl_data packets;
struct i2c_msg messages[2];
unsigned char reg_addr = 0x10;
unsigned char data;
fd = open(I2C_DEV_PATH, O_RDWR);
if (fd < 0) {
perror("Open");
return -1;
}
// 构造消息 1: 发送寄存器地址
messages[0].addr = SLAVE_ADDR;
messages[0].flags = 0; // 写
messages[0].len = 1;
messages[0].buf = ®_addr;
// 构造消息 2: 读取数据
messages[1].addr = SLAVE_ADDR;
messages[1].flags = I2C_M_RD; // 读
messages[1].len = 1;
messages[1].buf = &data;
// 构造 ioctl 数据包
packets.msgs = messages;
packets.nmsgs = 2;
// 发送
if (ioctl(fd, I2C_RDWR, &packets) < 0) {
perror("I2C_RDWR");
close(fd);
return -1;
}
printf("Read 0x%02x from reg 0x%02x\n", data, reg_addr);
close(fd);
return 0;
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
4. 常用工具
除了编写 C 代码,还可以使用 i2c-tools 软件包中的命令行工具进行快速调试:
i2cdetect -y <bus>:扫描总线上的设备。i2cget -y <bus> <chip-addr> <data-addr>:读取寄存器。i2cset -y <bus> <chip-addr> <data-addr> <value>:写入寄存器。i2cdump -y <bus> <chip-addr>:导出所有寄存器值。
详见 I2C 调试工具。
5. 注意事项
- 地址格式:Linux I2C 地址通常使用 7 位地址(不包含读写位)。例如,数据手册上写读地址
0xA1、写地址0xA0,则 Linux 下地址为0x50(0xA0 >> 1)。 - 独占访问:如果内核中已经有特定的驱动占用了该设备(probe 成功),
i2c-dev可能会被禁止访问,或者I2C_SLAVE设置会失败(提示 Device or resource busy)。此时可以使用I2C_SLAVE_FORCE强制访问,但需自行承担风险。 - 字节序:注意多字节数据的字节序(大小端)问题,通常 I2C 设备是 Big-Endian 或 Little-Endian,需要查阅手册。