首页 动态 版块 商城 我的
 退出
游客  
等级:
金币:
经验值:

I2C信号通讯协议 树莓派Pico与ESP32开发板 Python和C++例程【入门教程】

  13109 
 0
 4
RASPI   ydxhuo.com 

Image


一:I2C通讯协议基础介绍

1. I2C信号介绍

I2C(Inter-Integrated Circuit)是一种常用的串行通信协议,适用于短距离设备间的低速通信。广泛应用于各种单片机与传感器、EEPROM、LCD 模块等外围器件的短距离数据传输。其核心特点是仅需两根信号线即可实现双向通信,硬件成本低、布线简单,是单片机系统中最常用的通信接口之一。


I2C 通信仅依赖两根信号线,且需配合上拉电阻实现电平稳定,具体如下:

信号名称英文全称核心功能关键特性
SDASerial Data串行数据双向传输,用于发送 / 接收数据字节(8 位)
SCLSerial Clock串行时钟单向传输(由主设备生成),用于同步数据传输节奏

关键硬件细节:上拉电阻

SDA 和 SCL 均为开漏输出(Open-Drain) 结构,自身无法输出高电平,需通过外接上拉电阻(通常为 4.7kΩ~10kΩ,接 VCC)将电平拉至高电平。

当器件输出 “0” 时:引脚拉低,总线电平为低;

当器件输出 “1” 时:引脚高阻,总线电平由上拉电阻拉为高。

这种结构保证了多设备挂载时不会出现 “总线竞争”(即多个设备同时输出高低电平导致短路)。


2. I2C信号主从模式

I2C 采用 “主从架构”,通信由主设备(如单片机) 发起和控制,从设备(如传感器) 仅被动响应,具体分工如下:


主从角色模式核心职责
主设备(Master)1.生成 SCL 时钟信号2.发起 “起始信号” 和 “停止信号"3.发送从设备地址(选择通信对象)4.控制数据传输方向(读 / 写)5.生成 “应答信号(ACK)” 或 “非应答信号(NACK)
从设备(Slave)1. 拥有唯一的 7 位或 10 位地址2. 检测总线地址是否与自身匹配3. 按主设备指令发送 / 接收数据4. 向主设备返回 ACK/NACK


注:I2C 总线支持多主设备(需避免冲突)和多从设备(最多 128 个 7 位地址从设备),但单片机系统中 99% 以上为 “单主多从” 场景。


3. I2C信号核心时序

I2C 通信的完整过程由 “起始→地址→数据→应答→停止”5 个时序阶段组成,每个阶段的 SDA 和 SCL 电平变化严格定义,是协议正确性的核心。

1. 起始信号(Start Condition,S):通信开始的标志

时序定义:在 SCL 保持高电平期间,SDA 由高电平跳转为低电平(“高→低” 跳变)。

作用:告知总线上所有从设备 “通信即将开始”,从设备需准备接收后续地址信号。

波形示意图:


2. 从设备地址与读写控制:选择通信对象和方向

