В данном руководстве мы рассмотрим, как создать веб-сервер на базе ESP32 для отображения показаний датчика BME280, измеряющего температуру, влажность и давление. То есть, можно легко сделать компактную погодную станцию и выводить эти данные на веб-странице, что мы и сделаем.
Перед началом работы убедитесь, что дополнения для ESP32 установлены в вашей Arduino IDE.
Также, вы можете почитать и другие руководства, в которых используется датчик BME280.
Что нам потребуется?
Для работы нам необходимы следующие комплектующие:
Как работать с BME 280?
Датчик измеряет температуру, влажность и давление. Вы также можете рассчитать высоту над уровнем моря, оценив разность между измеренным давлением и давлением над уровнем моря.
Вариантов BME280 несколько, но мы будем использовать такой, как на картинке:
Датчик может обмениваться данными по протоколам SPI и I2C (бывают датчики, которые используют только I2C – у них всего четыре пина).
Пины для протокола SPI
- SCK – this is the SPI Clock pin
- SDO – MISO
- SDI – MOSI
- CS
Пины для протокола I2C
- SCK (SCL)
- SDI (SDA)
Схема
Мы будем использовать протокол I2C. Подключаем датчик к пинам SDA и SCL на плате ESP32, как показано на следующей схеме:
Библиотеки для датчика BME280
Чтобы получить показания от модуля датчика BME280, мы будем использовать библиотеку Adafruit_BME280. Вам также необходимо установить библиотеку Выполните следующие шаги, чтобы установить библиотеки в вашу Arduino IDE:
- Откройте IDE Arduino и выберите Скетч> Подключить библиотеку> Управлять библиотеками. Должен открыться менеджер библиотек.
- Найдите adafruit bme280 в поле поиска и установите библиотеку.
Чтобы использовать библиотеку BME280, также необходимо установить Adafruit Unified Sensor. Выполните следующие шаги, чтобы установить библиотеку в вашу среду разработки Arduino:
- Найдите Adafruit Unified Sensor в поле поиска. Прокрутите до конца, найдите библиотеку и установите ее.
После установки библиотек перезапустите IDE Arduino.
Получение показаний датчика
Чтобы понять, как получать показания, рассмотрим небольшой пример:
После установки необходимых библиотек перейдите к Файл> Примеры> Adafruit BME280 library> bme280 test.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 |
/********* Complete project details at https://randomnerdtutorials.com *********/ #include <Wire.h> #include <Adafruit_Sensor.h> #include <Adafruit_BME280.h> /*#include <SPI.h> #define BME_SCK 18 #define BME_MISO 19 #define BME_MOSI 23 #define BME_CS 5*/ #define SEALEVELPRESSURE_HPA (1013.25) Adafruit_BME280 bme; // I2C //Adafruit_BME280 bme(BME_CS); //Adafruit_BME280 bme(BME_CS, BME_MOSI, BME_MISO, BME_SCK); unsigned long delayTime; void setup() { Serial.begin(9600); Serial.println(F("BME280 test")); bool status; // настройки по умолчанию status = bme.begin(0x76); 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(); } |
Как работает код?
Подключаем библиотеки
1 2 3 4 5 |
#include <Wire.h> #include <Adafruit_Sensor.h> #include <Adafruit_BME280.h> |
Инициализируем протокол I2C
Вы можете закомментировать следующие строки, если используете протокол SPI
1 2 3 4 5 6 7 8 9 |
/*#include <SPI.h> #define BME_SCK 18 #define BME_MISO 19 #define BME_MOSI 23 #define BME_CS 5*/ |
Примечание: Если вы используете протокол SPI, то вам нужно сменить распиновку согласно следующей таблице:
SPI | MOSI | MISO | CLK | CS |
HSPI | GPIO 13 | GPIO 12 | GPIO 14 | GPIO 15 |
VSPI | GPIO 23 | GPIO 19 | GPIO 18 | GPIO 5 |
Давление на уровне моря
Создадим переменную SEALEVELPRESSURE_HPA
1 |
#define SEALEVELPRESSURE_HPA (1013.25) |
Так мы задаем значение давления на уровне моря (в гектопаскалях). Эта переменная будет использоваться для вычисления высоты над уровнем моря. Однако, так мы получим только приближенное значение, для более точного результата нужно указать давление на уровне моря для своей местности.
I2C
Для работы протокола I2C требуется создать объект Adafruit_BME280 с именем bme
1 |
Adafruit_BME280 bme; |
Если вы хотите использовать протокол SPI, закомментируйте предыдущую строку и раскомментируйте следующие:
1 2 3 |
//Adafruit_BME280 bme(BME_CS); //Adafruit_BME280 bme(BME_CS, BME_MOSI, BME_MISO, BME_SCK); |
setup()
Здесь запускаем монитор порта:
1 |
Serial.begin(9600); |
И запускаем датчик:
1 2 3 4 5 6 7 8 9 |
status = bme.begin(0x76); if (!status) { Serial.println("Could not find a valid BME280 sensor, check wiring!"); while (1); } |
Вывод показаний
Функция printValues() считывает значения с датчика и выводит их в монитор порта.
1 2 3 4 5 6 7 |
void loop() { printValues(); delay(delayTime); } |
Сама эта функция включает в себя следующие:
- bme.readTemperature() – считывает температуру в градусах Цельсия;
- bme.readHumidity() – считывает влажность;
- bme.readPressure() –считывает давление в гектопаскалях;
- bme.readAltitude(SEALEVELPRESSURE_HPA) – высчитывает высоту над уровнем моря
Загрузите код и откройте монитор порта на скорости 9600 бод. Данные должны начать поступать в монитор порта:
Создаем таблицу на HTML
Как вы наверное заметили, мы выводим показания датчика на веб-странице с помощью таблицы, написанной на HTML.
Для создания таблицы нам нужны тэги <table> и </table>.
Для создания ряда – <tr> и </tr>, заголовка – <th> и </th> , и для каждой ячейки – <td> и </td>.
Создаем следующую таблицу:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
<table> <tr> <th>MEASUREMENT</th> <th>VALUE</th> </tr> <tr> <td>Temp. Celsius</td> <td>--- *C</td> </tr> <tr> <td>Pressure</td> <td>--- hPa</td> </tr> <tr> <td>Approx. Altitude</td> <td>--- meters</td></tr> <tr> <td>Humidity</td> <td>--- %</td> </tr> </table> |
Заголовком нашей таблицы будут две ячейки с текстом MEASUREMENT и VALUE. Затем мы создаем пять рядов для отображения показаний датчика с помощью тэгов <tr> и </tr>. В каждом и рядов создаем по две ячейки с помощью тэгов <td> и </td>, в первой мы записываем название измеряемой величины, а во второй – значение. Три дефиса должны заменяться показаниями датчика, считываемых с помощью ранее написанной функции.
Вы можете сохранить код в файле, например table.html, открыть его и посмотреть, как он выглядит:
В данной таблице не использовались стили CSS. В зависимости от ваших пожеланий и навыков, вы можете сделать таблицу гораздо красивее.
Создаем веб-сервер
Итак, мы знаем, как получать показания датчиков и как выводить результаты в таблице. Теперь перейдем к созданию веб-сервера. Если вы изучали другие руководства по ESP32, большая часть кода должна быть вам знакома.
Скопируйте данный код в свою среду Arduino, но пока не загружайте. Вам потребуется указать свои учетные данные сети.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 |
/********* Rui Santos Complete project details at https://randomnerdtutorials.com *********/ // Подключаем библиотеку Wi-Fi #include <WiFi.h> #include <Wire.h> #include <Adafruit_BME280.h> #include <Adafruit_Sensor.h> //раскомментируйте эти строки, если используете SPI /*#include <SPI.h> #define BME_SCK 18 #define BME_MISO 19 #define BME_MOSI 23 #define BME_CS 5*/ #define SEALEVELPRESSURE_HPA (1013.25) Adafruit_BME280 bme; //Adafruit_BME280 bme(BME_CS); //Adafruit_BME280 bme(BME_CS, BME_MOSI, BME_MISO, BME_SCK); // Замените своими данными const char* ssid = "ЗАМЕНИТЕ СВОИМ SSID"; const char* password = "УКАЖИТЕ ПАРОЛЬ"; // Указываем, что сервер будет использовать 80 порт WiFiServer server(80); // Переменная для хранения HTTP запроса String header; // Текущее время unsigned long currentTime = millis(); // Переменная для сохранения времени подключения пользователя unsigned long previousTime = 0; // Определяем задержку в миллисекундах const long timeoutTime = 2000; void setup() { Serial.begin(115200); bool status; // настройки по умолчанию //status = bme.begin(); if (!bme.begin(0x76)) { Serial.println("Could not find a valid BME280 sensor, check wiring!"); while (1); } // Подключаемся к Wi-Fi Serial.print("Connecting to "); Serial.println(ssid); WiFi.begin(ssid, password); while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); } // Выводим IP-адрес и запускаем веб-сервер Serial.println(""); Serial.println("WiFi connected."); Serial.println("IP address: "); Serial.println(WiFi.localIP()); server.begin(); } void loop(){ WiFiClient client = server.available(); // Ждем подключения пользователя if (client) { // Если есть подключение, currentTime = millis(); previousTime = currentTime; Serial.println("New Client."); // выводим сообщение в монитор порта String currentLine = ""; // создаем строку для хранения входящих данных while (client.connected() && currentTime - previousTime <= timeoutTime) { // выполняем программу, пока пользователь подключен currentTime = millis(); if (client.available()) { // проверяем, есть ли входящее сообщение char c = client.read(); // читаем и Serial.write(c); // выводим в монитор порта header += c; if (c == '\n') { // если входящее сообщение – переход на новую строку (пустая строка) // то считаем это концом HTTP запроса и выдаем ответ if (currentLine.length() == 0) { // заголовок всегда начинается с ответа (например, HTTP/1.1 200 OK) // добавляем тип файла ответа: client.println("HTTP/1.1 200 OK"); client.println("Content-type:text/html"); client.println("Connection: close"); client.println(); // Выводим HTML-страницу client.println("<!DOCTYPE html><html>"); client.println("<head><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">"); client.println("<link rel=\"icon\" href=\"data:,\">"); // Добавляем стили CSS client.println("<style>body { text-align: center; font-family: \"Trebuchet MS\", Arial;}"); client.println("table { border-collapse: collapse; width:35%; margin-left:auto; margin-right:auto; }"); client.println("th { padding: 12px; background-color: #0043af; color: white; }"); client.println("tr { border: 1px solid #ddd; padding: 12px; }"); client.println("tr:hover { background-color: #bcbcbc; }"); client.println("td { border: none; padding: 12px; }"); client.println(".sensor { color:white; font-weight: bold; background-color: #bcbcbc; padding: 1px; }"); // Заголовок веб-страницы client.println("</style></head><body><h1>ESP32 with BME280</h1>"); client.println("<table><tr><th>MEASUREMENT</th><th>VALUE</th></tr>"); client.println("<tr><td>Temp. Celsius</td><td><span class=\"sensor\">"); client.println(bme.readTemperature()); client.println(" *C</span></td></tr>"); client.println("<tr><td>Temp. Fahrenheit</td><td><span class=\"sensor\">"); client.println(1.8 * bme.readTemperature() + 32); client.println(" *F</span></td></tr>"); client.println("<tr><td>Pressure</td><td><span class=\"sensor\">"); client.println(bme.readPressure() / 100.0F); client.println(" hPa</span></td></tr>"); client.println("<tr><td>Approx. Altitude</td><td><span class=\"sensor\">"); client.println(bme.readAltitude(SEALEVELPRESSURE_HPA)); client.println(" m</span></td></tr>"); client.println("<tr><td>Humidity</td><td><span class=\"sensor\">"); client.println(bme.readHumidity()); client.println(" %</span></td></tr>"); client.println("</body></html>"); // Ответ HTTP также заканчивается пустой строкой client.println(); // Прерываем выполнение программы break; } else { // если у нас есть новый запрос, очищаем строку currentLine = ""; } } else if (c != '\r') { // но, если отправляемая строка не пустая currentLine += c; // добавляем ее в конец строки } } } // Очищаем заголовок header = ""; // Сбрасываем соединение client.stop(); Serial.println("Client disconnected."); Serial.println(""); } } |
Укажите свои SSID и пароль в кавычках.
1 2 |
const char* ssid = ""; const char* password = ""; |
Убедитесь, что выбрали правильный COM-порт и загрузите свой код на плату. После загрузки откройте монитор порта на скорости 115200 бод и скопируйте IP-адрес платы.
В браузере вставьте этот адрес. Должна отобразиться страница с текущими показаниями датчика. Для обновления показаний требуется обновлять веб-страницу.
Как работает код?
Сначала подключаем необходимые библиотеки:
1 2 3 4 5 6 7 |
#include <WiFi.h> #include <Wire.h> #include <Adafruit_BME280.h> #include <Adafruit_Sensor.h> |
В следующей строке мы определяем переменную, которая хранит значение давления на уровне моряэ
1 |
#define SEALEVELPRESSURE_HPA (1013.25) |
Создаем объект с именем bme, который по умолчанию создает соединение по протоколу I2C
1 |
Adafruit_BME280 bme; // I2C |
Здесь требуется указать свои данные сети
1 2 3 |
const char* ssid = ""; const char* password = ""; |
Указываем, что веб-сервер использует 80 порт.
1 |
WiFiServer server(80); |
Создаем строковую переменную для хранения HTTP-запроса
1 |
String header; |
setup()
Запускаем монитор порта
1 |
Serial.begin(115200); |
Проверяем, успешно ли запустился датчик
1 2 3 4 5 |
if (!bme.begin(0x76)) { Serial.println("Could not find a valid BME280 sensor, check wiring!"); while (1); |
Устанавливаем соединение по Wi-Fi и ждем подключения, выводим IP-адрес платы в монитор порта
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
Serial.print("Connecting to "); Serial.println(ssid); WiFi.begin(ssid, password); while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); } Serial.println(""); Serial.println("WiFi connected."); Serial.println("IP address: "); Serial.println(WiFi.localIP()); server.begin(); |
loop()
В этой функции мы задаем, что должно происходить при подключении нового пользователя. В следующей строке ждем подключения клиента.
1 |
WiFiClient client = server.available(); |
Сохраняем полученные данные при подключении клиента. Запускаем цикл, выполняющийся пока пользователь подключен. Не рекомендуется изменять следующий код, если вы не до конца понимаете, что он делает.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
if (client) { // Если есть подключение, currentTime = millis(); previousTime = currentTime; Serial.println("New Client."); // выводим сообщение в монитор порта String currentLine = ""; // создаем строку для хранения входящих данных while (client.connected() && currentTime - previousTime <= timeoutTime) { // выполняем программу, пока пользователь подключен currentTime = millis(); if (client.available()) { // проверяем, есть ли входящее сообщение char c = client.read(); // читаем и Serial.write(c); // выводим в монитор порта header += c; if (c == '\n') { // если входящее сообщение – переход на новую строку (пустая строка) // то считаем это концом HTTP запроса и выдаем ответ if (currentLine.length() == 0) { // заголовок всегда начинается с ответа (например, HTTP/1.1 200 OK) // добавляем тип файла ответа: client.println("HTTP/1.1 200 OK"); client.println("Content-type:text/html"); client.println("Connection: close"); client.println(); |
Отображаем веб-страницу
Теперь необходимо создать веб-страницу для отображения показаний датчика.
Веб страница отправляется пользователю при выполнении команды client.println(). Вы можете выбрать, какие показания отправлять пользователю в аргументе функции.
Следующий код отправляет таблицу с показаниями датчика пользователю.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
client.println("<!DOCTYPE html><html>"); client.println("<head><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">"); client.println("<link rel=\"icon\" href=\"data:,\">"); // Добавляем стили CSS client.println("<style>body { text-align: center; font-family: \"Trebuchet MS\", Arial;}"); client.println("table { border-collapse: collapse; width:35%; margin-left:auto; margin-right:auto; }"); client.println("th { padding: 12px; background-color: #0043af; color: white; }"); client.println("tr { border: 1px solid #ddd; padding: 12px; }"); client.println("tr:hover { background-color: #bcbcbc; }"); client.println("td { border: none; padding: 12px; }"); client.println(".sensor { color:white; font-weight: bold; background-color: #bcbcbc; padding: 1px; }"); // Заголовок веб-страницы client.println("</style></head><body><h1>ESP32 with BME280</h1>"); client.println("<table><tr><th>MEASUREMENT</th><th>VALUE</th></tr>"); client.println("<tr><td>Temp. Celsius</td><td><span class=\"sensor\">"); client.println(bme.readTemperature()); client.println(" *C</span></td></tr>"); client.println("<tr><td>Temp. Fahrenheit</td><td><span class=\"sensor\">"); client.println(1.8 * bme.readTemperature() + 32); client.println(" *F</span></td></tr>"); client.println("<tr><td>Pressure</td><td><span class=\"sensor\">"); client.println(bme.readPressure() / 100.0F); client.println(" hPa</span></td></tr>"); client.println("<tr><td>Approx. Altitude</td><td><span class=\"sensor\">"); client.println(bme.readAltitude(SEALEVELPRESSURE_HPA)); client.println(" m</span></td></tr>"); client.println("<tr><td>Humidity</td><td><span class=\"sensor\">"); client.println(bme.readHumidity()); client.println(" %</span></td></tr>"); client.println("</body></html>"); |
Вывод показаний датчика
Для отображения показаний датчика нудно поместить их в соответствующие тэги <td> и </td>. Например, для отображения температуры:
1 2 3 |
client.println("<tr><td>Temp. Celsius</td><td><span class=\"sensor\">"); client.println(bme.readTemperature()); client.println(" *C</span></td></tr>"); |
Примечание: Тэг <span> полезен для выделения небольшого куска текста. В данном случае, мы использовали этот тэг для выделения показаний датчика.
Закрываем соединение
Очищаем переменную заголовка и с помощью функции client.stop() закрываем соединение.
1 2 3 |
header = ""; client.stop(); |
Заключение
Итак, мы разобрали, как считывать показания температуры, влажности и давления, а также научились примерно вычислять высоту над уровнем моря с помощью датчика BME280. Если вы поняли, как это работает, вам не составит труда изменить код для применения других датчиков или вывода других данных.
Вопросы по прошивке и работе с кодом лучше писать напрямую автору в комментариях к статье (на англ. языке)