Project, a Standalone Data Logger

Draft. This tutorial has not been checked against the physical product yet. Confirm wiring, pin assignments and voltage limits against the product documentation before building it.
DifficultyIntermediate
CategoryProjects
Supported boardsArduino Uno R3 and compatible ATmega328P boards
Time needed120 minutes
PrerequisitesLogging Data to an SD Card
Tested boardNot yet verified
Tested onNot yet verified
Arduino IDE versionNot yet verified
LibraryNot yet verified

What you will build

A box you leave somewhere for a week. It records a temperature every minute with a real date and time against it, and when you come back the card opens in a spreadsheet and draws a graph.

No laptop, no network, no watching. That is the whole point: this is the project that lets you find out what actually happened to the greenhouse overnight, or how cold the store room gets before dawn.

It is also the project where the failures matter. A logger that stops on Tuesday and tells nobody has wasted a week, so a good part of this page is about noticing and surviving.

Before you begin

Parts

Part Siratek product Its own tutorial
Arduino Uno 2219 Getting Started with Arduino Uno
Micro SD card module 6573 Logging Data to an SD Card
DS3231 real time clock 2522 Keeping Time with a DS3231
DS18B20 waterproof probe 49704 Waterproof Temperature with DS18B20
4.7 kilohm resistor Required by the probe
Micro SD card, 32 GB or smaller, FAT32
Breadboard and jumper wires Using a Breadboard

What shares what

Three devices, three different ways of talking:

Device Bus Pins
SD card SPI D10 to D13
DS3231 I2C A4, A5
DS18B20 1-Wire D2

They do not conflict, which is pleasant and worth noticing: SPI, I2C and 1-Wire are three separate arrangements and can all run at once. Between them they take six pins, leaving most of the board free.

The DS3231 is at address 0x68. Nothing else here is on I2C, so there is no clash — but if you add an MPU-6050 later there will be, and Keeping Time with a DS3231 explains the fix.

The power budget

Part Typical Peak
Uno 50 mA
SD card a few mA idle up to ~100 mA while writing
DS3231 under 1 mA
DS18B20 about 1 mA

On USB that is comfortable. The peak matters because a card write is exactly when a brownout is most damaging: a reset mid-write can leave the file system, not just the line, in a bad state.

The sketch below opens, writes and closes the file every single line for that reason. It is slower, and at one reading a minute the speed is irrelevant.

Choosing the interval

Worth a moment’s arithmetic before you start.

Interval Lines per day A week
1 second 86,400 600,000
1 minute 1,440 10,080
10 minutes 144 1,008

A week at one minute is ten thousand lines, which is about 300 KB and opens instantly in a spreadsheet. A week at one second is six hundred thousand lines, which most spreadsheets will refuse or struggle with, and tells you nothing a minute’s average would not.

One minute is the right default for temperature. Faster only if the thing you are measuring genuinely changes faster.

How it works

Four things happen, at three different rates:

Job How often
Read the probe and the clock Every minute
Write a line to the card Every minute
Write a heartbeat line Every hour, whether or not anything changed
Blink the LED Every reading

The heartbeat is the part people leave out and then regret. A log that only records events cannot distinguish “nothing happened” from “the logger stopped”. An hourly line, even an uninteresting one, means a gap in the file tells you exactly when something went wrong.

Pin reference

Device Pin Connect to
SD module GND Uno GND
SD module VCC Uno 5V
SD module MISO Uno D12
SD module MOSI Uno D11
SD module SCK Uno D13
SD module CS Uno D10
DS3231 GND, VCC Uno GND, 5V
DS3231 SDA, SCL Uno A4, A5
DS18B20 red Uno 5V
DS18B20 black Uno GND
DS18B20 yellow Uno D2, and 4.7 k to 5V
A logger with an SD card on SPI, a DS3231 clock on I2C and a DS18B20 probe on one wire, with the 4.7 kilohm pull-up the probe needs
Three devices, three different buses, six pins. The 4.7 k pull-up is not supplied with the probe.

Build it in stages

Stage 1: each part alone

