Логирование данных ESP32 с DHT22 и метками времени NTP в реальном времени

Логирование данных ESP32 с DHT22 и метками времени NTP в реальном времени

Вы когда-нибудь хотели создать проект, который не только считывает окружающую среду, но и запоминает её? С помощью всего нескольких компонентов и небольшого кода вы можете научить свой ESP32 делать именно это. В этом руководстве вы сделаете первые шаги в логировании данных.

Обзор проекта

В этом проекте мы создадим логгер данных с использованием ESP32.

обзор проекта логгера данных ESP32

ESP32 будет постоянно проверять температуру и влажность каждые 2 секунды с помощью датчика DHT22. Для каждого измерения ESP32 будет получать текущую дату и время с NTP-сервера (Network Time Protocol), так что каждая точка данных будет содержать точную метку времени. Метка времени будет отформатирована как «YYYY-MM-DD HH:MM:SS», что легко читается и понимается людьми.

Все данные будут сохраняться на microSD-карте в файле «log.txt». Каждое новое измерение будет добавляться в файл как новая строка, содержащая метку времени, температуру в градусах Цельсия и влажность в процентах, разделённые запятыми.

Рекомендуемое чтение

Прежде чем приступить к этому проекту, рекомендуется ознакомиться со следующими руководствами. Они помогут вам понять основы работы с microSD-картой на ESP32 и получения времени из интернета с помощью NTP-сервера.

Как использовать microSD-карту с ESP32

Начали проект с ESP32 и обнаружили, что не хватает места для данных? Будь то метеостанция, которой нужно…

/lastminuteengineers/esp32-microsd-card-tutorial/index
Получение даты и времени с NTP-сервера с помощью ESP32

Представьте, что вы создаёте умное устройство, которое включает лампу в спальне ровно в 7:00 утра, или метеостанцию, которая записывает данные о температуре каждые 15…

/lastminuteengineers/esp32-ntp-server-date-time-tutorial/index

Подготовка microSD-карты

Перед использованием microSD-карты в проекте важно убедиться, что она правильно отформатирована с использованием нужной файловой системы — FAT16 или FAT32. Это поможет ESP32 читать и записывать файлы без каких-либо проблем.

Если вы используете новую SD-карту, она, вероятно, уже отформатирована с файловой системой FAT. Однако это заводское форматирование может быть несовершенным, и вы можете столкнуться с проблемами. Если вы используете старую карту, которая уже использовалась ранее, её определённо нужно переформатировать. В любом случае рекомендуется отформатировать карту перед использованием в проекте.

Существует два способа форматирования microSD-карты:

Способ 1

Сначала вставьте microSD-карту в компьютер. Затем найдите диск вашей SD-карты и щёлкните по нему правой кнопкой мыши. Выберите «Форматировать» из меню. Появится окно — выберите FAT32 в качестве файловой системы, затем нажмите «Начать» для начала форматирования. Следуйте инструкциям на экране до завершения.

форматирование SD-карты в Windows

Способ 2

Для лучших результатов и меньшего количества ошибок настоятельно рекомендуется использовать официальную утилиту для форматирования SD-карт, созданную SD Association. Этот инструмент более надёжен, чем базовная утилита форматирования вашего компьютера. Вы можете скачать его с сайта SD Association. После установки запустите программу, выберите вашу SD-карту из списка дисков и нажмите кнопку «Format». Этот специальный инструмент помогает избежать распространённых проблем, вызванных неполным или некорректным форматированием, что может сэкономить вам много времени на устранение неполадок в будущем.

скриншот SD Formatter

Подключение датчика DHT22 и модуля microSD-карты к ESP32

Теперь пришло время подключить датчик DHT22 и модуль microSD-карты к ESP32.

Для модуля microSD-карты:

Начните с подключения вывода 3V3 на модуле microSD-карты к выводу питания 3,3 В на ESP32. Затем подключите вывод GND на модуле к одному из выводов GND на ESP32.

