Arduino - ESP8266和ESP32的I2C接口使用(超全)

简介

深入讲解I2C通信协议,以及,Arduino和ESP8266微控制器通过I2C连接设备需要哪些引脚、该通信协议的优缺点以及如何使用I2C多路复用器

详解

Arduino/ESP8266与其他设备通信,例如OLED显示器、气压传感器等,

可以使用I2C通信协议。

也可以使用两外两个通信协议:SPI和UART

I2C全称是Inter-Integrated Circuit,是1982年由飞利浦半导体公司(现在为NXP Semiconductors)发明的。I2C具有多种功能:

  • 同步(Synchronous):通过主机和从机之间共享的时钟信号,位输出与位采样同步。
  • 多主机(Multi-master):您可以有多个主机控制一个或多个从机。
  • 多从机(Multi-slave):您可以将多个从机连接到单个主机,类似于SPI。
  • 数据包交换(Packet switched):传输的数据分为包/消息,由数据头和有效负载组成。
  • 单端(Single-ended):数据通过单线传输。
  • 串行连接(Serial connection):数据通过单线一位一位地传输

I2C参考设计

在Arduino/ESP8266上,您将找到用于I2C通信的两个GPIO(SDA和SCL)。

如果不确定是否找到相应的引脚,请参见以下图片

ESP8266(NodeMCU):SDA:D2(I2C->数据);SCL:D1(I2C->时钟)

Arduino Nano开发板:SDA:A4;SCL:A5

I2C通信所需的两个引脚如下:

  • SDA(串行数据):主站和从站之间的连接,用于发送和接收数据。
  • SCL(串行时钟):在主机和从机之间共享时钟信号,其中主机始终控制时钟信号。

串行数据线和串行时钟线通过电阻上拉。

因此,当总线上没有数据传输时,SDA和SCL处于高电平状态。

典型电压为+ 5V和+ 3.3V,设备可以100 kHz或400 kHz进行通信。

所有I2C器件都通过集电极开路或漏极开路引脚连接到总线,以将总线拉低。

主机和从机之间的通信是通过拉低和释放高电平来切换总线而发生的。此外,位在时钟下降沿计时。

对于给定的总线设备,可能有四种潜在的操作模式,但是大多数设备仅使用单个角色,并且使用两种模式:

  • 主机发送 – 主机正在向从机设备发送数据,
  • 主节接收 – 主机正在从从机设备接收数据,
  • 从机发送 – 从机设备正在向主机发送数据,
  • 从机接收 – 从机设备正在从主机接收数据。

 

I2C消息协议

I2C消息协议根据位的电平分成固定的状态。在本文的以下部分,我们将仔细研究协议的六个不同状态。

  1. 开始条件:SCL为高电平时,SDA:高→低
  2. 停止条件:SCL为高电平时,SDA:低→高
  3. 地址帧:地址帧是7或10位序列,用于标识主机的每个从机。该标识符在所有从站上都是唯一的。每个从机将从主机发送的地址与其自己的地址进行比较。如果地址匹配,它将向主机发送一个ACK位→0。如果地址不匹配,则从站不执行任何操作,并且SDA线保持高电平。
  4. 读/写位

    写入:主机正在向从机发送数据:0

    读取:主机正在向从机请求数据:1

  5. ACK / NACK位。如果成功接收到地址帧或数据帧,则会从机设备向发送方返回ACK位→0。
  6. 数据帧:主机检测到来自从机的ACK位后,即可发送第一个数据帧。数据帧始终为8位长,后跟ACK / NACK位以验证是否已成功接收到该帧。主机将以固定间隔继续产生时钟脉冲。

I2C写入通讯时序

I2C读取通讯时序

 

 

重复启动条件

时钟延长

在某些情况下,主机数据速率将超过从机提供所需数据的能力。因此,从机可以在主机释放SCL线后按住SCL线。主机将等待从机释放点击线,然后再继续下一帧。

物理层

  与UART或SPI连接不同,I2C总线驱动器是“漏极开路”的,这意味着它们可以将相应的信号线拉低,但不能将其拉高。因此,当一台设备试图将线路驱动为高电平而另一台设备将其拉低时,则无法进行通信。这种架构避免了通信中的错误。

  但是如何将信号线拉高?每条信号线都有一个上拉电阻,可在没有器件将其置为低电平时将信号恢复为高电平。根据经验,选择一个电阻为4.7kΩ。连接到I2C通信的设备越多,电阻就必须越小。

  I2C在连接具有不同I / O电压的设备时具有一定的灵活性。对于以5V电压电平作为主设备的Arduino开发板,可以正常连接一个3.3V电压的从设备。但是,如果从机的电压低于3.3V(例如2.5V),则有可能需要I2C电平转换器。

I2C速度模式

  • 低速模式:10 kbit / s
  • 标准模式:100 kbit / s
  • 快速模式:400 kbit / s
  • 快速模式+:1 Mbit / s
  • 高速模式:3.4 Mbit / s
  • 超快速模式:5 Mbit / s

I2C通讯的优缺点

  • 优点:仅使用两根电线;支持多个主机和多个从机;众所周知且被广泛使用的协议。
  • 缺点:数据传输速率比SPI慢,数据帧的大小限制为8位

I2C示例

