引言
你是否曾经连接了一个新的I2C传感器或显示器到Arduino,然后疑惑:“这个设备到底用什么地址?”你不是一个人!I2C协议是一种强大的通信方式,仅用两根线就能与多个设备通信,但每个设备都有一个唯一的地址——有时这个地址在数据手册中并不明显。这时就需要I2C扫描仪登场了。它是一个简单的程序,会探测所有可能的地址,并报告哪些地址有响应。
在本教程中,我们将使用Arduino Uno和SSD1306 OLED显示屏(它本身也是总线上的一个已知设备)来构建一个I2C扫描仪。我们将在Velxio的浏览器内模拟器中运行所有内容,因此你无需任何硬件即可跟随操作。最后,你将了解I2C总线的工作原理、如何连接设备,以及如何编写扫描它们的代码。
让我们开始吧!
电路讲解

这个电路非常简单。我们只需要两个组件:
- Arduino Uno – 操作的大脑。
- SSD1306 OLED显示屏 – 一个128x64像素的单色显示屏,使用I2C进行通信。其默认地址为
0x3C。
接线细节
使用四根线将OLED连接到Arduino:
| Arduino引脚 | OLED引脚 | 线色 |
|---|---|---|
| A4 (SDA) | DATA | 蓝色 |
| A5 (SCL) | CLK | 橙色 |
| GND | GND | 黑色 |
| 5V | VIN | 红色 |
- SDA (A4) 是数据线。它负责在主设备(Arduino)和从设备(OLED)之间传输实际数据。
- SCL (A5) 是时钟线。它同步数据传输。
- GND 和 5V 为OLED供电。
注意: 在Arduino Uno上,A4和A5是专用的I2C引脚。其他开发板可能使用不同的引脚(例如,ESP32上的SDA/SCL)。
在Velxio模拟器中,你可以从组件选择器中添加OLED,并通过点击引脚进行接线。画布工具栏可以帮助你缩放和平移,使连接更容易。

代码详解

