A pulse sensor on an Arduino: heart rate on an OLED
Intermediate
Or see every lesson.
- 465parts on the bench
- 43boards running now
- 1.00xreal time, on every board
The Pulse Sensor is the round green board with a heart on it that you hold against a fingertip or clip to an earlobe. It has three wires and one output: a wave that rises with every heartbeat. An Arduino reads it with analogRead(), and the PulseSensor Playground library turns the wave into beats per minute.
The circuit above is an Uno, a Pulse Sensor on A0 and an SSD1306 OLED on I2C. The OLED draws the wave scrolling across the screen with the heart rate on top, and the Uno’s own LED on pin 13 blinks with each beat. Press Run, wait a few beats, then drag the heart rate on the sensor and watch the number follow.
What you need
- An Arduino Uno
- A Pulse Sensor (the Pulse Sensor Amped, or a clone)
- A 0.96 inch SSD1306 OLED, 128x64, I2C
- Jumper wires
Wiring
| Part and pin | Goes to | Note |
|---|---|---|
| Pulse Sensor S (purple) | A0 | The wave |
| Pulse Sensor VCC (red) | 5 V | Runs on 3 to 5.5 V |
| Pulse Sensor GND (black) | GND | |
| OLED SDA | A4 | Address 0x3C |
| OLED SCL | A5 | |
| OLED VCC and GND | 5 V and GND |
How the sensor sees a heartbeat
The bright green LED on the front shines into your skin, and a light sensor next to it measures how much comes back. Every heartbeat pushes a little more blood into the tiny vessels under the skin, and blood soaks up green light, so a little less comes back. An amplifier on the board turns that small change into a wave on the S wire.
With nothing on it, S sits at half the supply, about 512 on a 5 V Uno’s 0 to 1023 scale. With a finger on it, each beat is a quick rise to a peak, a fall with a smaller second bump called the dicrotic notch, and a dip before the next beat.
Counting beats with PulseSensor Playground
You could read A0 in loop() and look for peaks yourself, but the timing matters. The library sets up the Uno’s Timer 2 to read A0 every 2 milliseconds in the background, finds each beat, and averages the time between the last ten beats into a heart rate. Your loop() only has to ask.
setThreshold(550) sets the level a beat has to rise above, a little over the middle at 512. sawStartOfBeat() returns true once per beat, and getBeatsPerMinute() gives the rate. Run it at the starting 72 beats a minute and the serial monitor prints lines like "beat: 71 BPM ibi 834", where ibi is the inter-beat interval, the milliseconds since the last beat.
Because the rate is an average of ten beats, it does not jump. Drag the rate from 72 to 120 and the ibi drops to 500 within a beat, while the BPM climbs 76, 80, 84, 88 and on up to 120 over the next ten beats. Take the finger away (slider all the way left) and the beats stop: the serial monitor goes quiet and the wave goes flat. The number on the screen only changes on a beat, so it keeps the last rate; it reads "-- BPM" only after Run, before the first beat is found.
#define USE_ARDUINO_INTERRUPTS true
#include <PulseSensorPlayground.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
const int PULSE_PIN = A0;
const int LED = 13;
const int THRESHOLD = 550;
Adafruit_SSD1306 display(128, 64, &Wire, -1);
PulseSensorPlayground pulseSensor;
void setup() {
Serial.begin(115200);
display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
pulseSensor.analogInput(PULSE_PIN);
pulseSensor.blinkOnPulse(LED);
pulseSensor.setThreshold(THRESHOLD);
pulseSensor.begin();
}
void loop() {
if (pulseSensor.sawStartOfBeat()) {
int bpm = pulseSensor.getBeatsPerMinute();
Serial.print("beat: ");
Serial.print(bpm);
Serial.print(" BPM ibi ");
Serial.println(pulseSensor.getInterBeatIntervalMs());
}
// ...draw one column of the wave from getLatestSample()
delay(20);
}Drawing the wave
Every 20 ms the sketch takes the latest sample from the library and draws it one column further along the OLED, scaled from 0 to 1023 onto the 48 rows under the heart rate. At the right edge it starts again at the left, erasing as it goes, like a hospital monitor.
Sending a whole 128x64 frame over I2C takes time, so the sketch only calls display.display() every fifth column. The drawing happens in the Uno’s memory; display() is what copies it to the screen.
Getting a steady reading
A real pulse sensor is very sensitive to movement and pressure. Rest your finger on it lightly and keep still: press hard and you squeeze the blood out, so the wave goes flat. Room light leaking in at the edges adds noise, which is why the kit comes with a felt ring and a velcro strap.
If the beats double up, the threshold is catching the dicrotic notch as a second beat. Raise the threshold a little. If no beats come at all, the wave is too small to reach it; lower it, or check the wave with the serial plotter.
The simulated sensor draws a clean wave with its features in the right places, so it beats exactly on time. A real heart varies a little from beat to beat, and that is normal. This project is for learning, not a medical device.
Common mistakes
Reading the sensor once a second in loop() with delay(1000). A beat is over in a fraction of a second, so you miss it. Let the library sample it, or sample every few milliseconds yourself.
Counting every rise above the middle. The notch after each beat is a second rise, so the rate comes out double. Use a threshold above the notch, as the library does.
Using PWM on pins 3 and 11 at the same time. The library takes Timer 2, which also drives those two pins, so analogWrite() on them stops working. Use pins 5, 6, 9 or 10.
The OLED wired wrong. With its SDA pulled out, the sketch prints "no OLED: check SDA, SCL and power", but the beats still print, so the sensor half is fine.
Pressing too hard, or moving, and blaming the code.
Questions
How do I connect a pulse sensor to an Arduino?
Three wires: purple (signal) to A0, red to 5 V and black to GND. Then read A0, or use the PulseSensor Playground library to get the heart rate.
What threshold should I use for a pulse sensor?
Start at 550, the library’s default, a little above the middle reading of 512. Raise it if beats double up, lower it if none are found.
Why does the BPM change slowly?
PulseSensor Playground averages the time between the last ten beats. The ibi value is the last single beat if you want something faster.
Is a pulse sensor the same as a MAX30102?
No. The Pulse Sensor puts an analog wave on one pin. The MAX30102 is a digital I2C sensor with red and infrared LEDs that can also estimate blood oxygen. There is a page on it below.
Keep going
Build this for real
Open the editor, change a value and watch the number move with it. Nothing to install, and no account needed.