Research-only ECG capture prototype for an Arduino Uno R4 WiFi and AD8232 single-lead ECG front end.
The goal is to produce ECG files that look like the HL7 v3 AnnotatedECG XMLs used by real ECG systems, while keeping the hardware simple enough for an MVP bench prototype.
This repository contains:
- Arduino firmware that streams compact ECG samples over USB serial.
- A desktop capture tool that records HL7
AnnotatedECG-style XML, exports key events to CSV, and can generate a 500 Hz model-compatible XML file from 1000 Hz captures. - Basic tests that validate XML output, event detection, and downsampling logic without hardware.
tools/capture_ecg.py is the main desktop script. It:
- Sends
RATEandSTARTcommands to the Arduino over USB serial. - Reads streamed rows in this format:
seq,t_us,raw_adc,lead_off
- Converts raw ADC values into baseline-centered microvolt-like ECG digits using the configured AD8232 gain.
- Writes a full ECG XML file using an HL7 v3
AnnotatedECG-style structure. - Writes a companion CSV containing key events and quality markers.
- Optionally downsamples 1000 Hz captures into a derived 500 Hz XML file for model compatibility.
- Can run in
--simulatemode without hardware for testing the software pipeline.
The generated XML is intentionally deidentified by default. The example metadata file uses redacted placeholders, and real names, dates of birth, IDs, or clinical diagnoses should not be committed to Git.
The Arduino handles only timing and raw sampling:
AD8232 OUTPUT -> Arduino A0 -> USB serial rows
The Python script handles file generation and analysis:
USB serial rows -> samples -> HL7-style XML + events CSV + optional 500 Hz XML
The default XML contains:
- root element:
AnnotatedECGin the HL7 v3 namespace - time sequence:
TIME_ABSOLUTE - Lead I signal sequence:
MDC_ECG_LEAD_I - waveform values: integer
uVdigits inSLIST_PQ - optional deidentified
caseinformationmetadata from JSON
The Uno R4 + AD8232 MVP records Lead I only. The sample real ECG files you shared include 12 leads, but this prototype only has one analog ECG channel.
This MVP is an educational/research prototype only. It is not a medical device and must not be used for diagnosis, monitoring, treatment decisions, or emergency assessment.
When testing on a person, prefer running the laptop on battery power and avoid attaching body-connected circuits to unsafe mains-powered instruments.
| AD8232 pin | Arduino Uno R4 WiFi pin |
|---|---|
3.3V |
3.3V |
GND |
GND |
OUTPUT |
A0 |
LO+ |
D2 |
LO- |
D3 |
Start with the AD8232 electrode cable or a simple thumb-pad fixture for Lead I. USB serial is the only v0 data link.
Open and upload:
firmware/ecg_prototype_uno_r4_ad8232/ecg_prototype_uno_r4_ad8232.ino
Default behavior:
- Sample rate:
500 Hz - ADC resolution:
12 bit - Serial baud:
230400 - Stream format:
seq,t_us,raw_adc,lead_off
Commands can be sent over Serial Monitor or the capture tool:
RATE 500RATE 1000STARTSTOPSTATUS
Install Python dependencies:
py -m pip install -r requirements.txtCapture 10 seconds at 500 Hz:
py tools/capture_ecg.py --port COM5 --rate 500 --duration 10 --out recordingsCapture 10 seconds at 1000 Hz and also generate a 500 Hz XML file:
py tools/capture_ecg.py --port COM5 --rate 1000 --duration 10 --out recordings --downsample-500Run without hardware using synthetic ECG-like data:
py tools/capture_ecg.py --simulate --rate 1000 --duration 10 --out recordings --downsample-500The Uno R4 + AD8232 MVP only records Lead I. The uV values are baseline-centered and divided by an estimated AD8232 front-end gain. The default gain is 1000, which is useful for MVP captures but should be calibrated before clinical or dataset use:
py tools/capture_ecg.py --port COM5 --rate 1000 --duration 10 --front-end-gain 1000 --out recordingsSee docs/ad8232_calibration.md for the calibration workflow.
Optional metadata can be added with a JSON file:
py tools/capture_ecg.py --port COM5 --rate 1000 --duration 10 --metadata-json metadata/example_metadata.json --out recordingsKeep patient-identifying values out of Git. The example metadata file uses redacted placeholders.
If you need the original debug XML instead:
py tools/capture_ecg.py --port COM5 --rate 500 --duration 10 --xml-format custom --out recordingsOptional live plot requires matplotlib:
py -m pip install matplotlib
py tools/capture_ecg.py --port COM5 --rate 500 --duration 10 --plotEach capture creates:
<recording_id>.xml: full recording as HL7AnnotatedECG-style XML by default.<recording_id>_events.csv: lead-off, saturation, flatline, noisy segment, R-peak, RR interval, and heart-rate summary events.<recording_id>_500hz.xml: optional model-compatible 500 Hz XML generated from a higher-rate recording.
Example output folder:
recordings/
ks-20260525T070954Z-5a6c40d4.xml
ks-20260525T070954Z-5a6c40d4_events.csv
ks-20260525T070954Z-5a6c40d4-500hz.xml
The event CSV columns are:
recording_id,event_type,t_start_ms,t_end_ms,value,confidence,notes
The most useful early event types are:
lead_offsaturationflatlinenoisy_segmentr_peakrr_intervalheart_rate
Run tests:
py -m unittest discover testsIf py is unavailable, use the full Python executable path installed on your machine.
The tests use simulated samples, parse the generated XML, validate event CSV rows, and check 1000 Hz to 500 Hz downsampling.