Case file
Firmware for an Arduino Uno that samples an optical pulse sensor at exactly 200 Hz, finds each heartbeat, and streams the signal to a PC for live display. It began as a team project in my M.Sc. I rebuilt it in 2026, because the sketch we submitted did not compile and its detection logic only worked for one sensor setting.
Thesis
A pulse sensor gives a clean signal for one person on one day and a different signal for the next: different skin, different pressure, a different resting level. Our 2023 firmware used a fixed threshold of 500 ADC counts, so it worked when the signal happened to cross 500 and failed silently when it did not. Counting heartbeats on a microcontroller is not hard. Counting them for anyone, under any of those conditions, is the actual task.
What we found
The 2023 sketch did not compile. The serial library it needed could not be found, which our own slides admitted. There was also a missing semicolon, a string built by adding an integer to a pointer, and an interrupt handler with the wrong signature. Fixing those only exposed the logic. With no hysteresis, noise around the threshold counted as dozens of beats, and the reported rate pinned above 250 BPM. A sensor resting above or below 500 produced no beats at all.
Architecture
Timer1 in CTC mode fires an interrupt every 5 ms. The interrupt reads the sensor, runs the detector, drives the LED and pushes the sample into a ring buffer. The main loop drains that buffer and sends each sample either as an ASCII line for the Arduino Serial Plotter or as a 17-byte OpenEEG P2 frame for BrainBay, depending on one input pin. Sampling never waits on the serial port.
Engineering decisions
Registers rather than a timer library. Three register writes give 16 MHz / 8 / 10 000 = 200 Hz exactly, with nothing hidden.
No serial I/O inside the interrupt. A full transmit buffer would stretch the sampling period, and a stretched sampling period corrupts every rate the device reports. The ring buffer holds 160 ms of slack.
A 4-sample moving average. At 200 Hz its first zero falls exactly on 50 Hz, so the same filter that smooths the pulse also removes mains hum, for the cost of one addition and one subtraction per sample.
An adaptive threshold at 60% of recent peak heights. It follows a strong or a weak signal within a few beats, and the absolute level of the sensor no longer matters.
A refractory period that scales with the heart rate: half the median beat interval, clamped to 300 to 450 ms. A fixed 300 ms let the dicrotic wave count as a second beat at resting rates. The 450 ms cap keeps fast rates reachable.
The median of five intervals rather than a running mean. One missed or extra beat cannot move the display. The cost is a short lag on very fast rises, and the results report it.
A P2 encoder written from the protocol. The 2023 build failed on a missing library. The protocol is 17 bytes, so the firmware now has no external dependency at all.
The chart
The scorecard shows, for each of twelve signal scenarios, the share of time the reported rate is within 5 BPM of the truth. The 2026 firmware is at 100% in eight scenarios, 96% on a sudden step from 60 to 110 BPM, 95% with motion artefacts, 86% with an extreme dicrotic wave, and 71% on a ramp from 70 to 160 BPM in 20 seconds. The 2023 logic stays below 8% everywhere.
Testing
The detector is plain integer C with no Arduino dependencies, so the identical source compiles on a laptop. A harness feeds it synthetic pulse recordings with heart-rate variability, breathing modulation, noise, mains hum, baseline wander, motion artefacts and sensor removal. The 2023 logic, ported with only its compile errors fixed, reads the same samples. Unit tests check the two ASCII examples from the course brief byte for byte, the P2 frame layout and counter wrap, silence on a flat signal, and the LED staying on for exactly 100 ms.
Build status
Done. Firmware builds for the Uno at 4.0 KB of flash and 397 bytes of RAM. All unit tests pass. All 12 scenarios reproduce identically from a clean checkout. CI builds the firmware and runs the tests.
Pending. A bench run on a real sensor. Synthetic scenarios test the conditions the brief names; they are not clinical validation.
Credit
2023 team: Samuel Henry Mba (development lead, wrote the firmware), Tiseh Samim Basharkhah, Sruthi Jose, FH Technikum Wien, course Microprocessor Applications in Medicine. The 2026 firmware, tests and analysis are also mine.