Log temperature and humidity to an SD card

The ESP32 writes the temperature and humidity to a file on a microSD card every minute. A web page shows the latest values and lets you download the whole log for a spreadsheet.
  • Level:Intermediate
  • Duration: 1 h
  • Ages 11+
  • Platform: ESP32

Tools for this lesson

Helpful for this build. Open a tool to see tips and more projects that use it.

Log temperature and humidity to an SD card – EEMENTOR STEM™ lesson

Cloud services are handy, but they need the internet and an account. Scientists in the field often use a data logger instead: a small device that writes measurements to a memory card. This one measures the temperature and humidity every minute and adds a line to a file on a microSD card. The file is a CSV, so it opens straight in Excel, Google Sheets or LibreOffice. And because it's an ESP32, you can also look at the latest values and download the file from a web page, without taking the card out.

What you'll learn

  • How to write to a file on an SD card
  • How CSV files store a table as plain text
  • How the ESP32 can serve a file for download
  • How to add the date and time to every reading

What you need

  • ESP32 board, expansion board and USB cable (ESP32 IoT Starter Kit)
  • DHT11 temperature and humidity module (45-in-1 Sensor Kit)
  • SD card module (45-in-1 Sensor Kit)
  • A microSD card, up to 32 GB, formatted as FAT32
  • A 2.4 GHz Wi-Fi network
  • The printed parts from the 3D printing box below (logger case), or the cardboard version described there
  • Breadboard and jumper wires (ESP32 IoT Starter Kit)

Wiring

Breadboard wiring diagram: Log temperature and humidity to an SD card
Breadboard view. Check each connection against the wiring table below.
From To
DHT11 module S ESP32 GPIO 27
DHT11 module + and − 3V3 and GND
SD module CS ESP32 GPIO 5
SD module SCK ESP32 GPIO 18
SD module MOSI ESP32 GPIO 23
SD module MISO ESP32 GPIO 19
SD module VCC 5V
SD module GND GND

Sensor modules from different makers can label and order their pins differently. Always follow the labels printed on your module.

The SD card module has its own 3.3 V regulator, so its VCC goes to 5V on the expansion board. Format the card as FAT32 on a computer before you start.

The sketch

In the Arduino IDE, open Library Manager and install the DHT sensor library by Adafruit (say yes when it asks to install Adafruit Unified Sensor too).

Replace YOUR_WIFI_NAME and YOUR_WIFI_PASSWORD with your own network details (2.4 GHz only).

Change TIME_ZONE to your own time zone. The example is for Central Europe; search for "POSIX time zone" and your city to find yours (for example EET-2EEST,M3.5.0/3,M10.5.0/4 for Estonia or GMT0BST,M3.5.0/1,M10.5.0 for the UK).

#include <WiFi.h>
#include <WebServer.h>
#include <SPI.h>
#include <SD.h>
#include <DHT.h>
#include <time.h>

const char* WIFI_NAME = "YOUR_WIFI_NAME";
const char* WIFI_PASSWORD = "YOUR_WIFI_PASSWORD";
const char* TIME_ZONE = "CET-1CEST,M3.5.0,M10.5.0/3";   // your time zone
const char* LOG_FILE = "/climate.csv";
const unsigned long EVERY = 60000;                       // one reading a minute

const int DHT_PIN = 27;
const int SD_CS_PIN = 5;
DHT dht(DHT_PIN, DHT11);
WebServer server(80);

float lastTemp = NAN, lastHumidity = NAN;
String lastTime = "-";
unsigned long lastLog = 0;
long rows = 0;

String timestamp() {
  struct tm t;
  if (!getLocalTime(&t, 50)) return "";
  char text[20];
  strftime(text, sizeof(text), "%Y-%m-%d,%H:%M", &t);    // date and time in two columns
  return text;
}

void logReading() {
  float temp = dht.readTemperature();
  float humidity = dht.readHumidity();
  String when = timestamp();
  if (isnan(temp) || isnan(humidity) || when == "") {
    Serial.println("No reading or no time yet, trying again next minute");
    return;
  }
  File f = SD.open(LOG_FILE, FILE_APPEND);
  if (!f) {
    Serial.println("Could not open the file. Is the card in?");
    return;
  }
  if (f.size() == 0) f.println("Date,Time,Temperature (C),Humidity (%)");   // header for a new file
  f.printf("%s,%.1f,%.0f\n", when.c_str(), temp, humidity);
  f.close();                          // close after every line, so nothing is lost if the power goes
  lastTemp = temp;
  lastHumidity = humidity;
  lastTime = when;
  rows++;
  Serial.printf("%s  %.1f C  %.0f %%\n", when.c_str(), temp, humidity);
}

