在 Linux 下使用 I2C(Inter-Integrated Circuit)进行用户态编程 — 教程与实战
概述
I2C(Inter-Integrated Circuit)是一种常见的串行总线协议,广泛用于传感器、EEPROM、ADC/DAC 等外设。Linux 提供了用户态访问 I2C 的接口(/dev/i2c-X)。本教程介绍关键结构体、系统调用、示例代码及调试技巧,便于开发者快速上手并排查常见问题。
基本概念与接口
- 设备节点:Linux 下的 I2C 总线设备通常以
/dev/i2c-X命名(X 为总线编号)。 - 访问方式:通过
open/read/write/ioctl等系统调用在用户态与从设备交互。 - 主从模型:通常 Linux 作为主控制器(master),从设备(slave)由 7 位或 10 位地址标识。
关键结构体与标志位
struct i2c_msg(描述单次 I2C 事务):
struct i2c_msg {__u16 addr; // 从设备地址__u16 flags; // 操作标志__u16 len; // 数据长度__u8 *buf; // 数据缓冲区指针
};
常见 flags:
I2C_M_RD:表示读操作(否则为写操作)。I2C_M_TEN:使用 10 位地址。I2C_M_STOP:在消息结束生成 STOP(取决于驱动/控制器)。
用户态 I2C 编程流程(打开、设置地址、读写)
步骤概览:
- 打开设备节点:
open("/dev/i2c-X", O_RDWR) - 设置目标从设备地址:
ioctl(file, I2C_SLAVE, addr) - 读写数据:
write()/read()或组合多个i2c_msg使用I2C_RDWR
示例:打开设备并设置地址(包含基本错误处理)
int file = open("/dev/i2c-1", O_RDWR);
if (file < 0) {perror("Failed to open the i2c bus");// 处理打开失败(检查驱动、权限、设备节点)
}
int addr = 0x50;
if (ioctl(file, I2C_SLAVE, addr) < 0) {perror("Failed to acquire bus access and/or talk to slave");// 可能原因:设备不存在、驱动不支持该 ioctl 或权限问题
}
简单读写示例
写入单字节:
unsigned char out = 0xA0;
if (write(file, &out, 1) != 1) {perror("Failed to write to the i2c bus");
}
读取单字节:
unsigned char in;
if (read(file, &in, 1) != 1) {perror("Failed to read from the i2c bus");
} else {printf("Data read: 0x%02x\n", in);
}
使用 I2C_RDWR 进行复杂读写(示例与说明)
场景:先写寄存器地址,再读寄存器数据(常用于寄存器映射设备)。
写入多个字节(寄存器地址 + 数据):
unsigned char outbuf[3] = { 0x10, 0x01, 0x02 }; // reg=0x10, data=0x01,0x02
struct i2c_msg messages[1];
messages[0].addr = addr;
messages[0].flags = 0; // 写
messages[0].len = 3;
messages[0].buf = outbuf;
struct i2c_rdwr_ioctl_data ioctl_data = { messages, 1 };
if (ioctl(file, I2C_RDWR, &ioctl_data) < 0) {perror("Failed to write to the i2c bus");
}
读取寄存器(先写寄存器地址,随后读数据):
unsigned char reg = 0x10;
unsigned char inbuf[1];
struct i2c_msg messages[2];
messages[0].addr = addr;
messages[0].flags = 0; // 写寄存器地址
messages[0].len = 1;
messages[0].buf = ®
messages[1].addr = addr;
messages[1].flags = I2C_M_RD; // 读数据
messages[1].len = 1;
messages[1].buf = inbuf;
struct i2c_rdwr_ioctl_data ioctl_data2 = { messages, 2 };
if (ioctl(file, I2C_RDWR, &ioctl_data2) < 0) {perror("Failed to read from the i2c bus");
} else {printf("Register 0x10 read: 0x%02x\n", inbuf[0]);
}
完整示例程序(精简版,带错误提示)
#include <stdio.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <linux/i2c-dev.h>
#include <unistd.h>int main() {int file;int addr = 0x50;file = open("/dev/i2c-1", O_RDWR);if (file < 0) { perror("open"); return 1; }if (ioctl(file, I2C_SLAVE, addr) < 0) { perror("ioctl I2C_SLAVE"); close(file); return 1; }// ...读写示例(参考上文)...close(file);return 0;
}
i2c-tools — 常用命令与示例
i2cdetect:扫描 I2C 总线(注意:可能会影响设备)i2cdetect -y 1i2cget:读取寄存器i2cget -y 1 0x50 0x00i2cset:写入寄存器i2cset -y 1 0x50 0x00 0xFFi2cdump:以十六进制显示所有寄存器i2cdump -y 1 0x50
常见问题与排查建议
- 权限问题:
/dev/i2c-X通常需要 root 权限或将用户加入i2c组。 - 设备不存在:确认内核驱动已加载并且硬件连线正确(SDA、SCL、上拉电阻)。
- 总线冲突:使用
i2cdetect时要注意对总线的潜在影响;在生产环境避免盲扫。 - 超时与 NACK:设备地址错误或设备忙会导致 NACK,检查设备数据手册并尝试复位/重试。
扩展阅读与参考
linux/i2c.h、linux/i2c-dev.h源码注释- i2c-tools 官方仓库(包含
i2cdetect/i2cget/i2cset等实现) - 设备数据手册(寄存器与时序要求)
