Skip to content

Arduino - I2C 总线通信

I2C(Inter-Integrated Circuit)协议是一种流行的串行通信总线,用于将微控制器连接到传感器、存储模块和其他外设。它是一个两线制系统,非常适合需要连接多个设备而无需使用大量 GPIO 引脚的项目。

I2C 通信涉及两种角色:

  • 主设备(Master):发起通信、生成时钟信号并寻址从设备的设备。Arduino 通常充当主设备。
  • 从设备(Slave):监听其地址并响应主设备请求的设备。例如传感器、显示器和 I/O 扩展器。

使用的两根线是:

  • SDA(串行数据线):用于传输和接收数据的线路。
  • SCL(串行时钟线):承载时钟信号,同步数据传输的线路。

一个主设备可以在同一总线上与多达 112 个从设备通信,只要每个从设备都有唯一的地址。总线需要在 SDA 和 SCL 两条线上都连接上拉电阻(通常为 4.7kΩ),尽管许多传感器扩展板已经包含了这些电阻。

I2C 引脚因 Arduino 板而异。以下是常见的引脚:

  • Uno、Nano、Pro Mini:A4 (SDA)、A5 (SCL)
  • Mega、Due:20 (SDA)、21 (SCL)
  • Leonardo、Micro:2 (SDA)、3 (SCL)

Arduino 内置的 Wire 库简化了 I2C 通信。核心函数包括:

  • Wire.begin():初始化库并加入 I2C 总线。主设备调用时不带地址。从设备调用时带上其 7 位地址,例如 Wire.begin(0x08)。
  • Wire.beginTransmission(address):开始向指定地址的从设备进行传输。
  • Wire.write(data):向从设备发送数据字节。
  • Wire.endTransmission():结束传输。至关重要的是,它会返回一个状态码,您应该检查该状态码以查找错误。
  • Wire.requestFrom(address, byteCount):主设备向从设备请求特定数量的字节。
  • Wire.available():返回已接收并可读取的字节数。
  • Wire.read():读取一个已接收的字节。

实用示例:主设备发送器 / 从设备接收器

Section titled “实用示例:主设备发送器 / 从设备接收器”

在此示例中,一个作为主设备的 Arduino 向另一个作为从设备的 Arduino 发送命令,以控制其板载 LED。

#include <Wire.h>
#define SLAVE_ADDRESS 0x08 // 定义从设备的地址
void setup() {
Wire.begin(); // 作为主设备加入 I2C 总线
Serial.begin(9600);
Serial.println("I2C Master Writer Initialized");
}
void loop() {
// --- 打开 LED ---
Serial.println("Sending ON command...");
Wire.beginTransmission(SLAVE_ADDRESS);
Wire.write('1'); // 发送一个字节以打开 LED
byte error = Wire.endTransmission(); // 检查错误
if (error == 0) {
Serial.println("Command sent successfully.");
} else {
Serial.print("Error sending command: ");
Serial.println(error);
}
delay(2000);
// --- 关闭 LED ---
Serial.println("Sending OFF command...");
Wire.beginTransmission(SLAVE_ADDRESS);
Wire.write('0'); // 发送一个字节以关闭 LED
error = Wire.endTransmission();
if (error == 0) {
Serial.println("Command sent successfully.");
} else {
Serial.print("Error sending command: ");
Serial.println(error);
}
delay(2000);
}
#include <Wire.h>
#define SLAVE_ADDRESS 0x08 // 此从设备的地址
void setup() {
pinMode(LED_BUILTIN, OUTPUT);
digitalWrite(LED_BUILTIN, LOW); // 初始状态为 LED 关闭
Wire.begin(SLAVE_ADDRESS); // 带地址加入 I2C 总线
Wire.onReceive(receiveEvent); // 注册事件处理函数
Serial.begin(9600);
Serial.println("I2C Slave Receiver Initialized");
}
void loop() {
// 主循环可用于其他任务。
// 所有 I2C 逻辑由事件处理。
delay(100);
}
// 每当从主设备接收到数据时,都会调用此函数。
void receiveEvent(int byteCount) {
while (Wire.available()) { // 遍历所有接收到的字节
char c = Wire.read(); // 将接收到的字节作为字符
Serial.print("Command received: ");
Serial.println(c);
if (c == '1') {
digitalWrite(LED_BUILTIN, HIGH);
} else if (c == '0') {
digitalWrite(LED_BUILTIN, LOW);
}
}
}
  • 地址错误:最常见的问题。使用“I2C 扫描器”程序(sketch)来扫描总线并查找连接设备的地址。
  • 缺少上拉电阻:如果您的传感器模块没有上拉电阻,总线将无法工作。您需要一个 4.7kΩ 电阻从 SDA 连接到 5V,另一个从 SCL 连接到 5V。
  • 接线错误:仔细检查 SDA 是否连接到 SDA,SCL 是否连接到 SCL。接反会阻止通信。
  • 电源问题:确保总线上的所有设备共用一个地(GND)。

始终检查 Wire.endTransmission() 的返回值以构建健壮的系统:

  • 0:成功。
  • 1:数据过长,无法放入传输缓冲区。
  • 2:在传输地址时收到 NACK(非应答)。这意味着在该地址没有从设备响应。
  • 3:在传输数据时收到 NACK。从设备存在但未接受数据。
  • 4:其他错误。