Далее мы настроим выводы, используемые для связи по SPI. Поскольку microSD-картам нужна быстрая передача данных, они лучше всего работают при подключении к аппаратным выводам SPI ESP32. На ESP32 выводы SPI по умолчанию: GPIO 18 для CLK, GPIO 19 для MISO, GPIO 23 для MOSI и GPIO 5 для CS.

Вот краткая таблица соединений выводов:

Модуль microSD-карты

ESP32

3V3

3.3V

CS

5

MOSI

23

CLK

18

MISO

19

GND

GND

Для датчика DHT22:

Подключите вывод VCC на датчике DHT22 к выводу 3,3 В на ESP32, а землю — к земле. Затем подключите вывод Data датчика к выводу D4 на ESP32. Чтобы сигнал данных был сильным и чётким, необходимо добавить подтягивающий резистор 10 кОм между выводом Data и выводом VCC. Однако если вы используете плату расширения (breakout board), вы можете пропустить добавление этого резистора, так как он уже встроен в плату.

Вот краткая таблица соединений выводов:

DHTxx

ESP32

Примечания

VCC

3.3V

GND

GND

Data

D4

подтянут к питанию резистором 10 кОм

Полная схема подключения

На схеме ниже показано, как именно всё подключить:

подключение модуля microSD-карты и модуля DHT22 к ESP32

Настройка Arduino IDE

Мы будем использовать Arduino IDE для программирования ESP32, поэтому убедитесь, что у вас установлено дополнение ESP32, прежде чем продолжить:

Установка платы ESP32 в Arduino IDE

Микроконтроллер ESP32 быстро стал одной из самых популярных плат среди любителей, инженеров и людей, интересующихся Интернетом вещей (IoT)…

/lastminuteengineers/esp32-arduino-ide-tutorial/index

Установка библиотеки

Датчики DHTxx используют уникальный однопроводной протокол связи для передачи данных о температуре и влажности. Хотя этот протокол не является стандартным, он похож на протокол Dallas 1-Wire и в значительной степени зависит от очень точной синхронизации для правильной работы.

К счастью, вам не нужно беспокоиться о написании всего низкоуровневого кода для обработки этой синхронизации самостоятельно. Существует полезная библиотека под названием DHT sensor library, которая берёт на себя почти всю работу.

Для установки библиотеки:

  1. Сначала откройте программу Arduino IDE. Затем нажмите на значок Library Manager на левой боковой панели.

  2. Введите «DHT sensor» в поле поиска для фильтрации результатов.

  3. Найдите библиотеку «DHT sensor library», созданную Adafruit.

  4. Нажмите кнопку Install, чтобы добавить её в Arduino IDE.

установка библиотеки Adafruit DHT

Поскольку библиотека DHT sensor зависит от других библиотек для работы, вам будет предложено установить её зависимости, включая библиотеку Adafruit Unified Sensor.

Когда появится это сообщение, просто нажмите INSTALL ALL, чтобы убедиться, что всё настроено правильно.

установка зависимости библиотеки Adafruit DHT — Unified Sensor

Пример кода

Скопируйте приведённый ниже код в вашу Arduino IDE. Но прежде чем загружать код на ESP32, необходимо внести несколько важных изменений, чтобы всё работало правильно для вашей конфигурации.

  1. Сначала обновите следующие две переменные, указав учётные данные вашей сети, чтобы ESP32 мог подключиться к вашей сети.

// Replace with your network credentials
const char* ssid = "REPLACE_WITH_YOUR_SSID";
const char* password = "REPLACE_WITH_YOUR_PASSWORD";
  1. Далее вам нужно установить правильное смещение UTC для вашего часового пояса. UTC (Coordinated Universal Time) — это стандартное время, используемое во всём мире, и ваш часовой пояс находится на определённое количество часов впереди или позади UTC. Обратитесь к списку смещений UTC.

    Смещение должно быть записано в секундах. Например, если вы находитесь в часовом поясе, который на 5 часов отстаёт от UTC (как восточное стандартное время в США), ваше смещение будет -5 * 60 * 60, что равно -18000. Если вы находитесь в центральноевропейском времени, которое составляет UTC +1, ваше смещение будет 1 * 60 * 60, или 3600. Вот несколько примеров:

    • Для UTC -5.00 : -5 * 60 * 60 : -18000

    • Для UTC +1.00 : 1 * 60 * 60 : 3600

    • Для UTC +0.00 : 0 * 60 * 60 : 0

