Arduino I2C地址扫描与主从通讯实战指南

两块Arduino开发板之间的对话听起来像是科幻场景,但通过I2C协议,这完全可以成为你工作台上的现实。作为硬件爱好者最常用的通讯协议之一,I2C以其简洁的两线制连接方式赢得了广泛青睐。本文将带你从零开始,用两块Arduino UNO搭建一个完整的I2C通讯系统,涵盖地址扫描、主从机配置到双向数据传输的全过程。

1. I2C通讯基础与硬件准备

I2C(Inter-Integrated Circuit)是一种同步、多主多从的串行通讯总线,仅需两根信号线即可实现多个设备间的数据交换。在Arduino UNO上,I2C接口固定位于A4(SDA数据线)和A5(SCL时钟线)引脚。

所需材料清单

  • 2块Arduino UNO开发板
  • 4条杜邦线(建议使用不同颜色区分功能)
  • 1个面包板(可选,用于更整洁的布线)
  • 2个4.7kΩ上拉电阻(部分开发板已内置)

硬件连接示意图:

主机引脚 从机引脚 连接说明
A4 (SDA) A4 (SDA) 数据线,需接上拉电阻
A5 (SCL) A5 (SCL) 时钟线,需接上拉电阻
GND GND 共地连接,确保电位基准一致

提示:虽然部分Arduino开发板已内置上拉电阻,但为确保通讯稳定性,建议外接4.7kΩ电阻将SDA和SCL线拉高至VCC。

2. I2C地址扫描实战

在建立通讯前,我们需要确认从机设备的地址。以下是一个增强版的地址扫描程序,不仅能发现设备,还能识别可能的通讯错误。

#include <Wire.h>

void setup() {
  Wire.begin();
  Serial.begin(115200);
  while (!Serial); // 等待串口就绪
  Serial.println("=== I2C扫描仪 ===");
}

void loop() {
  byte error, address;
  int foundDevices = 0;
  
  Serial.println("开始扫描I2C总线...");
  
  for(address = 1; address < 127; address++) {
    Wire.beginTransmission(address);
    error = Wire.endTransmission();
    
    if (error == 0) {
      Serial.print("发现设备 @ 0x");
      if(address < 16) Serial.print("0");
      Serial.println(address, HEX);
      foundDevices++;
    }
    else if (error == 4) {
      Serial.print("地址 0x");
      if(address < 16) Serial.print("0");
      Serial.print(address, HEX);
      Serial.println(" 存在通讯错误");
    }
  }
  
  if (foundDevices == 0)
    Serial.println("未发现任何I2C设备");
  else
    Serial.print("扫描完成,共发现 ");
    Serial.print(foundDevices);
    Serial.println(" 个设备");
  
  delay(5000); // 5秒后重新扫描
}

常见I2C设备地址范围参考表:

设备类型 典型地址范围 备注
EEPROM 0x50-0x57 如AT24C系列
温度传感器 0x48-0x4F 如LM75
实时时钟 0x68 DS1307/DS3231
LCD显示器 0x27/0x3F PCF8574转接板

3. 主从机通讯实现

3.1 从机配置

从机需要设置一个唯一的I2C地址(通常为1-127之间的值)。以下是从机接收数据的完整示例:

#include <Wire.h>

#define SLAVE_ADDR 0x08  // 自定义从机地址

void receiveData(int byteCount);

void setup() {
  Wire.begin(SLAVE_ADDR);       // 以从机模式加入I2C总线
  Wire.onReceive(receiveData);  // 注册接收事件回调
  Serial.begin(9600);
  Serial.println("从机准备就绪");
}

void loop() {
  delay(100); // 保持低功耗
}

void receiveData(int byteCount) {
  Serial.print("收到 ");
  Serial.print(byteCount);
  Serial.println(" 字节数据:");
  
  while(Wire.available()) {
    char c = Wire.read();
    Serial.write(c);
  }
  Serial.println();
}

3.2 主机配置

主机负责初始化通讯并发送数据。以下是主机发送字符串和数值的示例:

#include <Wire.h>

#define SLAVE_ADDR 0x08  // 与从机地址匹配

void setup() {
  Wire.begin(); // 主机无需指定地址
  Serial.begin(9600);
}

void loop() {
  static int counter = 0;
  
  Wire.beginTransmission(SLAVE_ADDR);
  Wire.write("计数: ");
  Wire.write(counter);
  byte error = Wire.endTransmission();
  
  if(error == 0) {
    Serial.print("成功发送数据: ");
    Serial.println(counter);
  } else {
    Serial.print("传输错误代码: ");
    Serial.println(error);
  }
  
  counter++;
  delay(1000);
}

4. 双向通讯进阶实现

完整的双向通讯需要主从机都能发送和接收数据。以下是实现方案:

4.1 从机代码升级

#include <Wire.h>

#define SLAVE_ADDR 0x08
byte responseData[] = {0xAA, 0x55, 0x01}; // 示例响应数据

void setup() {
  Wire.begin(SLAVE_ADDR);
  Wire.onReceive(receiveEvent);
  Wire.onRequest(requestEvent); // 新增请求事件处理
  Serial.begin(9600);
}

void loop() { delay(100); }

void receiveEvent(int howMany) {
  while(Wire.available()) {
    Serial.print((char)Wire.read());
  }
}

void requestEvent() {
  Wire.write(responseData, sizeof(responseData));
  Serial.println("收到主机请求,已发送响应数据");
}

4.2 主机代码升级

#include <Wire.h>

#define SLAVE_ADDR 0x08

void setup() {
  Wire.begin();
  Serial.begin(9600);
}

void loop() {
  // 发送数据到从机
  Wire.beginTransmission(SLAVE_ADDR);
  Wire.write("请求数据");
  Wire.endTransmission();
  
  // 请求从机返回数据
  Wire.requestFrom(SLAVE_ADDR, 3); // 请求3字节
  
  Serial.print("从机响应: ");
  while(Wire.available()) {
    Serial.print(Wire.read(), HEX);
    Serial.print(" ");
  }
  Serial.println();
  
  delay(2000);
}

5. 调试技巧与常见问题解决

通讯不稳定排查清单

  1. 检查所有物理连接是否牢固
  2. 确认SDA和SCL线都正确接有上拉电阻
  3. 确保主从机共地
  4. 检查从机地址是否冲突
  5. 降低通讯速率测试(修改Wire库的时钟频率)

Wire库时钟频率调整

void setup() {
  Wire.begin();
  Wire.setClock(100000); // 设置为标准100kHz模式
  // Wire.setClock(400000); // 快速模式
  // Wire.setClock(10000); // 低速模式用于调试
}

常见错误代码解析

错误代码 含义 解决方案
0 成功 -
1 数据量过大 减少单次传输数据量
2 地址NACK 检查从机地址是否正确
3 数据NACK 检查从机是否准备好接收数据
4 其他错误 检查硬件连接和电源

在实际项目中,我发现最常遇到的问题是不正确的上拉电阻值。虽然理论上4.7kΩ是标准值,但在长导线或高干扰环境中,可能需要尝试2.2kΩ甚至1kΩ的电阻来改善信号质量。另一个容易忽视的细节是电源稳定性——当使用面包板供电时,确保电源能提供足够的电流,否则可能导致通讯异常。

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