Hello friends, I hope you all are doing great. Today, I am going to share the new MAX30102 Sensor Library for Proteus V1.1. The MAX30102 from Maxim (now Analog Devices) is a small pulse oximeter and heart-rate sensor, best known on the purple GY-MAX30102 breakout. With this MAX30102 Sensor Library for Proteus, you set the pulse rate, the SpO2 and the finger with presets and sliders, and your Arduino reads the red and infrared samples over I2C with the same SparkFun MAX3010x library you use on real hardware.

Updates

MAX30102 Sensor Library for Proteus

Latest version: V1.1 (September 2026, this tutorial)

Other health sensors: Heart Beat · MLX90614 · Flex Sensor

Helping Tools

Version 1.1 replaces the first version of this post. You now get two devices: MAX30102 Simple, with a control panel on the schematic, and MAX30102 Advance, with a pop-up window and four measured sensor test tools. The light is now a realistic pulse wave, and the new MAX30102 Arduino Proteus demo runs on two Arduino UNOs with a heartbeat LED and new commands, shown in the compact Simple interface of our TEP Serial Monitor. Have you read version 1.0? Then jump to "What's New" below.

NOTICE: This library is very special to our team. Our MAX30102 model has the chip's register map with its modes, the LED currents, the sample rate and averaging, the 32-sample FIFO with its pointers and overflow counter, the interrupts and the die temperature sensor, and it speaks I2C to the unmodified SparkFun library. It took our team a lot of hard work, many test runs and many design changes, above all for the pulse wave. Your feedback is the fuel that keeps us going, so please tell us what you think in the comments below or in the MAX30102 Library board of our forum. And if our free libraries help you, you can buy us a coffee. So, let's get started with the MAX30102 Sensor Library for Proteus:

Figure: The MAX30102 demo in Proteus 8.5: NODE 1 (Simple) and NODE 2 (Advance), U2's Sensor page at REST 60 (60 BPM, 98 %), and U4 moving from 72.3 / 71.4 BPM to "Pulse estimate: 60.0 BPM".

What is the MAX30102 Pulse Oximeter Sensor?

The MAX30102, made by Maxim Integrated (now part of Analog Devices), is an optical pulse oximeter and heart-rate sensor. Under one small window, it has a red LED, an infrared (IR) LED and a photodetector, which measures the light that comes back from your fingertip. Your Arduino reads the results over I2C, the 2-wire bus with a clock line (SCL) and a data line (SDA). For the chip itself, see the MAX30102 product page of Analog Devices.

Every heartbeat pushes more blood into the fingertip, and more blood means less light back, so the light dips a little at every beat. This light signal is the PPG (photoplethysmogram), and its rhythm is your pulse. SpO2, the oxygen saturation of the blood, shows in the ratio between the red and the IR pulse. But the sensor gives you only samples: your sketch computes the pulse and the SpO2 itself, exactly as with the real sensor.

The MAX30102 at a Glance

MAX30102 in numbers
FeatureValue
MeasuresRed and infrared light, as raw samples of up to 18 bits
InterfaceI2C, standard and fast mode, at the fixed address 0x57
Chip IDRegister PART_ID (0xFF) = 0x15
ModesHR, SpO2 (red + IR) and multi-LED
LED current0.2 mA per step; the demo uses 12.0 mA
Sample rate50 to 3200 Hz, with sample averaging of 1 to 32
ResolutionThe LED pulse width sets the ADC resolution, 15 to 18 bits; the ADC range is selectable
FIFO32 samples, with pointers, an overflow counter and rollover
InterruptsA_FULL (FIFO almost full), PPG_RDY (new sample) and DIE_TEMP_RDY; INT pin active low, open drain
Die temperature29 ms per conversion, in steps of 1/16 C
Breakout pinsVIN, GND, SCL, SDA, INT
SupplyThe GY-MAX30102 has its own 1.8 V and 3.3 V regulators; VIN takes 3.3 V or 5 V

MAX30102 Terms You Will See in This Article

MAX30102 terms
TermMeaning
PPGPhotoplethysmogram: the light signal of the pulse
BPMBeats per minute, the pulse rate
SpO2The oxygen saturation of the blood, in %
Systolic upstrokeThe fast rise of the pulse wave at the start of every beat, up to its peak
Diastolic shoulderThe small step on the falling side of the wave (the dicrotic wave)
AC / DCThe pulsing part of a signal / its average level
Ratio of ratios (R)(AC red / DC red) / (AC IR / DC IR); the SpO2 formula uses it
FIFOFirst in, first out: the chip's 32-sample memory
OverflowSamples lost because nobody read the full FIFO in time
Die temperatureThe temperature of the chip itself, not of your finger or your body

How the MAX30102 Turns a Heartbeat Into Samples

Red and IR Samples, 25 per Second

In SpO2 mode, the chip stores a red and an IR result as one sample pair. The demo sets 100 samples per second with averaging by 4, so 100 / 4 = 25 samples per second reach the FIFO, one every 40 ms. With the demo's 411 us LED pulse, each sample has 18 bits, and a pair is read as six bytes.

The 32-Sample FIFO

FIFO means first in, first out: older samples are read first. The MAX30102 keeps up to 32 samples, 1.28 s at 25 per second. The SparkFun library compares the write and the read pointer (FIFO_WR_PTR, FIFO_RD_PTR) to see how many new samples wait. Read too late, and samples are lost; OVF_COUNTER counts them.

