结论:应在用户空间用C++调用Linux标准I2C API(open/ioctl/read/write)操作/dev/i2c-1,而非编写内核驱动;内核已提供i2c-bcm2835等驱动,用户只需通过i2c-dev接口与之通信。

直接说结论:别在用户空间用 C++ 写“驱动程序”去控制 I2C 外设——你真正需要的,是用标准 Linux 用户空间 API(open + ioctl + read/write)安全读写 /dev/i2c-1,而不是写内核模块。
所谓“编写驱动程序”,在绝大多数树莓派 I2C 应用场景里是个误导性说法。真正的内核驱动(如 i2c-bcm2835、at24、bme280)早已由内核提供并加载;你要做的,是和已有的驱动对话,不是重造轮子。
为什么不能用 C++ 直接“写驱动”控制 I2C?
- Linux 内核驱动必须用 C 编写、编译为
.ko模块、运行在内核态,C++ 的 ABI、异常、RTTI、构造/析构语义在内核中不被支持; - 树莓派默认启用的
i2c-dev子系统,就是专为用户空间访问设计的桥梁——它把 I2C 总线抽象成字符设备文件(如/dev/i2c-1),你只需打开它、发ioctl命令、调用read/write即可; - 写内核驱动要处理内存映射、中断、并发、电源管理等复杂问题,而 95% 的传感器(BME280、BH1750、AP3216C、MPU6050)已有现成内核驱动或可通过
devicetree自动绑定; - 错误做法:用 C++ 尝试
mmapGPIO 寄存器或硬编码时序模拟 I2C —— 这既不可靠,又违反 Linux 设计哲学,还容易锁死总线。
正确做法:C++ 调用 Linux I2C 用户空间接口
这是你在树莓派上最常用、最稳定、最易调试的方式:
- 确保 I2C 已启用:
sudo raspi-config→ Interface Options → I2C → Yes,然后重启; - 加入
i2c用户组避免每次加sudo:sudo usermod -a -G i2c $USER,重新登录生效; - 在 C++ 代码中:
- 包含
<linux></linux>和<sys></sys>; - 用
open("/dev/i2c-1", O_RDWR)打开总线; - 用
ioctl(fd, I2C_SLAVE, 0x68)切换目标设备地址(例如 MPU6050); - 之后可用
read()/write()进行寄存器级通信,或用I2C_RDWR发送复合消息(推荐用于多字节读写);
- 包含
示例片段(简化版,仅示意流程):
int fd = open("/dev/i2c-1", O_RDWR);
if (fd ioctl(fd, I2C_SLAVE, 0x68); // 设置从机地址<p>uint8_t reg = 0x75;
write(fd, ®, 1);
uint8_t buf[2];
read(fd, buf, 2); // 读取 WHO_AM_I 寄存器</p>
注意:该方式本质是调用内核的 i2c_master_send/i2c_master_recv,底层由 i2c-bcm2835 驱动执行硬件操作,完全可靠。
什么时候才真需要写内核 I2C 驱动?
只有同时满足以下全部条件时,才考虑写内核模块:
- 目标设备没有现成内核驱动(比如某款冷门定制传感器,且厂商只提供裸寄存器手册);
- 你需要硬实时响应(如微秒级中断处理),而用户空间延迟无法接受;
- 该设备需被其他内核子系统(如 input、hwmon、leds)统一管理,必须暴露标准 sysfs 接口;
- 你能接受调试过程可能导致 kernel panic、需反复编译内核头文件、严格遵循 GPL 协议发布代码。
即便如此,也建议先尝试用 devicetree + simple-mfd 或 regmap-i2c 框架快速适配,而非从零手写 probe 函数。
真正容易被忽略的一点:I2C 地址冲突和上拉电阻状态,比代码逻辑更容易导致通信失败。i2cdetect -y 1 返回空或全 UU,大概率是硬件连接错误(VCC/GND 反接、SDA/SCL 接反)、设备未供电、或多个设备共用地址没做隔离。别急着改 C++ 代码,先拿万用表量电压、用逻辑分析仪看波形——这才是树莓派 I2C 开发里最耗时也最关键的环节。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











