В этом проекте показано, как сохранять данные с временными метками на карте microSD при помощи микроконтроллера ESP32. Для примера мы будем сохранять показания датчика температуры DS18B20 каждые 10 минут. ESP32 будет находиться в глубоком сне между сеансами чтения показаний и запрашивать дату и время, используя протокол NTP (Network Time Protocol — Протокол сетевого времени).
Вступление
Установка дополнения для ESP32 в Arduino IDE
Перед тем как начать, давайте вкратце опишем основные составляющие нашего проекта:
- Микроконтроллер ESP32 считывает показания температуры с датчика DS18B20.
- После получения температуры он запрашивает дату и время на сервере по протоколу NTP. Для этого ESP32 понадобится соединение по интерфейсу Wi-Fi.
- Данные (температура и временная метка) записываются на карту microSD.
- Затем микроконтроллер ESP32 переходит в режим сна на 10 минут.
- ESP32 просыпается и повторяет весь процесс.
Необходимые для проекта компоненты
Подготовка модуля для microSD
Для записи данных на карту microSD посредством микроконтроллера ESP32 мы будем использовать модуль с SPI интерфейсом.
Форматирование micro SD
Перед использованием необходимо произвести форматирование MicroSD карты. Для этого выполните следующее.
- Вставьте карту microSD в компьютер. Пройдите в «My Computer» (Мой компьютер) и нажмите правой кнопкой мыши на «SD card» (Карта SD). Выберите «Format» (Форматировать).
- Появиться новое окно. Выберите «FAT32» и нажмите «Start» (Начать), чтобы запустить процесс форматирования, и следуйте указаниям на экране.
Схема подключения компонентов
Соедините элементы, как показано на нижеследующей схеме, созданной с помощью программы Fritzing (полный обзор программы по ссылке).
Для подключения модуля карты microSD можно также использовать следующую таблицу.
Модуль MicroSD | ESP32 |
3V3 | 3V3 |
CS | GPIO 5 |
MOSI | GPIO 23 |
CLK | GPIO 18 |
MISO | GPIO 19 |
GND | GND |
На следующем рисунке показан пример готового устройства.
Подготовка среды разработки Arduino IDE для работы с ESP32
Подготовка среды разработки Arduino IDEДля среды Arduino IDE существует дополнение, которое позволяет программировать микросхему ESP32 посредством этой среды и её языка программирования.
Следуйте одному из нижеприведённых руководств, чтобы подготовить среду Arduino IDE для работы с ESP32, если вы этого ещё не сделали.
Установка необходимых для работы библиотек
Перед загрузкой кода необходимо проверить наличие всех необходимых библиотек: библиотеки OneWire Пауля Стоффрегена и библиотеки Dallas Temperature для работы с датчиком DS18B20. Также необходима библиотека TPClient (форк, созданный пользователем Taranais), которая отправляет запросы к серверу NTP.
Для установки этих библиотек нужно выполнить следующие действия:
Библиотека OneWire
- Скачать библиотеку OneWire по ссылке.
- Разархивируйте файл с расширением .zip
- Смените название папки OneWire-master на OneWire.
- Переместите папку OneWire в вашу папку с установленными библиотеками среды Arduino IDE.
- Перезапустите среду Arduino IDE.
Библиотека Dallas Temperature
- Скачать библиотеку DallasTemperature по ссылке.
- Разархивируйте файл с расширением .zip
- Смените название папки Arduino-Temperature-Control-Library-master на DallasTemperature.
- Переместите папку DallasTemperature в вашу папку с установленными библиотеками среды Arduino IDE.
- Перезапустите среду Arduino IDE.
Библиотека NTPClient
- Скачайте библиотеку NTPClient по ссылке.
- Разархивируйте файл с расширением .zip
- Смените название папки NTPClient-master на NTPClient.
- Переместите папку NTPClient в вашу папку с установленными библиотеками среды Arduino IDE.
- Перезапустите среду Arduino IDE.
Загрузка программного кода регистратора данных
Ниже представлен код, который необходимо загрузить в микроконтроллер ESP32. Перед загрузкой необходимо изменить код, указав параметры вашей сети: идентификатор SSID и пароль.
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 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 |
/********* Rui Santos Complete project details at https://randomnerdtutorials.com *********/ // Libraries for SD card #include "FS.h" #include "SD.h" #include "SPI.h" //DS18B20 libraries #include "OneWire.h" #include "DallasTemperature.h" // Libraries to get time from NTP Server #include "WiFi.h" #include "NTPClient.h" #include "WiFiUdp.h" // Define deep sleep options uint64_t uS_TO_S_FACTOR = 1000000; // Conversion factor for micro seconds to seconds // Sleep for 10 minutes = 600 seconds uint64_t TIME_TO_SLEEP = 600; // Replace with your network credentials const char* ssid = "REPLACE_WITH_YOUR_SSID"; const char* password = "REPLACE_WITH_YOUR_PASSWORD"; // Define CS pin for the SD card module #define SD_CS 5 // Save reading number on RTC memory RTC_DATA_ATTR int readingID = 0; String dataMessage; // Data wire is connected to ESP32 GPIO 21 #define ONE_WIRE_BUS 21 // Setup a oneWire instance to communicate with a OneWire device OneWire oneWire(ONE_WIRE_BUS); // Pass our oneWire reference to Dallas Temperature sensor DallasTemperature sensors(&oneWire); // Temperature Sensor variables float temperature; // Define NTP Client to get time WiFiUDP ntpUDP; NTPClient timeClient(ntpUDP); // Variables to save date and time String formattedDate; String dayStamp; String timeStamp; void setup() { // Start serial communication for debugging purposes Serial.begin(115200); // Connect to Wi-Fi network with SSID and password 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."); // Initialize a NTPClient to get time timeClient.begin(); // Set offset time in seconds to adjust for your timezone, for example: // GMT +1 = 3600 // GMT +8 = 28800 // GMT -1 = -3600 // GMT 0 = 0 timeClient.setTimeOffset(3600); // Initialize SD card SD.begin(SD_CS); if(!SD.begin(SD_CS)) { Serial.println("Card Mount Failed"); return; } uint8_t cardType = SD.cardType(); if(cardType == CARD_NONE) { Serial.println("No SD card attached"); return; } Serial.println("Initializing SD card..."); if (!SD.begin(SD_CS)) { Serial.println("ERROR - SD card initialization failed!"); return; // init failed } // If the data.txt file doesn't exist // Create a file on the SD card and write the data labels File file = SD.open("/data.txt"); if(!file) { Serial.println("File doens't exist"); Serial.println("Creating file..."); writeFile(SD, "/data.txt", "Reading ID, Date, Hour, Temperature \r\n"); } else { Serial.println("File already exists"); } file.close(); // Enable Timer wake_up esp_sleep_enable_timer_wakeup(TIME_TO_SLEEP * uS_TO_S_FACTOR); // Start the DallasTemperature library sensors.begin(); getReadings(); getTimeStamp(); logSDCard(); // Increment readingID on every new reading readingID++; // Start deep sleep Serial.println("DONE! Going to sleep now."); esp_deep_sleep_start(); } void loop() { // The ESP32 will be in deep sleep // it never reaches the loop() } // Function to get temperature void getReadings(){ sensors.requestTemperatures(); temperature = sensors.getTempCByIndex(0); // Temperature in Celsius //temperature = sensors.getTempFByIndex(0); // Temperature in Fahrenheit Serial.print("Temperature: "); Serial.println(temperature); } // Function to get date and time from NTPClient void getTimeStamp() { while(!timeClient.update()) { timeClient.forceUpdate(); } // The formattedDate comes with the following format: // 2018-05-28T16:00:13Z // We need to extract date and time formattedDate = timeClient.getFormattedDate(); Serial.println(formattedDate); // Extract date int splitT = formattedDate.indexOf("T"); dayStamp = formattedDate.substring(0, splitT); Serial.println(dayStamp); // Extract time timeStamp = formattedDate.substring(splitT+1, formattedDate.length()-1); Serial.println(timeStamp); } // Write the sensor readings on the SD card void logSDCard() { dataMessage = String(readingID) + "," + String(dayStamp) + "," + String(timeStamp) + "," + String(temperature) + "\r\n"; Serial.print("Save data: "); Serial.println(dataMessage); appendFile(SD, "/data.txt", dataMessage.c_str()); } // Write to the SD card (DON'T MODIFY THIS FUNCTION) 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(); } // Append data to the SD card (DON'T MODIFY THIS FUNCTION) 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(); } |
Скачать код можно по ссылке.
Как работает и что делает этот код?
В этом примере микроконтроллер ESP32 находится в режиме глубокого сна между сеансами чтения показаний. Из‑за режима глубокого сна весь ваш код должен находиться в функции setup(), так как ESP32 никогда не дойдёт до функции loop().
Импорт библиотек
Сначала импортируем необходимые библиотеки для модуля карты microSD.
1 2 3 |
#include "FS.h" #include "SD.h" #include "SPI.h" |
Далее — библиотеки для работы с датчиком температуры DS18B20.
1 2 |
#include "OneWire.h" #include "DallasTemperature.h" |
Следующие библиотеки позволяют отправлять запросы для получения даты и времени от сервера NTP.
1 2 3 |
#include "WiFi.h" #include "NTPClient.h" #include "WiFiUdp.h" |
Настройка времени глубокого сна
В этом примере используется коэффициент пересчёта для перевода микросекунд в секунды, так что вы можете задать время сна в переменной TIME_TO_SLEEP в секундах.
В нашем случае мы настраиваем время сна для ESP32 на 10 минут (600 секунд). Для установки другого времени сна микроконтроллера ESP32 необходимо просто ввести количество секунд для переменной TIME_TO_SLEEP.
1 2 3 4 |
// Define deep sleep options uint64_t uS_TO_S_FACTOR = 1000000; // Conversion factor for micro seconds to seconds // Sleep for 10 minutes = 600 seconds uint64_t TIME_TO_SLEEP = 600; |
Установка учётных данных сети
Запишите ваши учётные данные сети в следующие переменные, чтобы микроконтроллер ESP32 смог подключиться к локальной сети.
1 2 3 |
// Replace with your network credentials const char* ssid = "REPLACE_WITH_YOUR_SSID"; const char* password = "REPLACE_WITH_YOUR_PASSWORD"; |
Инициализация датчиков и переменных
Далее определяем вывод SD карты microSD. В нашем случае это вывод общего назначения GPIO 5.
1 |
#define SD_CS 5 |
Создаём переменную под названием readingID для хранения идентификатора считанных показаний. Это поможет упорядочить их. Для хранения значений переменных во время глубокого сна мы можем записать их в памяти часов реального времени. Чтобы данные сохранялись в этой памяти, необходимо просто добавить перед переменной следующее: RTC_DATA_ATTR.
1 2 |
// Save reading number on RTC memory RTC_DATA_ATTR int readingID = 0; |
Создаём строковую переменную типа String для хранения данных, подлежащих записи на карту microSD.
1 |
String dataMessage; |
Затем создаются необходимые экземпляры библиотек для датчика температуры. Датчик температуры подключается к линии ввода-вывода общего назначения GPIO 21.
1 2 3 4 5 6 |
// Data wire is connected to ESP32 GPIO21 #define ONE_WIRE_BUS 21 // Setup a oneWire instance to communicate with a OneWire device OneWire oneWire(ONE_WIRE_BUS); // Pass our oneWire reference to Dallas Temperature sensor DallasTemperature sensors(&oneWire); |
Затем создаём переменную с плавающей запятой для хранения показания температуры, полученного от датчика DS18B20.
1 |
float temperature; |
Следующие две строки кода создают клиента для протокола NTP для получения даты и времени от сервера.
1 2 |
WiFiUDP ntpUDP; NTPClient timeClient(ntpUDP); |
Затем инициализируем строковые переменные, служащие для хранения даты и времени.
1 2 3 |
String formattedDate; String dayStamp; String timeStamp; |
Функция запуска setup()
Если вы использует режим глубокого сна микроконтроллера ESP32, весь ваш код должен находиться в функции setup(), так как ESP32 никогда не дойдёт до функции loop().
Подключение к сети Wi-Fi
Следующие фрагменты кода служат для подключения к сети Wi-Fi. Подключение к сети Wi-Fi необходимо, чтобы запрашивать дату и время на сервере NTP.
1 2 3 4 5 6 7 |
Serial.print("Connecting to "); Serial.println(ssid); WiFi.begin(ssid, password); while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); } |
Инициализация клиента NTP
Далее инициализируем клиента NTP, чтобы запрашивать дату и время на сервере NTP.
1 |
timeClient.begin(); |
Вы можете использовать метод setTimeOffset(), чтобы отрегулировать время для своего часового пояса.
1 |
timeClient.setTimeOffset(3600); |
Вот несколько примеров для различных часовых поясов:
- GMT +1 = 3600,
- GMT +8 = 28800,
- GMT -1 = -3600,
- GMT 0 = 0.
Инициализация модуля карты microSD
Далее инициализируем карту microSD. Следующие операторы if проверяют, корректно ли подсоединена карта microSD.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
SD.begin(SD_CS); if(!SD.begin(SD_CS)) { Serial.println("Card Mount Failed"); return; } uint8_t cardType = SD.cardType(); if(cardType == CARD_NONE) { Serial.println("No SD card attached"); return; } Serial.println("Initializing SD card..."); if (!SD.begin(SD_CS)) { Serial.println("ERROR - SD card initialization failed!"); return; // init failed } |
Затем пытаемся открыть файл data.txt на карте microSD.
1 |
File file = SD.open("/data.txt"); |
Если файл не существует, нам необходимо создать его и записать заголовок для файла с расширением.txt.
1 |
writeFile(SD, "/data.txt", "Reading ID, Date, Hour, Temperature \r\n"); |
Если файл существует, то код выполняется дальше.
1 2 3 |
else { Serial.println("File already exists"); } |
Наконец мы закрываем файл.
1 |
file.close(); |
Включение функции пробуждения по таймеру
Далее включаем функцию пробуждения по таймеру, время для которого вы определили ранее в переменной TIME_TO_SLEEP.
1 |
esp_sleep_enable_timer_wakeup(TIME_TO_SLEEP * uS_TO_S_FACTOR); |
Инициализация библиотеки для DS18B20
Далее мы инициализируем библиотеку для датчика температуры DS18B20.
1 |
sensors.begin(); |
Получение показаний и запись данных
После инициализации всех компонентов мы можем получать показания и временные метки и записывать всё это на карту microSD.
Для упрощения понимания кода мы создали следующие функции:
- getReadings(): считывает температуру от датчика DS18B20;
- getTimeStamp(): получает дату и время от сервера NTP;
- logSDcard(): записывает полученные данные на карту microSD.
Поле выполнения этих задач, мы увеличиваем на единицу идентификатор показания readingID.
1 |
esp_deep_sleep_start(); |
Наконец микроконтроллер ESP32 переходит в глубокий сон.
1 |
readingID++; |
getReadings()
Давайте посмотрим на функцию getReadings(). Она считывает температуру от датчика DS18B20.
1 2 |
sensors.requestTemperatures(); temperature = sensors.getTempCByIndex(0); // Temperature in Celsius |
По умолчанию код получает температуру в градусах Цельсия. Вы можете раскомментировать следующую строку и закомментировать предыдущую, чтобы получать температуру в градусах Фаренгейта.
1 |
//temperature = sensors.getTempFByIndex(0); // Temperature in Fahrenheit |
getTimeStamp()
Эта функция получает дату и время. Следующие строки обеспечивают получение правильного времени.
1 2 3 |
while(!timeClient.update()) { timeClient.forceUpdate(); } |
Иногда клиенты NTP получают 1970 год. Чтобы быть уверенными, что этого не случиться, мы инициируем обновление.Затем преобразуем дату и время в читаемый формат с помощью метода getFormattedDate().
1 |
formattedDate = timeClient.getFormattedDate(); |
Дата и время возвращаются в следующем виде.
1 |
2018-04-30T16:00:13Z |
Нам необходимо разделить эту строку, чтобы получить отдельно время и дату. Это мы и делаем далее.
1 2 3 4 5 6 7 |
// Extract date int splitT = formattedDate.indexOf("T"); dayStamp = formattedDate.substring(0, splitT); Serial.println(dayStamp); // Extract time timeStamp = formattedDate.substring(splitT+1, formattedDate.length()-1); Serial.println(timeStamp); |
Дата сохраняется в переменной dayStamp, а время — в переменной timeStamp.
logSDCard()
Функция logSDCard() объединяет всю информацию в строковой переменной dataMessage. Показания разделены запятыми.
1 |
dataMessage = String(readingID) + "," + String(dayStamp) + "," + String(timeStamp) + "," + String(temperature) + "\r\n"; |
Примечание: символы «\r\n» в конце переменной dataMessage, служат для того, чтобы следующее показание записывалось в следующей строке.
Затем с помощью следующих строк мы записываем всю информацию в файл data.txt на карте microSD.
1 |
appendFile(SD, "/data.txt", dataMessage.c_str()); |
Примечание: функция appendFile() принимает для сообщения только переменные типа const char. Поэтому используйте метод c_str() для преобразования переменной dataMessage.
writeFile() и appendFile()
Последние две функции writeFile() и appendFile() используются для записи и добавления данных на карту microSD. Они есть в примерах, идущих вместе с библиотекой для работы с картой SD, и вам не нужно их изменять.
Чтобы попробовать другие примеры для работы с картой microSD выберите пункт меню «File > Examples > SD(esp32)» («Файл > Примеры > SD(esp32)»).
Загрузка кода
Теперь загрузите код в микроконтроллер ESP32. Убедитесь, что выбрали правильные плату и последовательный COM-порт.
Демонстрация работы
Откройте окно последовательного COM-порта (Serial Monitor) и настройте скорость передачи на 115 200 бод. Нажмите кнопку «Enable» на плате с микроконтроллером ESP32 и удостоверьтесь, что всё работает должным образом (ESP32 подключён к локальной сети, а карта microSD установлена корректно).
Оставьте микроконтроллер ESP32 работающим на несколько часов, чтобы проверить, что всё работает, как ожидалось. После этого извлеките карту microSD и вставьте в компьютер. На карте должен быть файл под названием data.txt.
Вы можете скопировать содержимое файла в Google Таблицы и затем разделить данные по столбцам используя в качестве разделителей запятые. Выберите столбец, в котором находятся данные, и нажмите «Data > Split text to columns» («Данные > Разделить на колонки»)… Теперь вы можете строить графики для анализа данных.
В этом руководстве мы показали как записывать данные на карту microSD, используя микроконтроллер ESP32. Мы также показали, как считывать показания температуры от датчика DS18B20 и как отправлять запрос на получение времени от сервера, работающего по протоколу NTP.
3 комментария. Оставить новый
Еще бы добавить SIM800l чтоб отправлять данные с CD например на народный мониторинг в отсутствие WiFi
Спасибо большое за хорошую статью. Поясните, пожалуйста, как указать правильную дату и время создания файла из полученного времени, а не ерунду 01.01.2001 по умолчанию. Спасибо.
Отличная статья. Только есть один момент, если программу попытаться использовать в реальном регистраторе, то после вынять/вставить SDшку нужно обязательно нажать reset, иначе процесс прервётся. На мой ламерский взгляд, в блоке “// Initialize SD card” setup лучше убрать return. Тогда карточка подхватится в том же цикле readingID (конечно пропустив те ID, карточки не было)