There is one trap. With rollover on, as in the demo, a full FIFO keeps writing until the write pointer reaches the read pointer: FIFO_WR_PTR = FIFO_RD_PTR. The SparkFun check() then sees no new samples at all, until clearFIFO(). Our v1.1 model does the same and flags it (FIFO FULL). So keep check() frequent.

The Pulse Wave in Our Model

Version 1.0 used a sine wave. In v1.1, every heartbeat has a fast systolic upstroke and peak, then the diastolic shoulder (the dicrotic wave, at about a third of the pulse, fading at a fast heart rate) and a slow run-off. The raw counts dip at every beat; the board and the pop-up show the wave inverted, like a pulse oximeter (up = more blood). From the peak to the foot, the wave only falls, so any threshold or peak detector counts exactly one beat per heartbeat, from 40 to 200 BPM.

SpO2 Through the Ratio of Ratios

Divide each colour's pulsing part (AC) by its average level (DC), then the red result by the IR result: that is the ratio of ratios, R = (AC red / DC red) / (AC IR / DC IR). Maxim's reference code turns it into SpO2 = -45.060 x R² + 30.354 x R + 94.845; R = 0.6 gives about 96.8 %, and R = 1.0 gives 80.1 %. Our model sizes the red pulse so that R gives the SpO2 you set.

The Die Temperature

The chip also measures its own die temperature in steps of 1/16 C, 29 ms per conversion. It is the temperature of the silicon, not of your finger; in the model, you set it with DIE TEMPERATURE.

What's New in MAX30102 Sensor Library for Proteus V1.1

If you used version 1.0, here is what has changed:

  • Two devices in TEPMAX30102.LIB: MAX30102 Simple (MAX30102TEP, the v1.0 device) and MAX30102 Advance (MAX30102ADVTEP) with a pop-up window.
  • A redrawn, live board: the purple GY-MAX30102 with an animated pulse monitor, PWR / I2C / INT LEDs (a TEP addition) and the live I2C address.
  • A realistic pulse wave instead of the v1.0 sine: systolic upstroke, diastolic shoulder, slow run-off, shown inverted like a pulse oximeter.
  • SpO2 through the ratio of ratios: the red pulse is sized so that R gives the SpO2 you set.
  • Finger detection in one click: FINGER ON / OFF presets or a click on the pulse view.
  • Six presets next to the v1.0 sliders; the Simple panel closes with a red X.
  • The sample log: the samples your sketch takes out of the FIFO are recorded, and the tools compute from exactly those.
  • FIFO overflow and a full FIFO are flagged, as the real chip behaves.
  • Die temperature on both panels and a TEMP command in the demo.
  • An I2C log with every transaction decoded, and the SCL clock measured on the pin.
  • Four measured test tools on the Advance.
  • A two-UNO demo with the TEP Serial Monitor instead of the Virtual Terminal: a heartbeat LED and the commands HELP, STATUS, LED, TEMP and RAW ON / OFF; every v1.0 output line is kept.
  • The same chip model: the FIFO, the modes and the timing of v1.0 are unchanged.
  • Lighter package: about 1.61 MB, without the C++ source code and the AVR core archive.

And your old designs keep working; here, the v1.0 demo with the v1.1 DLL:

Figure: A v1.0 design after the update: the v1.0 slider panel ("DRAG TO CHANGE"), and the v1.0 sketch prints "Pulse estimate: 72.3 BPM | SpO2 es..." in its narrow Virtual Terminal.

MAX30102 Sensor Library for Proteus: Simple vs Advance

Both devices run the same model with the same properties; only the panel differs:

MAX30102 Simple vs Advance
FeatureSimpleAdvance
Full MAX30102 model (modes, LED currents, sample rate, averaging, FIFO, interrupts, die temperature, INT pin)✔✔
Purple GY-MAX30102 board: live pulse view, PWR / I2C / INT LEDs✔✔
Control panel on the schematic (six presets, the v1.0 sliders, what the sketch read)✔✘
Pop-up window you can move, resize and minimise✘✔
Sensor page: a large live pulse view, presets, click-sliders with - / + for all four inputs, the last reading as tiles✘✔
I2C check, Timing, Step response and Pulse & SpO2 test✘✔
TEP Dark / Light theme, text size, Help page with Copy diagnostics✘✔
Same device name as v1.0 (old designs keep working)✔✘

Choose Simple to keep the controls beside your circuit, and Advance when your sheet is full or you want hard numbers about your sketch. The demo has one of each.

Download MAX30102 Sensor Library for Proteus

Click the button below to download MAX30102-Sensor-Library-for-Proteus-v1.1.zip (about 1.61 MB, without the C++ source code):

