> ## Content Index
> Fetch the complete content index at: https://mfitzp.ghost.io/llms.txt
> Use this file to discover other available public pages before exploring further.

# Heart rate sensors
- URL: https://mfitzp.ghost.io/heartrate-sensor-modules/
- Published: 2018-02-27T21:00:00.000Z
- Updated: 2026-09-09T12:36:21.000Z
- Description: Photoplethysmography, because that's a real word
- Author: Martin Fitzpatrick
- Tags: Biology, Electronics, Python, #Import 2026-08-24 14:26

Pulse sensors are a common feature of fitness monitors, used to track your activity and cardiac fitness over time. These external monitors use the reflection and absorption of bright green/infra-red light to detect the pulse wave travelling down the artery — a technique called *photoplethysmography* (PPG). This same technique is used in hospital finger-clip monitors. Wrist-worn devices like the Fitbit typically use green-light sensors, while the iPhone monitor uses a combination of green and infra-red light. The use of infra-red light for pulse monitoring has a longer history, and is the type of choice used in hospital monitors, because (in combination with a red LED) allows both heart rate and oxygen saturation sensing.

## How it works

Haemoglobin, the oxygen-carrying component of red blood cells, is a strong *reflector* of red light, and a strong *absorber* of green light. Together these characteristics give oxygenated blood its red color.

The image below shows the relative absorption by blood of different wavelengths of light. Green, red and infra-red regions are highlighted.