const long gmtOffset_sec = -18000;
  1. Вам также нужно установить смещение летнего времени (в секундах). Если в вашей стране или регионе действует летнее время, установите смещение летнего времени на 3600 секунд (что равно 1 часу). В противном случае просто установите его на 0.

const int daylightOffset_sec = 3600;

После внесения этих изменений загрузите код.

// Libraries for SD card
#include "FS.h"
#include "SD.h"
#include <SPI.h>

// Library for DHTxx sensor
#include "DHT.h"

// Libraries to get time from NTP Server
#include <WiFi.h>
#include "time.h"

// Replace with your network credentials
const char* ssid = "REPLACE_WITH_YOUR_SSID";
const char* password = "REPLACE_WITH_YOUR_PASSWORD";

// Timer variables
unsigned long lastTime = 0;
unsigned long timerDelay = 2000;  // Log data every 2 seconds

// DHT sensor setup
#define DHTPIN 4
#define DHTTYPE DHT22
DHT dht(DHTPIN, DHTTYPE);  // Initialize DHT sensor

// NTP server setup
const char* ntpServer = "pool.ntp.org";
const long gmtOffset_sec = -18000;
const int daylightOffset_sec = 3600;

// Function that gets formatted date and time in YYYY-MM-DD HH:MM:SS format
String getFormattedDateTime() {
  struct tm timeinfo;
  if (!getLocalTime(&timeinfo)) {
    Serial.println("Failed to obtain time");
    return "";
  }
  char timeStringBuff[20];  // Buffer for formatted time string
  strftime(timeStringBuff, sizeof(timeStringBuff), "%Y-%m-%d %H:%M:%S", &timeinfo);
  return String(timeStringBuff);
}

// Function that writes to the SD card
void writeFile(fs::FS& fs, const char* path, const char* message) {
  Serial.printf("Writing file: %s\n", path);
  File file = fs.open(path, FILE_WRITE);
  if (!file) {
    Serial.println("Failed to open file for writing");
    return;
  }
  if (file.print(message)) {
    Serial.println("File written");
  } else {
    Serial.println("Write failed");
  }
  file.close();
}

// Function that appends data to the SD card
void appendFile(fs::FS& fs, const char* path, const char* message) {
  Serial.printf("Appending to file: %s\n", path);
  File file = fs.open(path, FILE_APPEND);
  if (!file) {
    Serial.println("Failed to open file for appending");
    return;
  }
  if (file.print(message)) {
    Serial.println("Message appended");
  } else {
    Serial.println("Append failed");
  }
  file.close();
}

void setup() {
  Serial.begin(115200);

  // Initialize DHT sensor
  dht.begin();
  Serial.println("DHT sensor initialized");

  // Connect to WiFi
  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, password);
  Serial.print("Connecting to WiFi ..");
  while (WiFi.status() != WL_CONNECTED) {
    Serial.print('.');
    delay(1000);
  }
  Serial.println("");
  Serial.print("Connected to WiFi. IP address: ");
  Serial.println(WiFi.localIP());

  // Initialize SD card
  if (!SD.begin()) {
    Serial.println("Card Mount Failed");
    return;
  }
  Serial.println("SD card initialized");

  // Configure time with NTP server
  configTime(gmtOffset_sec, daylightOffset_sec, ntpServer);
  Serial.println("NTP time configured");

  // Check if the log file exists, create if it doesn't
  File file = SD.open("/log.txt");
  if (!file) {
    Serial.println("Log file doesn't exist");
    Serial.println("Creating log file...");
    writeFile(SD, "/log.txt", "Timestamp,Temperature (°C),Humidity (%)\r\n");
  } else {
    Serial.println("Log file already exists");
  }
  file.close();

  Serial.println("Setup complete. Starting data logging...");
}

