4. Arduino框架下I2C编程:Wire库详解
好,咱们进入实战环节。这一章我带你彻底搞懂Arduino框架下的Wire库。说白了,Wire库就是ESP32跟I2C设备对话的翻译官。你调用它,它帮你把数据发到SDA/SCL线上。
我个人习惯把Wire库的API分成三组:初始化、主机发送、主机接收。下面一个一个拆开讲。
4.1 begin() 与从机地址配置
先看最基础的 Wire.begin()。这个函数干两件事:初始化I2C外设,把ESP32设成主机模式。
#include <Wire.h>
void setup() {
Wire.begin(); // 默认引脚:SDA=21, SCL=22
// Wire.begin(16, 17); // 你也可以指定其他引脚
}
嗯,这里要注意。ESP32的I2C引脚是可以任意映射的。我在项目中遇到过客户把SDA和SCL接反了,结果调了两天没反应。你想想看,硬件连线错了,软件再怎么调也没用。
如果你想让ESP32作为从机,那就传一个地址进去:
Wire.begin(0x42); // ESP32作为从机,地址0x42
不过咱们课程主要讲主机模式,从机模式后面章节会单独说。
Wire.begin() 不加参数。如果后续要改引脚,再传参。这样代码可读性更好。
4.2 beginTransmission() 与 endTransmission()
这是主机发送数据的核心组合。beginTransmission() 告诉Wire库:我要开始跟某个从机说话了。参数就是从机地址。
Wire.beginTransmission(0x3C); // 开始跟地址0x3C的设备通信
Wire.write(0x00); // 发送寄存器地址
Wire.write(0x42); // 发送数据
Wire.endTransmission(); // 结束传输
endTransmission() 会返回一个状态码,告诉你这次传输是否成功。这个返回值很多人忽略,但我建议你一定要检查。
| 返回值 | 含义 |
|---|---|
| 0 | 成功 |
| 1 | 数据过长,超出缓冲区 |
| 2 | 从机地址发送后收到NACK |
| 3 | 数据发送后收到NACK |
| 4 | 其他错误 |
4.3 requestFrom() 从机读取数据
读数据跟写数据不太一样。你需要先告诉从机你要读哪个寄存器,然后再发起读取请求。
// 第一步:写寄存器地址
Wire.beginTransmission(0x3C);
Wire.write(0x00); // 要读取的寄存器地址
Wire.endTransmission(false); // false表示不发送停止条件
// 第二步:读取数据
Wire.requestFrom(0x3C, 1); // 从0x3C读取1个字节
if (Wire.available()) {
byte data = Wire.read();
}
这里有个细节:endTransmission(false) 中的 false 参数。它告诉总线不要发送停止条件,这样紧接着的 requestFrom() 可以发送重复起始条件。很多传感器要求这种操作顺序。
我刚开始学的时候,总忘记加这个 false,结果读出来的数据全是0xFF。后来才明白,停止条件之后总线就释放了,再读就得重新寻址。
4.4 从机地址配置详解
从机地址是I2C通信的门牌号。地址错了,数据就到不了。地址对了,一切好说。
I2C地址通常是7位,范围0x08到0x77。0x00到0x07是保留地址,0x78到0x7F也是保留的。你想想看,如果选了保留地址,总线上的设备会怎么反应?
常见的地址配置方式有两种:
- 固定地址:芯片出厂就定死了,比如某些温度传感器固定0x48
- 可配置地址:通过引脚电平选择,比如AD0引脚接GND是0x3C,接VCC是0x3D
地址检查三步法:
- 看数据手册的"Slave Address"章节
- 注意7位地址和8位地址的区别(左移一位)
- 用I2C扫描程序验证
说到扫描程序,我每次接新传感器都会先跑一遍。下面这个代码我用了好几年:
#include <Wire.h>
void setup() {
Serial.begin(115200);
Wire.begin();
Serial.println("开始扫描I2C设备...");
for (byte addr = 1; addr < 127; addr++) {
Wire.beginTransmission(addr);
byte error = Wire.endTransmission();
if (error == 0) {
Serial.print("找到设备,地址: 0x");
Serial.println(addr, HEX);
}
}
Serial.println("扫描完成");
}
void loop() {
// 空循环
}
这个程序会遍历所有可能的I2C地址。如果某个地址有设备应答,就打印出来。我在项目中用这个程序排查过很多次硬件问题——有时候是虚焊,有时候是地址配置错了,一扫描就现原形。
4.5 知识体系总览
下面这张图把Wire库的核心API和它们之间的关系画出来了。你看一眼就能明白整个调用流程。
从这张图能看出来,Wire库的API其实就两条主线。左边是写操作,右边是读操作。中间是初始化。你只要记住这个流程,写任何I2C驱动都不会乱。
4.6 常见问题与避坑指南
- 地址左移问题:有些数据手册给的是8位地址(比如0x78),但Wire库要7位(0x3C)。记得右移一位。
- 上拉电阻:ESP32内部有弱上拉,但长线或高速通信时建议外接4.7kΩ上拉电阻。我遇到过不加电阻,通信距离超过10cm就出错的情况。
- 时钟延展:有些慢速传感器会拉低SCL,ESP32默认支持时钟延展。但如果遇到通信超时,可以试试降低I2C时钟频率。
好了,这一章的内容就这些。Wire库的API不多,但每个参数都有讲究。你写代码的时候多想想:地址对不对?停止条件要不要发?返回值检查了没有?养成这些习惯,I2C通信基本不会出大问题。