When you search for example code for Arduino, you usually find fragmented snippets that assume perfect hardware conditions. In the real world, I2C buses hang, SD cards fail to mount, and microcontrollers brown out. A robust data logger needs explicit pin definitions, hardware fault detection, and a watchdog timer to recover from fatal locks.
This guide provides a complete, production-ready architecture for logging temperature, humidity, and barometric pressure. We are pairing an I2C environmental sensor with an SPI-based storage module to avoid bus contention, wrapping it all in a C++ sketch that handles initialization failures gracefully.
The Decision Path: Which Arduino Board and Sensor Combo?
Before writing a single line of code, you must select hardware that matches your power and memory constraints. Here is the decision framework for choosing your microcontroller and sensor breakout for a logging project.
| Scenario / Requirement | Recommended Board | Why It Wins |
|---|---|---|
| Bench prototyping & 5V logic tolerance | Arduino Uno R3 (ATmega328P) | Massive physical footprint makes wiring easy; 5V I/O matches standard SD modules. |
| Battery-powered remote deployment | Arduino Nano 33 IoT (SAMD21) | Native 3.3V logic, deep sleep modes, and built-in crypto/WiFi for MQTT offloading. |
| Compact, low-cost robust logging (Our Pick) | Arduino Nano V3.0 (ATmega328P) | Small breadboard footprint, 32KB flash, native AVR watchdog timer support, and cheap to replace if you fry a clone. |
Hardware Spec Sheet and Pin Mapping
Mixing I2C and SPI on the same ATmega328P is highly reliable because they use entirely separate hardware peripherals and pins. This prevents the MISO/MOSI bus contention issues that plague dual-SPI designs.
Bill of Materials (BOM)
- Microcontroller: Arduino Nano V3.0 (Official ABX00028 or reputable ATmega328P clone) - ~$12.00
- Sensor: Adafruit BME280 I2C Breakout (Product ID 2652) - ~$19.95. Note: Generic $3 clones often lack onboard 3.3V regulators and pull-up resistors, leading to I2C bus noise.
- Storage: Adafruit MicroSD Breakout Board+ (Product ID 254) - ~$7.50. Includes a 3.3V logic level shifter, which is mandatory when driving from the Nano's 5V SPI pins.
- Media: 8GB or 16GB MicroSDHC card (Formatted strictly to FAT32).
Wiring Pinout Table
| Module | Module Pin | Arduino Nano Pin | Wire Color (Suggested) |
|---|---|---|---|
| BME280 | VIN | 5V | Red |
| BME280 | GND | GND | Black |
| BME280 | SCK (SCL) | A5 | Blue |
| BME280 | SDI (SDA) | A4 | Green |
| MicroSD | 5V | 5V | Red |
| MicroSD | GND | GND | Black |
| MicroSD | CS | D10 | Orange |
| MicroSD | DI (MOSI) | D11 | Yellow |
| MicroSD | DO (MISO) | D12 | Purple |
| MicroSD | CLK (SCK) | D13 | White |
The Complete, Compilable Example Code for Arduino
This sketch targets the ATmega328P architecture. It utilizes the hardware Wire library for I2C and includes the AVR Watchdog Timer (WDT) to automatically reboot the Nano if the main loop hangs for more than 8 seconds—a common failure mode when an SD card write operation blocks indefinitely.
#include <Wire.h>
#include <SPI.h>
#include <SD.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_BME280.h>
#include <avr/wdt.h>
// --- PIN DEFINITIONS ---
#define SD_CS_PIN 10
#define STATUS_LED_PIN 13
#define BME_I2C_ADDR 0x77 // Adafruit breakouts use 0x77; some clones use 0x76
// --- OBJECT INSTANTIATION ---
Adafruit_BME280 bme;
File dataFile;
// --- FAULT HANDLING ---
void haltWithError(const char* errorMsg) {
Serial.println(errorMsg);
// Blink LED rapidly to indicate hardware fault
while (1) {
digitalWrite(STATUS_LED_PIN, HIGH);
delay(100);
digitalWrite(STATUS_LED_PIN, LOW);
delay(100);
wdt_reset(); // Keep watchdog alive so we don't reboot in a tight fault loop
}
}
void setup() {
pinMode(STATUS_LED_PIN, OUTPUT);
digitalWrite(STATUS_LED_PIN, HIGH); // Solid ON during setup
Serial.begin(115200);
while (!Serial) { delay(10); } // Wait for native USB serial (ignored on Nano V3)
// Enable 8-second Watchdog Timer
wdt_enable(WDTO_8S);
Serial.println(F("Initializing BME280 and SD Logger..."));
// 1. Initialize I2C Sensor
if (!bme.begin(BME_I2C_ADDR)) {
haltWithError("FATAL: Could not find a valid BME280 sensor, check wiring or I2C address!");
}
// Configure BME280 for forced mode to save power between reads
bme.setSampling(Adafruit_BME280::MODE_FORCED,
Adafruit_BME280::SAMPLING_X1, // Temp
Adafruit_BME280::SAMPLING_X1, // Pressure
Adafruit_BME280::SAMPLING_X1, // Humidity
Adafruit_BME280::FILTER_OFF);
// 2. Initialize SPI SD Card
if (!SD.begin(SD_CS_PIN)) {
haltWithError("FATAL: SD initialization failed! Check CS pin and FAT32 format.");
}
digitalWrite(STATUS_LED_PIN, LOW); // Setup complete, LED OFF
Serial.println(F("Setup complete. Logging data..."));
}
void loop() {
wdt_reset(); // Reset watchdog timer at the start of every loop
// Trigger a forced reading
bme.takeForcedMeasurement();
float tempC = bme.readTemperature();
float humidity = bme.readHumidity();
float pressurePa = bme.readPressure();
unsigned long runtimeMs = millis();
// Open file in append mode
dataFile = SD.open("datalog.csv", FILE_WRITE);
if (dataFile) {
// Write CSV formatted data
dataFile.print(runtimeMs);
dataFile.print(",");
dataFile.print(tempC);
dataFile.print(",");
dataFile.print(humidity);
dataFile.print(",");
dataFile.println(pressurePa);
dataFile.close(); // Crucial: flushes buffer to physical card
// Echo to serial for bench debugging
Serial.print("Logged: ");
Serial.print(tempC); Serial.print(" C, ");
Serial.print(humidity); Serial.print(" %, ");
Serial.print(pressurePa); Serial.println(" Pa");
} else {
Serial.println("ERROR: Failed to open datalog.csv for writing.");
}
// Wait 10 seconds before next reading (non-blocking delay preferred in advanced builds)
for (unsigned long start = millis(); millis() - start < 10000; ) {
wdt_reset(); // Pet the dog during the delay
delay(100);
}
}
Debugging: First Three Things to Check When It Fails
When your compile fails or the hardware hangs, do not start rewriting the logic. 90% of embedded failures are physical layer or environment issues. Here are the exact error strings and how to fix them.
1. Exact Error: SD initialization failed!
If the serial monitor prints this and the LED starts rapid-blinking, the ATmega328P cannot mount the filesystem.
- Cause A (Most Likely): The SD card is formatted as exFAT or NTFS. The standard Arduino
SD.hlibrary only supports FAT16 and FAT32. Use the official SD Card Formatter tool to format cards 32GB or smaller to FAT32. - Cause B: You are using a 64GB+ SDXC card. The
SD.hlibrary struggles with SDHC/SDXC high-capacity addressing. Stick to 16GB or 32GB MicroSDHC cards. - Cause C: SPI bus contention. Ensure no other SPI devices are sharing the bus without proper Chip Select (CS) management. If you have an SPI display connected, its CS pin must be held HIGH when not in use.
2. Exact Error: Could not find a valid BME280 sensor!
The I2C bus scanned the address space and found nothing at 0x77.
- Cause A: I2C Address mismatch. Many cheap clone BME280 boards route the SDO pin to GND internally, shifting the address to
0x76. Change#define BME_I2C_ADDR 0x77to0x76in the code and re-upload. - Cause B: Missing pull-up resistors. The I2C specification requires pull-ups on SDA and SCL. While the Adafruit BME280 breakout includes 10kΩ pull-ups onboard, generic bare-bones modules do not. If using a clone, solder 4.7kΩ resistors between the SDA/SCL lines and the 3.3V VCC pin.
3. Exact Error: fatal error: Adafruit_BME280.h: No such file or directory
This is an IDE-level compilation failure before the code ever reaches the board.
- Cause A: You installed the wrong library. Open the Arduino IDE Library Manager (Ctrl+Shift+I), search for "Adafruit BME280", and install the official library by Adafruit. It will prompt you to install dependencies; click "Install All" to ensure
Adafruit Unified Sensoris also present.
How to Extend or Simplify the Build
Depending on your deployment environment, you may need to strip this project down to its bare essentials or scale it up for remote telemetry.
To Simplify (The Bench Test Build)
If you are just testing the sensor on your desk and don't need persistent storage:
- Remove the
#include <SD.h>and allSD.begin()/dataFilelogic. - Remove the
avr/wdt.hwatchdog includes. Watchdogs are for unattended deployments; they are a nuisance when you are actively tweaking code on the bench. - Rely purely on
Serial.print()and view the output in the Serial Plotter.
To Extend (The Remote IoT Build)
If this logger is going inside a weatherproof enclosure on a pole:
- Add Real-Time: The ATmega328P
millis()counter rolls over after ~49 days and doesn't know the actual date. Add a DS3231 RTC module on the same I2C bus (it uses address0x68, so it won't conflict with the BME280). - Add Telemetry: Swap the Arduino Nano V3 for an ESP32-WROOM-32. You will need to update the pin definitions (ESP32 uses different GPIO mappings for hardware I2C/SPI) and replace the SD logging block with an MQTT publish payload via WiFi.
- Power Management: Put the BME280 into deep sleep and use the ESP32's native touch-wake or RTC-wake features to draw microamps between 15-minute logging intervals.






