必须先用i2cdetect -l查总线编号、再用i2cdetect -y 1扫描设备地址;树莓派默认为i2c-1(GPIO2/SDA、GPIO3/SCL),未启用需raspi-config开启,地址显示UU表示被内核驱动占用。

确认 I2C 总线编号和设备地址
不查清楚 /dev/i2c-X 编号和从机地址,后续所有操作都会失败。树莓派默认启用的通常是 i2c-1(对应物理引脚 GPIO2/3),但不同型号或自定义配置可能不同。先运行:
i2cdetect -l
列出所有可用适配器;再用:
i2cdetect -y 1
扫描 i2c-1 上的从机地址(把 1 换成你实际的编号)。输出中显示的十六进制数(如 40)就是你要用的 addr。
常见错误现象:
立即学习“C++免费学习笔记(深入)”;
-
i2cdetect显示全--:I2C 驱动未启用,需raspi-config → Interfacing Options → P5 I2C → Yes并重启 - 显示地址但读写失败:硬件接线松动、上拉电阻缺失(通常需 4.7kΩ 接 3.3V)、从机供电异常
- 地址显示为
UU:该地址已被内核驱动占用(如rtc-ds1307),不能用i2c-dev再访问
打开设备节点并设置从机地址
必须用 O_RDWR 打开 /dev/i2c-X,且调用 ioctl(fd, I2C_SLAVE, addr) 绑定目标从机。这一步不是“连接”,而是告诉内核“接下来所有读写都发给这个地址的设备”。
关键点:
-
open()返回负值时,检查权限:普通用户需加入i2c用户组(sudo usermod -aG i2c $USER),或临时用sudo -
ioctl(..., I2C_SLAVE, ...)失败常见原因是地址格式错——传入的是纯数值(如0x40),不是左移一位后的 8 位地址(内核会自己处理) - 一个
fd只能绑定一个从机地址;换设备必须重新ioctl
示例片段:
int fd = open("/dev/i2c-1", O_RDWR);
if (fd < 0) { /* perror("open") */ }
if (ioctl(fd, I2C_SLAVE, 0x40) < 0) { /* perror("ioctl I2C_SLAVE") */ }用 i2c_smbus_read_word_data 等函数读写寄存器
Linux 内核提供的 <i2c></i2c> 封装了 SMBus 协议常用操作,比裸 read()/write() 更安全、语义更清晰。它隐含处理了起始信号、地址帧、寄存器地址字节等细节。
使用场景与参数差异:
-
i2c_smbus_read_byte_data(fd, reg):读取从机中单字节寄存器reg的值(返回__s32,需检查是否) -
i2c_smbus_read_word_data(fd, reg):读取 2 字节(小端序),常用于 ADC 值、温度原始数据 -
i2c_smbus_write_byte_data(fd, reg, value):向寄存器reg写入单字节value - 不要混用
i2c_smbus_*和裸write():前者走 ioctl 路径,后者是底层字节流,协议层不兼容
性能影响:这些函数每次调用都是一次完整 SMBus transaction(含 start/stop),频繁读写小寄存器会有明显延迟;批量操作建议用 i2c_smbus_read_i2c_block_data。
错误处理不能只靠返回值 -1
i2c_smbus_* 函数出错时返回负值,但具体原因得看 errno。常见错误码含义:
-
EIO:SMBus transaction failed —— 物理层问题(线没接好、从机死机、地址错) -
ENXIO:No such device ——ioctl(I2C_SLAVE)后首次通信就失败,大概率从机未响应 ACK -
EAGAIN:Resource temporarily unavailable —— 总线被其他进程占用(如i2cdetect正在跑),需重试 -
EINVAL:Invalid argument ——reg超出从机支持范围,或fd未正确绑定
容易被忽略的地方:SMBus 协议要求某些操作(如 block read)必须由从机主动提供长度字段,而很多廉价传感器只实现基本 byte/word 操作。强行调用不支持的函数会导致超时而非立即报错,程序卡住几秒才返回 EIO。


