现在让我们看看代码。在Velxio中打开代码编辑器(你会看到带有语法高亮的Monaco编辑器)。该程序是用Arduino C++编写的,并使用内置的Wire库。
包含库文件
#include <Wire.h>
Wire库处理所有底层的I2C通信。它已包含在Arduino IDE中,因此无需额外安装。
设置函数
void setup() {
Wire.begin();
Serial.begin(9600);
Serial.println("===========================");
Serial.println(" I2C总线扫描仪 (TWI)");
Serial.println("===========================");
Serial.println("正在扫描...");
Serial.println();
int devicesFound = 0;
for (byte addr = 1; addr < 127; addr++) {
Wire.beginTransmission(addr);
byte error = Wire.endTransmission();
if (error == 0) {
Serial.print(" 在0x");
if (addr < 16) Serial.print("0");
Serial.print(addr, HEX);
// 识别已知地址
switch (addr) {
case 0x27: Serial.print(" (PCF8574 LCD背板)"); break;
case 0x3C: Serial.print(" (SSD1306 OLED)"); break;
case 0x48: Serial.print(" (温度传感器)"); break;
case 0x50: Serial.print(" (EEPROM)"); break;
case 0x68: Serial.print(" (DS1307 RTC)"); break;
case 0x76: Serial.print(" (BME280传感器)"); break;
case 0x77: Serial.print(" (BMP180/BMP280)"); break;
}
Serial.println();
devicesFound++;
}
}
Serial.println();
Serial.print("扫描完成。共发现 ");
Serial.print(devicesFound);
Serial.println(" 个设备。");
if (devicesFound == 0) {
Serial.println("未找到I2C设备。请检查连接。");
}
}
让我们分解一下:
Wire.begin()– 将Arduino初始化为I2C主设备。Serial.begin(9600)– 以9600波特率启动串口通信,以便我们可以在串口监视器中看到结果。- 循环 遍历地址1到126(0x01到0x7E)。地址0保留用于通用呼叫,地址127保留用于特殊用途。
Wire.beginTransmission(addr)– 开始向给定地址发送数据。Wire.endTransmission()– 结束发送并返回一个错误代码。如果错误为0,表示设备已确认(ACK)——我们找到了一个设备!- 打印地址 – 我们以十六进制格式打印,并添加前导零以提高可读性。
- Switch语句 – 通过已知地址识别常见设备。这是可选的,但很有帮助。
- 计数设备 – 每收到一个ACK,我们就递增
devicesFound。 - 最终报告 – 打印找到的设备总数。
循环函数
void loop() {
// 每10秒重新扫描一次
delay(10000);
Serial.println("\n正在重新扫描...");
setup();
}
loop()函数只是等待10秒,然后再次调用setup()进行重新扫描。这在系统运行时添加或移除设备时非常有用。
学生关键概念
什么是I2C?
I2C(Inter-Integrated Circuit),也称为TWI(Two-Wire Interface),是一种同步串行通信协议。它使用两条双向开漏线:SDA(数据)和SCL(时钟)。总线上的每个设备都有一个唯一的7位地址(尽管也存在10位地址)。主设备(Arduino)发起通信并生成时钟信号。
扫描仪的工作原理
扫描仪发送一个起始条件,后跟地址字节(7位地址 + 读/写位)。如果存在具有该地址的设备,它会通过在第9个时钟脉冲期间将SDA拉低来响应ACK(确认)。如果没有设备响应,则会收到NACK(未确认),并且endTransmission()返回一个非零错误代码。
为什么要使用扫描仪?
- 查找未知地址 – 许多I2C设备具有可配置的地址(例如,通过焊接焊盘)。扫描仪会告诉你实际地址。
- 调试接线 – 如果设备没有响应,扫描仪可以确认它是否正确连接。
- 检查冲突 – 两个具有相同地址的设备会导致通信错误。扫描仪可以帮助识别重复地址。
常见陷阱和调试技巧
- 接线错误 – 仔细检查SDA和SCL是否正确连接。交换它们将无法工作。
- 缺少上拉电阻 – I2C线路需要上拉电阻(通常为4.7kΩ)连接到VCC。许多扩展板已包含它们,但如果你从头开始构建,请不要忘记。
- 地址冲突 – 如果两个设备共享同一个地址,两者都无法可靠工作。使用扫描仪检测冲突。
- 波特率不匹配 – 扫描仪使用默认的100 kHz(标准模式)。某些设备可能需要更慢的速度。你可以使用
Wire.setClock(400000)将其更改为快速模式。 - 串口监视器不显示输出 – 确保串口监视器中的波特率与
9600匹配。
在Velxio中,你可以从编辑器工具栏打开串口监视器。如果没有看到输出,请检查代码是否编译成功(在控制台中查找错误)。

建议的扩展功能
一旦基本扫描仪正常工作,可以尝试以下增强功能:
- 在OLED上显示结果 – 不打印到串口,而是在SSD1306显示屏上显示地址列表。你需要
Adafruit_SSD1306和Adafruit_GFX库。 - 添加按钮触发扫描 – 将一个按钮连接到Arduino引脚,仅在按下时扫描。
- 扫描特定设备 – 修改代码,仅查找特定地址范围(例如,PCF8574的0x20-0x2F)。
- 使用LCD – 用16x2 I2C LCD(地址0x27或0x3F)替换OLED。
- 记录到SD卡 – 如果你有SD卡模块,将扫描结果记录到SD卡以供后续分析。
要在Velxio中添加库,请使用库管理器。

结论
你已经构建了一个通用的I2C扫描仪,它适用于任何兼容Arduino的开发板。这个工具对于调试和探索新的I2C模块非常宝贵。最棒的是?你现在就可以在浏览器中通过Velxio运行它,无需任何硬件。
准备好亲自尝试了吗?打开实时示例并开始扫描:
祝你编程愉快!