void handleRoot() {
  File f = SD.open(LOG_FILE);
  long bytes = f ? f.size() : 0;
  if (f) f.close();
  String html = "<!doctype html><meta name=viewport content='width=device-width'><meta http-equiv=refresh content=60>"
                "<title>Climate logger</title><body style='font-family:sans-serif;max-width:420px;margin:24px auto;padding:0 16px'>"
                "<h1>Climate logger</h1><p style='font-size:2em'>" + String(lastTemp, 1) + " &deg;C &middot; " + String(lastHumidity, 0) + " %</p>"
                "<p>Last reading: " + lastTime + "<br>Readings since start: " + String(rows) + "<br>File size: " + String(bytes / 1024.0, 1) + " kB</p>"
                "<p><a href='/download'>Download climate.csv</a></p>"
                "<form action='/clear' method='post' onsubmit=\"return confirm('Delete all readings?')\"><button>Start a new log</button></form>";
  server.send(200, "text/html", html);
}

void handleDownload() {
  File f = SD.open(LOG_FILE);
  if (!f) {
    server.send(404, "text/plain", "No log yet");
    return;
  }
  server.sendHeader("Content-Disposition", "attachment; filename=climate.csv");
  server.streamFile(f, "text/csv");
  f.close();
}

void handleClear() {
  SD.remove(LOG_FILE);
  rows = 0;
  server.sendHeader("Location", "/");
  server.send(303);
}

void setup() {
  Serial.begin(115200);
  dht.begin();
  if (!SD.begin(SD_CS_PIN)) {
    Serial.println("SD card not found. Check the wiring and the card, then press EN.");
    while (true) delay(1000);
  }

  WiFi.begin(WIFI_NAME, WIFI_PASSWORD);
  Serial.print("Connecting to Wi-Fi");
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.print(" connected. Open http://");
  Serial.println(WiFi.localIP());
  configTzTime(TIME_ZONE, "pool.ntp.org");
  server.on("/", handleRoot);
  server.on("/download", handleDownload);
  server.on("/clear", HTTP_POST, handleClear);
  server.begin();
  lastLog = millis() - EVERY + 5000;   // first reading after 5 seconds
}

void loop() {
  server.handleClient();
  if (millis() - lastLog >= EVERY) {
    lastLog = millis();
    logReading();
  }
}

Look at your data

Open the Serial Monitor at 115200 baud: after connecting, the ESP32 prints its address, such as http://192.168.1.42. Open it in a browser on a phone or computer in the same Wi-Fi.

  1. The page shows the latest reading and refreshes itself every minute.
  2. Tap Download climate.csv and open the file in a spreadsheet program. Each minute is one row.
  3. Select the Time and Temperature columns and insert a line chart. Can you see when the heating came on, or when someone opened a window?

How it works

An SD card is a small block of flash memory. The SD library organises it into files and folders, just like on a computer, using the FAT32 format, so a computer can read the card too. The ESP32 talks to the card over SPI; the CS pin tells the card when it's being spoken to.

CSV stands for "comma-separated values": every line is one row of a table, and commas separate the columns. It's plain text, so any spreadsheet program can open it. The sketch opens the file in append mode, writes one line and closes it again straight away. If the power fails, at most one reading is lost and the file stays readable.

For the download, server.streamFile() sends the file in small pieces, so even a log of several megabytes fits through the ESP32's small memory. The Content-Disposition header tells the browser to save it as a file instead of showing it.

Try this next

  • Start a new file every day, named after the date, so the files stay small.
  • Log the minimum and maximum of each hour instead of every minute.
  • Add a sound or light sensor as a new column.

Troubleshooting

  • "SD card not found": check the six SD wires, that VCC is on 5V, and that the card is formatted as FAT32 (cards over 32 GB often aren't).
  • The temperature shows "nan": the DHT11 isn't answering. Check its S wire on GPIO 27.
  • The times are wrong: set TIME_ZONE to your own time zone.
  • It never connects to Wi-Fi: check the network name and password (both are case-sensitive) and make sure the network is 2.4 GHz.
  • A printed part is too tight or too loose: printers differ by a few tenths of a millimetre. Sand or file a tight hole, and fix a loose part with a drop of hot glue or a strip of tape.

Build it yourself

Everything used in this lesson comes in one kit, with parts tested by our engineers.

ESP32 IoT Starter Kit | Arduino IDE Compatible | Free Tutorials $57.00

Shop the kit