Run the minimal example from each of the three component tutorials, one at a time. Specifically, confirm:

  • The card initialises and lists its contents.
  • The clock returns a sensible time, with lostPower() handled so it is not reset on every boot.
  • The probe reports a device count of 1, not 0, and a plausible temperature.

A device count of 0 means the 4.7 k resistor is missing. A -127.00 means the same thing.

Stage 2: all three, to serial

Combine them with no card writing at all: read the clock and the probe once a minute and print a CSV line to the serial monitor.

Watch it for five minutes. Then unplug the probe and watch what happens, because that is a rehearsal for the thing that will go wrong in the field.

Stage 3: write to the card

Add the file. The complete sketch below.

The complete sketch

// Siratek: a standalone temperature logger.
// Writes one CSV line a minute, with a real timestamp, and keeps going
// when a part fails.

#include <SPI.h>
#include <SD.h>
#include <Wire.h>
#include <RTClib.h>
#include <OneWire.h>
#include <DallasTemperature.h>

const int chipSelect = 10;
const int ledPin = LED_BUILTIN;
const char* FILENAME = "LOG.CSV";          // 8.3 names only

RTC_DS3231 rtc;
OneWire oneWire(2);
DallasTemperature probe(&oneWire);

const unsigned long INTERVAL  = 60000UL;   // one minute
const unsigned long HEARTBEAT = 3600000UL; // one hour

unsigned long lastReading = 0;
unsigned long lastHeartbeat = 0;

bool cardReady = false;
bool clockReady = false;

unsigned long linesWritten = 0;
unsigned long writeFailures = 0;

void setup() {
  pinMode(ledPin, OUTPUT);
  Serial.begin(9600);
  Serial.println(F("Siratek: data logger starting."));

  clockReady = rtc.begin();
  if (!clockReady) {
    Serial.println(F("  no clock: timestamps will be elapsed minutes"));
  } else if (rtc.lostPower()) {
    Serial.println(F("  clock had lost power, setting from compile time"));
    rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
  }

  probe.begin();
  Serial.print(F("  probes found: "));
  Serial.println(probe.getDeviceCount());
  if (probe.getDeviceCount() == 0) {
    Serial.println(F("  check the 4.7k between data and 5V"));
  }

  cardReady = SD.begin(chipSelect);
  if (!cardReady) {
    Serial.println(F("  no card: logging disabled, serial only"));
  } else if (!SD.exists(FILENAME)) {
    File f = SD.open(FILENAME, FILE_WRITE);
    if (f) {
      f.println(F("timestamp,temperature_c,note"));
      f.close();
      Serial.println(F("  created LOG.CSV with a header"));
    }
  }

  // Take the first reading straight away rather than waiting a minute.
  lastReading = millis() - INTERVAL;
  lastHeartbeat = millis();
}

// Build a timestamp, or an elapsed count if the clock is missing.
void appendTimestamp(String& line) {
  if (!clockReady) {
    line += F("elapsed_");
    line += String(millis() / 60000UL);
    return;
  }

  DateTime now = rtc.now();
  char buf[20];
  snprintf(buf, sizeof(buf), "%04d-%02d-%02d %02d:%02d:%02d",
           now.year(), now.month(), now.day(),
           now.hour(), now.minute(), now.second());
  line += buf;
}

void writeLine(const String& line) {
  Serial.println(line);

  if (!cardReady) {
    return;
  }

  // Open, write, close, every time: a closed file survives a power cut.
  File f = SD.open(FILENAME, FILE_WRITE);
  if (f) {
    f.println(line);
    f.close();
    linesWritten++;
  } else {
    writeFailures++;
    Serial.println(F("  could not open the file"));

    // The card may have been removed. Try to pick it up again next time.
    if (writeFailures > 3) {
      cardReady = SD.begin(chipSelect);
      writeFailures = 0;
    }
  }
}

void takeReading() {
  digitalWrite(ledPin, HIGH);

  probe.requestTemperatures();
  float c = probe.getTempCByIndex(0);

  String line = "";
  appendTimestamp(line);
  line += ",";

  if (c == DEVICE_DISCONNECTED_C) {
    line += ",probe not responding";
  } else {
    line += String(c, 1);
    line += ",";
  }

  writeLine(line);
  digitalWrite(ledPin, LOW);
}