void loop() {
  if ((millis() - lastTime) > timerDelay) {
    // Get formatted date and time
    String formattedDateTime = getFormattedDateTime();

    // Get temperature and humidity readings
    float temp = dht.readTemperature();
    float hum = dht.readHumidity();

    // Only proceed if we got valid data
    if (formattedDateTime != "" && !isnan(temp) && !isnan(hum)) {
      // Concatenate all info separated by commas
      String dataMessage = formattedDateTime + "," + String(temp, 2) + "," + String(hum, 2) + "\r\n";
      Serial.print("Saving data: ");
      Serial.print("Time: ");
      Serial.print(formattedDateTime);
      Serial.print(", Temperature: ");
      Serial.print(temp, 2);
      Serial.print("°C");
      Serial.print(", Humidity: ");
      Serial.print(hum, 2);
      Serial.println("%");

      // Append the data to file
      appendFile(SD, "/log.txt", dataMessage.c_str());
    } else {
      // Print error messages for debugging
      if (formattedDateTime == "") {
        Serial.println("Error: Could not obtain time from NTP server");
      }
      if (isnan(temp) || isnan(hum)) {
        Serial.println("Error: Could not read from DHT sensor");
      }
    }

    lastTime = millis();
  }
}

Демонстрация

После загрузки скетча откройте Serial Monitor и убедитесь, что скорость передачи данных установлена на 115200. Затем нажмите кнопку EN (сброс) на вашем ESP32. Если всё настроено правильно, вы должны увидеть сообщения в Serial Monitor, показывающие, как ESP32 подключается к Wi-Fi, получает время из интернета, считывает данные с датчика и сохраняет их на SD-карту.

вывод логгера данных ESP32 DHT22 в Serial Monitor

Дайте вашему ESP32 поработать некоторое время — может быть, несколько часов — чтобы он мог собрать достаточно данных о температуре и влажности. Когда вы будете готовы проверить результаты, извлеките microSD-карту из модуля и вставьте её в компьютер с помощью кардридера. На карте вы должны найти файл log.txt.

содержимое microSD-карты логгера данных ESP32 DHT22

Этот файл содержит все показания, включая дату и время, температуру в градусах Цельсия и влажность в процентах.

файл лога логгера данных ESP32 DHT22

Вы можете открыть файл log.txt с помощью простого текстового редактора или, что ещё лучше, скопировать его содержимое в Microsoft Excel или Google Sheets. Там вы можете создать диаграммы или графики, которые помогут визуализировать, как температура и влажность изменялись с течением времени. Это значительно упрощает анализ данных.

график логгера данных ESP32 DHT22

Объяснение кода

Скетч начинается с подключения нескольких библиотек: библиотеки FS, SD и SPI позволяют ESP32 использовать протокол SPI для связи с microSD-картой и обрабатывать файловые операции, такие как создание, запись и чтение файлов. Библиотека DHT помогает ESP32 считывать температуру и влажность с датчика DHT22. Библиотеки WiFi и time позволяют ESP32 подключаться к интернету и получать текущее время с NTP-сервера.

// Libraries for SD card
#include "FS.h"
#include "SD.h"
#include <SPI.h>

// Library for DHTxx sensor
#include "DHT.h"

// Libraries to get time from NTP Server
#include <WiFi.h>
#include "time.h"

Далее мы определяем две переменные для вашего Wi-Fi-подключения: одну для имени сети (SSID) и одну для пароля. Этот шаг очень важен, потому что ESP32 должен подключиться к вашему Wi-Fi, чтобы связаться с NTP-сервером и получить правильное время. Вам нужно заменить текст-заполнитель на ваше реальное имя сети и пароль.

// Replace with your network credentials
const char* ssid = "REPLACE_WITH_YOUR_SSID";
const char* password = "REPLACE_WITH_YOUR_PASSWORD";

Далее мы создаём простой таймер с помощью двух переменных. Переменная lastTime запоминает, когда мы в последний раз записывали данные, а timerDelay устанавливает, как часто мы записываем снова. Здесь timerDelay установлен на 2000 миллисекунд (2 секунды), поэтому ESP32 будет пытаться записывать данные каждые две секунды.

