How to Save Arduino Serial Data to CSV: Python and No-Code Options
You have an Arduino printing sensor readings to the Serial Monitor. The values look fine on screen, but now you need a CSV file you can graph, share, or import into Excel.
The important detail is that the Arduino IDE Serial Monitor is a viewing tool, not a dependable data recorder. For a five-second sanity check it is fine. For a 45-minute thermistor run, a battery discharge test, or repeated bench measurements, copy-pasting text from the Serial Monitor is how good data quietly becomes questionable data.
This guide shows the practical path:
- Format the Arduino output so it is CSV-friendly.
- Close the Arduino Serial Monitor so another program can open the port.
- Use a small Python script if you want a code-based logger.
- Use DaqSense if you want parsing, live viewing, and CSV recording without maintaining a script.
Step 1: Format Your Arduino Output for CSV
CSV logging works best when every serial line has the same number of fields in the same order. Avoid labels, units, and extra text inside the data stream.
Avoid output like this:
Serial.print("Temperature: ");
Serial.print(tempC);
Serial.print(" C, Humidity: ");
Serial.print(humidity);
Serial.println(" %");
Print only the values:
Serial.print(tempC, 2);
Serial.print(",");
Serial.print(humidity, 2);
Serial.print(",");
Serial.println(rawAdc);
If you want column headers, print them once in setup(), not repeatedly in loop():
void setup() {
Serial.begin(115200);
Serial.println("time_ms,temp_c,humidity_pct,raw_adc");
}
void loop() {
int rawAdc = analogRead(A0);
float tempC = 24.73;
float humidity = 48.20;
Serial.print(millis());
Serial.print(",");
Serial.print(tempC, 2);
Serial.print(",");
Serial.print(humidity, 2);
Serial.print(",");
Serial.println(rawAdc);
delay(1000);
}
That produces a stream like:
time_ms,temp_c,humidity_pct,raw_adc
1000,24.73,48.20,621
2000,24.75,48.18,622
3000,24.76,48.16,623
Before logging, confirm three things:
| Check | Why it matters |
|---|---|
| One sample per line | The logger can treat each newline as one CSV row. |
| Same number of commas every line | Excel and analysis scripts will not shift columns mid-file. |
| No commas inside text fields | Commas inside labels or notes create accidental extra columns. |
| Baud rate is known | Your logger must match Serial.begin(...). |
Step 2: Close the Arduino Serial Monitor
Serial ports are normally exclusive. If the Arduino IDE Serial Monitor is open, Python, DaqSense, CoolTerm, PuTTY, and most other tools cannot open the same port.
Close these before starting your logger:
- Arduino IDE Serial Monitor
- Arduino IDE Serial Plotter
- VS Code serial extensions
- PlatformIO device monitor
- Any previous Python logger still running in a terminal
If you see an error like “access denied”, “device busy”, or “resource temporarily unavailable”, another program still has the port open. Unplugging and replugging the board can help, but it is better to find the program holding the connection.
Step 3: Log Arduino Serial Data to CSV with Python
Python is a good option when you want a scriptable workflow, automated filenames, or custom post-processing. The common library is pyserial.
Install it:
python3 -m pip install pyserial
Find your port:
| System | Typical Arduino port |
|---|---|
| Windows | COM3, COM4, COM5 |
| macOS | /dev/cu.usbmodem... or /dev/cu.wchusbserial... |
| Linux | /dev/ttyACM0 or /dev/ttyUSB0 |
You can also list ports with Python:
python3 -m serial.tools.list_ports
Here is a practical starter logger:
import csv
import time
from pathlib import Path
import serial
PORT = "/dev/cu.usbmodem1101" # Change this to COM3, /dev/ttyACM0, etc.
BAUD = 115200
OUTFILE = Path("arduino_log.csv")
with serial.Serial(PORT, BAUD, timeout=2) as ser, OUTFILE.open("w", newline="") as f:
writer = csv.writer(f)
writer.writerow(["host_time_s", "device_time_ms", "temp_c", "humidity_pct", "raw_adc"])
# Give the Arduino a moment to reset after the serial port opens.
time.sleep(2)
ser.reset_input_buffer()
print(f"Logging {PORT} at {BAUD} baud. Press Ctrl+C to stop.")
while True:
raw = ser.readline()
if not raw:
continue
line = raw.decode("utf-8", errors="replace").strip()
parts = [part.strip() for part in line.split(",")]
# Skip headers, boot messages, and malformed lines.
if len(parts) != 4 or not parts[0].isdigit():
print(f"Skipping: {line}")
continue
writer.writerow([time.time(), *parts])
f.flush()
print(line)
Why this script is more robust than the tiny examples you often see:
- It waits for the Arduino reset that happens when many boards open a serial connection.
- It clears stale boot text before recording.
- It skips malformed lines instead of crashing.
- It flushes the CSV file as it records, which reduces data loss if the run is interrupted.
- It records host time separately from the Arduino’s
millis()timestamp.
The tradeoff is maintenance. The moment your firmware adds a fifth column, changes delimiter, prints a debug message, or needs calibration math, you are editing Python again.
Step 4: Use DaqSense When You Want a Bench Tool Instead of a Script
If the job is “capture this serial stream cleanly while I work on hardware”, use DaqSense instead of turning every test into a small software project.
The DaqSense workflow is:
- Select the Arduino serial port.
- Set the baud rate to match
Serial.begin(...). - Choose the delimiter, usually a comma.
- Watch incoming rows split into columns live.
- Click record to save the raw and parsed stream to CSV.
That is especially useful when:
| Bench need | Python script | DaqSense |
|---|---|---|
| Quick one-off automated capture | Good | Good |
| Live table while recording | You build it | Built in |
| Change delimiter or column count | Edit code | Change parser settings |
| Skip malformed boot lines | Write guard logic | Parser handles it visually |
| Apply calibration formulas | Add code | Configure live math |
| Share workflow with a teammate | Share script and dependencies | Share the tool/profile |
Python is still the right answer for fully automated tests, CI rigs, or custom hardware-in-the-loop workflows. For everyday bench logging, a dedicated serial data logger saves time because the boring parts are already handled.
Common CSV Logging Mistakes
| Symptom | Likely cause | Fix |
|---|---|---|
| CSV has one giant column | Arduino output uses spaces or labels instead of commas | Print clean comma-separated values. |
| Python says the port is busy | Arduino Serial Monitor is still open | Close every serial monitor and rerun the script. |
| First rows contain junk | Board reset or bootloader text appeared after opening the port | Wait, clear input buffer, or skip malformed rows. |
| Numbers open as dates in Excel | Excel guessed the wrong type | Import through Data > From Text/CSV and set column types. |
| Rows have shifted columns | Debug text or units are mixed into the data | Keep human-readable debug messages off the logged stream. |
Once your CSV is clean, the rest of the workflow gets easier: Excel imports correctly, Python analysis scripts are shorter, and teammates can reproduce the same test without asking what each column meant.