Logging data with Arduino requires a microcontroller, a non-volatile storage medium like a microSD breakout, and a sensor communicating via I2C or SPI. For a robust environmental logger, the Arduino Uno R4 WiFi paired with an Adafruit MicroSD breakout and a BME280 sensor is the current 2026 benchmark for hobbyist reliability. The Uno R4 provides a 12-bit ADC and a built-in ESP32-S3 for future wireless expansion, while the Adafruit breakouts include onboard logic level shifting, preventing the 5V-to-3.3V bus contention that destroys cheaper clone modules.
Parts List and Specifications
Do not substitute the Adafruit breakouts with unbranded $2 clones unless you are prepared to wire a dedicated logic level converter (like the BSS138) for the SPI and I2C buses. Feeding 5V logic into a 3.3V SD card or BME280 will degrade the silicon and cause intermittent write failures.
| Component | Exact Variant / Part Number | Estimated Price (2026) | Why This Part? |
|---|---|---|---|
| Microcontroller | Arduino Uno R4 WiFi (ABX00087) | $27.50 | Renesas RA4M1 core with ESP32-S3; native 12-bit ADC. |
| Storage | Adafruit MicroSD card breakout+ (PID 254) | $7.50 | Includes 3.3V LDO and level shifters for 5V SPI buses. |
| Sensor | Adafruit BME280 I2C/SPI (PID 2652) | $14.95 | Measures temp, humidity, pressure; onboard voltage regulation. |
| Media | SanDisk 8GB microSDHC (Class 10) | $6.00 | SDHC ensures FAT32 compatibility; Class 10 handles write spikes. |
Wiring the BME280 and MicroSD Breakout
The Uno R4 WiFi operates at 5V logic. Because we are using Adafruit breakouts with integrated level shifting, we can wire them directly to the 5V and digital pins. The SD card communicates over the hardware SPI bus, while the BME280 uses the I2C bus. This prevents pin conflicts and keeps the wiring clean.
| Breakout Pin | Arduino Uno R4 WiFi Pin | Protocol / Function |
|---|---|---|
| MicroSD 5V | 5V | Power (feeds onboard 3.3V LDO) |
| MicroSD GND | GND | Common Ground |
| MicroSD MOSI | D11 | SPI Master Out Slave In |
| MicroSD MISO | D12 | SPI Master In Slave Out |
| MicroSD SCK | D13 | SPI Clock |
| MicroSD CS | D10 | SPI Chip Select (Active Low) |
| BME280 VIN | 5V | Power (feeds onboard 3.3V LDO) |
| BME280 GND | GND | Common Ground |
| BME280 SDA | A4 | I2C Data |
| BME280 SCL | A5 | I2C Clock |
The Firmware: Compilable Code with Error Handling
This firmware targets the Arduino Uno R4 WiFi. It uses the standard SD.h library and the Adafruit BME280 library. Install the Adafruit BME280 Library and Adafruit Unified Sensor library via the Arduino Library Manager before compiling.
The code implements a safe-write strategy: it opens the file, appends the data, and closes the file on every loop iteration. While this slightly increases wear on the SD card's flash controller, it guarantees that if the Arduino loses power unexpectedly, you only lose the current row of data, not the entire file buffer.
#include <Wire.h>
#include <SPI.h>
#include <SD.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_BME280.h>
// --- PIN DEFINITIONS ---
#define SD_CS_PIN 10
#define BME_I2C_ADDRESS 0x77 // Use 0x76 for generic clone boards
// --- OBJECTS ---
Adafruit_BME280 bme;
const char* logFileName = "datalog.csv";
void setup() {
Serial.begin(115200);
while (!Serial) {
delay(10); // Wait for serial port on native USB boards
}
// Initialize I2C and BME280
Wire.begin();
if (!bme.begin(BME_I2C_ADDRESS)) {
Serial.println("Could not find a valid BME280 sensor, check wiring!");
while (1) {
delay(100); // Halt execution on sensor failure
}
}
Serial.println("BME280 sensor initialized.");
// Initialize SPI and SD Card
if (!SD.begin(SD_CS_PIN)) {
Serial.println("SD initialization failed!");
while (1) {
delay(100); // Halt execution on SD failure
}
}
Serial.println("SD card initialized.");
// Write CSV header if file is new
if (!SD.exists(logFileName)) {
File dataFile = SD.open(logFileName, FILE_WRITE);
if (dataFile) {
dataFile.println("Millis,Temp_C,Humidity_Pct,Pressure_hPa");
dataFile.close();
}
}
}
void loop() {
// Read sensor data
float temperature = bme.readTemperature();
float humidity = bme.readHumidity();
float pressure = bme.readPressure() / 100.0F; // Convert Pa to hPa
unsigned long timeStamp = millis();
// Open file in append mode (FILE_WRITE appends in standard SD.h)
File dataFile = SD.open(logFileName, FILE_WRITE);
if (dataFile) {
dataFile.print(timeStamp);
dataFile.print(",");
dataFile.print(temperature, 2);
dataFile.print(",");
dataFile.print(humidity, 2);
dataFile.print(",");
dataFile.println(pressure, 2);
dataFile.close(); // Crucial: flushes buffer and updates FAT table
Serial.print("Logged: ");
Serial.println(timeStamp);
} else {
Serial.println("Error opening datalog.csv for writing.");
}
// Log every 5 seconds
delay(5000);
}
Debugging: "SD initialization failed" and Other Common Errors
When building data loggers, the serial monitor will inevitably throw errors. Here is how to diagnose the two most common failure modes.
Error 1: "SD initialization failed!"
This exact string is printed when SD.begin() returns false. The Arduino cannot mount the FAT filesystem or communicate with the SD controller.
- Incorrect Filesystem Format (Most Common): The standard
SD.hlibrary only supports FAT16 and FAT32. If you formatted a 32GB+ card on Windows or macOS, it likely defaulted to exFAT, which the Arduino cannot read. Download the official SD Memory Card Formatter from the SD Association and format the card as FAT32. - CS Pin Wiring or Logic Level Fault: Verify that the Chip Select (CS) pin is wired to D10. If you are using a clone SD module without a logic level converter, the 5V MISO/MOSI lines may be back-feeding the SD card's 3.3V rail, causing the controller to lock up.
- Power Brownout: SD cards can pull up to 150mA during write operations. If you are powering the Uno via a weak USB hub, the voltage may sag below 4.5V, causing the SD controller to reset mid-initialization. Measure the 5V pin with a multimeter during the boot sequence; it should not drop below 4.7V.
Error 2: "Could not find a valid BME280 sensor, check wiring!"
This string triggers when the I2C bus fails to acknowledge the sensor's address.
- Wrong I2C Address: Adafruit boards default to
0x77. Most cheap Amazon/AliExpress clones default to0x76. Change the#define BME_I2C_ADDRESSin the code to match your hardware. - Missing Pull-up Resistors: The Adafruit breakout includes 10k pull-ups on SDA and SCL. If you are using a bare BME280 chip on a custom PCB, you must add 4.7k pull-up resistors to the 3.3V rail.
1. Reformat the SD card using the official SD Association Formatter (not the OS disk utility).
2. Check continuity on the CS pin and verify the breakout board's 3.3V LDO is outputting 3.2V–3.4V under load.
3. Ensure no other SPI devices are connected to the bus holding the MISO line high or low.
Extending and Simplifying Your Data Logger
Once the base logger is running, you will likely want to adapt it for specific field conditions. Here is how to scale the design up or down.
How to Extend the Build (Field-Ready)
The current code uses millis() for timestamps, which resets to zero every time the board loses power. To log real-world time, add a DS3231 Real Time Clock (RTC) module via I2C. Because the DS3231 shares the I2C bus with the BME280, no extra pins are required. You will need to add the RTClib library and sync the SD file creation date using the fileTimestampCallback feature in the SdFat library (which replaces SD.h for advanced users).
For outdoor deployments, add a watchdog timer (WDT) to the code. If the SD card physically jams or the I2C bus locks up due to ESD, the WDT will force a hardware reset, preventing the logger from hanging indefinitely in the field.
How to Simplify the Build (Bench/Indoor)
If you do not need local storage and want to eliminate the SD card breakout entirely, leverage the Uno R4 WiFi's onboard ESP32-S3. You can strip out the SD.h library and use the WiFiS3 library to push the BME280 telemetry directly to an MQTT broker (like Mosquitto) or a local InfluxDB instance via HTTP POST. This reduces the hardware BOM cost by $13.50 and eliminates SD card corruption as a failure vector, though it introduces network dependency.
FAQ: Logging Data with Arduino
What is the maximum SD card size for logging data with Arduino?
The standard Arduino SD.h library supports SDHC cards up to 32GB. While you can physically insert a 64GB or 128GB SDXC card, it will only work if you force-format it to FAT32 using third-party tools like GUIFormat, as modern operating systems refuse to format drives larger than 32GB as FAT32. For data logging, a high-endurance 8GB or 16GB SanDisk High Endurance card is preferable; 32GB+ cards simply take too long to scan during the SD.begin() mount process, delaying your boot time.
Why does my Arduino data logger skip rows or corrupt data?
Row skipping or CSV corruption is almost always caused by removing the SD card while the Arduino is powered, or a power brownout during a write cycle. When SD.open() is called, the FAT table is updated. If power drops before dataFile.close() is executed, the file cluster chain becomes orphaned. Always use the open-write-close pattern shown in the code above, rather than keeping the file open for hours. Additionally, ensure your power supply can deliver at least 500mA to handle the SD card's write spikes.
Can I log data with Arduino without an SD card module?
Yes. If you only need to log a few kilobytes of data, you can write to the Arduino's internal EEPROM or, on the Uno R4, the internal Data Flash. However, flash memory has a limited write endurance (typically 10,000 to 100,000 cycles). For continuous logging every 5 seconds, internal flash will wear out in a few days. For non-volatile logging without an SD card, consider adding an FRAM breakout (like the MB85RC256V), which offers virtually unlimited write endurance and communicates over I2C.
How do I reduce power consumption for battery-powered logging?
To run a data logger on a 18650 Li-ion cell for months, you must implement deep sleep. The Uno R4 WiFi can draw up to 100mA idle. By integrating a DS3231 RTC, you can use its programmable alarm pin to trigger an interrupt on the Arduino's wake pin. Put the microcontroller into deep sleep between readings, and use a MOSFET (like the IRLZ44N) to completely cut power to the SD card and BME280 breakouts while sleeping, eliminating their quiescent current draw. This can drop average system consumption to under 50µA.