// Timer variables
unsigned long lastTime = 0;
unsigned long timerDelay = 2000;  // Log data every 2 seconds

Далее мы сообщаем ESP32, к какому выводу подключён датчик DHT22. В нашем случае это вывод GPIO 4. Мы также указываем тип используемого датчика DHT. После этого мы создаём объект DHT в коде. Этот объект позволяет нам использовать простые команды для получения показаний температуры и влажности с датчика.

// DHT sensor setup
#define DHTPIN 4
#define DHTTYPE DHT22
DHT dht(DHTPIN, DHTTYPE);  // Initialize DHT sensor

После этого мы настраиваем параметры NTP. Мы выбираем сервер времени — в данном случае «pool.ntp.org» — и сообщаем ESP32, как корректировать время от UTC (Coordinated Universal Time) до вашего местного часового пояса. Мы используем gmtOffset_sec для сдвига часового пояса и daylightOffset_sec для добавления летнего времени, если в вашей местности оно применяется. Например, gmtOffset_sec равный -18000 означает UTC-5, что используется в восточном стандартном времени, а смещение летнего времени 3600 добавляет один час для летнего времени. Вам нужно изменить эти значения в зависимости от вашего местоположения. Если они неправильные, ваши метки времени будут выглядеть некорректно, даже если время NTP работает нормально.

// NTP server setup
const char* ntpServer = "pool.ntp.org";
const long gmtOffset_sec = -18000;
const int daylightOffset_sec = 3600;

Сервер «pool.ntp.org» является хорошим выбором по умолчанию, потому что он автоматически подключается к ближайшему серверу времени. При желании вы можете выбрать региональный сервер, например «europe.pool.ntp.org» или «asia.pool.ntp.org», в зависимости от вашего местоположения.

Регион

Имя хоста

Весь мир

pool.ntp.org

Азия

asia.pool.ntp.org

Европа

europe.pool.ntp.org

Северная Америка

north-america.pool.ntp.org

Океания

oceania.pool.ntp.org

Южная Америка

south-america.pool.ntp.org

Затем мы определяем пользовательскую функцию getFormattedDateTime(). Эта функция запрашивает у системы текущее время, которое было установлено ранее с помощью NTP-сервера. Затем она форматирует дату и время в читаемую строку вида «2025-08-13 16:45:23» с помощью встроенной функции strftime(). Если функции не удаётся получить время, она выводит сообщение и возвращает пустую строку.

// Function that gets formatted date and time in YYYY-MM-DD HH:MM:SS format
String getFormattedDateTime() {
  struct tm timeinfo;
  if (!getLocalTime(&timeinfo)) {
    Serial.println("Failed to obtain time");
    return "";
  }
  char timeStringBuff[20];  // Buffer for formatted time string
  strftime(timeStringBuff, sizeof(timeStringBuff), "%Y-%m-%d %H:%M:%S", &timeinfo);
  return String(timeStringBuff);
}

Мы также создаём ещё две пользовательские функции для записи на SD-карту. Первая, writeFile(), используется для создания нового файла; она применяется в начале для создания файла лога со строкой заголовка. Вторая, appendFile(), используется для добавления новых данных в конец существующего файла — именно её мы используем снова и снова в процессе логирования данных. Обе функции открывают файл, пытаются записать данные, а затем закрывают файл по завершении. Если что-то идёт не так, они выводят сообщение в Serial Monitor.

// Function that writes to the SD card
void writeFile(fs::FS& fs, const char* path, const char* message) {
  Serial.printf("Writing file: %s\n", path);
  File file = fs.open(path, FILE_WRITE);
  if (!file) {
    Serial.println("Failed to open file for writing");
    return;
  }
  if (file.print(message)) {
    Serial.println("File written");
  } else {
    Serial.println("Write failed");
  }
  file.close();
}