void heartbeat() {
  String line = "";
  appendTimestamp(line);
  line += ",,alive: ";
  line += String(linesWritten);
  line += " lines";
  writeLine(line);
}

void loop() {
  if (millis() - lastReading >= INTERVAL) {
    lastReading = millis();
    takeReading();
  }

  if (millis() - lastHeartbeat >= HEARTBEAT) {
    lastHeartbeat = millis();
    heartbeat();
  }
}

Four decisions that make this a logger rather than a demonstration

It runs without any of its parts. No card, it logs to serial. No clock, it timestamps with elapsed minutes. No probe, it writes a line saying so. A logger that refuses to start because one thing is missing has thrown away everything else it could have recorded.

The probe failure is recorded, not skipped. A missing line is ambiguous; a line saying “probe not responding” is evidence. When you open the file in a week, the difference matters.

It tries to recover the card. Three failures in a row and it calls SD.begin() again, which picks up a card that was removed and put back, or one that glitched. It is four lines and it turns a permanent failure into a gap.

The heartbeat proves it is alive. An hourly line means a gap in the file has a start time and an end time. Without it you know only that the file stops.

A note on String

This sketch uses String to build each line, and at one line a minute that is fine: the heap has time to settle between allocations.

If you log every second, replace it with snprintf() into a fixed char buffer. Logging Data to an SD Card explains why, and the symptom of ignoring it is a sketch that runs for hours and then freezes.

Testing it

Before leaving it anywhere, do these five. Each one rehearses a real failure.

Try this It should
Run it for five minutes Five lines, a minute apart, with sensible times
Pull the probe out Keep logging, with “probe not responding”
Put the probe back Resume recording temperatures
Pull the card out and put it back Report failures, then recover within a few minutes
Unplug the power mid-run On restart, continue appending to the same file

The last is the important one. Unplug it, plug it back in, and open the card: the header should appear once, not twice, and the earlier lines should still be there.

Then take the card to a computer, open LOG.CSV in a spreadsheet, and chart the temperature column. That is the project working.

Making it yours

  • More sensors. A second DS18B20 on the same pin and the same resistor, which is the whole point of 1-Wire. Add a column per probe.
  • Humidity. A DHT22 on a spare pin. The weather station project has the reading code.
  • Run it on batteries. This is the real upgrade, and it leads straight to Running an ATmega328P Without the Board: the Uno’s regulator and USB chip draw far more than the logging does. A bare chip asleep between readings runs for months.
  • A display. An OLED showing the current reading and the line count, so you can tell at a glance that it is working without pulling the card.
  • A watchdog. Making a Project Survive Unattended covers it, and this is exactly the kind of project it exists for.

Troubleshooting

Symptom Likely cause Check or fix
Card will not initialise Too large, or not FAT32 32 GB or smaller, FAT32
Card fails intermittently Long jumper leads, or a cheap card Shorten them; try another card
File never appears Filename longer than 8.3 LOG.CSV, not templog.csv
Header appears twice Written unconditionally Guard it with SD.exists()
Temperature reads -127 No 4.7 k pull-up Between the data wire and 5V
Probe count is 0 Same cause Fit the resistor
Timestamps all identical Clock not set Check lostPower() handling
Timestamps reset on every boot adjust() called unconditionally Guard it with lostPower()
Timestamps say elapsed_ Clock not found Scan the bus; expect 0x68
File corrupted after a power cut File left open Open, write, close each line
Logger stopped and you cannot tell when No heartbeat An hourly line gives you the gap
Runs for hours then freezes String fragmenting the heap snprintf() into a fixed buffer

Sources and attribution

All text, wiring guidance and code in this project are original Siratek work.

  • Component behaviour is as described in Logging Data to an SD Card, Keeping Time with a DS3231 and Waterproof Temperature with DS18B20, including the 8.3 filename limit, the lostPower() behaviour and the probe’s required pull-up resistor.

Current figures are typical and vary with the card. No third-party tutorial text, diagrams, photographs or code has been reproduced here.

Product used in this tutorial: Sdcard Module Small
Shop
Sidebar
0 Wishlist
0 Cart