Breadboard SD card data logger with AHT20 sensor, SD card module and RTC module, outdoors on a rock

Beginner

Build an SD Card Data Logger

StorageSensorsIoT

A real, offline data logger -- no Wi-Fi required, no cloud account, just accurate timestamped readings saved straight to a card.

7 real parts included

Total for this kit: 1,679.00 ฿

Doesn't include any order discount you may get at checkout.

What you'll need

Project Goal

This project builds a real, offline data logger: a sensor, a clock, and a storage card working together to save timestamped readings over time, with no Wi-Fi and no cloud account required.

What the Controller Does

On a fixed schedule, the controller wakes up, reads the sensor, asks the RTC for the current time, and appends both as one new line to a file on the SD card. Three simple steps, repeated indefinitely.

What the Sensor Does

The AHT20 reports real temperature and humidity readings over I2C on demand, giving the logger something genuinely worth recording every cycle.

What the SD Card Module and RTC Do

The SD card module gives the controller a place to write data that survives a power cycle, something its own onboard memory can not do at any real scale. The DS3231 real-time clock keeps accurate time on its own, battery-backed, so every logged row gets a real, correct timestamp even if the controller itself has no idea what time it is.

CSV-Style Logging

Each reading gets appended as one new timestamped row in a plain text file, the same simple format spreadsheets open natively. Over days or weeks, that file becomes a real, genuinely useful dataset. Worth a real limitation stated plainly: SD cards have a finite write endurance, and frequent writes over months or years can eventually wear one out.

Software and Library Concepts

Get each part working on its own first: read the sensor and print it, read the RTC and print it, write a test file to the SD card, all separately confirmed before combining them into one logging loop.

Testing and Troubleshooting

  • The file will not create or write at all, check the SD card is formatted FAT32, a very common real gotcha with these modules.
  • Timestamps come out wrong, the RTC does not know the correct time out of the box, it must be set once via code the first time it is used.
  • Logging stops working after a while, check the card's available free space, or confirm the file is actually being closed or flushed properly in code rather than left open.
  • Readings look identical every single row, confirm the sensor is actually being re-read each cycle rather than the first value being reused.

Optional Future Upgrade: Wi-Fi Logging

Once this offline version is working reliably, the Wireless Environmental Data Logger project shows how the same sensor and RTC combination can feed a remote log instead of, or alongside, the local SD card.

Wiring

  • AHT20 VCC → ESP32-C3 SuperMini 3.3V
  • AHT20 GND → GND
  • AHT20 SDA → GPIO8 (`SDA_PIN`) — shared I2C bus
  • AHT20 SCL → GPIO9 (`SCL_PIN`) — shared I2C bus
  • DS3231 VCC → 3.3V
  • DS3231 GND → GND
  • DS3231 SDA → GPIO8 (same shared I2C bus as AHT20)
  • DS3231 SCL → GPIO9 (same shared I2C bus as AHT20)
  • SD Module VCC → 3.3V (most breakout modules are 3.3V logic; check yours if it has a 5V-tolerant regulator)
  • SD Module GND → GND
  • SD Module SCK → GPIO4 (`SPI_SCK_PIN`)
  • SD Module MISO → GPIO5 (`SPI_MISO_PIN`)
  • SD Module MOSI → GPIO6 (`SPI_MOSI_PIN`)
  • SD Module CS → GPIO7 (`SD_CS_PIN`)

AHT20 and DS3231 share one I2C bus with two different addresses, which is normal — no extra wiring needed beyond both riding SDA/SCL together. Insert a FAT32-formatted microSD card before powering on; if `SD.begin()` fails, the most common causes are a bad CS wire or an unformatted/incompatible card, not the sensors. The RTC time-set line only needs to run once (it sets the clock to your computer's compile time) — remove or comment it out after the first successful upload so a dead coin cell doesn't silently re-trigger a wrong reset.

Wiring Diagram

VCC → 3.3V GND SDA → GPIO8 SCL → GPIO9 VCC GND SDA → GPIO8 SCL → GPIO9 VCC GND SCK → GPIO4 MISO → GPIO5 MOSI → GPIO6 CS → GPIO7 ESP32-C3 SuperMini AHT20 DS3231 SD Module GND 3.3V

Starter Code

#include <Wire.h>
#include <SPI.h>
#include <SD.h>
#include <RTClib.h>
#include <Adafruit_AHTX0.h>

#define SDA_PIN 8    // ESP32-C3 default I2C SDA - adjust if your board differs
#define SCL_PIN 9    // ESP32-C3 default I2C SCL - adjust if your board differs

#define SPI_SCK_PIN  4  // ESP32-C3 default hardware SPI pins - adjust to match your board's silkscreen if different
#define SPI_MISO_PIN 5
#define SPI_MOSI_PIN 6
#define SD_CS_PIN    7  // SD module chip select - any free digital GPIO

const char* LOG_FILENAME = "/datalog.csv";
const unsigned long LOG_INTERVAL_MS = 5000; // log every 5 seconds

Adafruit_AHTX0 aht;
RTC_DS3231 rtc;
unsigned long lastLog = 0;

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

  Wire.begin(SDA_PIN, SCL_PIN);

  if (!aht.begin()) {
    Serial.println("AHT20 not found - check I2C wiring");
    while (true) delay(1000);
  }

  if (!rtc.begin()) {
    Serial.println("DS3231 not found - check I2C wiring");
    while (true) delay(1000);
  }

  if (rtc.lostPower()) {
    // Sets the RTC to the sketch's compile time. Only needed once after the
    // coin cell has been out / on first power-up - remove after the RTC is set.
    Serial.println("RTC lost power, setting time to compile time");
    rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
  }

  SPI.begin(SPI_SCK_PIN, SPI_MISO_PIN, SPI_MOSI_PIN, SD_CS_PIN);
  if (!SD.begin(SD_CS_PIN)) {
    Serial.println("SD card init failed - check wiring/card");
    while (true) delay(1000);
  }

  if (!SD.exists(LOG_FILENAME)) {
    File f = SD.open(LOG_FILENAME, FILE_WRITE);
    if (f) {
      f.println("timestamp,temperature_C,humidity_percent");
      f.close();
    }
  }

  Serial.println("Logger ready");
}

void loop() {
  if (millis() - lastLog < LOG_INTERVAL_MS) return;
  lastLog = millis();

  sensors_event_t humidity, temp;
  aht.getEvent(&humidity, &temp);
  DateTime now = rtc.now();

  char timestamp[20];
  snprintf(timestamp, sizeof(timestamp), "%04d-%02d-%02d %02d:%02d:%02d",
           now.year(), now.month(), now.day(), now.hour(), now.minute(), now.second());

  File f = SD.open(LOG_FILENAME, FILE_WRITE);
  if (f) {
    f.printf("%s,%.2f,%.2f\n", timestamp, temp.temperature, humidity.relative_humidity);
    f.close();
    Serial.printf("Logged: %s, %.2f C, %.2f %%\n", timestamp, temp.temperature, humidity.relative_humidity);
  } else {
    Serial.println("Failed to open log file for append");
  }
}

Not sure about a step? Ask us