接下来,我们在以下章节不再介绍理论知识,而是介绍一些实际用例。

在控制一台I2C设备之前,我们首先必须找出其十六进制地址。

因此,我们的第一个示例是I2C HEX地址扫描器。

找到I2C LCD显示器的十六进制地址后,我们将相应地控制该显示器,以通过I2C从Arduino或NodeMCU向LCD显示器发送消息。

下图显示了I2C LCD显示屏分别与Arduino Uno和NodeMCU之间的硬件连接。

下表列出了Arduino Uno和LCD显示屏之间的硬件连接关系。

下表列出了Node MCU和LCD显示屏之间的硬件连接关系。

如何找到I2C设备的十六进制地址?

#include "Wire.h"
//“ Wire.h”库允许微控制器与I2C设备通信。因此,每当您要使用I2C通信时,该库都是必不可少的。
void setup(){
  Serial.begin(115200); 
  while(!Serial){} // Waiting for serial connection
 
  Serial.println();
  Serial.println("Start I2C scanner ...");
  Serial.print("\r\n");
  byte count = 0;

//该草图代码仅使用setup函数,因为我们只需要一次扫描所有连接的设备。

首先,我们将波特率定义为115200,然后记住将串口监视器的波特率设置为相同的值。

然后我们等到建立串行连接后,我们才能够扫描设备。

在串口监视器上定义了一些很酷的打印之后,我们将变量计数定义为零。找到I2C设备后,该变量将增加,该变量是连接的I2C设备的总和。

Wire.begin();
  for (byte i = 8; i < 120; i++)
  {
    Wire.beginTransmission(i);
    if (Wire.endTransmission() == 0)
      {
      Serial.print("Found I2C Device: ");
      Serial.print(" (0x");
      Serial.print(i, HEX);
      Serial.println(")");
      count++;
      delay(1);
      }
  }
//通过Wire.begin()函数,微控制器可以作为主机或从机加入I2C总线。
如果在Wire.begin()之类的函数中未提供地址,该设备将像我们希望的那样作为主机设备加入。
要扫描所有可能的I2C HEX地址,我们使用for循环。为了开始传输到可能的I2C从设备,我们使用Wire.beginTransmission(address)函数。
如果存在有效的I2C从设备,则通过Wire.endTransmission()结束到从设备的传输,我们将得到0。
我们在串口监视器上打印所连接设备的十六进制地址。同样,如果找到I2C设备,则在将计数器连接到下一个设备之前,将计数器增加1并稍加延迟。
  Serial.print("\r\n");
  Serial.println("Finish I2C scanner");
  Serial.print("Found ");
  Serial.print(count, HEX);
  Serial.println(" Device(s).");
}

void loop() {}
//在代码的末尾,我们打印找到的I2C设备的总数。我们没有使用循环函数。

如果我们看一下串口监视器,结果如下:

我们发现已连接的I2C LCD显示器的十六进制地址为0x27。下一个示例将需要此地址。

控制LCD液晶显示器

  使用I2C HEX扫描仪,我们发现LCD显示屏的HEX地址为0x27。

现在,我们可以对脚本进行编程以与LCD显示器进行通讯了。对于此草图代码,我们需要LiquidCrystal_I2C库。

  首先,我们必须包含Wire库和LiquidCrystal_I2C库,该库做了大量工作,并且向我们提供了用于LCD显示器的易于使用的接口。

  我们需要定义显示器的十六进制地址以及正在使用的显示器,例如16×2或20×4。在此示例中,我使用20×4显示屏。

  在setup函数中,我们使用lcd.init()初始化微控制器和显示器之间的连接。初始化后,我们使用lcd.backlight()打开背光灯。

  设置好显示后,我们可以将光标设置到位置(列= 1,行= 0),并写入文本的第一部分。然后,将光标置于下一行,并写入其余字符串。

#include "Wire.h"
#include "LiquidCrystal_I2C.h"

// set the LCD address to 0x27 for a 16 chars and 2 line display
LiquidCrystal_I2C lcd(0x27, 20, 4); 

void setup()
{
  lcd.init();

  lcd.backlight();
  lcd.setCursor(1, 0);
  lcd.print("This is");
  lcd.setCursor(1, 1);
  lcd.print("DIYI0T.com");
}

void loop(){}

I2C多路复用器

  如果您想将许多传感器连接到Arduino,并且您的常规I2C连接不能满足要求?

传感器可能具有相同的I2C地址。您的问题的解决方案是PCF8574 1对8 I2C扩展器。

PCF8574 Breakout支持与具有相同地址的多个I2C器件进行通信,从而使其易于与它们进行连接器。

PCF8574是一个八通道I2C扩展器,它允许八个独立的I2C器件由单个主机I2C总线控制。

PCF8574是用于两线双向总线(I2C)的8位输入/输出(I / O)扩展器,设计用于2.5V至6V的工作电压。待机电流消耗仅为10μA,非常低。

P0…P7是具有推挽设计结构的P端口输入/输出。在这里,将I2C设备连接到PCF8574。我们将LED连接到P0。

A0…A2是PCF8574本身的地址输入。我们不需要它们,因此将它们连接到GND。

多路复用器的程序代码和设备的控制与上述相同。

posted @ 2024-02-02 12:14  Citrusliu  阅读(5294)  评论(0编辑  收藏  举报