// Function that appends data to the SD card
void appendFile(fs::FS& fs, const char* path, const char* message) {
  Serial.printf("Appending to file: %s\n", path);
  File file = fs.open(path, FILE_APPEND);
  if (!file) {
    Serial.println("Failed to open file for appending");
    return;
  }
  if (file.print(message)) {
    Serial.println("Message appended");
  } else {
    Serial.println("Append failed");
  }
  file.close();
}

В разделе setup() мы запускаем последовательную связь, чтобы выводить сообщения в Serial Monitor. Мы также инициализируем датчик DHT с помощью dht.begin().

Serial.begin(115200);

// Initialize DHT sensor
dht.begin();
Serial.println("DHT sensor initialized");

Затем мы пытаемся подключить ESP32 к Wi-Fi сети. Мы используем функцию WiFi.mode() для перевода радиомодуля Wi-Fi в режим станции и функцию WiFi.begin(), передавая ей имя и пароль вашей Wi-Fi сети.

// Connect to WiFi
WiFi.mode(WIFI_STA);
WiFi.begin(ssid, password);

Пока ESP32 пытается подключиться, мы проверяем статус подключения с помощью WiFi.status(). Если подключение ещё не установлено, он ждёт немного и пробует снова, пока не подключится.

Serial.print("Connecting to WiFi ..");
while (WiFi.status() != WL_CONNECTED) {
  Serial.print('.');
  delay(1000);
}
Serial.println("");

Для справки, функция WiFi.status() может возвращать следующие статусы:

  • WL_CONNECTED: Подключён к Wi-Fi сети.

  • WL_NO_SHIELD: Wi-Fi модуль не обнаружен (не применимо к ESP32, но актуально для некоторых плат Arduino).

  • WL_IDLE_STATUS: Временный статус во время попытки подключения после вызова WiFi.begin().

  • WL_NO_SSID_AVAIL: Не найдено сетей, соответствующих указанному SSID.

  • WL_SCAN_COMPLETED: Сканирование сетей завершено.

  • WL_CONNECT_FAILED: Не удалось подключиться после всех попыток.

  • WL_CONNECTION_LOST: Соединение с сетью потеряно.

  • WL_DISCONNECTED: Не подключён ни к какой сети.

После подключения ESP32 к вашей Wi-Fi сети он выводит IP-адрес, полученный от вашего маршрутизатора, с помощью WiFi.localIP(), чтобы вы знали, что он в сети.

Serial.print("Connected to WiFi. IP address: ");
Serial.println(WiFi.localIP());

Далее мы пытаемся инициализировать SD-карту. Если карта не инициализируется, будет выведено сообщение об ошибке, и работа прекратится.

// Initialize SD card
if (!SD.begin()) {
  Serial.println("Card Mount Failed");
  return;
}
Serial.println("SD card initialized");

Затем мы вызываем функцию configTime() для синхронизации внутренних часов ESP32 с NTP-сервером, используя смещения часового пояса, которые мы установили ранее. После этого ESP32 знает правильную дату и время и будет отслеживать их, даже если Wi-Fi отключится.

// Configure time with NTP server
configTime(gmtOffset_sec, daylightOffset_sec, ntpServer);
Serial.println("NTP time configured");

Прежде чем начать логирование данных, мы проверяем, существует ли уже файл /log.txt на microSD-карте. Если нет, мы создаём его и записываем строку заголовка, обозначающую столбцы: Timestamp, Temperature (°C) и Humidity (%). Это упрощает чтение файла позже в Excel или Google Sheets. Если файл уже существует, мы оставляем его без изменений, чтобы сохранить старые данные. Затем мы закрываем файл и выводим сообщение о завершении настройки, чтобы вы знали, что система готова к записи.

// Check if the log file exists, create if it doesn't
File file = SD.open("/log.txt");
if (!file) {
  Serial.println("Log file doesn't exist");
  Serial.println("Creating log file...");
  writeFile(SD, "/log.txt", "Timestamp,Temperature (°C),Humidity (%)\r\n");
} else {
  Serial.println("Log file already exists");
}
file.close();

Serial.println("Setup complete. Starting data logging...");

