A Temperature Logger on microSD: DHT22, DS3231 and a CSV File
Every 5 seconds the ESP32 writes the time, temperature and humidity to a CSV file on a memory card — no Wi-Fi, no cloud. The microSD module over SPI, the SD library, why paths start with "/", why close() matters, and opening the data in Excel.
About the project
Not all data has to go to the internet. A sensor in a greenhouse, a cellar or a car can simply write its measurements to a microSD card. 8 GB holds decades of CSV lines. Later you put the card into a computer and open the file in Excel or Google Sheets. We build a logger: the DHT22 measures, the DS3231 keeps the exact time, and every 5 seconds a line is added to /log.csv.
What you need
- ESP32 DevKit
- microSD module (SPI) + a card up to 32 GB formatted as FAT32
- DHT22 sensor
- DS3231 clock with a cell
- Libraries: SD (part of arduino-esp32), RTClib, DHT sensor library
Wiring
microSD module (hardware SPI, VSPI):
| Module | ESP32 |
|---|---|
| VCC | 5V (the blue module with a regulator) |
| GND | GND |
| MISO | GPIO19 |
| MOSI | GPIO23 |
| SCK | GPIO18 |
| CS | GPIO5 |
DS3231: SDA → GPIO21, SCL → GPIO22, VCC → 3V3, GND → GND. DHT22: DATA → GPIO27, VCC → 3V3.
The most common blue module has an AMS1117 regulator and a level shifter. It does not work from 3.3 V: power it from 5V. There are also modules without a regulator, and those, on the contrary, must run from 3.3 V only.
Rules of the SD library on the ESP32
- A path always starts with "/":
"/log.csv". Plain"log.csv"does not open on the ESP32. - Modes:
FILE_READ(the default);FILE_WRITE, which overwrites the file from scratch;FILE_APPEND, which adds to the end. A logger needs APPEND. if (f)checks whether the file opened. The card may have been removed, or the file may not exist.close()is mandatory. The card writes in 512-byte sectors, and until the file is closed (orflush()is called) the data sits in a buffer. Cut the power and the data is lost.- Opening and closing the file for every line is fine for a logger that writes every few seconds. For hundreds of writes a second, keep the file open and
flush()every 1–2 seconds.
Code
// microSD logger: DHT22 + DS3231 clock → /log.csv every 5 s
#include <SD.h>
#include <RTClib.h>
#include <DHT.h>
const int SD_CS = 5;
const unsigned long PERIOD_MS = 5000;
RTC_DS3231 rtc;
DHT dht(27, DHT22);
unsigned long lastLog = 0;
// the CSV header goes only into a new (or just deleted) file
void ensureHeader() {
if (SD.exists("/log.csv")) return;
File f = SD.open("/log.csv", FILE_WRITE);
f.println("time,temperature,humidity");
f.close();
}
void logLine() {
float t = dht.readTemperature();
float h = dht.readHumidity();
if (isnan(t) || isnan(h)) return;
DateTime now = rtc.now();
char line[48];
snprintf(line, sizeof line, "%04d-%02d-%02d %02d:%02d:%02d,%.1f,%.0f",
now.year(), now.month(), now.day(), now.hour(), now.minute(), now.second(), t, h);
ensureHeader();
File f = SD.open("/log.csv", FILE_APPEND);
if (!f) {
Serial.println("Cannot open /log.csv");
return;
}
f.println(line);
f.close(); // only close() (or flush()) really writes the data to the card
Serial.println(line);
}
void setup() {
Serial.begin(115200);
dht.begin();
if (!rtc.begin()) Serial.println("DS3231 not found");
else if (rtc.lostPower()) rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
if (!SD.begin(SD_CS)) {
Serial.println("Card mount failed: check the power and wiring");
while (true) delay(1000);
}
ensureHeader();
Serial.println("Logging to /log.csv. Commands: dump — show the file, clear — delete it");
}
void loop() {
if (millis() - lastLog >= PERIOD_MS) {
lastLog = millis();
logLine();
}
if (Serial.available()) {
String cmd = Serial.readStringUntil('\n');
cmd.trim();
if (cmd == "dump") {
File f = SD.open("/log.csv");
while (f.available()) Serial.write(f.read());
f.close();
} else if (cmd == "clear") {
SD.remove("/log.csv");
Serial.println("File deleted");
}
}
}
Opening it in Excel
The file looks like this:
time,temperature,humidity
2026-09-30 14:05:00,22.4,55
2026-09-30 14:05:05,22.5,55
With a European locale, Excel expects a semicolon as the separator and a comma as the decimal mark. Use "Data → From Text/CSV" and pick "comma" as the delimiter. Google Sheets opens the file as is.
Good to know
- The card won't mount. Check the module's supply first, then the format: FAT32 (cards of 64 GB and larger ship as exFAT and need reformatting). Keep the SPI wires as short as possible.
- The current while writing jumps to 80–100 mA. On a weak supply a write can reset the ESP32.
- Endurance. Each sector survives thousands to tens of thousands of writes. One line every 5 seconds means years of service.
- The simulator saves the card's files with the project (up to 256 KB), so the data survives a sketch restart, just like on a real card.