在本教程中,我们将先容以下观点:
将I2C设备与ESP32连接利用ESP32扫描I2C地址ESP32利用不同的I2C引脚(变动默认I2C引脚)ESP32利用两个I2C总线接口我们将利用Arduino IDE对ESP32进行编程,因此在连续本教程之前,您该当在Arduino IDE中安装ESP32插件。具有多个I2C器件的ESP321. 相同的总线,不同的地址
2. 相同的地址·
ESP32 I2C通讯协议先容I²C是一个同步,多主,多从通信协议。您可以连接:
一个主设备有多个从设备:例如,您的ESP32利用I2C从BME280传感器读取数据,并将传感器读数写入I2C OLED显示屏。多个主设备掌握同一个从设备:例如,两个ESP32板将数据写入同一I2C OLED显示器。我们在ESP32中多次利用此协议与外部设备(例如传感器和显示器)进行通信。在这种情形下,ESP32是主芯片,外部设备是从芯片。
ESP32 I2C总线接口ESP32通过其两个I2C总线接口支持I2C通信,这两个接口可以用作I2C主设备或从设备,详细取决于用户的配置。根据ESP32数据表,ESP32的I2C接口支持:
标准模式(100 Kbit / s) 快速模式(400 Kbit / s) 高达5 MHz,但受到SDA上拉强度的限定 7位/ 10位寻址模式 双寻址模式。用户可以对命令寄存器进行编程以掌握I²C接口,从而具有更大的灵巧性将I2C设备与ESP32连接I2C通信协议利用两条线共享信息。一个用于时钟旗子暗记(SCL),另一个用于发送和吸收数据(SDA)。
把稳:在许多分支板上,SDA线也可能标记为SDI,SCL线也可能标记为SCK。
SDA和SCL线为低电平有效,因此应利用电阻将其上拉。对付5V器件,范例值为4.7k Ohm;对付3.3V器件,范例值为2.4k Ohm。
我们在项目中利用的大多数传感器都是已内置电阻的分线板。因此,常日,当您处理此类电子元件时,您无需为此担心。
将I2C器件连接到ESP32常日很大略,只需将GND连接到GND,将SDA连接到SDA,将SCL连接到SCL并将正电源连接到外围设备常日为3.3V(但这取决于您利用的模块)。
将ESP32与Arduino IDE结合利用时,默认的I2C引脚为GPIO 22(SCL)和GPIO 21(SDA),但您可以将代码配置为利用任何其他引脚。
通过I2C通信,总线上的每个从站都有其自己的地址,此十六进制数许可ESP32与每个设备通信。
I2C地址常日可以在组件的数据表中找到。但是,如果很难查明,则可能须要运行I2C查找程序以查明I2C地址。
您可以利用以下程序找到设备的I2C地址。
#include <Wire.h>
void setup() {
Wire.begin();
Serial.begin(115200);
Serial.println("\nI2C Scanner");
}
void loop() {
byte error, address;
int nDevices;
Serial.println("Scanning...");
nDevices = 0;
for(address = 1; address < 127; address++ ) {
Wire.beginTransmission(address);
error = Wire.endTransmission();
if (error == 0) {
Serial.print("I2C device found at address 0x");
if (address<16) {
Serial.print("0");
}
Serial.println(address,HEX);
nDevices++;
}
else if (error==4) {
Serial.print("Unknow error at address 0x");
if (address<16) {
Serial.print("0");
}
Serial.println(address,HEX);
}
}
if (nDevices == 0) {
Serial.println("No I2C devices found\n");
}
else {
Serial.println("done\n");
}
delay(5000);
}
您会在串口监视器中看到类似的内容。此特定示例适用于。
ESP32利用不同的I2C引脚(变动默认I2C引脚)
利用ESP32,您险些可以将任何引脚设置为具有I2C功能,您只须要在代码中进行设置即可。
将ESP32与Arduino IDE合营利用时,请利用Wire.h库与利用I2C的设备进行通信。利用此库,您可以按以下办法初始化I2C:
Wire.begin(I2C_SDA, I2C_SCL);
因此,您只须要在I2C_SDA和I2C_SCL变量上设置所需的SDA和SCL GPIO 。
但是,如果您利用库与这些传感器进行通信,则这可能无法正常事情,选择其他引脚可能会有些棘手。发生这种情形是由于,如果在初始化库时不通报自己的Wire实例,这些库可能会覆盖您的引脚。
在这种情形下,您须要仔细查看.cpp库文件,并理解如何通报自己的TwoWire参数。
例如,如果您仔细看一下,您会创造可以将自己的TwoWire通报给begin()方法。
因此,利用其他引脚(例如,GPIO 33作为SDA和GPIO 32作为SCL)从BME280读取示例程序如下。
#include <Wire.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_BME280.h>
#define I2C_SDA 33
#define I2C_SCL 32
#define SEALEVELPRESSURE_HPA (1013.25)
TwoWire I2CBME = TwoWire(0);
Adafruit_BME280 bme;
unsigned long delayTime;
void setup() {
Serial.begin(115200);
Serial.println(F("BME280 test"));
I2CBME.begin(I2C_SDA, I2C_SCL, 100000);
bool status;
status = bme.begin(0x76, &I2CBME);
if (!status) {
Serial.println("Could not find a valid BME280 sensor, check wiring!");
while (1);
}
Serial.println("-- Default Test --");
delayTime = 1000;
Serial.println();
}
void loop() {
printValues();
delay(delayTime);
}
void printValues() {
Serial.print("Temperature = ");
Serial.print(bme.readTemperature());
Serial.println(" C");
Serial.print("Pressure = ");
Serial.print(bme.readPressure() / 100.0F);
Serial.println(" hPa");
Serial.print("Approx. Altitude = ");
Serial.print(bme.readAltitude(SEALEVELPRESSURE_HPA));
Serial.println(" m");
Serial.print("Humidity = ");
Serial.print(bme.readHumidity());
Serial.println(" %");
Serial.println();
}
让我们看一下利用其他I2C引脚的干系部分。
首先,在I2C_SDA和I2C_SCL变量上定义新的I2C引脚。在这种情形下,我们利用的是GPIO 33和GPIO 32。
#define I2C_SDA 33
#define I2C_SCL 32
创建一个新的TwoWire实例。在这种情形下,它称为I2CBME。这只是创建一个I2C总线。
TwoWire I2CBME = TwoWire(0);
在setup()中,利用您先前定义的引脚初始化I2C通信。第三个参数是时钟频率。
I2CBME.begin(I2C_SDA, I2C_SCL, 400000);
末了,用您的传感器地址和TwoWire工具初始化一个BME280工具。
status = bme.begin(0x76, &I2CBME);
之后,您可以对bme工具利用常规方法来要求温度,湿度和压力。
把稳:如果您利用的库在其文件中利用诸如wire.begin()之类的语句,则可能须要注释该行,以便可以利用自己的引脚。
具有多个I2C器件的ESP32如前所述,每个I2C设备都有其自己的地址,因此可能在同一总线上具有多个I2C设备。
多个I2C设备(相同的总线,不同的地址)当我们有多个具有不同地址的设备时,如何设置它们很大略:
将两个外设都连接到ESP32的SCL和SDA线路;在代码中,通过其地址引用每个外围设备;看下面的示例,该示例从BME280传感器(通过I2C)获取传感器读数,并将结果显示在I2C OLED显示器上。
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_BME280.h>
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1);
Adafruit_BME280 bme;
void setup() {
Serial.begin(115200);
if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
Serial.println(F("SSD1306 allocation failed"));
for(;;);
}
bool status = bme.begin(0x76);
if (!status) {
Serial.println("Could not find a valid BME280 sensor, check wiring!");
while (1);
}
delay(2000);
display.clearDisplay();
display.setTextColor(WHITE);
}
void loop() {
display.clearDisplay();
display.setTextSize(1);
display.setCursor(0,0);
display.print("Temperature: ");
display.setTextSize(2);
display.setCursor(0,10);
display.print(String(bme.readTemperature()));
display.print(" ");
display.setTextSize(1);
display.cp437(true);
display.write(167);
display.setTextSize(2);
display.print("C");
display.setTextSize(1);
display.setCursor(0, 35);
display.print("Humidity: ");
display.setTextSize(2);
display.setCursor(0, 45);
display.print(String(bme.readHumidity()));
display.print(" %");
display.display();
delay(1000);
}
由于OLED和BME280具有不同的地址,因此我们可以利用相同的SDA和SCL线,而不会涌现任何问题。OLED显示地址为0x3C,BME280地址为0x76。
if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
Serial.println(F("SSD1306 allocation failed"));
for(;;);
}
bool status = bme.begin(0x76);
if (!status) {
Serial.println("Could not find a valid BME280 sensor, check wiring!");
while (1);
}
多个I2C设备(相同地址)
但是,如果您有多个具有相同地址的外设怎么办?例如,多个OLED显示器或多个BME280传感器?有几种办理方案。
变动设备的I2C地址;利用I2C多路复用器。变动I2C地址许多分线板都可以选择根据其电路变动I2C地址。例如,看下面的OLED显示器。
通过将电阻器放在一侧或另一侧,可以选择不同的I2C地址。其他组件也会发生这种情形。
利用I2C多路复用器但是,在前面的示例中,这仅许可您在同一总线上具有两个I2C显示器:一个具有0x3C地址,另一个具有0x3D地址。
此外,有时变动I2C地址并非易事。因此,为了在同一I2C总线中拥有多个具有相同地址的设备,您可以利用I2C多路复用器,如TCA9548A,它许可您与多达8个具有相同地址的设备进行通信。
ESP32利用两个I2C总线接口
要利用ESP32的两个I2C总线接口,您须要创建两个TwoWire实例。
TwoWire I2Cone = TwoWire(0);
TwoWire I2Ctwo = TwoWire(1)
然后,以所需的频率在所需的引脚上初始化I2C通信。
void setup() {
I2Cone.begin(SDA_1, SCL_1, freq1);
I2Ctwo.begin(SDA_2, SCL_2, freq2);
}
然后,您可以利用Wire.h库中的方法与I2C总线接口进行交互。
一个更大略的替代方法是利用预定义的Wire()和Wire1()工具。Wire()。begin()利用默认引脚和默认频率在第一条I2C总线上创建I2C通信。对付Wire1.begin(),您该当通报所需的SDA和SCL引脚以及频率。
setup(){
Wire.begin();
Wire1.begin(SDA_2, SCL_2, freq);
}
这种方法许可您利用两条I2C总线,个中一条利用默认参数。
为了更好地理解其事情事理,我们来看一个大略的示例,该示例从两个BME280传感器读取温度,湿度和压力。
每个传感器都连接到不同的I2C总线。
· I2C总线1:利用GPIO 27(SDA)和GPIO 26(SCL);
· I2C总线2:利用GPIO 33(SDA)和GPIO 32(SCL);
#include <Wire.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_BME280.h>
#define SDA_1 27
#define SCL_1 26
#define SDA_2 33
#define SCL_2 32
TwoWire I2Cone = TwoWire(0);
TwoWire I2Ctwo = TwoWire(1);
Adafruit_BME280 bme1;
Adafruit_BME280 bme2;
void setup() {
Serial.begin(115200);
Serial.println(F("BME280 test"));
I2Cone.begin(SDA_1, SCL_1, 100000);
I2Ctwo.begin(SDA_2, SCL_2, 100000);
bool status1 = bme1.begin(0x76, &I2Cone);
if (!status1) {
Serial.println("Could not find a valid BME280_1 sensor, check wiring!");
while (1);
}
bool status2 = bme2.begin(0x76, &I2Ctwo);
if (!status2) {
Serial.println("Could not find a valid BME280_2 sensor, check wiring!");
while (1);
}
Serial.println();
}
void loop() {
Serial.print("Temperature from BME1= ");
Serial.print(bme1.readTemperature());
Serial.println(" C");
Serial.print("Humidity from BME1 = ");
Serial.print(bme1.readHumidity());
Serial.println(" %");
Serial.print("Pressure from BME1 = ");
Serial.print(bme1.readPressure() / 100.0F);
Serial.println(" hPa");
Serial.println("--------------------");
Serial.print("Temperature from BME2 = ");
Serial.print(bme2.readTemperature());
Serial.println(" C");
Serial.print("Humidity from BME2 = ");
Serial.print(bme2.readHumidity());
Serial.println(" %");
Serial.print("Pressure from BME2 = ");
Serial.print(bme2.readPressure() / 100.0F);
Serial.println(" hPa");
Serial.println("--------------------");
delay(5000);
}
让我们看一下利用两个I2C总线接口的干系部分。
定义要利用的SDA和SCL引脚:
#define SDA_1 27
#define SCL_1 26
#define SDA_2 33
#define SCL_2 32
创建两个TwoWire工具(两个I2C总线接口):
TwoWire I2Cone = TwoWire(0);
TwoWire I2Ctwo = TwoWire(1);
创建Adafruit_BME280库的两个实例以与您的传感器进行交互:bme1和bme2。
Adafruit_BME280 bme1;
Adafruit_BME280 bme2;
在定义的引脚和频率上初始化I2C通信:
I2Cone.begin(SDA_1, SCL_1, 100000);
I2Ctwo.begin(SDA_2, SCL_2, 100000);
然后,应利用精确的地址和I2C总线初始化bme1和bme2工具。bme1利用I2Cone:
bool status = bme1.begin(0x76, &I2Cone);
而bme2用场I2Ctwo:
bool status1 = bme2.begin(0x76, &I2Ctwo);
现在,您可以在bme1和bme2工具上利用Adafruit_BME280库中的方法来读取温度,湿度和压力。
另一种选择
为了大略起见,可以利用预定义的Wire()和Wire1()工具:
· Wire():在默认引脚GPIO 21(SDA)和GPIO 22(SCL)上创建I2C总线
· Wire1(SDA_2,SCL_2,freq):以所需的频率在定义的SDA_2和SCL_2引脚上创建I2C总线。
这是相同的示例,但是利用此方法。现在,您的一个传感器利用默认引脚,另一个传感器利用GPIO 32和GPIO 33。
#include <Wire.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_BME280.h>
#define SDA_2 33
#define SCL_2 32
Adafruit_BME280 bme1;
Adafruit_BME280 bme2;
void setup() {
Serial.begin(115200);
Serial.println(F("BME280 test"));
Wire.begin();
Wire1.begin(SDA_2, SCL_2);
bool status1 = bme1.begin(0x76);
if (!status1) {
Serial.println("Could not find a valid BME280_1 sensor, check wiring!");
while (1);
}
bool status2 = bme2.begin(0x76, &Wire1);
if (!status2) {
Serial.println("Could not find a valid BME280_2 sensor, check wiring!");
while (1);
}
Serial.println();
}
void loop() {
Serial.print("Temperature from BME1= ");
Serial.print(bme1.readTemperature());
Serial.println(" C");
Serial.print("Humidity from BME1 = ");
Serial.print(bme1.readHumidity());
Serial.println(" %");
Serial.print("Pressure from BME1 = ");
Serial.print(bme1.readPressure() / 100.0F);
Serial.println(" hPa");
Serial.println("--------------------")
Serial.print("Temperature from BME2 = ");
Serial.print(bme2.readTemperature());
Serial.println(" C");
Serial.print("Humidity from BME2 = ");
Serial.print(bme2.readHumidity());
Serial.println(" %");
Serial.print("Pressure from BME2 = ");
Serial.print(bme2.readPressure() / 100.0F);
Serial.println(" hPa");
Serial.println("--------------------");
delay(5000);
}
您该当在串口监视器上同时得到两个传感器的读数。
在本教程中,您理解了有关ESP32的I2C通信协议的更多信息。希望对您有帮助。