The MAX30102 shines red and infrared light into a fingertip and measures what comes back; the IR signal pulses with each heartbeat. The SparkFun library’s beat detector turns that into BPM.
On the S3-DevKitC-1 wire it to 3V3 and the default I2C pins GPIO8 (SDA) and GPIO9 (SCL). Open the Serial Monitor on the UART port and rest a fingertip on the sensor.
Wiring
| ESP32-S3-DevKitC-1 | MAX30102 pulse oximeter module | Wire | Notes |
|---|---|---|---|
| 3V3 | VIN | red | |
| GND | GND | black | |
| 8 | SDA | blue | |
| 9 | SCL | yellow |
3.3–5 V (module regulators) part, 3.3 V board. Many purple modules pull SDA/SCL up to 1.8 V. That usually still reads on 3.3 V boards, but some don't — if the scanner can't find 0x57, use a module whose pull-ups go to 3.3 V.
Board quirks
- Many purple MAX30102 boards pull SDA/SCL up to 1.8 V. The S3 usually still reads them, but if the sketch can’t find 0x57, use a module whose pull-ups go to 3.3 V.
- GPIO8/9 are the S3’s default I2C pins. The sketch uses 400 kHz (I2C_SPEED_FAST) to keep up with the sensor’s sample rate.
- Rest the fingertip lightly and keep still: pressing hard or moving ruins the reading. Bright room light leaking in also adds noise.
- This shows how optical pulse sensing works. It is not a medical device — don’t use it for health decisions.
Code
// MAX30102 pulse sensor on an ESP32-S3-DevKitC-1 — heart rate (BPM) from the IR signal.
// VIN → 3V3, GND → GND, SDA → GPIO8, SCL → GPIO9 (S3 default I2C). INT unused. Address 0x57.
// For learning only — not a medical device.
#include <Wire.h>
#include <MAX30105.h>
#include <heartRate.h>
MAX30105 sensor;
constexpr uint8_t RATE_SIZE = 4;
uint8_t rates[RATE_SIZE];
uint8_t rateSpot = 0;
uint32_t lastBeat = 0;
float bpm = 0;
int avgBpm = 0;
void setup() {
Serial.begin(115200);
Wire.begin(8, 9);
if (!sensor.begin(Wire, I2C_SPEED_FAST)) {
Serial.println("MAX30102 not found at 0x57 - check wiring (some modules pull I2C up to 1.8 V)");
while (true) delay(1000);
}
sensor.setup(); // library defaults: 400 Hz sample rate, 411 us pulse width
sensor.setPulseAmplitudeRed(0x0A); // red LED low: we only use IR here
sensor.setPulseAmplitudeGreen(0);
Serial.println("Place your fingertip on the sensor and keep still");
}
void loop() {
long ir = sensor.getIR();
if (checkForBeat(ir)) {
uint32_t delta = millis() - lastBeat;
lastBeat = millis();
bpm = 60.0f / (delta / 1000.0f);
if (bpm > 20 && bpm < 255) {
rates[rateSpot++] = (uint8_t)bpm;
rateSpot %= RATE_SIZE;
int sum = 0;
for (uint8_t i = 0; i < RATE_SIZE; i++) sum += rates[i];
avgBpm = sum / RATE_SIZE;
}
}
static uint32_t lastPrint = 0;
if (millis() - lastPrint > 1000) {
lastPrint = millis();
if (ir < 50000) Serial.println("No finger?");
else Serial.printf("IR %ld BPM %.0f Avg BPM %d\n", ir, bpm, avgBpm);
}
}// MAX30102 pulse sensor on an ESP32-S3-DevKitC-1 — heart rate (BPM) from the IR signal.
// VIN → 3V3, GND → GND, SDA → GPIO8, SCL → GPIO9 (S3 default I2C). INT unused. Address 0x57.
// For learning only — not a medical device.
#include <Wire.h>
#include <MAX30105.h>
#include <heartRate.h>
MAX30105 sensor;
constexpr uint8_t RATE_SIZE = 4;
uint8_t rates[RATE_SIZE];
uint8_t rateSpot = 0;
uint32_t lastBeat = 0;
float bpm = 0;
int avgBpm = 0;
void setup() {
Serial.begin(115200);
Wire.begin(8, 9);
if (!sensor.begin(Wire, I2C_SPEED_FAST)) {
Serial.println("MAX30102 not found at 0x57 - check wiring (some modules pull I2C up to 1.8 V)");
while (true) delay(1000);
}
sensor.setup(); // library defaults: 400 Hz sample rate, 411 us pulse width
sensor.setPulseAmplitudeRed(0x0A); // red LED low: we only use IR here
sensor.setPulseAmplitudeGreen(0);
Serial.println("Place your fingertip on the sensor and keep still");
}
void loop() {
long ir = sensor.getIR();
if (checkForBeat(ir)) {
uint32_t delta = millis() - lastBeat;
lastBeat = millis();
bpm = 60.0f / (delta / 1000.0f);
if (bpm > 20 && bpm < 255) {
rates[rateSpot++] = (uint8_t)bpm;
rateSpot %= RATE_SIZE;
int sum = 0;
for (uint8_t i = 0; i < RATE_SIZE; i++) sum += rates[i];
avgBpm = sum / RATE_SIZE;
}
}
static uint32_t lastPrint = 0;
if (millis() - lastPrint > 1000) {
lastPrint = millis();
if (ir < 50000) Serial.println("No finger?");
else Serial.printf("IR %ld BPM %.0f Avg BPM %d\n", ir, bpm, avgBpm);
}
}Library: SparkFun MAX3010x Pulse and Proximity Sensor Library (SparkFun Electronics) — compiled with SparkFun MAX3010x Pulse and Proximity Sensor Library 1.1.2.
Expected serial output
Place your fingertip on the sensor and keep still
No finger?
IR 102344 BPM 74 Avg BPM 71
IR 102877 BPM 69 Avg BPM 72Place your fingertip on the sensor and keep still No finger? IR 102344 BPM 74 Avg BPM 71 IR 102877 BPM 69 Avg BPM 72
Common errors
“MAX30102 not found at 0x57”
Wiring, or the 1.8 V pull-up issue above. Run an I2C scan.
BPM jumps between 40 and 200
Movement or pressure. Rest the finger with tape, and wait for the 4-beat average to settle.