ESP32 Метеостанция на печатной плате (Температура, Влажность, Давление, Дата и Время)
В этом проекте вы узнаете, как собрать PCB Shield (печатную плату-шилд) для ESP32, работающий как интерфейс метеостанции. На плате установлен датчик температуры, влажности и давления BME280, фоторезистор (LDR), кнопка, OLED-дисплей и множество адресных RGB-светодиодов WS2812B. OLED-дисплей показывает показания датчиков, а светодиоды создают различные световые эффекты, соответствующие отображаемым данным. Также отображаются дата и время.
Ресурсы
Все необходимые файлы для сборки проекта доступны по ссылкам ниже (или вы можете посетить страницу проекта на GitHub):
`Скачать все файлы <https://github.com/RuiSantosdotme/ESP32-Weather-Station-PCB/archive/main.zip>`_
Обзор проекта
Аппаратные особенности PCB метеостанции
Шилд спроектирован с штыревыми разъёмами для установки платы ESP32 сверху. Поэтому, если вы хотите собрать и использовать нашу PCB, вам необходимо приобрести такую же плату ESP32. Мы используем ESP32 DEVKIT DOIT V1 (модель с 36 GPIO).
Если вы хотите повторить этот проект и у вас другая модель ESP32, вы можете собрать схему на макетной плате или модифицировать разводку PCB под распиновку вашей платы ESP32. В рамках проекта мы предоставляем все необходимые файлы для модификации PCB.
Шилд включает в себя:
Датчик температуры, влажности и давления BME280;
LDR (фоторезистор — датчик освещённости);
OLED-дисплей 0.96 дюйма I2C;
Кнопка;
12 адресных RGB-светодиодов WS2812B;
Если вы собираете проект на макетной плате, вместо отдельных WS2812B можно использовать адресную RGB-ленту или адресное RGB-кольцо с тем же количеством светодиодов (12).
Назначение пинов PCB метеостанции
В следующей таблице показано назначение пинов для каждого компонента на шилде:
Компонент |
Назначение пинов ESP32 |
|---|---|
BME280 |
GPIO 21 (SDA), GPIO 22 (SCL) |
OLED-дисплей |
GPIO 21 (SDA), GPIO 22 (SCL) |
Фоторезистор (LDR) |
GPIO 33 |
Кнопка |
GPIO 18 |
Адресные RGB-светодиоды |
GPIO 27 |
Программные возможности PCB метеостанции
Существует бесчисленное множество способов запрограммировать одну и ту же схему для получения разных результатов с различными функциями. В данном проекте мы запрограммируем PCB следующим образом:
OLED-дисплей отображает пять различных экранов:
Текущая дата и время;
Температура;
Влажность;
Давление;
Освещённость.
Каждый экран отображается 15 секунд перед переключением на следующий.
Также можно нажать кнопку для переключения экранов.
На каждом экране адресные RGB-светодиоды WS2812B показывают разный эффект:
На экране даты и времени RGB-светодиоды отображают эффект радуги;
На остальных экранах RGB-светодиоды работают как индикатор-шкала. Например, 100% влажности зажигает все светодиоды, 50% влажности — половину.
Цвет светодиодов отличается для каждого экрана: зелёный для температуры, синий для влажности, фиолетовый для давления и жёлтый для освещённости.
Тестирование схемы на макетной плате
Перед проектированием и изготовлением PCB важно протестировать схему на макетной плате. Если вы не хотите делать PCB, вы всё равно можете повторить этот проект, собрав схему на макетной плате.
Необходимые компоненты
Для сборки схемы на макетной плате вам понадобятся следующие компоненты (компоненты для PCB показаны в следующем разделе):
DOIT ESP32 DEVKIT V1 Board — читайте Best ESP32 Development Boards
BME280 (4 пина)
I2C OLED-дисплей (4 пина)
Вы можете использовать ссылки выше или перейти на MakerAdvisor.com/tools для поиска всех компонентов по лучшей цене!
Собрав все компоненты, соберите схему по следующей принципиальной схеме:
Проектирование PCB
Для проектирования схемы и PCB мы использовали EasyEDA — браузерное программное обеспечение для проектирования печатных плат. Если вы хотите настроить свою PCB, просто загрузите следующие файлы:
Проектирование схемы работает как в любом другом инструменте для схемотехники — вы размещаете компоненты и соединяете их проводниками. Затем вы назначаете каждому компоненту посадочное место (footprint).
После назначения компонентов разместите каждый из них. Когда вы будете довольны расположением, выполните все соединения и разведите вашу PCB.
Сохраните проект и экспортируйте Gerber-файлы.
Примечание: вы можете скачать файлы проекта и отредактировать их для настройки шилда под свои нужды.
`Скачать Gerber .zip файл <https://github.com/RuiSantosdotme/ESP32-Weather-Station-PCB/raw/main/Gerber_PCB_ESP32%20Weather%20Station%20Interface_2020-12-06_17-30-24.zip>`_
Заказ PCB на PCBWay
Этот проект спонсируется PCBWay. PCBWay — это полнофункциональный сервис по производству печатных плат.
Превратите свои макетные схемы в профессиональные PCB — получите 10 плат примерно за $5 + доставка (которая зависит от вашей страны).
Когда у вас есть Gerber-файлы, вы можете заказать PCB. Следуйте следующим шагам.
Скачайте Gerber-файлы — `нажмите здесь для скачивания .zip файла <https://github.com/RuiSantosdotme/ESP32-Weather-Station-PCB/raw/main/Gerber_PCB_ESP32%20Weather%20Station%20Interface_2020-12-06_17-30-24.zip>`_
Перейдите на сайт PCBWay и откройте страницу PCB Instant Quote.
PCBWay может извлечь все параметры PCB и автоматически заполнить их. Используйте «Quick-order PCB (Autofill parameters)».
Нажмите кнопку «+ Add Gerber file», чтобы загрузить предоставленные Gerber-файлы.
Вот и всё. Вы также можете использовать OnlineGerberViewer, чтобы проверить, что ваша PCB выглядит правильно.
Если вы не торопитесь, можно использовать способ доставки China Post, чтобы значительно снизить стоимость. По нашему мнению, они переоценивают время доставки China Post.
Вы можете увеличить количество заказываемых PCB и изменить цвет паяльной маски. Мы заказали синий цвет.
Когда всё готово, вы можете заказать PCB, нажав «Save to Cart» и завершив заказ.
Распаковка
Примерно через неделю при использовании доставки DHL, мы получили печатные платы в офис.
Как обычно, всё хорошо упаковано, а качество PCB действительно высокое. Надписи на шелкографии отлично напечатаны и легко читаются. Кроме того, припой легко ложится на контактные площадки.
Мы очень довольны сервисом PCBWay. Вот некоторые другие проекты, которые мы собрали с помощью сервиса PCBWay:
Пайка компонентов
Следующий шаг — пайка компонентов на PCB. Мы использовали SMD-светодиоды, SMD-резисторы и SMD-конденсаторы. Их может быть немного сложно паять, но PCB выглядит намного лучше.
Если вы никогда не паяли SMD, рекомендуем посмотреть несколько видео, чтобы научиться. Также можно приобрести SMD DIY набор для пайки для практики.
Вот список всех компонентов, необходимых для сборки PCB:
DOIT ESP32 DEVKIT V1 Board (36 GPIO)
12x SMD WS2812B адресных RGB-светодиодов
2x SMD резистора 10 кОм (1206)
12x конденсаторов 10 нФ (0805)
Кнопка (0.55 мм)
Гнездо штыревого разъёма (2.54 мм)
BME280 (4 пина)
I2C SSD1306 OLED-дисплей 0.96 дюйма (4 пина)
Вот инструменты для пайки, которые мы использовали:
Читайте наш обзор паяльника TS80: TS80 Soldering Iron Review – Best Portable Soldering Iron.
Начните с пайки SMD-компонентов. Затем припаяйте штыревые разъёмы. И наконец, припаяйте остальные компоненты или используйте разъёмы, если не хотите подключать компоненты на постоянной основе.
Вот как выглядит ESP32 Shield после сборки всех компонентов.
Плата ESP32 должна идеально встать на штыревые разъёмы с другой стороны PCB.
Программирование PCB интерфейса метеостанции
Код для этого проекта отображает показания датчиков на разных экранах OLED-дисплея, а также дату и время. Адресные RGB-светодиоды показывают различные цвета и анимации в соответствии с тем, что отображается на экране.
Вы можете запрограммировать PCB любым удобным для вас способом.
Мы будем программировать ESP32 с помощью Arduino IDE. Убедитесь, что у вас установлено дополнение для платы ESP32.
Если вы хотите программировать ESP32/ESP8266 с помощью VS Code + PlatformIO, следуйте руководству:
Установка библиотек (Arduino IDE)
Для этого проекта необходимо установить все эти библиотеки в Arduino IDE.
Все эти библиотеки можно установить через менеджер библиотек Arduino IDE. Просто перейдите в Sketch > Include Library > Manage Libraries и найдите нужную библиотеку по имени.
Установка библиотек (VS Code + PlatformIO)
Если вы программируете ESP32 с помощью PlatformIO, включите библиотеки в файл platformio.ini следующим образом:
[env:esp32doit-devkit-v1]
platform = espressif32
board = esp32doit-devkit-v1
framework = arduino
monitor_speed = 115200
lib_deps = adafruit/Adafruit NeoPixel @ ^1.7.0
adafruit/Adafruit SSD1306 @ ^2.4.1
adafruit/Adafruit Unified Sensor @ ^1.1.4
adafruit/Adafruit BME280 Library @ ^2.1.2
adafruit/Adafruit GFX Library @ ^1.10.3
adafruit/Adafruit BusIO @ ^1.6.0
Код
Скопируйте следующий код в Arduino IDE или в файл main.cpp, если используете PlatformIO.
/*
Rui Santos
Complete project details at https://RandomNerdTutorials.com/esp32-weather-station-pcb/
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 <Arduino.h>
#include <Adafruit_NeoPixel.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_BME280.h>
#include <WiFi.h>
#include <time.h>
// Insert your network credentials
const char* ssid = "REPLACE_WITH_YOUR_SSID";
const char* password = "REPLACE_WITH_YOUR_PASSWORD";
// NTP Server Details
const char* ntpServer = "pool.ntp.org";
const long gmtOffset_sec = 0;
const int daylightOffset_sec = 3600;
// OLED Display
#define SCREEN_WIDTH 128 // OLED display width, in pixels
#define SCREEN_HEIGHT 64 // OLED display height, in pixels
#define I2Cdisplay_SDA 21
#define I2Cdisplay_SCL 22
TwoWire I2Cdisplay = TwoWire(1);
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &I2Cdisplay, -1);
// WS2812B Addressable RGB LEDs
#define LED_PIN 27 // GPIO the LEDs are connected to
#define LED_COUNT 12 // Number of LEDs
Adafruit_NeoPixel strip(LED_COUNT, LED_PIN, NEO_GRB + NEO_KHZ800);
// BME280
#define I2C_SDA 21
#define I2C_SCL 22
TwoWire I2CBME = TwoWire(0);
Adafruit_BME280 bme;
// LDR (Light Dependent Resistor)
#define ldr 33
// Pushbutton
#define buttonPin 18
int buttonState; // current reading from the input pin
int lastButtonState = LOW; // previous reading from the input pin
unsigned long lastDebounceTime = 0; // the last time the output pin was toggled
unsigned long debounceDelay = 50; // the debounce time; increase if the output flickers
// Screens
int displayScreenNum = 0;
int displayScreenNumMax = 4;
unsigned long lastTimer = 0;
unsigned long timerDelay = 15000;
unsigned char temperature_icon[] ={
0b00000001, 0b11000000, // ###
0b00000011, 0b11100000, // #####
0b00000111, 0b00100000, // ### #
0b00000111, 0b11100000, // ######
0b00000111, 0b00100000, // ### #
0b00000111, 0b11100000, // ######
0b00000111, 0b00100000, // ### #
0b00000111, 0b11100000, // ######
0b00000111, 0b00100000, // ### #
0b00001111, 0b11110000, // ########
0b00011111, 0b11111000, // ##########
0b00011111, 0b11111000, // ##########
0b00011111, 0b11111000, // ##########
0b00011111, 0b11111000, // ##########
0b00001111, 0b11110000, // ########
0b00000111, 0b11100000, // ######
};
unsigned char humidity_icon[] ={
0b00000000, 0b00000000, //
0b00000001, 0b10000000, // ##
0b00000011, 0b11000000, // ####
0b00000111, 0b11100000, // ######
0b00001111, 0b11110000, // ########
0b00001111, 0b11110000, // ########
0b00011111, 0b11111000, // ##########
0b00011111, 0b11011000, // ####### ##
0b00111111, 0b10011100, // ####### ###
0b00111111, 0b10011100, // ####### ###
0b00111111, 0b00011100, // ###### ###
0b00011110, 0b00111000, // #### ###
0b00011111, 0b11111000, // ##########
0b00001111, 0b11110000, // ########
0b00000011, 0b11000000, // ####
0b00000000, 0b00000000, //
};
unsigned char arrow_down_icon[] ={
0b00001111, 0b11110000, // ########
0b00011111, 0b11111000, // ##########
0b00011111, 0b11111000, // ##########
0b00011100, 0b00111000, // ### ###
0b00011100, 0b00111000, // ### ###
0b00011100, 0b00111000, // ### ###
0b01111100, 0b00111110, // ##### #####
0b11111100, 0b00111111, // ###### ######
0b11111100, 0b00111111, // ###### ######
0b01111000, 0b00011110, // #### ####
0b00111100, 0b00111100, // #### ####
0b00011110, 0b01111000, // #### ####
0b00001111, 0b11110000, // ########
0b00000111, 0b11100000, // ######
0b00000011, 0b11000000, // ####
0b00000001, 0b10000000, // ##
};
unsigned char sun_icon[] ={
0b00000000, 0b00000000, //
0b00100000, 0b10000010, // # # #
0b00010000, 0b10000100, // # # #
0b00001000, 0b00001000, // # #
0b00000001, 0b11000000, // ###
0b00000111, 0b11110000, // #######
0b00000111, 0b11110000, // #######
0b00001111, 0b11111000, // #########
0b01101111, 0b11111011, // ## ######### ##
0b00001111, 0b11111000, // #########
0b00000111, 0b11110000, // #######
0b00000111, 0b11110000, // #######
0b00010001, 0b11000100, // # ### #
0b00100000, 0b00000010, // # #
0b01000000, 0b10000001, // # # #
0b00000000, 0b10000000, // #
};
// Clear the LEDs
void colorWipe(uint32_t color, int wait, int numNeoPixels) {
for(int i=0; i<numNeoPixels; i++) { // For each pixel in strip...
strip.setPixelColor(i, color); // Set pixel's color (in RAM)
strip.show(); // Update strip to match
delay(wait); // Pause for a moment
}
}
// Rainbow cycle along all LEDs. Pass delay time (in ms) between frames.
void rainbow(int wait) {
long firstPixelHue = 256;
for(int i=0; i<strip.numPixels(); i++) { // For each pixel in strip...
int pixelHue = firstPixelHue + (i * 65536L / strip.numPixels());
strip.setPixelColor(i, strip.gamma32(strip.ColorHSV(pixelHue)));
}
strip.show(); // Update strip with new contents
delay(wait); // Pause for a moment
}
// Create display marker for each screen
void displayIndicator(int displayNumber) {
int xCoordinates[5] = {44, 54, 64, 74, 84};
for (int i =0; i<5; i++) {
if (i == displayNumber) {
display.fillCircle(xCoordinates[i], 60, 2, WHITE);
}
else {
display.drawCircle(xCoordinates[i], 60, 2, WHITE);
}
}
}
//SCREEN NUMBER 0: DATE AND TIME
void displayLocalTime(){
struct tm timeinfo;
if(!getLocalTime(&timeinfo)){
Serial.println("Failed to obtain time");
}
Serial.println(&timeinfo, "%A, %B %d %Y %H:%M:%S");
//GET DATE
//Get full weekday name
char weekDay[10];
strftime(weekDay, sizeof(weekDay), "%a", &timeinfo);
//Get day of month
char dayMonth[4];
strftime(dayMonth, sizeof(dayMonth), "%d", &timeinfo);
//Get abbreviated month name
char monthName[5];
strftime(monthName, sizeof(monthName), "%b", &timeinfo);
//Get year
char year[6];
strftime(year, sizeof(year), "%Y", &timeinfo);
//GET TIME
//Get hour (12 hour format)
/*char hour[4];
strftime(hour, sizeof(hour), "%I", &timeinfo);*/
//Get hour (24 hour format)
char hour[4];
strftime(hour, sizeof(hour), "%H", &timeinfo);
//Get minute
char minute[4];
strftime(minute, sizeof(minute), "%M", &timeinfo);
//Display Date and Time on OLED display
display.clearDisplay();
display.setTextColor(WHITE);
display.setTextSize(3);
display.setCursor(19,5);
display.print(hour);
display.print(":");
display.print(minute);
display.setTextSize(1);
display.setCursor(16,40);
display.print(weekDay);
display.print(", ");
display.print(dayMonth);
display.print(" ");
display.print(monthName);
display.print(" ");
display.print(year);
displayIndicator(displayScreenNum);
display.display();
rainbow(10);
}
// SCREEN NUMBER 1: TEMPERATURE
void displayTemperature(){
display.clearDisplay();
display.setTextSize(2);
display.drawBitmap(15, 5, temperature_icon, 16, 16 ,1);
display.setCursor(35, 5);
float temperature = bme.readTemperature();
display.print(temperature);
display.cp437(true);
display.setTextSize(1);
display.print(" ");
display.write(167);
display.print("C");
display.setCursor(0, 34);
display.setTextSize(1);
display.print("Humidity: ");
display.print(bme.readHumidity());
display.print(" %");
display.setCursor(0, 44);
display.setTextSize(1);
display.print("Pressure: ");
display.print(bme.readPressure()/100.0F);
display.print(" hpa");
displayIndicator(displayScreenNum);
display.display();
int temperaturePer = map(temperature, -5, 36, 0, LED_COUNT-1);
colorWipe(strip.Color(0, 255, 0), 50, temperaturePer);
}
// SCREEN NUMBER 2: HUMIDITY
void displayHumidity(){
display.clearDisplay();
display.setTextSize(2);
display.drawBitmap(15, 5, humidity_icon, 16, 16 ,1);
display.setCursor(35, 5);
float humidity = bme.readHumidity();
display.print(humidity);
display.print(" %");
display.setCursor(0, 34);
display.setTextSize(1);
display.print("Temperature: ");
display.print(bme.readTemperature());
display.cp437(true);
display.print(" ");
display.write(167);
display.print("C");
display.setCursor(0, 44);
display.setTextSize(1);
display.print("Pressure: ");
display.print(bme.readPressure()/100.0F);
display.print(" hpa");
displayIndicator(displayScreenNum);
display.display();
int humidityPer = map(humidity, 0, 100, 0, LED_COUNT-1);
colorWipe(strip.Color(0, 0, 255), 50, humidityPer);
}
// SCREEN NUMBER 3: PRESSURE
void displayPressure(){
display.clearDisplay();
display.setTextSize(2);
display.drawBitmap(0, 5, arrow_down_icon, 16, 16 ,1);
display.setCursor(20, 5);
display.print(bme.readPressure()/100.0F);
display.setTextSize(1);
display.print(" hpa");
display.setCursor(0, 34);
display.setTextSize(1);
display.print("Temperature: ");
display.print(bme.readTemperature());
display.cp437(true);
display.print(" ");
display.write(167);
display.print("C");
display.setCursor(0, 44);
display.setTextSize(1);
display.print("Humidity: ");
display.print(bme.readHumidity());
display.print(" hpa");
displayIndicator(displayScreenNum);
display.display();
colorWipe(strip.Color(255, 0, 255), 50, 12);
}
// SCREEN NUMBER 4: LUMINOSITY
void displayLDR(){
display.clearDisplay();
display.setTextSize(2);
display.drawBitmap(33, 5, sun_icon, 16, 16 ,1);
display.setCursor(53, 5);
int ldrReading = map(analogRead(ldr), 0, 4095, 100, 0);
display.print(ldrReading);
display.print(" %");
display.setTextSize(1);
display.setCursor(0, 34);
display.print("Temperature: ");
display.print(bme.readTemperature());
display.print(" ");
display.cp437(true);
display.write(167);
display.print("C");
display.setCursor(0, 44);
display.setTextSize(1);
display.print("Humidity: ");
display.print(bme.readHumidity());
display.print(" %");
display.setCursor(0, 44);
displayIndicator(displayScreenNum);
display.display();
int ldrReadingPer = map(ldrReading, 0, 100, 0, LED_COUNT-1);
colorWipe(strip.Color(255, 255, 0), 50, ldrReadingPer);
}
// Display the right screen accordingly to the displayScreenNum
void updateScreen() {
colorWipe(strip.Color(0, 0, 0), 1, LED_COUNT);
if (displayScreenNum == 0){
displayLocalTime();
}
else if (displayScreenNum == 1) {
displayTemperature();
}
else if (displayScreenNum ==2){
displayHumidity();
}
else if (displayScreenNum==3){
displayPressure();
}
else {
displayLDR();
}
}
void setup() {
Serial.begin(115200);
// Initialize the pushbutton pin as an input
pinMode(buttonPin, INPUT);
I2CBME.begin(I2C_SDA, I2C_SCL, 100000);
I2Cdisplay.begin(I2Cdisplay_SDA, I2Cdisplay_SCL, 100000);
// Initialize OLED Display
if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
Serial.println(F("SSD1306 allocation failed"));
for(;;);
}
display.clearDisplay();
display.setTextColor(WHITE);
// Initialize BME280
bool status = bme.begin(0x76, &I2CBME);
if (!status) {
Serial.println("Could not find a valid BME280 sensor, check wiring!");
while (1);
}
// Initialize WS2812B LEDs
strip.begin(); // INITIALIZE NeoPixel strip object (REQUIRED)
strip.show(); // Turn OFF all pixels ASAP
strip.setBrightness(50); // Set BRIGHTNESS to about 1/5 (max = 255)
// Connect to Wi-Fi
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.");
// Init and get the time
configTime(gmtOffset_sec, daylightOffset_sec, ntpServer);
}
void loop() {
// read the state of the switch into a local variable
int reading = digitalRead(buttonPin);
// Change screen when the pushbutton is pressed
if (reading != lastButtonState) {
lastDebounceTime = millis();
}
if ((millis() - lastDebounceTime) > debounceDelay) {
if (reading != buttonState) {
buttonState = reading;
if (buttonState == HIGH) {
updateScreen();
Serial.println(displayScreenNum);
if(displayScreenNum < displayScreenNumMax) {
displayScreenNum++;
}
else {
displayScreenNum = 0;
}
lastTimer = millis();
}
}
}
lastButtonState = reading;
// Change screen every 15 seconds (timerDelay variable)
if ((millis() - lastTimer) > timerDelay) {
updateScreen();
Serial.println(displayScreenNum);
if(displayScreenNum < displayScreenNumMax) {
displayScreenNum++;
}
else {
displayScreenNum = 0;
}
lastTimer = millis();
}
}
Мы получаем дату и время с NTP-сервера. Поэтому ESP32 должен подключиться к интернету. Вставьте данные вашей сети в следующие переменные, и код сразу заработает.
const char* ssid = "REPLACE_WITH_YOUR_SSID";
const char* password = "REPLACE_WITH_YOUR_PASSWORD";
Как работает код
Прочтите этот раздел, если хотите понять, как работает код, или перейдите к следующему разделу.
Этот код довольно длинный, но простой. Если вы хотите полностью понять, как он работает, вам может понадобиться ознакомиться со следующими руководствами:
ESP32 NTP Client-Server: получение даты и времени (Arduino IDE)
ESP32/ESP8266: показания температуры и влажности DHT на OLED-дисплее
ESP32 с датчиком BME280 в Arduino IDE (давление, температура, влажность)
Подключение библиотек
Сначала необходимо подключить нужные библиотеки.
#include <Arduino.h>
#include <Adafruit_NeoPixel.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_BME280.h>
#include <WiFi.h>
#include <time.h>
Сетевые учётные данные
Вставьте данные вашей сети в следующие строки, чтобы ESP32 мог подключиться к вашей сети для запроса даты и времени с NTP-сервера.
const char* ssid = "REPLACE_WITH_YOUR_SSID";
const char* password = "REPLACE_WITH_YOUR_PASSWORD";
NTP-сервер
Затем необходимо определить следующие переменные для настройки и получения времени с NTP-сервера: ntpServer, gmtOffset_sec и daylightOffset_sec.
Мы будем запрашивать время у pool.ntp.org — кластера серверов времени, которыми может пользоваться любой.
const char* ntpServer = "pool.ntp.org";
Смещение GMT
Переменная gmtOffset_sec определяет смещение в секундах между вашим часовым поясом и GMT. Мы живём в Португалии, поэтому смещение равно 0. Измените переменную gmtOffset_sec в соответствии с вашим часовым поясом.
const long gmtOffset_sec = 0;
Смещение летнего времени
Переменная daylightOffset_sec определяет смещение в секундах для перехода на летнее время. Обычно это один час, что соответствует 3600 секундам.
const int daylightOffset_sec = 3600;
Подробнее о получении времени с NTP-сервера: ESP32 NTP Client-Server: получение даты и времени (Arduino IDE)
OLED-дисплей
Переменные SCREEN_WIDTH и SCREEN_HEIGHT определяют размеры OLED-дисплея в пикселях. Мы используем OLED-дисплей 0.96 дюйма: 128 x 64 пикселя.
#define SCREEN_WIDTH 128 // OLED display width, in pixels
#define SCREEN_HEIGHT 64 // OLED display height, in pixels
OLED-дисплей подключён к GPIO 22 (SCL) и GPIO 21 (SDA).
#define I2Cdisplay_SDA 21
#define I2Cdisplay_SCL 22
TwoWire I2Cdisplay = TwoWire(1);
Инициализируйте объект дисплея с определёнными ранее шириной и высотой с протоколом I2C (&I2Cdisplay).
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &I2Cdisplay, -1);
Адресные RGB-светодиоды WS2812B
Необходимо определить GPIO, к которому подключены RGB-светодиоды. Адресные RGB-светодиоды взаимодействуют с ESP32 по протоколу One Wire. Поэтому все светодиоды могут управляться одним GPIO. В данном случае они подключены к GPIO 27.
#define LED_PIN 27
Переменная LED_COUNT сохраняет количество адресных RGB-светодиодов, которыми мы хотим управлять. В данном случае их 12.
#define LED_COUNT 12
Создайте объект Adafruit_NeoPixel с именем strip для управления адресными RGB-светодиодами.
Adafruit_NeoPixel strip(LED_COUNT, LED_PIN, NEO_GRB + NEO_KHZ800);
Датчик BME280
Создайте объект Adafruit_BME280 с именем bme на стандартных пинах ESP32.
#define I2C_SDA 21
#define I2C_SCL 22
TwoWire I2CBME = TwoWire(0);
Adafruit_BME280 bme;
LDR
Определите GPIO, к которому подключён LDR.
const int ldr = 33; // LDR (Light Dependent Resistor)
Кнопка
Определите GPIO, к которому подключена кнопка.
#define buttonPin 18
Следующие переменные используются для обработки нажатия кнопки.
int buttonState; // current reading from the input pin
int lastButtonState = LOW; // previous reading from the input pin
unsigned long lastDebounceTime = 0; // the last time the output pin was toggled
unsigned long debounceDelay = 50; // the debounce time; increase if the output flickers
Экраны OLED
Как упоминалось ранее, OLED будет отображать пять различных экранов. Каждый экран пронумерован от 0 до 4. Переменная displayScreenNum хранит номер экрана, который должен отображаться на OLED — начинается с нуля. displayNumMax хранит максимальное количество экранов.
int displayScreenNum = 0;
int displayScreenNumMax = 4;
Следующие переменные используются для управления таймером отображения каждого экрана в течение 15 секунд. Вы можете изменить период отображения экрана в переменной timerDelay.
unsigned long lastTimer = 0;
unsigned long timerDelay = 15000;
Иконки
На каждом экране OLED отображает иконку, связанную с показываемым значением. На экране температуры — термометр (temperature_icon), на экране влажности — капля (humidity_icon), на экране давления — стрелка (arrow_down_icon), а на экране освещённости — солнце (sun_icon). Нам нужно включить эти иконки в код. Эти иконки имеют размер 16x16 пикселей.
unsigned char temperature_icon[] ={
0b00000001, 0b11000000, // ###
0b00000011, 0b11100000, // #####
0b00000111, 0b00100000, // ### #
0b00000111, 0b11100000, // ######
0b00000111, 0b00100000, // ### #
0b00000111, 0b11100000, // ######
0b00000111, 0b00100000, // ### #
0b00000111, 0b11100000, // ######
0b00000111, 0b00100000, // ### #
0b00001111, 0b11110000, // ########
0b00011111, 0b11111000, // ##########
0b00011111, 0b11111000, // ##########
0b00011111, 0b11111000, // ##########
0b00011111, 0b11111000, // ##########
0b00001111, 0b11110000, // ########
0b00000111, 0b11100000, // ######
};
unsigned char humidity_icon[] ={
0b00000000, 0b00000000, //
0b00000001, 0b10000000, // ##
0b00000011, 0b11000000, // ####
0b00000111, 0b11100000, // ######
0b00001111, 0b11110000, // ########
0b00001111, 0b11110000, // ########
0b00011111, 0b11111000, // ##########
0b00011111, 0b11011000, // ####### ##
0b00111111, 0b10011100, // ####### ###
0b00111111, 0b10011100, // ####### ###
0b00111111, 0b00011100, // ###### ###
0b00011110, 0b00111000, // #### ###
0b00011111, 0b11111000, // ##########
0b00001111, 0b11110000, // ########
0b00000011, 0b11000000, // ####
0b00000000, 0b00000000, //
};
unsigned char arrow_down_icon[] ={
0b00001111, 0b11110000, // ########
0b00011111, 0b11111000, // ##########
0b00011111, 0b11111000, // ##########
0b00011100, 0b00111000, // ### ###
0b00011100, 0b00111000, // ### ###
0b00011100, 0b00111000, // ### ###
0b01111100, 0b00111110, // ##### #####
0b11111100, 0b00111111, // ###### ######
0b11111100, 0b00111111, // ###### ######
0b01111000, 0b00011110, // #### ####
0b00111100, 0b00111100, // #### ####
0b00011110, 0b01111000, // #### ####
0b00001111, 0b11110000, // ########
0b00000111, 0b11100000, // ######
0b00000011, 0b11000000, // ####
0b00000001, 0b10000000, // ##
};
unsigned char sun_icon[] ={
0b00000000, 0b00000000, //
0b00100000, 0b10000010, // # # #
0b00010000, 0b10000100, // # # #
0b00001000, 0b00001000, // # #
0b00000001, 0b11000000, // ###
0b00000111, 0b11110000, // #######
0b00000111, 0b11110000, // #######
0b00001111, 0b11111000, // #########
0b01101111, 0b11111011, // ## ######### ##
0b00001111, 0b11111000, // #########
0b00000111, 0b11110000, // #######
0b00000111, 0b11110000, // #######
0b00010001, 0b11000100, // # ### #
0b00100000, 0b00000010, // # #
0b01000000, 0b10000001, // # # #
0b00000000, 0b10000000, // #
};
Подробнее об отображении иконок читайте в нашем руководстве по OLED: ESP32 OLED-дисплей с Arduino IDE.
Функции colorWipe() и rainbow()
Функции colorWipe() и rainbow() используются для управления адресными RGB-светодиодами.
Функция colorWipe() используется для включения или очистки определённых светодиодов.
void colorWipe(uint32_t color, int wait, int numNeoPixels) {
for(int i=0; i<numNeoPixels; i++) { // For each pixel in strip...
strip.setPixelColor(i, color); // Set pixel's color (in RAM)
strip.show(); // Update strip to match
delay(wait); // Pause for a moment
}
}
Функция rainbow(), как следует из названия, отображает эффект радуги.
// Rainbow cycle along all LEDs. Pass delay time (in ms) between frames.
void rainbow(int wait) {
long firstPixelHue = 256;
for(int i=0; i<strip.numPixels(); i++) { // For each pixel in strip...
int pixelHue = firstPixelHue + (i * 65536L / strip.numPixels());
strip.setPixelColor(i, strip.gamma32(strip.ColorHSV(pixelHue)));
}
strip.show(); // Update strip with new contents
delay(wait); // Pause for a moment
}
Вы можете узнать больше об этих и других функциях управления лентой, посмотрев примеры библиотеки Neopixel.
Функция displayIndicator()
Функция displayIndicator() создаёт пять маленьких кружков в нижней части дисплея в соответствии с текущим отображаемым экраном.
// Create display marker for each screen
void displayIndicator(int displayNumber) {
int xCoordinates[5] = {44, 54, 64, 74, 84};
for (int i =0; i<5; i++) {
if (i == displayNumber) {
display.fillCircle(xCoordinates[i], 60, 2, WHITE);
}
else {
display.drawCircle(xCoordinates[i], 60, 2, WHITE);
}
}
}
Функция drawCircle(x, y, radius, color) создаёт кружок. Функция fillCircle(x, y, radius, color) создаёт закрашенный кружок.
Мы размещаем центры кружков по следующим координатам x: 44, 54, 64, 74 и 84.
int xCoordinates[5] = {44, 54, 64, 74, 84};
Мы рисуем закрашенный кружок для текущего экрана и «пустой» кружок для остальных:
if (i == displayNumber) {
display.fillCircle(xCoordinates[i], 60, 2, WHITE);
}
else {
display.drawCircle(xCoordinates[i], 60, 2, WHITE);
}
Экран 0: Дата и время
Первый экран на OLED отображает дату и время. Это делает функция displayLocalTime(). Она также отображает эффект радуги на адресных RGB-светодиодах.
//SCREEN NUMBER 0: DATE AND TIME
void displayLocalTime(){
struct tm timeinfo;
if(!getLocalTime(&timeinfo)){
Serial.println("Failed to obtain time");
}
Serial.println(&timeinfo, "%A, %B %d %Y %H:%M:%S");
//GET DATE
//Get full weekday name
char weekDay[10];
strftime(weekDay, sizeof(weekDay), "%a", &timeinfo);
//Get day of month
char dayMonth[4];
strftime(dayMonth, sizeof(dayMonth), "%d", &timeinfo);
//Get abbreviated month name
char monthName[5];
strftime(monthName, sizeof(monthName), "%b", &timeinfo);
//Get year
char year[6];
strftime(year, sizeof(year), "%Y", &timeinfo);
//GET TIME
//Get hour (12 hour format)
/*char hour[4];
strftime(hour, sizeof(hour), "%I", &timeinfo);*/
//Get hour (24 hour format)
char hour[4];
strftime(hour, sizeof(hour), "%H", &timeinfo);
//Get minute
char minute[4];
strftime(minute, sizeof(minute), "%M", &timeinfo);
//Display Date and Time on OLED display
display.clearDisplay();
display.setTextColor(WHITE);
display.setTextSize(3);
display.setCursor(19,5);
display.print(hour);
display.print(":");
display.print(minute);
display.setTextSize(1);
display.setCursor(16,40);
display.print(weekDay);
display.print(", ");
display.print(dayMonth);
display.print(" ");
display.print(monthName);
display.print(" ");
display.print(year);
displayIndicator(displayScreenNum);
display.display();
rainbow(10);
}
Подробнее о получении даты и времени с NTP-сервера с ESP32: ESP32 NTP Client-Server: получение даты и времени (Arduino IDE)
Экран 1: Температура
Второй экран отображает температуру. Это делается вызовом функции displayTemperature(). Эта функция также зажигает светодиоды зелёным цветом в соответствии с значением температуры.
// SCREEN NUMBER 1: TEMPERATURE
void displayTemperature(){
display.clearDisplay();
display.setTextSize(2);
display.drawBitmap(15, 5, temperature_icon, 16, 16 ,1);
display.setCursor(35, 5);
float temperature = bme.readTemperature();
display.print(temperature);
display.cp437(true);
display.setTextSize(1);
display.print(" ");
display.write(167);
display.print("C");
display.setCursor(0, 34);
display.setTextSize(1);
display.print("Humidity: ");
display.print(bme.readHumidity());
display.print(" %");
display.setCursor(0, 44);
display.setTextSize(1);
display.print("Pressure: ");
display.print(bme.readPressure()/100.0F);
display.print(" hpa");
displayIndicator(displayScreenNum);
display.display();
int temperaturePer = map(temperature, -5, 36, 0, LED_COUNT-1);
colorWipe(strip.Color(0, 255, 0), 50, temperaturePer);
}
Экран 2: Влажность
Функция displayHumidity() аналогична функции displayTemperature(), но отображает значение влажности.
// SCREEN NUMBER 2: HUMIDITY
void displayHumidity(){
display.clearDisplay();
display.setTextSize(2);
display.drawBitmap(15, 5, humidity_icon, 16, 16 ,1);
display.setCursor(35, 5);
float humidity = bme.readHumidity();
display.print(humidity);
display.print(" %");
display.setCursor(0, 34);
display.setTextSize(1);
display.print("Temperature: ");
display.print(bme.readTemperature());
display.cp437(true);
display.print(" ");
display.write(167);
display.print("C");
display.setCursor(0, 44);
display.setTextSize(1);
display.print("Pressure: ");
display.print(bme.readPressure()/100.0F);
display.print(" hpa");
displayIndicator(displayScreenNum);
display.display();
int humidityPer = map(humidity, 0, 100, 0, LED_COUNT-1);
colorWipe(strip.Color(0, 0, 255), 50, humidityPer);
}
Экран 3: Давление
Функция displayPressure() аналогична двум предыдущим функциям, но отображает значение давления.
// SCREEN NUMBER 3: PRESSURE
void displayPressure(){
display.clearDisplay();
display.setTextSize(2);
display.drawBitmap(0, 5, arrow_down_icon, 16, 16 ,1);
display.setCursor(20, 5);
display.print(bme.readPressure()/100.0F);
display.setTextSize(1);
display.print(" hpa");
display.setCursor(0, 34);
display.setTextSize(1);
display.print("Temperature: ");
display.print(bme.readTemperature());
display.cp437(true);
display.print(" ");
display.write(167);
display.print("C");
display.setCursor(0, 44);
display.setTextSize(1);
display.print("Humidity: ");
display.print(bme.readHumidity());
display.print(" hpa");
displayIndicator(displayScreenNum);
display.display();
colorWipe(strip.Color(255, 0, 255), 50, 12);
}
Экран 4: Освещённость
Наконец, последний экран отображает освещённость (функция displayLDR()).
void displayLDR(){
display.clearDisplay();
display.setTextSize(2);
display.drawBitmap(33, 5, sun_icon, 16, 16 ,1);
display.setCursor(53, 5);
int ldrReading = map(analogRead(ldr), 0, 4095, 100, 0);
display.print(ldrReading);
display.print(" %");
display.setTextSize(1);
display.setCursor(0, 34);
display.print("Temperature: ");
display.print(bme.readTemperature());
display.print(" ");
display.cp437(true);
display.write(167);
display.print("C");
display.setCursor(0, 44);
display.setTextSize(1);
display.print("Humidity: ");
display.print(bme.readHumidity());
display.print(" %");
display.setCursor(0, 44);
displayIndicator(displayScreenNum);
display.display();
int ldrReadingPer = map(ldrReading, 0, 100, 0, LED_COUNT-1);
colorWipe(strip.Color(255, 255, 0), 50, ldrReadingPer);
}
Функция updateScreen()
Функция updateScreen() вызывает нужные функции в соответствии с экраном, который мы хотим отобразить:
void updateScreen() {
colorWipe(strip.Color(0, 0, 0), 1, LED_COUNT);
if (displayScreenNum == 0){
displayLocalTime();
}
else if (displayScreenNum == 1) {
displayTemperature();
}
else if (displayScreenNum ==2){
displayHumidity();
}
else if (displayScreenNum==3){
displayPressure();
}
else {
displayLDR();
}
}
setup()
Установите кнопку как вход.
pinMode(buttonPin, INPUT);
Инициализация OLED-дисплея.
if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
Serial.println(F("SSD1306 allocation failed"));
for(;;);
}
Инициализация датчика BME280:
if (!bme.begin(0x76)) {
Serial.println("Could not find a valid BME280 sensor, check wiring!");
while (1);
}
При первом запуске ESP32 мы хотим очистить OLED-дисплей. Также устанавливаем белый цвет текста.
display.clearDisplay();
display.setTextColor(WHITE);
Инициализация WS2812B светодиодов и установка яркости. Вы можете изменить яркость на любое другое значение, подходящее для вашего окружения.
strip.begin(); // INITIALIZE NeoPixel strip object (REQUIRED)
strip.show(); // Turn OFF all pixels ASAP
strip.setBrightness(50); // Set BRIGHTNESS to about 1/5 (max = 255)
Инициализация Wi-Fi и подключение ESP32 к вашей локальной сети, чтобы ESP32 мог подключиться к NTP-серверу для получения даты и времени.
// Connect to Wi-Fi
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.");
Настройка NTP-сервера с ранее определёнными параметрами.
configTime(gmtOffset_sec, daylightOffset_sec, ntpServer);
printLocalTime();
loop()
В цикле loop() следующие строки переключают экран (displayScreenNum) каждый раз при нажатии кнопки.
// read the state of the switch into a local variable
int reading = digitalRead(buttonPin);
// Change screen when the pushbutton is pressed
if (reading != lastButtonState) {
lastDebounceTime = millis();
}
if ((millis() - lastDebounceTime) > debounceDelay) {
if (reading != buttonState) {
buttonState = reading;
if (buttonState == HIGH) {
updateScreen();
Serial.println(displayScreenNum);
if(displayScreenNum < displayScreenNumMax) {
displayScreenNum++;
}
else {
displayScreenNum = 0;
}
lastTimer = millis();
}
}
}
lastButtonState = reading;
Следующие строки переключают экраны каждые 15 секунд (timerDelay).
if ((millis() - lastTimer) > timerDelay) {
updateScreen();
Serial.println(displayScreenNum);
if(displayScreenNum < displayScreenNumMax) {
displayScreenNum++;
}
else {
displayScreenNum = 0;
}
lastTimer = millis();
}
Демонстрация
После загрузки кода на плату нажмите встроенную кнопку RESET, чтобы ESP32 начал выполнять код.
Для полной демонстрации рекомендуем посмотреть видео выше.
Заключение
Мы надеемся, что этот проект показался вам интересным и вы сможете собрать его самостоятельно. Вы можете воспользоваться сервисом PCBWay и получить высококачественную PCB для своих проектов.
Вы можете запрограммировать ESP32 другим кодом, подходящим для ваших нужд. Также можно отредактировать Gerber-файлы и добавить другие функции на PCB или другие датчики.
У нас есть другие похожие проекты, включающие создание и проектирование PCB, которые могут вам понравиться:
ESP32 IoT Shield PCB с панелью управления для выходов и датчиков
Экстремальное энергосбережение с микроконтроллером: PCB с защёлкивающимся питанием
Узнайте больше об ESP32 с нашими ресурсами:
Источник: Random Nerd Tutorials — :doc:`ESP32 Weather Station Interface PCB Shield <../esp32-weather-station-pcb/index>`