ESP32 I2C: настройка пинов, несколько шин и периферийных устройств (Arduino IDE)
ESP32 имеет два интерфейса шины I2C, которые могут работать как мастер (master) или как подчиненное устройство (slave). В этом руководстве мы рассмотрим протокол связи I2C с ESP32 в Arduino IDE: как выбрать пины I2C, подключить несколько устройств I2C к одной шине и как использовать два интерфейса шины I2C.
В этом руководстве мы рассмотрим следующие темы:
Мы будем программировать ESP32 с помощью Arduino IDE, поэтому перед началом работы с этим руководством у вас должно быть установлено дополнение ESP32 в Arduino IDE. Следуйте следующему руководству, чтобы установить ESP32 в Arduino IDE, если вы этого еще не сделали.
Введение в протокол I2C для ESP32
I2C означает Inter Integrated Circuit (произносится «ай-квадрат-си»), и это синхронный протокол связи с несколькими мастерами и несколькими подчиненными устройствами. Вы можете подключить:
несколько подчиненных устройств к одному мастеру: например, ваш ESP32 считывает данные с датчика BME280 по I2C и выводит показания на I2C OLED дисплей.
несколько мастеров, управляющих одним подчиненным устройством: например, две платы ESP32, записывающие данные на один и тот же I2C OLED дисплей.
Мы часто используем этот протокол с ESP32 для связи с внешними устройствами, такими как датчики и дисплеи. В таких случаях ESP32 является мастером (master), а внешние устройства – подчиненными (slave).
У нас есть несколько руководств по работе ESP32 с устройствами I2C:
Интерфейсы шины I2C ESP32
ESP32 поддерживает связь I2C через два интерфейса шины I2C, которые могут работать как мастер или подчиненное устройство, в зависимости от конфигурации пользователя. Согласно техническому описанию ESP32, интерфейсы I2C ESP32 поддерживают:
Стандартный режим (100 Кбит/с)
Быстрый режим (400 Кбит/с)
До 5 МГц, однако ограничен силой подтяжки SDA
7-битный/10-битный режим адресации
Двойной режим адресации. Пользователи могут программировать командные регистры для управления интерфейсами I2C, что обеспечивает большую гибкость
Подключение устройств I2C к ESP32
Протокол связи I2C использует два провода для обмена информацией. Один используется для тактового сигнала (SCL), а другой – для отправки и приема данных (SDA).
Примечание: на многих платах расширения линия SDA также может быть обозначена как SDI, а линия SCL – как SCK.
Линии SDA и SCL имеют активный низкий уровень, поэтому они должны быть подтянуты к питанию через резисторы. Типичные значения – 4,7 кОм для устройств на 5В и 2,4 кОм для устройств на 3,3В.
Большинство датчиков, которые мы используем в наших проектах, представляют собой платы расширения, на которых уже установлены подтягивающие резисторы. Поэтому обычно при работе с такими электронными компонентами вам не нужно беспокоиться об этом.
Подключение устройства I2C к ESP32 обычно так же просто, как подключение GND к GND, SDA к SDA, SCL к SCL и положительного напряжения питания к периферийному устройству, обычно 3,3В (но это зависит от используемого модуля).
Устройство I2C |
ESP32 |
|---|---|
SDA |
SDA (по умолчанию GPIO 21) |
SCL |
SCL (по умолчанию GPIO 22) |
GND |
GND |
VCC |
обычно 3,3В или 5В |
При использовании ESP32 с Arduino IDE пины I2C по умолчанию – GPIO 22 (SCL) и GPIO 21 (SDA), но вы можете настроить свой код на использование любых других пинов.
Рекомендуемое чтение: Справочное руководство по GPIO ESP32
Сканирование I2C адреса с ESP32
При связи I2C каждое подчиненное устройство на шине имеет свой собственный адрес – шестнадцатеричное число, которое позволяет ESP32 общаться с каждым устройством.
I2C адрес обычно можно найти в техническом описании компонента. Однако, если его трудно найти, вам может потребоваться запустить скетч сканера I2C, чтобы определить I2C адрес.
Вы можете использовать следующий скетч для определения I2C адресов ваших устройств.
/*********
Rui Santos
Complete project details at https://randomnerdtutorials.com
*********/
#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);
}
Вы получите примерно такой результат в мониторе последовательного порта. Этот конкретный пример для I2C LCD дисплея.
Использование других пинов I2C с ESP32 (изменение пинов I2C по умолчанию)
С ESP32 вы можете назначить почти любому пину возможности I2C, вам просто нужно указать это в коде.
При использовании ESP32 с Arduino IDE, используйте библиотеку Wire.h для связи с устройствами по I2C. С этой библиотекой вы инициализируете I2C следующим образом:
Wire.begin(I2C_SDA, I2C_SCL);
Таким образом, вам просто нужно установить желаемые GPIO для SDA и SCL в переменных I2C_SDA и I2C_SCL.
Однако, если вы используете библиотеки для связи с этими датчиками, это может не сработать, и выбор других пинов может быть немного сложным. Это происходит потому, что эти библиотеки могут перезаписать ваши пины, если вы не передадите свой собственный экземпляр Wire при инициализации библиотеки.
В таких случаях вам нужно внимательнее изучить файлы .cpp библиотеки и посмотреть, как передать свои собственные параметры TwoWire.
Например, если вы внимательно посмотрите на библиотеку Adafruit BME280, вы обнаружите, что можете передать свой собственный TwoWire в метод begin().
Итак, пример скетча для чтения данных с BME280 с использованием других пинов, например GPIO 33 в качестве SDA и GPIO 32 в качестве SCL, выглядит следующим образом.
/*
Rui Santos
Complete project details at https://RandomNerdTutorials.com/esp32-i2c-communication-arduino-ide/
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files.
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
*/
#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;
// default settings
// (you can also pass in a Wire library object like &Wire2)
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");
// Convert temperature to Fahrenheit
/*Serial.print("Temperature = ");
Serial.print(1.8 * bme.readTemperature() + 32);
Serial.println(" *F");*/
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 в переменных I2C_SDA и I2C_SCL. В данном случае мы используем 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);
Наконец, инициализируйте объект BME280 с адресом вашего датчика и объектом TwoWire.
status = bme.begin(0x76, &I2CBME);
После этого вы можете использовать обычные методы вашего объекта bme для запроса температуры, влажности и давления.
Примечание: если библиотека, которую вы используете, содержит оператор wire.begin() в своем файле, вам может потребоваться закомментировать эту строку, чтобы вы могли использовать свои собственные пины.
ESP32 с несколькими устройствами I2C
Как мы упоминали ранее, каждое устройство I2C имеет свой собственный адрес, поэтому можно иметь несколько устройств I2C на одной шине.
Когда у нас есть несколько устройств с разными адресами, их настройка тривиальна:
подключите оба периферийных устройства к линиям SCL и SDA ESP32;
в коде обращайтесь к каждому периферийному устройству по его адресу;
Посмотрите на следующий пример, который получает показания датчика BME280 (по I2C) и отображает результаты на I2C OLED дисплее.
/*
Rui Santos
Complete project details at https://RandomNerdTutorials.com/esp32-i2c-communication-arduino-ide/
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files.
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
*/
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_BME280.h>
#define SCREEN_WIDTH 128 // OLED display width, in pixels
#define SCREEN_HEIGHT 64 // OLED display height, in pixels
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 temperature
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 humidity
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);
}
Рекомендуемое чтение: ESP32 OLED дисплей с Arduino IDE
Но что если у вас есть несколько периферийных устройств с одинаковым адресом? Например, несколько OLED дисплеев или несколько датчиков BME280? Есть несколько решений.
изменить I2C адрес устройства;
использовать I2C мультиплексор.
Многие платы расширения имеют возможность изменить I2C адрес в зависимости от их схемотехники. Например, посмотрите на следующий OLED дисплей.
Размещая резистор на одной или другой стороне, вы можете выбрать разные I2C адреса. Это также работает с другими компонентами.
Однако в предыдущем примере это позволяет иметь только два I2C дисплея на одной шине: один с адресом 0x3C и другой с адресом 0x3D.
Кроме того, иногда изменить I2C адрес не так просто. Поэтому, чтобы иметь несколько устройств с одинаковым адресом на одной шине I2C, вы можете использовать I2C мультиплексор, такой как TCA9548A, который позволяет общаться с 8 устройствами с одинаковым адресом.
У нас есть подробное руководство, объясняющее, как использовать I2C мультиплексор для подключения нескольких устройств с одинаковым адресом к ESP32: Руководство по TCA9548A I2C мультиплексору: ESP32, ESP8266, Arduino.
ESP32 с двумя интерфейсами шины I2C
Для использования двух интерфейсов шины I2C ESP32 вам нужно создать два экземпляра 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(); //uses default SDA and SCL and 100000HZ freq
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);
Нажмите на изображение для увеличения
/*
Rui Santos
Complete project details at https://RandomNerdTutorials.com/esp32-i2c-communication-arduino-ide/
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files.
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
*/
#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() {
// Read from bme1
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("--------------------");
// Read from bme2
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);
Затем вы должны инициализировать объекты bme1 и bme2 с правильным адресом и шиной I2C. bme1 использует I2Cone:
bool status = bme1.begin(0x76, &I2Cone);
А bme2 использует I2Ctwo:
bool status1 = bme2.begin(0x76, &I2Ctwo);
Теперь вы можете использовать методы библиотеки Adafruit_BME280 для объектов bme1 и bme2 для чтения температуры, влажности и давления.
Другая альтернатива
Для простоты вы можете использовать предопределенные объекты Wire() и Wire1():
Wire(): создает связь I2C на пинах по умолчанию GPIO 21 (SDA) и GPIO 22 (SCL)
Wire1(SDA_2, SCL_2, freq): создает связь I2C на определенных пинах SDA_2 и SCL_2 с заданной частотой.
Нажмите на изображение для увеличения
Вот тот же пример, но с использованием этого метода. Теперь один из ваших датчиков использует пины по умолчанию, а другой – GPIO 32 и GPIO 33.
/*
Rui Santos
Complete project details at https://RandomNerdTutorials.com/esp32-i2c-communication-arduino-ide/
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files.
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
*/
#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() {
// Read from bme1
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("--------------------");
// Read from bme2
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 Master и Slave (I2C связь между двумя ESP32)
Вы можете обмениваться данными между двумя платами ESP32, используя протокол связи I2C. Одна плата должна быть настроена как подчиненное устройство (slave), а другая – как мастер (master).
Плата-подчиненное устройство содержит данные, которые мастер может запросить.
Вы должны соединить обе платы ESP32, используя соответствующие пины I2C. Не забудьте соединить пины GND вместе.
На предыдущем рисунке показана схема подключения для двух плат ESP32 DOIT V1. Пины I2C по умолчанию – GPIO 21 (SDA) и GPIO 22 (SCL). Если вы используете ESP32-C3, ESP32-S3 или другую модель, пины I2C по умолчанию могут отличаться. Пожалуйста, проверьте распиновку используемой платы. Вы также можете использовать пользовательские пины I2C.
Для полного руководства по обмену данными между двумя платами ESP32 с использованием протокола связи I2C перейдите по ссылке ниже:
Заключение
В этом руководстве вы узнали больше о протоколе связи I2C с ESP32. Мы надеемся, что информация в этой статье была для вас полезной.
Чтобы узнать больше о GPIO ESP32, прочитайте наше справочное руководство: Справочник по распиновке ESP32: какие GPIO пины следует использовать?
Узнайте больше об ESP32 с нашими ресурсами:
Learn ESP32 with Arduino IDE (видеокурс + электронная книга)
MicroPython Programming with ESP32 and ESP8266 (электронная книга)
Спасибо за чтение.
Источник: ESP32 I2C Communication: Set Pins, Multiple Bus Interfaces and Peripherals (Arduino IDE)