起始信号后,主设备会连续发送8 位数据,其中前 7 位为 “从设备地址”,第 8 位为 “读写控制位(R/W#)”:

第 8 位 = 0:表示主设备向从设备写数据(Write);

第 8 位 = 1:表示主设备从从设备读数据(Read)。


时序要求:

SCL 为高电平时,SDA 电平必须稳定(数据有效);

SCL 为低电平时,SDA 可跳变(准备下一位数据)。

示例:若从设备地址为 0x48(二进制1001000),主设备要向其写数据,则发送的 8 位为10010000(0x48 + 0);若要读数据,则发送10010001(0x48 + 1)。


3. 应答信号(ACK/NACK):数据接收确认

每传输 8 位数据(地址或数据字节)后,接收方需向发送方返回 1 位 “应答信号”,确认数据已接收:

ACK(应答):接收方在第 9 个 SCL 高电平期间,将 SDA 拉为低电平(表示 “已接收,可继续传输”);

NACK(非应答):接收方在第 9 个 SCL 高电平期间,将 SDA 保持高电平(表示 “未接收 / 接收完成”,主设备通常会终止通信)。


关键规则:

地址字节的应答方:从设备(确认地址匹配);

数据字节的应答方:

写操作时:从设备(确认数据接收);

读操作时:主设备(确认数据接收,若主设备无需更多数据,会返回 NACK)。

波形示意图(ACK):


4. 数据传输:8 位为单位的双向传输

地址和 ACK 确认后,进入数据传输阶段,每个数据单元为8 位(1 字节),传输方向由 “读写控制位” 决定:

写操作(主→从):主设备连续发送数据字节,每字节后等待从设备的 ACK;

读操作(从→主):从设备连续发送数据字节,每字节后等待主设备的 ACK/NACK(主设备要终止时发 NACK)。

时序要求:与地址传输一致 ——SCL 高电平时 SDA 稳定(数据有效),SCL 低电平时 SDA 跳变。

5. 停止信号(Stop Condition,P):通信结束的标志

时序定义:在 SCL 保持高电平期间,SDA 由低电平跳转为高电平(“低→高” 跳变)。

作用:告知总线上所有从设备 “本次通信结束”,从设备可进入空闲状态。

波形示意图:


4. I2C信号传输速率

I2C 支持不同的传输速率,以适配不同的应用场景(速率越高,对布线和器件的要求越严格):


模式名称最大传输速率适用场景
标准模式(Standard-mode)100 kbps低速、长距离(如 1m 以上)、对稳定性要求高的场景
快速模式(Fast-mode)400 kbps中速、短距离(如 0.5m 内)温湿度传感器(SHT30)、OLED 
高速模式(Fast-mode Plus)1 Mbps高速、短距离(如 0.3m 以内)场景


注:部分高端器件支持 10Mbps 的 “超高速模式(High-speed mode)”,但单片机系统中极少使用。




二:树莓派使用I2C示例

1. Pico开发板I2C引脚

在树莓派Pico开发板的引脚上有两个 I2C 控制器分别为(I2C0 和 I2C1)


其中代表I2C0的引脚如下:

SDA (数据): GPIO0 (默认) 或 GPIO4、GPIO8、GPIO12、GPIO16、GPIO20

SCL (时钟): GPIO1 (默认) 或 GPIO5、GPIO9、GPIO13、GPIO17、GPIO21


其中代表I2C1的引脚如下:

SDA (数据): GPIO2 (默认) 或 GPIO6、GPIO10、GPIO14、GPIO18、GPIO26

SCL (时钟): GPIO3 (默认) 或 GPIO7、GPIO11、GPIO15、GPIO19、GPIO27


2.初始化I2C引脚代码

使用 MicroPython 初始化 I2C 控制器:

from machine import Pin, I2C
import time

# 初始化I2C0,使用默认引脚
i2c0 = I2C(0, scl=Pin(1), sda=Pin(0), freq=400000)

# 初始化I2C1,使用默认引脚
i2c1 = I2C(1, scl=Pin(3), sda=Pin(2), freq=400000)

print("I2C初始化完成")

3.扫描2C引脚代码

连接好 I2C的设备后,可以扫描总线上的设备地址:

from machine import Pin, I2C

# 初始化I2C
i2c = I2C(0, scl=Pin(1), sda=Pin(0), freq=400000)

# 扫描I2C设备
devices = i2c.scan()

if devices:
    print("找到I2C设备:")
    for dev in devices:
        print(f"设备地址: 0x{dev:02x}")
else:
    print("未找到I2C设备")

4.I2C 数据发送与接收

以下是 I2C 主机发送数据到从设备和从从设备接收数据的示例:

from machine import Pin, I2C
import time

# 初始化I2C
i2c = I2C(0, scl=Pin(1), sda=Pin(0), freq=400000)

# 假设我们有一个地址为0x48的I2C设备
DEVICE_ADDR = 0x48

def send_data(register, data):
    """向指定寄存器发送数据"""
    try:
        # 格式: i2c.writeto_mem(设备地址, 寄存器, 数据, 数据格式)
        i2c.writeto_mem(DEVICE_ADDR, register, bytes([data]), addrsize=8)
        print(f"已发送数据 0x{data:02x} 到寄存器 0x{register:02x}")
        return True
    except OSError as e:
        print(f"发送失败: {e}")
        return False

def receive_data(register, length):
    """从指定寄存器接收指定长度的数据"""
    try:
        # 读取数据
        data = i2c.readfrom_mem(DEVICE_ADDR, register, length, addrsize=8)
        print(f"从寄存器 0x{register:02x} 接收数据: {[hex(b) for b in data]}")
        return data
    except OSError as e:
        print(f"接收失败: {e}")
        return None

# 示例用法
if __name__ == "__main__":
    # 向寄存器0x00发送数据0xAA
    send_data(0x00, 0xAA)
    time.sleep(0.1)
    
    # 从寄存器0x01读取2字节数据
    receive_data(0x01, 2)

三:ESP32使用I2C示例

1.ESP32 默认 I2C 引脚

信号GPIO 引脚
SDAGPIO 21
SCLGPIO 22

2.I2C 地址扫描器

下面提供两个常用的程序示例:I2C 设备地址扫描器 和 指定 GPIO 自定义 I2C 通信示例。

示例 1:I2C 地址扫描器(用于检测挂载的芯片地址)

当你接入一个新的 I2C 传感器但不知道它的硬件地址时,可以用这段代码扫描总线:


#include <Wire.h>

void setup() {
  Serial.begin(115200);
  while (!Serial);

  // 初始化默认 I2C (SDA=21, SCL=22)
  Wire.begin();

  Serial.println("\nI2C 总线扫描器启动...");
}

void loop() {
  byte error, address;
  int nDevices = 0;

  Serial.println("正在扫描...");

  for (address = 1; address < 127; address++) {
    // 尝试与该地址建立通信
    Wire.beginTransmission(address);
    error = Wire.endTransmission();

    if (error == 0) {
      Serial.print("发现 I2C 设备,地址: 0x");
      if (address < 16) Serial.print("0");
      Serial.print(address, HEX);
      Serial.println(" !");
      nDevices++;
    } else if (error == 4) {
      Serial.print("未知错误,地址: 0x");
      if (address < 16) Serial.print("0");
      Serial.println(address, HEX);
    }
  }

  if (nDevices == 0) {
    Serial.println("未找到任何 I2C 设备\n");
  } else {
    Serial.println("扫描完成\n");
  }

  delay(5000); // 每 5 秒重新扫描一次
}


3.读写从机设备数据

如果你的传感器接在了其他引脚(如 SDA=GPIO 18, SCL=GPIO 19),或者需要向芯片写入/读取寄存器:

#include <Wire.h>

// 定义自定义 I2C 引脚
#define CUSTOM_SDA 18
#define CUSTOM_SCL 19

// 假设目标设备地址(根据实际修改,例如 0x68)
#define SENSOR_ADDR 0x68 

void setup() {
  Serial.begin(115200);

  // 指定 SDA 和 SCL 引脚,波特率设置为 400kHz (Fast Mode)
  Wire.begin(CUSTOM_SDA, CUSTOM_SCL, 400000);
  
  Serial.println("I2C 自定义引脚初始化完成");
}

// 向从机寄存器写入数据
void writeRegister(uint8_t devAddr, uint8_t regAddr, uint8_t data) {
  Wire.beginTransmission(devAddr);
  Wire.write(regAddr); // 发送要写入的寄存器地址
  Wire.write(data);    // 发送数据
  uint8_t result = Wire.endTransmission(); // 结束传输并获取状态
  
  if (result == 0) {
    Serial.println("写入成功");
  } else {
    Serial.print("写入失败,错误代码: ");
    Serial.println(result);
  }
}

// 从从机寄存器读取数据
uint8_t readRegister(uint8_t devAddr, uint8_t regAddr) {
  uint8_t data = 0;
  
  // 1. 先告诉设备我们要读取哪个寄存器
  Wire.beginTransmission(devAddr);
  Wire.write(regAddr);
  Wire.endTransmission(false); // 发送重复起始条件 (Repeated START),不释放总线

  // 2. 请求读取 1 个字节
  Wire.requestFrom(devAddr, (uint8_t)1);
  if (Wire.available()) {
    data = Wire.read();
  }
  
  return data;
}

void loop() {
  // 示例:每秒从传感器读取一次 WHO_AM_I 寄存器(假设寄存器地址为 0x75)
  uint8_t chipID = readRegister(SENSOR_ADDR, 0x75);
  Serial.print("读取到的芯片 ID: 0x");
  Serial.println(chipID, HEX);

  delay(1000);
}


4.注意事项

硬件模块的板载上拉电阻:

大部分买到的传感器模块(如 MPU6050、SSD1306 OLED、BMP280)板子上都已经贴片好了 10kΩ 或 4.7kΩ 的上拉电阻。如果你直接用杜邦线将模块连接到 ESP32,通常不需要额外接电阻。但如果是自己设计的 PCB,记得在 SDA/SCL 上加 4.7kΩ 上拉电阻。


逻辑电平匹配:

ESP32 的 GPIO 工作电压是 3.3V。如果连接 5V 的 I2C 设备(如某些老版 Arduino 模块或 LCD1602),需要加装 3.3V-5V 双向逻辑电平转换模块,否则可能会损坏 ESP32 的引脚。


线长与速率:


标准模式:100 kHz


快速模式:400 kHz


杜邦线过长(超过 20-30cm)会导致寄生电容增大,可能产生通信错误。线路较长时建议降低波特率(如设置为 100000),或减小上拉电阻的阻值(如降至



作者签名: www.ydxhuo.com   楼主  2025-08-25 01:54:55
回复列表
Powered by YDXHUO _VERSION
© 2017-2026 跃动芯火版权
您的IP: 216.73.216.31 , 2026-08-12 18:59:55
Powered by YDXHUO _VERSION
© 2017-2026 跃动芯火版权
(4) 分享
分享

请保存二维码或复制链接进行分享

取消
免责声明
1、本站资源,均来自网络与个人作者,所有资源和文章仅限用于学习和研究目的 。
2、不得用于商业或非法用途,否则,一切责任由该用户承担 !

侵权删除请致信 E-Mail:ydxho@qq.com
已有0次打赏