В функции loop() программа проверяет, сколько времени прошло, сравнивая текущее время из millis() со значением lastTime. Если разница больше нашего timerDelay, значит, пора записать новые данные.

Мы вызываем функцию getFormattedDateTime() для получения текущего времени в виде строки и используем объект DHT для считывания температуры и влажности.

if ((millis() - lastTime) > timerDelay) {
  // Get formatted date and time
  String formattedDateTime = getFormattedDateTime();

  // Get temperature and humidity readings
  float temp = dht.readTemperature();
  float hum = dht.readHumidity();

Далее мы проверяем, что все три показания валидны: строка времени не должна быть пустой, а температура и влажность не могут быть NaN (Not a Number — не число). Если они валидны, мы создаём строку данных, объединяя время, температуру (округлённую до двух десятичных знаков) и влажность (также округлённую), с запятыми между ними и переводом строки в конце. Мы выводим эту строку в Serial Monitor, чтобы вы могли видеть данные в реальном времени, а затем вызываем appendFile() для сохранения их на SD-карту в файл log.txt.

Если метка времени пуста или показания датчика невалидны (что отобразится как «NaN»), мы выводим сообщения об ошибках для помощи в отладке.

// Only proceed if we got valid data
if (formattedDateTime != "" && !isnan(temp) && !isnan(hum)) {
  // Concatenate all info separated by commas
  String dataMessage = formattedDateTime + "," + String(temp, 2) + "," + String(hum, 2) + "\r\n";
  Serial.print("Saving data: ");
  Serial.print("Time: ");
  Serial.print(formattedDateTime);
  Serial.print(", Temperature: ");
  Serial.print(temp, 2);
  Serial.print("°C");
  Serial.print(", Humidity: ");
  Serial.print(hum, 2);
  Serial.println("%");

  // Append the data to file
  appendFile(SD, "/log.txt", dataMessage.c_str());
} else {
  // Print error messages for debugging
  if (formattedDateTime == "") {
    Serial.println("Error: Could not obtain time from NTP server");
  }
  if (isnan(temp) || isnan(hum)) {
    Serial.println("Error: Could not read from DHT sensor");
  }
}

Наконец, мы обновляем lastTime текущим значением millis(), чтобы следующий цикл логирования начался примерно через две секунды.

lastTime = millis();

Устранение неполадок

Ошибка Card Mount Failed

Если вы видите сообщение об ошибке «Card Mount Failed» в Serial Monitor, это обычно означает, что есть проблема с подключением модуля SD-карты к ESP32. Перепроверьте все соединения, чтобы убедиться, что каждый провод подключён к правильному выводу и что они надёжно закреплены. Также ваша microSD-карта должна быть отформатирована в файловой системе FAT16 или FAT32 — другие форматы, такие как exFAT, работать не будут.

Некоторые сайты могут советовать вам питать модуль microSD-карты от 5 В, но не делайте этого, если вы не абсолютно уверены, что ваш модуль имеет встроенный стабилизатор напряжения и преобразователь логических уровней. Большинство microSD-карт рассчитаны на работу при 3,3 вольтах, и подача на них 5 В напрямую может навсегда повредить карту.

Ошибка Could not obtain time from NTP server

Другая распространённая проблема — сообщение «Error: Could not obtain time from NTP server». Это означает, что ESP32 не смог получить текущее время из интернета. Наиболее вероятная причина заключается в том, что ваш ESP32 не смог успешно подключиться к интернету. Убедитесь, что ваша сеть имеет доступ к интернету.

Также имейте в виду, что система NTP использует UDP-порт 123, и в некоторых сетях — особенно в школьных, корпоративных или гостевых сетях — этот порт может быть заблокирован брандмауэром. Если это так, попробуйте подключить ESP32 к другой Wi-Fi сети, например к домашнему маршрутизатору или мобильной точке доступа.

Вы также можете попробовать другие адреса NTP-серверов, например «time.nist.gov». Вы также можете указать несколько серверов в функции configTime(), чтобы при отказе одного сервера другие могли служить резервными.