How a PPG Sensor Actually Measures Blood Flow

Photoplethysmography (PPG) is an optical technique that measures volumetric changes in blood circulation by shining light into the skin and capturing the reflection or transmission with a photodiode. When the heart pumps, arterial blood volume increases, absorbing more light and reducing the amount that bounces back to the sensor. Between beats, blood volume drops and light reflection increases. By tracking these microscopic fluctuations in light intensity, the sensor generates a waveform that directly maps to your cardiac cycle.

The resulting optical signal consists of two distinct components: a large DC baseline representing static tissue, venous blood, and non-pulsatile arterial blood, and a tiny AC ripple representing the pulsatile arterial blood. Isolating this AC component yields the heart rate. When using dual-wavelength sensors (typically Red at 660nm and Infrared at 880nm), the device compares the AC-to-DC ratios of both wavelengths. Because oxygenated hemoglobin absorbs more IR light and deoxygenated hemoglobin absorbs more Red light, calculating the ratio between these two specific absorption profiles allows the microcontroller to estimate blood oxygen saturation (SpO2).

Wiring and Interfacing: Analog vs. I2C Digital PPG Modules

The most common mistake makers make with optical heart rate modules is conflating analog and digital outputs. A PPG sensor's raw output is a minute analog current from the photodiode, but how that signal reaches your microcontroller depends entirely on the breakout board you buy.

Analog modules (like the classic PulseSensor Amped) contain onboard op-amps and filters that condition the signal into a clean 0V to VCC analog voltage. Digital modules (like those based on the Maxim MAX30102 or MAX30105) contain an internal analog-to-digital converter (ADC) and state machine, outputting raw digital counts over an I2C bus. You must treat these as entirely different engineering problems.

PPG Sensor Module Specifications and Wiring Guide
Module Type Interface Supply Range (VCC) Logic Level Output Type
PulseSensor Amped (Analog) Analog Voltage 3.3V - 5.0V Matches VCC 0V to VCC Conditioned Waveform
Generic MAX30102 Breakout I2C (Address 0x57) 3.3V (Strict) 3.3V Only 18-bit Raw ADC Counts via I2C
Adafruit MAX30105 Breakout I2C (Address 0x57) 3.3V - 5.0V 3.3V / 5V Tolerant 18-bit Raw ADC Counts via I2C
⚠️ Hardware Warning: Never power a bare, cheaply manufactured MAX30102 breakout board with 5V. The internal silicon requires 1.8V, and budget boards often lack robust onboard voltage regulation. Feeding them 5V from an Arduino Uno's 5V pin will permanently brick the I2C bus. Always use the 3.3V pin on your ESP32 or Arduino.

ESP32 I2C Pin Mapping

  • VCC: 3.3V
  • GND: GND
  • SDA: GPIO 21 (Default ESP32 I2C SDA)
  • SCL: GPIO 22 (Default ESP32 I2C SCL)
  • INT: Optional (Connect to any GPIO with interrupt capability if using FIFO threshold triggers)

Translating Raw ADC Counts to Heart Rate and SpO2

Once you are reading data, you need to convert raw sensor outputs into physical units. The math diverges sharply depending on whether you are using an analog or digital module.

Analog Output Math (PulseSensor Amped)

The physical unit here is simply relative amplitude. If your ESP32's ADC is 12-bit (0-4095) and referenced to 3.3V, the voltage at any given millisecond is:

V_out = (ADC_raw / 4095) * 3.3

To find Heart Rate (BPM), you do not use the voltage value directly. Instead, you implement a peak-detection algorithm to find the time delta (Δt) in seconds between two consecutive systolic peaks. The physical unit conversion is:

BPM = 60 / Δt

Digital I2C Math (MAX30102 / MAX30105)

The MAX30102 outputs an 18-bit unsigned integer ranging from 0 to 262,143. Do not attempt to convert this to volts; the internal transimpedance amplifier (TIA) gain settings make voltage calculations irrelevant for the end application. You work directly in ADC counts.

For Heart Rate, apply a digital bandpass filter to the raw IR counts to isolate the AC ripple, then measure the time between zero-crossings or peaks just like the analog method.

For SpO2, the physical unit is a percentage derived from the "Ratio of Ratios" (R). According to research published in the NCBI on PPG signal processing, the empirical formula requires isolating the AC and DC components of both the Red and IR channels:

R = (AC_red / DC_red) / (AC_ir / DC_ir)

Once you calculate R, you map it to SpO2 using a linear approximation calibrated against commercial pulse oximeters:

SpO2 (%) ≈ 110 - (25 * R)

Note: This linear equation is an approximation. Medical-grade devices use complex, multi-point lookup tables derived from human hypoxia studies to map R to SpO2, especially below 90% saturation.

Defeating Motion Artifacts and Ambient Light Interference

PPG sensors are notoriously fragile in real-world environments. If your bench tests look perfect but your wearable project fails when the user walks, you are running into the three primary interference sources.

  1. Motion Artifacts (MA): When the user moves, the tissue and sensor shift relative to each other, causing massive low-frequency swings in the DC baseline that easily swallow the AC heart signal. The Fix: Implement a digital bandpass filter with cutoff frequencies of 0.5 Hz to 3.5 Hz (which corresponds to 30 BPM to 210 BPM). For advanced builds, use an onboard IMU (like the MPU6050) to feed accelerometer data into an adaptive Least Mean Squares (LMS) filter to subtract the motion noise from the optical signal.
  2. Ambient Light Ingress: Sunlight contains massive amounts of IR radiation, and indoor fluorescent lights flicker at 50Hz/60Hz, both of which will saturate the photodiode. The Fix: The MAX30102 handles this via Ambient Light Cancellation (ALC). It pulses the LED, takes a reading, then turns the LED off and takes a second "dark" reading, subtracting the latter from the former in hardware. Ensure your I2C initialization library explicitly enables the ALC register.
  3. Skin Tone, Tattoos, and Perfusion: Melanin and tattoo ink absorb optical energy, drastically lowering the signal-to-noise ratio. Cold hands (poor perfusion) constrict capillaries, doing the same. The Fix: You must dynamically scale the LED drive current. While default libraries often set the LED current to 4.4mA, you can write to the LED1_Pulse_Amp register to push up to 16mA (or 50mA on the MAX30105) to punch through high-melanin skin or dark ink.
💡 Pro-Tip for Wearables: If you are building a wrist-worn device, switch to a Green LED (525nm). Green light does not penetrate as deeply as IR, making it highly sensitive to superficial capillary beds and significantly less prone to motion artifacts caused by muscle flexing. Reserve Red/IR for fingertip clips or transmissive earlobe sensors.

PPG Sensor Frequently Asked Questions

Why is my MAX30102 PPG sensor reading stuck at zero or max value?

If your I2C scanner finds the device at 0x57 but the FIFO buffer only returns zeros or 262143 (max 18-bit value), you likely have a power or pull-up issue. First, verify you have 4.7kΩ pull-up resistors on the SDA and SCL lines; many cheap breakout boards omit these. Second, check your VCC rail. If the internal LDO drops out due to insufficient current supply (the LEDs can draw up to 50mA combined), the internal ADC will rail out or reset. Power the module directly from a dedicated 3.3V LDO rather than the ESP32's onboard USB-derived 3.3V pin if you are running high LED currents.

Can a standalone PPG sensor measure blood pressure?

Not directly or accurately without heavy caveats. PPG sensors can estimate blood pressure using a technique called Pulse Transit Time (PTT), which measures the time delay between the electrical spike of the heartbeat (requiring a separate ECG sensor) and the arrival of the pulse wave at the PPG sensor. While Analog Devices and other semiconductor firms have published extensive research on PTT algorithms, it requires rigorous, frequent per-user calibration against a traditional cuff. A standalone PPG sensor attempting to guess blood pressure purely from waveform morphology (pulse wave analysis) is currently considered unreliable for anything beyond novelty applications.

Which LED color is best: green vs. IR PPG sensor wavelengths?

The choice depends entirely on the physical mounting location. Use Green (525nm) for reflective, wrist-worn wearables (like smartwatches). Green light is absorbed well by blood but scatters heavily in tissue, keeping the optical path shallow and minimizing noise from underlying muscle movement. Use Infrared (880nm) for transmissive setups (like a fingertip clip) or deep-tissue reflective SpO2 measurements. IR penetrates much deeper, reaching the arterial beds necessary for accurate oxygen saturation calculations, but it is highly susceptible to motion artifacts if used on the wrist.