![Red and green light absorbance by blood.](https://storage.ghost.io/c/50/3e/503e92e5-9187-4058-a6fd-d37b3deb22ec/content/images/2026/09/pulsesensor_wavelength.png)

The differences in absorption between oxygenated (red) and deoxygenated (blue) blood can be used with paired red-IR LEDs to determine blood oxygenation percentage. See [the MAX30100 sensor](https://www.martinfitzpatrick.com/heartrate-sensor-modules/?ref=mfitzp.ghost.io) for an example.

During a pulse there is a wave of increase blood pressure through the arteries, slightly stretching the elastic arterial walls and bringing a pulse of highly oxygenated blood. This change in arterial size and increase concentration of blood is what is exploited to detect the pulse with PPG.

**Green light** is absorbed by red blood cells, but scattered by the tissues. Between pulses, scattered light will be reflected back out towards the incident light and can be detected. However, during a pulse, the small increase in blood volume leads to an increased absorption of the green light. This results in a reduction of reflected signal. We cab detect this increased absorbance by the *reduction* in *reflection* of green light.

**Red/infra-red light** is reflected by red blood cells. Between pulses most light is transmitted and scattered into the tissues. During a pulse the small increase in blood volume leads to an increased reflection of light by red blood cells, and a reduction in transmission. We can detect this increased scattering of light either by the *reduction* in IR *transmission*, or alternatively an *increase* in *reflection*.

![Red and green light interactions with blood.](https://blog.martinfitzpatrick.com/static/tutorials/heartrate-sensor-modules/pulsesensor_theory.png)

Below we look at the main types and how to interface with them from different microcontrollers. You can also use one of these sensors [to build a working heart monitor](https://www.martinfitzpatrick.com/calculating-heart-rate-from-sensor-data/?ref=mfitzp.ghost.io).

## Analogue Sensors (KY-039/Pulsesensor.com)

### Sensor types

**IR-phototransistor sensors (KY-039)** like the [Keyes KY-039](http://geni.us/ky039?ref=mfitzp.ghost.io) use a bright infrared (IR) LED and a photo transistor to detect the *reduced IR transmission* or *increased IR reflection* during the pulse. These sensors are more suited to transmission use because of their construction.

![KY-039 Heart beat sensor](https://blog.martinfitzpatrick.com/static/tutorials/heartrate-sensor-modules/keyes-039_pulse_sensor.jpg)

![KY-039 Reverse](https://blog.martinfitzpatrick.com/static/tutorials/heartrate-sensor-modules/keyes-039_pulse_sensor_reverse.jpg)

**Green-light phototransistors** like the [PulseSensor.com](http://geni.us/pulsesensor?ref=mfitzp.ghost.io) function in reverse using reflected, rather than transmitted light to detect the pulse. This is done using a rather pleasant green LED, which when wired up looks a bit like a tripod from the War of the Worlds. By using green light these sensors detect the *reduction in reflection* due to blood absorbing the green light. The circuitry for these sensors is a little more sophisticated than a raw light sensor, with automatic amplification and noise cancellation features. This makes the subsequent HR calculation a little simpler. The sensors are similar to those found in wearable heart rate/fitness devices.

![Pulsesensor.com Active](https://blog.martinfitzpatrick.com/static/tutorials/heartrate-sensor-modules/pulsesensor_d1.jpg)

![Pulsesensor.com Reverse](https://blog.martinfitzpatrick.com/static/tutorials/heartrate-sensor-modules/pulsesensor_rear.jpg)

See [this tutorial](https://www.martinfitzpatrick.com/calculating-heart-rate-from-sensor-data/?ref=mfitzp.ghost.io) for an example of building a heart rate monitor using a Pulsesensor.com sensor.

### Reading analogue sensors

The principal for reading both types of sensor is the same. With the sensor + connected to Vref (3.3V or 5V; depending on controller), and GND to GND, the measured value is readable via the S pin. The analogue output from this pin varies between GND (light completely blocked) and Vref (light completely passing).

The variation caused by the pulse is a tiny fraction of that caused by putting your finger in front of the sensor, so calculating the pulse value requires a bit of code (see later).

#### Raspberry Pi

Wire the + on the sensor to *+3.3v* on your Pi. While the sensor can handle 5V itself, the input voltage sets the max output from the S pin. More than 3.3V will damage your Pi's GPIO.

As the readings are analogue you will need a Analogue to Digital Converter (ADC) such as the MCP3008 between the sensor and your Pi. The Pi then communicates with the chip via SPI.

Add the MCP3008 to a breadboard, with the notch facing up towards your breakout board. Using hardware SPI we can wire the MCP3008 up as follows:

| MCP3008     | Raspberry Pi |
| ----------- | ------------ |
| VDD (16)    | 3.3V         |
| VREF (15)   | 3.3V         |
| AGND (14)   | GND          |
| DGND (13)   | GND          |
| CLK (12)    | SCLK (11)    |
| DOUT (11)   | MISO (9)     |
| DIN (10)    | MOSI (10)    |
| CS/SHDN (9) | CE0 (8)      |

The analogue and digital ground pins are wired together here. This is fine since we aren't looking for highly accurate readings.

Wire the 3.3V and GND pins up first, leaving the SCLK, MISO, MOSI and CEO pins to wire to your Pi. They connect to pins 11, 9, 10 and 8 respectively, with the first 3 in order up one side of the GPIO, and the last on the other side, level with the first pin you connected.

Once connected you can use the `gpiozero.MCP3008()` interface to read values out directly.

```Python
import gpiozero
hr = gpiozero.MCP3008(channel=0)  # Set the channel if you're using a different one

>>> hr.value
    0.7663

```

To check that the sensor is responding correctly, try writing the values out in a loop (using `end="\r"` keeps our output on a single line).

```Python
while True:
    print("%4f" % hr.value, end="\r")

```

If you put your finger between the emitter and sensor while running this you should see the number increase, indicating *increased* resistance as the light has to travel through your finger. Somewhere buried in the noisy variation you see is your heart beating.

#### MicroPython (ESP8266 inc. Wemos D1)

If you're running on a ESP8266 device you have access to an ADC pin, labelled as A0 on the Wemos D1\. Once your sensor is wired to this pin you can use following MicroPython code to read the value from the sensor:

```Python
import machine
adc = machine.ADC(0)

>>> adc.read()
550

```

The values output by this ADC are integers in the range 0-1023\. A reading of 550 is equivalent to 0.54 on a 0-1 scale.

Below is a plot of data from the Pulsesensor.com sensor, as read using a Wemos D1\. Notice that the Pulsesensor.com sensor auto-calibrates the reading range.

![Complete set of data from the sensor.](https://blog.martinfitzpatrick.com/static/tutorials/heartrate-sensor-modules/pulsesensor_data.png)

![Zoomed in region](https://blog.martinfitzpatrick.com/static/tutorials/heartrate-sensor-modules/pulsesensor_sample.png)

#### BBC micro:bit

The micro:bit can read analog values on pins 0, 1, 2, 3, 4 and 10.

```Python
from microbit import *
>>> pin0.read_analog()
550

```

The values output by this ADC are integers in the range 0-1023\. A reading of 550 is equivalent to 0.54 on a 0-1 scale.

## Digital sensors (MAX30100/RCWL-0530)

Digital sensors based on MAX30100 chips offer both heart rate sending and oximetry (oxygen saturation measurements). These include 2 LEDs (IR and visible red light) to detect pulse and oxygen saturation levels respectively.

![A digital heart rate sensor using the MAX30100 chip.](https://blog.martinfitzpatrick.com/static/tutorials/heartrate-sensor-modules/max30100.jpg)

Oximeters pair two light emitters and receivers working at slightly different wavelengths. Using the different light absorbance profiles of oxygenated and deoxygenated blood it is possible to determine the level of O2 in the blood in addition to the pulse.

![](https://storage.ghost.io/c/50/3e/503e92e5-9187-4058-a6fd-d37b3deb22ec/content/images/2026/09/pulsesensor_wavelength-1.png)

### Reading digital sensors

The MAX30100 chip [datasheet](https://datasheets.maximintegrated.com/en/ds/MAX30100.pdf?ref=mfitzp.ghost.io) provides an I2C interface to read the resulting data, which can be interfaced directly to any I2C supporting micro controller. It is available at I2C channel 0x57 (87).

There was no simple interface available for connecting with a MAX30100 based sensor from a Raspberry Pi (the Intel IoT UMP project looks promising, but doesn't build on Pi as of September 2017). As a stop-gap I re-implemented a C library in Python. The [source code](https://github.com/mfitzp/max30100?ref=mfitzp.ghost.io) is available. This currently works with Raspberry Pi only, although adapting to MicroPython should be as simple as writing an adaptor for the I2C API.

You can create an interface to your device/chip by creating an instance of the MAX30100 class.

```Python
import max30100
mx30 = max30100.MAX30100()

```

To read the sensor use `.read_sensor()`. This stores the current measurement, making this value available under two properties. Historic values are in a queue.

```Python
mx30.read_sensor()

# The latest value is now available by .ir
mx30.ir

```

You can switch on O2 sensing as follows.

```Python
ms30.enable_spo2()

```

Subsequent calls to `.read_sensor()` will now populate the `.red` attribute.

```Python
# The latest value is now available by .ir and .red
mx30.ir, mx30.red

```

For more information on using the max30100 package, including the O2 saturation sensor, see the [documentation on Github](https://github.com/mfitzp/max30100?ref=mfitzp.ghost.io).

## Calculating a heart rate

Regardless of what sensor you use, you will end up with the same output — a stream of values, indicating transmission or reflection of a particular wavelength of light.

![Pulse sensor data, zoomed on a subset of the data](https://blog.martinfitzpatrick.com/static/tutorials/heartrate-sensor-modules/pulsesensor_sample.png)

There are a number of ways to calculate the heart rate from this data, but peak-counting is a simple and effective method. For a working example of how to do this [see this Wemos D1 heart rate monitor](https://www.martinfitzpatrick.com/wemos-heart-rate-sensor-display-micropython/?ref=mfitzp.ghost.io).