MAX30102 Sensor Library for Proteus V1.1
  • README.txt: a detailed guide to the files, the wiring, the model, the test tools and real hardware.
  • Proteus Library Files: TEPMAX30102.LIB (both devices), TEPSERIALMON.LIB and our Arduino UNO library ArduinoV3TEP.LIB / ArduinoV3TEP.IDX.
  • Proteus Model Files: TEPMAX30102.DLL and TEPSERIALMON.DLL.
  • Proteus Simulation: MAX30102-ArduinoUnoV3.pdsprj, MAX30102_Demo.hex (both UNOs run it) and copies of both DLLs.
  • Arduino Code: MAX30102_Demo.ino and the unmodified SparkFun MAX3010x library 1.1.2 with its nine examples (MIT licence, in its LICENSE.md; its spo2_algorithm keeps Maxim Integrated's own permission notice).

No Proteus yet? Read How to Download and Install Proteus 7 and 8. New to libraries? Follow How to Install a New Library in Proteus.

How to Install MAX30102 Sensor Library for Proteus

  1. Close Proteus and extract the whole zip file.
  2. Copy the four files from Proteus Library Files into the LIBRARY folder, usually C:\Program Files (x86)\Labcenter Electronics\Proteus 8 Professional\LIBRARY (on some PCs C:\ProgramData\Labcenter Electronics\Proteus 8 Professional\LIBRARY).
  3. Copy TEPMAX30102.DLL and TEPSERIALMON.DLL into the MODELS folder.
  4. Start Proteus, press P and search for MAX30102. You get the Simple and the Advance device (category Sensors > Optical, as in v1.0).

Updating from v1.0? Replace TEPMAX30102.LIB and TEPMAX30102.DLL, and delete TEPMAX30102.IDX if there is one in LIBRARY. The DLL keeps its v1.0 name, so a v1.0 design keeps its v1.0 symbol and runs with the new model, with the v1.0 slider panel (no new board, no red X); re-pick the part to get the v1.1 board. Tested in Proteus 8.5; Proteus 7 is not supported.

The MAX30102 Board in Proteus

We drew the sensor as a clean TEP board in the purple of the real GY-MAX30102: two big gold mounting holes, the black MAX30102 with its window over the red and IR LEDs, the small parts and the "GY-MAX30102 HEART RATE + SPO2" print. Here is U1, the Simple device, at the start values:

Figure: U1 (Simple) at 72 BPM and 98 %: SKETCH READ: IR 54765, RED 52547, 72 BPM 98.0 %; MODE SPO2, 25 SAMPLES/S, LED 12.0 / 12.0 MA.

The Pulse View

Above the board sits a small pulse monitor: a beating heart, the BPM, the SpO2 and FINGER ON (or NO FINGER). Below scrolls the PPG trace, the IR samples the sensor makes, inverted: the fast rise, the peak, the small shoulder and the slow run-off of every beat. A click on the view puts the finger on or takes it off.

Pinout

The five pins keep the v1.0 names, order and places, the order of the purple board's header.

MAX30102 pins in Proteus (VIN GND SCL SDA INT)
PinWhat it doesDemo connection
VINPowerSupply terminal
GNDGroundGND
SCLI2C clockArduino A5
SDAI2C dataArduino A4
INTInterrupt output, active low (it also drives the INT LED)Not connected

LEDs and Indicators

Board animations (the same on both devices)
IndicatorWhat it shows
Pulse viewThe beating heart, the BPM, the SpO2, the finger and the PPG trace
PWR LED (red)Lit while the board has power
I2C LED (blue)Flashes with every I2C transaction to this sensor
INT LED (amber)Lit while the INT pin is active
I2C 0x57The live address
PANEL / OPEN PANEL, SIMPLE / ADVANCEThe panel button and the device badge

The PWR, I2C and INT LEDs are a TEP addition; the real board has no LED. With the demo, the blue I2C LED flashes with every FIFO read.

Component Properties

MAX30102 properties (double-click the sensor > Edit Properties; the v1.0 properties, the same on both devices)
PropertyMeaningDefault
PULSEThe pulse rate in BPM, 30 to 22072
OXYGENThe SpO2 the red / IR ratio stands for, 70 to 100 %98
FINGER1 = a finger on the sensor, 0 = none1
TEMPERATUREThe die temperature in C, -40 to 8525

Every Run starts again from these properties, not from the last slider positions. The panel style comes from the device (Simple or Advance), not from a property.

MAX30102 Simple: The Control Panel on the Sheet

Beside the Simple board sits the MAX30102 CONTROLS panel (on the right of the image above):

The MAX30102 Simple control panel
PartWhat it does
Header, red XMAX30102 CONTROLS; the X closes the panel
Six presetsFINGER ON, FINGER OFF, REST 60, WALK 95, RUN 150 and LOW SPO2; the active ones turn dark blue
PULSE RATE, SPO2 TARGET, DIE TEMPERATUREThe three v1.0 sliders; the finger is a preset or a click on the pulse view
SKETCH READWhat your sketch really read over I2C, decoded by the model: the last IR and red sample, and the pulse and SpO2 in the last 100 samples it read
SET lineThe pulse, SpO2, finger and die temperature you set
MODE lineThe mode, the samples per second and the LED currents your sketch chose

SKETCH READ is the line to watch: it is not what the sliders say, but what your Arduino got from the chip.

RUN 150: a Fast Pulse

Figure: RUN 150: 150 BPM and an SpO2 target of 95 %; SKETCH READ: IR 48801, RED 49208, 150 BPM 95.0 %.

Click RUN 150: the pulse jumps to 150 BPM and the SpO2 target to 95 %. The trace packs about twice as many beats into the same width, and at this rate the diastolic shoulder has almost faded, as in real life. SKETCH READ follows with 150 BPM 95.0 %.

LOW SPO2: Less Oxygen

Figure: LOW SPO2: 80 BPM and 85 %; SKETCH READ: IR 45167, RED 45603, 80 BPM 85.0 %.

Click LOW SPO2: 80 BPM and 85 %. The trace hardly changes, because the SpO2 is not in the shape of the IR wave but in the size of the red pulse: for less oxygen, the formula needs a larger R, so the model makes the red pulse larger. SKETCH READ shows 85.0 %, computed from your sketch's samples.

FINGER OFF: Nothing on the Window

Figure: FINGER OFF: NO FINGER, a dark heart and a flat trace; SKETCH READ: IR 500, RED 500, and dashes instead of a pulse.

Click FINGER OFF (or the pulse view). The heart turns dark, the board says NO FINGER, and the trace jumps to a flat line: the samples drop to IR 500 and RED 500 (inverted, less light is drawn higher). The demo treats any sample under 5000 as "no finger", and its heartbeat LED stays dark.

Closing the Panel

Click the red X: the board button says PANEL: CLOSED, and the sensor keeps running. PANEL brings the panel back. Here, U1 is at REST 60:

Figure: The panel closed: PANEL: CLOSED; the pulse view still shows 60 BPM, 98 % and FINGER ON.

MAX30102 Advance: The Pop-Up Sensor Window

The Advance device keeps only the board on the schematic. Here is U2, at the start values:

Figure: U2 (Advance) with OPEN PANEL and the ADVANCE badge; VIN goes to the supply, INT stays open.

At Run, the "TEP MAX30102 Advance" pop-up opens with the look of our TEP Serial Monitor and no Windows title bar: move it by its header, resize or minimise it, and reopen it with OPEN PANEL. The header shows the state (U2 · 25 / s), the Sensor and Test tabs and the theme, Settings and Help icons.

The Sensor Page

Figure: The Sensor page at 72 BPM and 98 %: FINGER ON THE SENSOR, the last reading as tiles, and SKETCH READ: IR 49232, RED 49590, 72 BPM 98.0 %.
  • Toolbar chips and green bar: I2C 0x57, 25 readings / s, INT idle, and "SPO2 · 25 SAMPLES / S", what your sketch set.
  • LIVE VIEW: the large heart, the BPM, the SpO2, FINGER ON THE SENSOR and the inverted PPG wave; click it for the finger.
  • CONTROLS: the six presets and click-sliders with - / + for PULSE RATE, SPO2 TARGET, FINGER PRESENT (in words) and DIE TEMPERATURE.
  • YOUR SKETCH READ: tiles with the last IR and red sample, and the pulse and SpO2 in the last 100 samples, here 72.0 BPM and 98.0 %.
  • WHAT THE ARDUINO READ OVER I2C: SKETCH READ, the SET and MODE lines, the FIFO (0 of 32, 0 overflows, 2607 samples read), 74334 transactions and an SCL clock of 86 kHz.

In the featured image, I clicked REST 60 here, and U4 showed what a 4 s window does: 72.3 and 71.4 BPM, then 67.4 BPM from a window with samples of both rates, then 60.0 BPM.

The Test Page: Four Measured Sensor Test Tools

The four tools on the Test tab only change the sensor's inputs (and restore them) and watch the I2C reads your Arduino really makes; they never talk to your Arduino. Each tool ends with a bold "Good for:" line. All results below come from U2 with the demo sketch.

1. I2C Check: What Did Your Sketch Write Into the Chip?

Figure: I2C check: PART_ID 15, SPO2_CONFIG 27, FIFO_CONFIG 50, LEDs 3C = 12.0 mA, 0 NACKs and an SCL clock of 90 kHz.

Good for: "checking that the MAX30102 answers and what your sketch really wrote into it." It works live, without a Run button. Read the register table like a checklist:

  • PART_ID 15: a MAX30102 answers ("0x15 = MAX30102 / MAX30105, rev 3").
  • MODE_CONFIG 03 and SPO2_CONFIG 27: SpO2 mode (red + IR), 100 Hz, a 411 us LED pulse = 18 bits, the 4096 nA range.
  • FIFO_CONFIG 50: averaging by 4, rollover on.
  • LED1_PA (red) and LED2_PA (IR) 3C: 60 steps of 0.2 mA = 12.0 mA each.
  • FIFO_WR_PTR = FIFO_RD_PTR = 0C: "0 samples waiting"; OVF_COUNTER 00: no sample lost.
  • INT_ENABLE_1 00 (no interrupts) and TEMP_CONFIG 00: idle, after 11 conversions of 29 ms.

Below: 0x57 (fixed), 0 NACKs and the SCL clock, measured on the pin: 90 kHz. The newest transactions show check() at work: about every 6 ms, it reads both FIFO pointers. While both read 0B, nothing is new; at 46.638 s, the write pointer is at 0C, so the library reads FIFO_DATA, six bytes.

2. Timing: How Often Is There New Data?

Figure: Timing, 3 s: a FIFO sample every 40.00 ms (25.0 / s), 75 reads every 40.0 ms, data age 3.9 / 6.9 ms.

Good for: "seeing how often new data exists and whether your sketch reads it in time." Choose a 1, 3 or 10 s window and click Run test. In 3 s, the sensor made 75 conversions, one every 40.00 ms, exactly as the registers expect: "a FIFO sample every 40.0 ms (100 Hz / 4 averaged); the 32-sample FIFO holds 1.28 s."

The sketch read 75 times, every 40.0 ms on average: every sample once, with no busy NACKs and no overflow, at a data age of 3.9 ms on average and 6.9 ms at most. The gaps in the list are 36.6 or 42.6 ms, because the sketch looks only about every 6 ms.

3. Step Response: How Quickly Does a Change Reach Your Sketch?

Figure: Step response, pulse rate x 3: 72 to 120 to 72 to 120 BPM, 225 readings per step, 90 % after 3104.7, 3465.4 and 3265.8 ms.

Good for: "seeing how quickly a change reaches your sketch - conversion, filter and read period together." Choose Finger on, Pulse rate (72 to 120 BPM) or Die temperature (25 to 40 C), and 1, 3 or 5 steps. The tool waits for a steady start, steps the input and plots the set value (dashed) and your sketch's readings (dots); a pulse reading is the pulse in the last 100 samples your sketch read (4 s).

Step response, pulse rate, 3 steps (U2, the demo sketch; 9 s per step)
StepChangeFirst reading (10 %)90 % reachedSettled (2 %)Readings
172 to 120 BPM507.1 ms3104.7 ms3104.7 ms225
2120 to 72 BPM825.2 ms3465.4 ms3465.4 ms225
372 to 120 BPM588.8 ms3265.8 ms3265.8 ms225

Each pulse step lasts 9 s: 225 readings at 25 per second. 90 % (here also the 2 % "settled" point) came after 3.1 to 3.5 s. That is not a slow sensor; it is the 4 s window. Right after the step, the window still holds mostly old beats, and with every new beat an old one drops out, so the dots climb in small stairs. The Finger on step shows in the very next sample, 40 ms later.

4. Pulse & SpO2: Does Your Maths Get the Right Numbers?

Figure: Pulse & SpO2: 5 of 5 set points OK, every error +0.0 / +0.0; the wave = the last 160 IR samples, inverted; grey bars = set, blue = from your sketch's samples.

Good for: "checking your pulse and SpO2 maths on clean signals before you try a real finger." The tool holds five set points for 7 s each and computes the pulse and SpO2 from the 88 samples your sketch read in the second half of each:

Pulse & SpO2 test (U2, the demo sketch; 88 samples per set point; every verdict OK)
Set pointSetFrom your sketch's samplesErrorVerdict
Rest60 BPM / 98 %60.0 BPM / 98.0 %+0.0 / +0.0OK
Walk90 BPM / 97 %90.0 BPM / 97.0 %+0.0 / +0.0OK
Jog120 BPM / 95 %120.0 BPM / 95.0 %+0.0 / +0.0OK
Run150 BPM / 92 %150.0 BPM / 92.0 %+0.0 / +0.0OK
Low SpO275 BPM / 88 %75.0 BPM / 88.0 %+0.0 / +0.0OK

The pulse is one count per systolic upstroke (the IR falling through the middle, never two counts closer than 0.25 s), and the SpO2 is the ratio of ratios with Maxim's formula. Every set point came out exact. The wave above the bars shows the last 160 IR samples, inverted: each sharp peak, the shoulder and the long run-off. The tool checks the samples your sketch received; if your code prints another number, the difference comes from your maths.

Settings and Help

The gear opens Settings: theme (TEP Dark or Light), text size, open the panel at Run, and the window size, saved for your Windows user.

Figure: Settings, saved under HKCU\Software\TheEngineeringProjects\TEP MAX30102 Advance.

The ? icon opens Help & Support: eight cards with their links (the MAX30102 forum board, this article, updates, donate, website, forum), and Copy diagnostics for a bug report. A links.ini next to the DLL can change the links.

Figure: Help & Support: every card shows its link; TEP MAX30102 Advance v1.1, build 2026-10-08, TEPMAX30102.DLL.

MAX30102 Heart-Rate Monitor with Arduino in Proteus

Open MAX30102-ArduinoUnoV3.pdsprj from the Proteus Simulation folder, with MAX30102_Demo.hex and both DLLs beside it. Its two Arduino UNOs from our Arduino Library for Proteus V3.0 run the same HEX file:

  • NODE 1 - SIMPLE: UNO 1 (ARD1) with the MAX30102 Simple (U1), the heartbeat LED D1 on A0 and Serial Monitor U3.
  • NODE 2 - ADVANCE: UNO 2 (ARD2) with the MAX30102 Advance (U2), the heartbeat LED D2 on A0 and Serial Monitor U4.

Both sensors sit at 0x57; each UNO has its own I2C bus, so that is no conflict.

Figure: The whole circuit running, without the pop-up windows: U1 at REST 60 with its heartbeat LED D1 caught lit, U2 at 72 BPM.

The heartbeat LED lights for 100 ms at every beat in the samples, so a screenshot catches it only now and then.

Wiring

Demo wiring (the same on both UNOs, as in the v1.0 demo)
FromToWhy
MAX30102 VIN / GNDSupply / groundPower (the PWR LED lights)
MAX30102 SCL / SDAArduino A5 / A4I2C clock and data
MAX30102 INTNot connectedOptional: active low, it drives the INT LED and can go to D2 for attachInterrupt
LED + 220 ΩA0 to GNDThe heartbeat LED
Serial Monitor RXD / TXD / GNDArduino D1 / D0 / groundShows what the Arduino prints, sends what you type
Serial Monitor RXD2Not connectedI2C has no serial line to watch

A4 and A5 are the UNO's I2C pins (see our Introduction to Arduino UNO). The Wire library turns on the UNO's internal pull-ups, so the simulation needs no external resistors.

The Arduino Code

The sketch uses the unmodified SparkFun MAX3010x library 1.1.2, like v1.0; its class MAX30105 handles the MAX30102, too:

#include <Wire.h>
#include <MAX30105.h>
#include <math.h>
MAX30105 sensor; // SparkFun's MAX3010x driver also supports the two-LED MAX30102.
uint16_t infrared[100],red[100];

The two arrays hold one window of 100 IR and 100 red samples as 16-bit values, 400 bytes. New in v1.1: the LED pin, the sample rate and the LED current:

const uint8_t LED_PIN=A0;
const float SAMPLES_PER_S=25;      // 100 Hz / 4 (setup below)
float ledMa=12.0;                  // powerLevel 60 x 0.2 mA

In setup(), begin() checks the chip, and setup(60,4,2,100,411,4096) sets LED power 60 (12 mA), averaging 4, LED mode 2 (red + IR), 100 samples per second, a 411 us pulse and the 4096 nA range. Then come the two v1.0 lines and the new v1.1 line:

  Serial.begin(9600); Wire.begin(); Wire.setWireTimeout(25000,true);
  pinMode(LED_PIN,OUTPUT); digitalWrite(LED_PIN,LOW);
  ready=sensor.begin(Wire,I2C_SPEED_STANDARD);
  if(!ready) { Serial.println(F("MAX30102 not found. Check VIN, GND, SDA and SCL.")); return; }
  sensor.setup(60,4,2,100,411,4096);
  Serial.println(F("TEP MAX30102 - simulated red/IR pulse waveform."));
  Serial.println(F("First estimate appears after about four seconds."));
  Serial.println(F("TEP MAX30102 Demo v1.1 - type HELP in the Serial Monitor"));

The loop calls check(), which moves new samples from the chip's FIFO into the library, and takes them out pair by pair. Under 5000 means no finger; over 65535 does not fit the 16-bit arrays:

  sensor.check();
  while(sensor.available()) {
    uint32_t ir=sensor.getFIFOIR(),r=sensor.getFIFORed(); sensor.nextSample();
    // Also skips the driver's initial empty software-ring entry.
    if(ir<5000 || r<5000) {
      used=0; finger=false;
      if(millis()-lastNotice>1000) { Serial.println(F("No finger / low optical signal.")); lastNotice=millis(); }
      continue;
    }
    finger=true;
    if(ir>65535 || r>65535) { used=0; Serial.println(F("Reduce LED power for this UNO demo.")); continue; }

Then the v1.1 parts: RAW ON prints the pair, and the heartbeat LED lights when the IR climbs back through the middle of the last window. Every 100 samples (4 s), reportWindow() prints the estimates:

    if(raw) { Serial.print(ir); Serial.print(','); Serial.println(r); }
    if(beatThreshold && previousIr<beatThreshold && ir>=beatThreshold) { beatOn=true; beatAt=millis(); digitalWrite(LED_PIN,HIGH); }   // a beat
    previousIr=(uint16_t)ir;
    infrared[used]=(uint16_t)ir; red[used]=(uint16_t)r; ++used;
    if(used==100) { reportWindow(); used=0; }
  }
  if(beatOn && (millis()-beatAt>=100 || !finger)) { beatOn=false; digitalWrite(LED_PIN,LOW); }
  delay(5);

How the Demo Counts the Pulse

reportWindow() puts a threshold halfway between the lowest and the highest IR sample of the window and counts the upward crossings, the moments the IR climbs back after the dip of a beat. C crossings span C - 1 beats:

  uint16_t threshold=irMin+(irMax-irMin)/2;
  beatThreshold=threshold;
  int first=-1,last=-1,crossings=0;
  for(uint8_t i=1;i<100;++i) if(infrared[i-1]<threshold && infrared[i]>=threshold) {
    if(first<0) first=i; last=i; ++crossings;
  }
  if(crossings<2) { Serial.println(F("Collecting pulse cycles...")); return; }
  float bpm=60.0f*SAMPLES_PER_S*(crossings-1)/(last-first);   // = 1500 (crossings-1) / (last-first), as v1.0

So BPM = 60 x 25 x (C - 1) / D, with D the samples between the first and the last crossing (the 1500 of v1.0). At 72 BPM, a beat is 20.8 samples long, so four beats span 83 or 84 whole samples: 1500 x 4 / 83 = 72.3 BPM and 1500 x 4 / 84 = 71.4 BPM. That is why the estimate moves between these two. And because the v1.1 wave climbs back only once per beat, there is one crossing per heartbeat.

How the Demo Computes the SpO2

  float ratio=((float)(rMax-rMin)/rSum)/((float)(irMax-irMin)/irSum);
  // Approximation documented by Maxim's reference SpO2 algorithm.
  float oxygen=-45.060f*ratio*ratio+30.354f*ratio+94.845f;
  oxygen=constrain(oxygen,0.0f,100.0f);

Max minus min is the AC part of each channel, and the sum stands for its DC part (both have 100 samples, so the count cancels). The demo does not call Maxim's complete reference algorithm (more on that below).

The New Commands

HELP prints one line, short enough for the Serial Monitor's Simple interface:

void help() {
  Serial.println(F("Commands: HELP, STATUS, LED mA (0..50), TEMP, RAW ON|OFF"));
}

LED sets both currents in 0.2 mA steps and starts a new window, because the old samples had another brightness:

    ledMa=mA; const uint8_t level=(uint8_t)(mA*5+0.5);
    sensor.setPulseAmplitudeRed(level); sensor.setPulseAmplitudeIR(level); used=0;

STATUS answers in two lines, so each one fits the small window:

void status() {
  Serial.print(F("Status: SpO2 mode (red + IR), 100 Hz / 4 = 25 samples/s, LED ")); Serial.print(ledMa,1);
  Serial.println(F(" mA")); Serial.print(F("  finger ")); Serial.print(finger ? F("ON") : F("OFF"));

A new Run brings back the start values. To change the sketch, export a new HEX file and load it into both UNOs; see How to get the HEX file from Arduino. New to Arduino? Start with our Arduino Tutorial for Beginners.

Serial Monitor Commands

Commands of the demo sketch, upper or lower case (monitor quick buttons: STATUS, LED 10, TEMP, HELP)
CommandWhat it does
HELPThe command list (one line)
STATUSTwo lines: the mode, the samples per second and the LED current; the finger and the last estimate
LED mAThe red and IR LED current, 0 to 50 mA (start: 12 mA); above about 14 mA the samples overflow the demo's 16-bit buffer
TEMPThe die temperature now
RAW ON | OFFPrint every sample as IR,RED (25 lines per second), for example for a plotter

How to Run the Demo

  1. Press Run. Both Serial Monitors (9600 baud) and U2's pop-up open.
  2. After the first estimate (about 4 s), click RUN 150, LOW SPO2, FINGER OFF and FINGER ON on U1's panel.
  3. Click HELP, STATUS and TEMP on U3, and type RAW ON / RAW OFF. Try LED 10, LED 20 ("Reduce LED power for this UNO demo.") and LED 12.
  4. On U2, try the Sensor page and the four tools; close its window and reopen it with OPEN PANEL.

MAX30102 Proteus Simulation Results

The Serial Monitor in Its Simple Interface

Both monitors in this demo run in the Simple interface of our TEP Serial Monitor, as in every monitor screenshot here. Only the log, the send box and one slim toolbar row stay visible (baud rate, follow, pause, search and clear), so the window stays small.

  • Switch to it: right-click the log and choose Simple interface, or press Ctrl+Shift+U.
  • Back to the full view: click the corner button (the expand arrows at the top right), press Esc or Ctrl+Shift+U, or right-click and choose Full interface.
  • Your choice is remembered.

Type commands such as STATUS or LED 10 in the send box and press Enter; the quick buttons (STATUS, LED 10, TEMP, HELP) come back with the full view. Every line the sketch prints is 80 characters or less, so it fits this small window. Read all about the monitor in TEP Serial Monitor Advance for Proteus.

Start-Up and RUN 150

After Run, U3 prints the two v1.0 lines and the new v1.1 line, then an estimate every 100 samples (4 s):

Figure: U3 in the Simple interface: the three start lines, 72.3 / 72.3 / 71.4 BPM at 98.0 %, then RUN 150: "150.0 BPM | SpO2 estimate: 95.0 %".
TEP MAX30102 - simulated red/IR pulse waveform.
First estimate appears after about four seconds.
TEP MAX30102 Demo v1.1 - type HELP in the Serial Monitor
Pulse estimate: 72.3 BPM | SpO2 estimate: 98.0 % | Die temp: 25.0 C
Pulse estimate: 72.3 BPM | SpO2 estimate: 98.0 % | Die temp: 25.0 C
Pulse estimate: 71.4 BPM | SpO2 estimate: 98.0 % | Die temp: 25.0 C
Pulse estimate: 150.0 BPM | SpO2 estimate: 95.0 % | Die temp: 25.0 C

These are the v1.0 numbers: 72.3 and 71.4 BPM, 98.0 % and 25.0 C. Then I clicked RUN 150 on U1, and the first window with only RUN 150 samples printed 150.0 BPM and 95.0 %. A window with samples from before and after a click gives a value in between, so read the next one.

LOW SPO2 and No Finger

Figure: U3: a mixed window (122.1 BPM, 84.8 %) after the preset change, then FINGER OFF: "No finger / low optical signal." about once a second.
Pulse estimate: 150.0 BPM | SpO2 estimate: 95.0 % | Die temp: 25.0 C
Pulse estimate: 122.1 BPM | SpO2 estimate: 84.8 % | Die temp: 25.0 C
No finger / low optical signal.
No finger / low optical signal.
No finger / low optical signal.

Next, LOW SPO2 and FINGER OFF on U1. The line with 122.1 BPM and 84.8 % is such a mixed window. Then the samples drop under 5000: the sketch drops its half-full window and prints "No finger / low optical signal." about once a second.

HELP, STATUS and TEMP

Figure: U3: back to 60.0 BPM and 98.0 %, then HELP, the two-line STATUS and TEMP; TX = what you sent, RX = what the sketch printed.
Pulse estimate: 66.2 BPM | SpO2 estimate: 85.4 % | Die temp: 25.0 C
Pulse estimate: 60.0 BPM | SpO2 estimate: 98.0 % | Die temp: 25.0 C
HELP
Commands: HELP, STATUS, LED mA (0..50), TEMP, RAW ON|OFF
STATUS
Status: SpO2 mode (red + IR), 100 Hz / 4 = 25 samples/s, LED 12.0 mA
  finger ON, last estimate 60.0 BPM 98.0 %
TEMP
Die temp: 25.0 C

With the finger back, the estimates return: 66.2 BPM from another mixed window, then 60.0 BPM and 98.0 %, the values of REST 60. Once you send a command, the log marks your lines TX and the sketch's lines RX. STATUS gives the mode, 25 samples per second and 12.0 mA, then, indented, the finger and the last estimate. TEMP reads the die temperature now: 25.0 C.

RAW ON: Every Sample

Figure: U3 with RAW ON: every sample as IR,RED, 25 lines per second; the estimates still come in between.
50121,50065
50307,50164
51123,50602
52295,51229
53348,51793
54029,52158
54376,52344
54539,52431
54629,52479
54696,52515
54753,52546
54806,52574

Type RAW ON: the sketch answers "Raw samples: ON (IR,RED per sample)" and prints every pair, 25 lines per second, with the estimates in between. Here, the IR climbs from 50121 to 54806 in twelve samples (0.48 s): the end of a beat, when more light comes back. Feed the numbers to a plotter, and you see the dips of the raw wave. RAW OFF stops it.

How Accurate Are the Pulse and SpO2 Estimates?

We tested the demo's estimate on a PC with the real sketch and library, from 40 to 180 BPM at SpO2 98, 92, 88 and 85 %, also at half the LED current: always within 2 BPM and 1 % (worst 1.4 BPM and 0.1 %).

Many makers try Maxim's reference algorithm next; the SparkFun library ships it as spo2_algorithm, and its Example8_SPO2 runs it. It works on our model too, but it measures the distance between the beats in whole samples, and at 25 samples per second one sample is a big step: 120 BPM (12.5 samples a beat) reads 125 (1500 / 12), 90 BPM reads 88 or 93, and 180 BPM reads 187. In our tests, it stayed within that resolution, apart from one quirk of its own.

Its last 4 samples are not averaged, so now and then it reports a "valley" at sample 95 to 99 where the IR has none, and that window's BPM comes out high (75 BPM reads 88); the next windows are right again. That is the algorithm, not the model: it does the same with a real MAX30102.

Troubleshooting

  • "MAX30102 not found. Check VIN, GND, SDA and SCL.": check VIN / GND (the PWR LED must be lit), SDA on A4 and SCL on A5; then restart the simulation.
  • "No finger / low optical signal.": FINGER is 0 (click FINGER ON), or the LED current is very low.
  • "Reduce LED power for this UNO demo.": the samples are above 65535, too big for the demo's 16-bit arrays. Type LED 12 or lower.
  • "Collecting pulse cycles..." or "No usable pulse waveform.": the pulse is too slow for one 4 s window, or there is no pulse in the samples.
  • The samples stop after your own sketch was busy for more than about 1.3 s: the FIFO is full and its pointers are equal (banner: FIFO FULL). Keep check() frequent or call clearFIFO().
  • The part is not simulated, no panel or no monitor window: TEPMAX30102.DLL or TEPSERIALMON.DLL is missing from MODELS and the project folder; click OPEN MONITOR on a closed monitor.
  • The Advance pop-up does not appear: click OPEN PANEL on the board, or turn "Open the panel at Run" back on in Settings.
  • No red X, or the board looks like v1.0: the design still holds the v1.0 symbol; replace TEPMAX30102.LIB, delete TEPMAX30102.IDX and re-pick the part.
  • Your own sketch uses SoftwareSerial to receive: Proteus 8.5 cannot receive with SoftwareSerial; use the hardware Serial, as the demo does, or AltSoftSerial.

Things to Know Before Using a Real MAX30102 Module

The demo sketch uses only Wire and the SparkFun library, so it runs on a real UNO and GY-MAX30102, too. Keep in mind:

  • Supply: the purple GY-MAX30102 has its own 1.8 V and 3.3 V regulators, so feed VIN with 3.3 V or 5 V from the Arduino.
  • Pull-ups: the board has 4.7 k pull-ups on SDA and SCL, on many purple boards to 1.8 V. If a 5 V UNO cannot read the sensor, add 4.7 k pull-ups from SDA / SCL to 3.3 V, or use a level shifter; 3.3 V boards such as the ESP32 are easier.
  • Address: 0x57, fixed; there is no address pin.
  • INT: active low, open drain.
  • A real finger gives a much smaller pulse, about 1 % of the light instead of our 10 %, with noise and motion artefacts. Keep the finger still and shield it from ambient light.
  • A proper algorithm: the demo's window maths is an educational estimate for the clean model wave; for a real finger, use SparkFun's heartRate.h or Maxim's spo2_algorithm.
  • Not a medical device: the SpO2 of a hobby sensor is not a medical measurement. Never use it for health decisions.

Limitations of the Simulation

  • Not modelled: the real optics (the pulse wave is a model with 10 % modulation), ambient light and its cancellation (ALC), the proximity mode, noise, motion artefacts and the sample-rate / pulse-width limits of the real chip.
  • The model is our own implementation, written from the MAX30102 data sheet (register map, FIFO, timing); it contains no Maxim / Analog Devices code.
  • Licences: MIT for the SparkFun MAX3010x library 1.1.2; its spo2_algorithm keeps Maxim Integrated's own permission notice. The HEX file also contains the Arduino AVR core 1.8.6, which includes LGPL-licensed code; its source code is on GitHub.
  • Tested in Proteus 8.5 on 8 Oct 2026 (the demo, both panels, the four tools, a v1.0 design and every screenshot here, taken with the final model) and with 1,913 automatic PC checks, all passed, 39 of them with the real demo sketch and the unmodified SparkFun library on two UNOs; the unchanged v1.0 test passed too. Proteus 7 is not supported.

More sensors: our MPU6050 Sensor Library for Proteus for motion, VL53L0X Sensor Library for Proteus for distance, TCS34725 Sensor Library for Proteus for colour, BME280 Sensor Library for Proteus or BMP280 Sensor Library for Proteus for the weather and BH1750 Light Sensor Library for Proteus for light. To send your pulse readings to another Arduino by radio, add our HC-12 Library for Proteus.

So, that was all about the MAX30102 Sensor Library for Proteus V1.1. I hope the realistic pulse wave, the live pulse view, the FIFO and the four test tools make the MAX30102 much easier to understand, so your heart-rate project works the first time you put a finger on a real sensor. If you use the MAX30102 Sensor Library for Proteus in a project, please share your feedback in the comments or in our forum, and if you have any questions, ask in the comments and I will help you out. Till the next tutorial, take care and have fun!