In diesem Tutorial zeige ich dir Schritt für Schritt, wie du den MAX30102 Sensor anschließt, programmierst und die Messwerte visualisierst.
Den MAX30100 habe ich bereits im Beitrag „MAX30100 am Arduino: Blutsauerstoff und Herzfrequenz messen leicht gemacht“ ausführlich vorgestellt. In diesem Beitrag gehen wir einen Schritt weiter und schauen uns den Nachfolger, den MAX30102, genauer an.
Du lernst dabei nicht nur, wie du die Werte für Herzfrequenz und Blutsauerstoff ausliest, sondern auch, wie du diese einfach und anschaulich darstellen kannst.
Außerdem werfen wir einen Blick darauf, welche Verbesserungen der MAX30102 gegenüber dem MAX30100 bietet und wie genau die gemessenen Werte in der Praxis sind.
Hinweis: Die Idee, mir den MAX30102 genauer anzuschauen, kam übrigens aus einem Kommentar unter meinem Beitrag zum MAX30100. Dort wurde ich gefragt, wie sich der neuere Sensor auslesen lässt.
Ein wichtiger Punkt dabei:
Viele Bibliotheken, die für den MAX30100 gedacht sind, sind nicht ohne Weiteres mit dem MAX30102 kompatibel. Deshalb muss man zunächst eine passende Library finden und den Code entsprechend anpassen.
Solltest auch du Fragen, Probleme oder konkrete Wünsche zu einem Projekt haben, kannst du dich jederzeit gerne per E-Mail oder über mein Ticketsystem bei mir melden.




Was ist der MAX30102?
Der MAX30102 ist ein kompakter Sensor zur Messung der Herzfrequenz (Puls) und der Sauerstoffsättigung im Blut (SpO2). Solche Sensoren kommen häufig in Fitness-Trackern oder Smartwatches zum Einsatz und eignen sich auch hervorragend für eigene Mikrocontroller-Projekte mit Arduino oder ESP32.
Die Messung basiert auf dem Prinzip der sogenannten Pulsoximetrie. Dabei wird ausgenutzt, dass sauerstoffreiches und sauerstoffarmes Blut Licht unterschiedlich stark absorbieren.
Im Detail funktioniert das so:
Der Sensor verfügt über eine rote und eine infrarote LED sowie eine Photodiode. Diese LEDs senden abwechselnd Licht durch das Gewebe (z. B. deinen Finger). Ein Teil des Lichts wird vom Blut absorbiert, während der Rest von der Photodiode erfasst wird.
👉 Der entscheidende Punkt:
- Sauerstoffreiches Blut absorbiert mehr Infrarotlicht
- Sauerstoffarmes Blut absorbiert mehr rotes Licht
Aus dem Verhältnis dieser beiden Messungen kann der Sensor die Sauerstoffsättigung (SpO2) berechnen.
Zusätzlich nutzt der MAX30102 die kleinen Schwankungen im Blutfluss, die durch den Herzschlag entstehen. Dadurch lässt sich gleichzeitig die Herzfrequenz (BPM) bestimmen.
Technische Daten des MAX30102 Moduls
Bevor wir mit der Programmierung starten, werfen wir einen kurzen Blick auf die wichtigsten technischen Daten des MAX30102. Diese geben dir ein gutes Gefühl dafür, was der Sensor leisten kann und worauf du beim Einsatz achten solltest.
| Eigenschaft | Wert |
|---|---|
| Versorgungsspannung | 3.3 – 5 Volt |
| Stromaufnahme | max. 20 mA (kontinuierlich) |
| ADC Auflösung | 14 Bit |
| Schnittstelle | I2C |
| Logiklevel | 3.3V |
| I2C Taktfrequenz | 0 – 400 kHz |
| I2C Adresse (Write) | 0xAE |
| I2C Adresse (Read) | 0xAF |
| IR LED Wellenlänge | 870 – 900 nm (Peak: 880 nm) |
| Red LED Wellenlänge | 650 – 670 nm (Peak: 660 nm) |
| Temperatursensor Genauigkeit | ±1 °C bei 25 °C |
| Temperaturbereich | -40 °C bis +85 °C |
| Temperatur Messdauer | ca. 29 ms |
| Abmessungen | 14 x 14 mm |
Preis & Beschaffung
Den MAX30102 Sensor gibt es in unterschiedlichen Ausführungen. Häufig findet man Module mit der Aufschrift „MAX3010x“, die sowohl für den MAX30100 als auch den MAX30102 beworben werden.
Mit genau so einem Modul habe ich ebenfalls gestartet – allerdings ohne Erfolg. Der Sensor ließ sich nicht zuverlässig betreiben. Bei der Recherche in verschiedenen Foren bin ich auf ähnliche Erfahrungen gestoßen: Offenbar gibt es Varianten, bei denen die Beschaltung bzw. Spannungsversorgung nicht sauber umgesetzt ist.
Aus diesem Grund bin ich auf ein Modul mit der eindeutigen Bezeichnung MAX30102 gewechselt. Dieses habe ich bei https://www.androegg.de/shop/max30100-max30102-herzfrequenz-haemoglobin-sensor/
bezogen – und hier funktionierte der Sensor direkt ohne Probleme.
Preislich liegt das Modul bei rund 5 €. Damit ist es zwar kein absoluter „China-Billig-Sensor“, aber für Experimente und eigene Projekte definitiv eine solide Wahl.
Anschluss des MAX30102 am Arduino Nano
Der Anschluss des MAX30102 ist denkbar einfach, da der Sensor über die I2C-Schnittstelle kommuniziert.
Folgende Pins werden benötigt:
| MAX30102 | Arduino Nano |
|---|---|
| VIN | 3.3V |
| GND | GND |
| SDA | A4 |
| SCL | A5 |
| INT | optional |
Benötige Bauteile für den Aufbau der Schaltung
Im Grunde benötigst du für den Anschluss an einen Mikrocontroller lediglich:
- einen Mikrocontroller (zbsp. Arduino Nano, Arduino UNO),
- vier Breadboardkabel, männlich-männlich, 10cm
- ein 400 Pin Breadboard
Im späteren verlauf zeige ich dir noch wie man die Werte visualisiert, hier verwende ich dann:
- eine 8×8 LED Punkt Matrix,
- fünf Breadboardkabel, männlich-weiblich, 10cm, sowie
- einen Buzzer für den typischen Ton bei einem Herzschlag
Hinweis von mir: Die mit einem Sternchen (*) markierten Links sind Affiliate-Links. Wenn du über diese Links einkaufst, erhalte ich eine kleine Provision, die dazu beiträgt, diesen Blog zu unterstützen. Der Preis für dich bleibt dabei unverändert. Vielen Dank für deine Unterstützung!
Benötigte Bibliothek zum auslesen des MAX30102 Sensors in der Arduino IDE
Für den MAX30102 Sensor gibt es diverse Bibliotheken, am zuverlässigsten hat bei mir die MAX3010x-Sensor-Library von Daniel Weise funktioniert welche du entweder über den internen Bibliotheksverwalter der Arduino IDE installieren, oder vom GitHub Repository devxplained/MAX3010x-Sensor-Library als ZIP-Datei laden kannst.
Beispiel – visualisieren des Herzschlags sowie des Blutsauerstoffs im seriellen Plotter der Arduino IDE
Um die beiden Werte für Herzschlag und Blutsauerstoff im seriellen Plotter anzeigen zu lassen müssen wir die Werte für den seriellen Monitor leicht anpassen denn wir möchten zwei Linien im Plotter anzeigen lassen was ein definiertes Format benötigt.
Vorher:
Serial.print("Heart Rate:");
Serial.println(average_bpm);
Serial.print("SpO2: ");
Serial.println(average_spo2);
Nachher:
Serial.print("HeartRate:");
Serial.println(average_bpm);
Serial.print("SpO2:");
Serial.println(average_spo2);
Zusätzlich müssen wir noch im Setup die If-Bedingung anpassen damit die Ausgabe „Sensor initialized“ nicht mehr erscheint, denn diese würde als zusätzliche Linie (jedoch ohne Werte) erscheinen.
if (!sensor.begin() && !sensor.setSamplingRate(kSamplingRate)) {
Serial.println("Sensor not found");
while (1)
;
}
angepasster Quellcode für das visualisieren der Werte im seriellen Plotter
#include <MAX3010x.h>
#include "filters.h"
#include "MATRIX7219.h"
// Sensor (adjust to your sensor type)
MAX30105 sensor;
const auto kSamplingRate = sensor.SAMPLING_RATE_400SPS;
const float kSamplingFrequency = 400.0;
// Finger Detection Threshold and Cooldown
const unsigned long kFingerThreshold = 10000;
const unsigned int kFingerCooldownMs = 500;
// Edge Detection Threshold (decrease for MAX30100)
const float kEdgeThreshold = -2000.0;
// Filters
const float kLowPassCutoff = 5.0;
const float kHighPassCutoff = 0.5;
// Averaging
const bool kEnableAveraging = false;
const int kAveragingSamples = 5;
const int kSampleThreshold = 5;
void setup() {
Serial.begin(9600);
if (!sensor.begin() && !sensor.setSamplingRate(kSamplingRate)) {
Serial.println("Sensor not found");
while (1)
;
}
}
// Filter Instances
LowPassFilter low_pass_filter_red(kLowPassCutoff, kSamplingFrequency);
LowPassFilter low_pass_filter_ir(kLowPassCutoff, kSamplingFrequency);
HighPassFilter high_pass_filter(kHighPassCutoff, kSamplingFrequency);
Differentiator differentiator(kSamplingFrequency);
MovingAverageFilter<kAveragingSamples> averager_bpm;
MovingAverageFilter<kAveragingSamples> averager_r;
MovingAverageFilter<kAveragingSamples> averager_spo2;
// Statistic for pulse oximetry
MinMaxAvgStatistic stat_red;
MinMaxAvgStatistic stat_ir;
// R value to SpO2 calibration factors
// See https://www.maximintegrated.com/en/design/technical-documents/app-notes/6/6845.html
float kSpO2_A = 1.5958422;
float kSpO2_B = -34.6596622;
float kSpO2_C = 112.6898759;
// Timestamp of the last heartbeat
long last_heartbeat = 0;
// Timestamp for finger detection
long finger_timestamp = 0;
bool finger_detected = false;
// Last diff to detect zero crossing
float last_diff = NAN;
bool crossed = false;
long crossed_time = 0;
void loop() {
auto sample = sensor.readSample(1000);
float current_value_red = sample.red;
float current_value_ir = sample.ir;
// Detect Finger using raw sensor value
if (sample.red > kFingerThreshold) {
if (millis() - finger_timestamp > kFingerCooldownMs) {
finger_detected = true;
}
} else {
// Reset values if the finger is removed
differentiator.reset();
averager_bpm.reset();
averager_r.reset();
averager_spo2.reset();
low_pass_filter_red.reset();
low_pass_filter_ir.reset();
high_pass_filter.reset();
stat_red.reset();
stat_ir.reset();
finger_detected = false;
finger_timestamp = millis();
}
if (finger_detected) {
current_value_red = low_pass_filter_red.process(current_value_red);
current_value_ir = low_pass_filter_ir.process(current_value_ir);
// Statistics for pulse oximetry
stat_red.process(current_value_red);
stat_ir.process(current_value_ir);
// Heart beat detection using value for red LED
float current_value = high_pass_filter.process(current_value_red);
float current_diff = differentiator.process(current_value);
// Valid values?
if (!isnan(current_diff) && !isnan(last_diff)) {
// Detect Heartbeat - Zero-Crossing
if (last_diff > 0 && current_diff < 0) {
crossed = true;
crossed_time = millis();
}
if (current_diff > 0) {
crossed = false;
}
// Detect Heartbeat - Falling Edge Threshold
if (crossed && current_diff < kEdgeThreshold) {
if (last_heartbeat != 0 && crossed_time - last_heartbeat > 300) {
// Show Results
int bpm = 60000 / (crossed_time - last_heartbeat);
float rred = (stat_red.maximum() - stat_red.minimum()) / stat_red.average();
float rir = (stat_ir.maximum() - stat_ir.minimum()) / stat_ir.average();
float r = rred / rir;
float spo2 = kSpO2_A * r * r + kSpO2_B * r + kSpO2_C;
if (bpm > 50 && bpm < 250) {
// Average?
if (kEnableAveraging) {
int average_bpm = averager_bpm.process(bpm);
int average_r = averager_r.process(r);
int average_spo2 = averager_spo2.process(spo2);
// Show if enough samples have been collected
if (averager_bpm.count() >= kSampleThreshold) {
Serial.print("HeartRate:");
Serial.println(average_bpm);
Serial.print("SpO2:");
Serial.println(average_spo2);
}
} else {
Serial.print("HeartRate:");
Serial.println(bpm);
Serial.print("SpO2:");
Serial.println(spo2);
}
}
// Reset statistic
stat_red.reset();
stat_ir.reset();
}
crossed = false;
last_heartbeat = crossed_time;
}
}
last_diff = current_diff;
}
}
Der Quellcode stammt von https://github.com/devxplained/MAX3010x-Sensor-Library/tree/main/examples/MAX30105PulseoximeterHeartrate ich habe lediglich kleine Anpassungen getätigt!
Praxisbeispiel: Herzschlag sichtbar machen
In diesem Praxisbeispiel nutze ich den MAX30102 nicht nur zur Messung, sondern visualisiere den Herzschlag direkt über eine LED Matrix und einen Buzzer.


Die 8×8 LED Punkt Matrix mit MAX7219 Treiberchip habe ich bereits in diversen Beiträgen verwendet und dir gezeigt wie darauf Texte & Symbole angezeigt werden.
Quellcode für die Arduino IDE
#define DIN 8 #define CS 9 #define CLK 10 uint8_t count = 1; MATRIX7219 mx(DIN, CS, CLK, count);
#include <MAX3010x.h>
#include "filters.h"
#include "MATRIX7219.h"
MAX30105 sensor;
// Sensor
const auto kSamplingRate = sensor.SAMPLING_RATE_400SPS;
const float kSamplingFrequency = 400.0;
// Finger detection
const unsigned long kFingerThreshold = 10000;
const unsigned int kFingerCooldownMs = 500;
// Beat detection
const float kLowPassCutoff = 5.0;
const float kHighPassCutoff = 0.5;
const float kEdgeThreshold = -2000.0;
// Dot Matrix
#define DIN 8
#define CS 9
#define CLK 10
uint8_t count = 1;
MATRIX7219 mx(DIN, CS, CLK, count);
// Buzzer
#define BUZZER 4
const int TON_PAUSE = 125;
const int FREQ = 1200;
const int PAUSE = 300;
// Filter
LowPassFilter low_pass_filter(kLowPassCutoff, kSamplingFrequency);
HighPassFilter high_pass_filter(kHighPassCutoff, kSamplingFrequency);
Differentiator differentiator(kSamplingFrequency);
// Status
long finger_timestamp = 0;
bool finger_detected = false;
long last_heartbeat = 0;
float last_diff = NAN;
bool crossed = false;
long crossed_time = 0;
void setup() {
Serial.begin(9600);
if (sensor.begin() && sensor.setSamplingRate(kSamplingRate)) {
Serial.println("Sensor initialized");
} else {
Serial.println("Sensor not found");
while (1)
;
}
mx.begin();
mx.clear();
mx.setBrightness(3);
drawHeartSmall();
pinMode(BUZZER, OUTPUT);
}
void loop() {
auto sample = sensor.readSample(1000);
float red = sample.red;
// Finger erkannt?
if (red > kFingerThreshold) {
if (millis() - finger_timestamp > kFingerCooldownMs) {
finger_detected = true;
}
} else {
// Reset wenn Finger weg
finger_detected = false;
finger_timestamp = millis();
low_pass_filter.reset();
high_pass_filter.reset();
differentiator.reset();
last_diff = NAN;
crossed = false;
last_heartbeat = 0;
drawHeartSmall();
return;
}
if (!finger_detected) {
return;
}
// Signal filtern
float filtered = low_pass_filter.process(red);
float heartbeat_signal = high_pass_filter.process(filtered);
float current_diff = differentiator.process(heartbeat_signal);
if (!isnan(current_diff) && !isnan(last_diff)) {
// Zero crossing
if (last_diff > 0 && current_diff < 0) {
crossed = true;
crossed_time = millis();
}
if (current_diff > 0) {
crossed = false;
}
// Beat erkannt
if (crossed && current_diff < kEdgeThreshold) {
if (last_heartbeat != 0 && crossed_time - last_heartbeat > 300) {
int bpm = 60000 / (crossed_time - last_heartbeat);
if (bpm > 50 && bpm < 250) {
Serial.print("Beat! BPM: ");
Serial.println(bpm);
pulseHeart();
}
}
crossed = false;
last_heartbeat = crossed_time;
}
}
last_diff = current_diff;
}
void pulseHeart() {
drawHeartBig();
tone(BUZZER, FREQ, TON_PAUSE);
delay(PAUSE);
drawHeartSmall();
delay(PAUSE);
}
void drawHeartSmall() {
mx.clear();
mx.setRow(3, 0b00100100, 1);
mx.setRow(4, 0b01111110, 1);
mx.setRow(5, 0b01111110, 1);
mx.setRow(6, 0b00111100, 1);
mx.setRow(7, 0b00011000, 1);
}
void drawHeartBig() {
mx.clear();
mx.setRow(1, 0b00000000, 1);
mx.setRow(2, 0b01100110, 1);
mx.setRow(3, 0b10011001, 1);
mx.setRow(4, 0b10000001, 1);
mx.setRow(5, 0b01000010, 1);
mx.setRow(6, 0b00100100, 1);
mx.setRow(7, 0b00011000, 1);
}
Problem ist hier damit der wechsel von dem großen zu dem kleinen Herz vernünftig angezeigt werden kann muss eine Pause eingelegt werden. Diese kleine pause von derzeit 300ms. sorgt jedoch dafür das nicht jeder Herzschlag korrekt erkannt wird. Man müsste hier quasi mit dem Wert der Pause „spielen“ um ein gutes Mittelmaß zu finden.
Fazit
Der MAX30102 ist ein spannender Sensor, mit dem sich Herzfrequenz und Blutsauerstoff relativ einfach messen lassen. Der Anschluss über I2C ist schnell erledigt und stellt auch für Einsteiger kein großes Hindernis dar.
Hat man einmal eine passende und funktionierende Bibliothek gefunden, ist der Rest ebenfalls gut beherrschbar. Besonders interessant wird das Ganze, wenn man die reinen Messwerte nicht nur im seriellen Monitor betrachtet, sondern direkt visualisiert – zum Beispiel im Plotter oder über eine LED-Matrix.
Gerade die Kombination aus Sensor, Visualisierung und kleinen Zusatzfeatures wie einem Buzzer macht aus einem einfachen Messaufbau schnell ein kleines, greifbares Projekt.
Ausblick
Im nächsten Beitrag gehen wir noch einen Schritt weiter: Statt eines klassischen Arduino verwende ich dann einen ESP32. Zusätzlich kommt ein größeres OLED-Display zum Einsatz, auf dem wir die Messwerte in Form eines kleinen Dashboards darstellen.
Der große Vorteil des ESP32 liegt dabei in seinen zusätzlichen Schnittstellen. Neben der reinen Messung können die Daten auch direkt über WiFi oder Bluetooth an andere Systeme übertragen werden – zum Beispiel an ein Smartphone, ein Dashboard im Netzwerk oder eine eigene IoT-Anwendung.
Damit wird aus dem einfachen Sensorprojekt schnell die Grundlage für ein vernetztes Gesundheits- oder Monitoring-System.
FAQ – Häufige Fragen zum MAX30102
Wie genau ist der MAX30102 Sensor?
Der MAX30102 liefert für DIY-Projekte und Experimente durchaus brauchbare Werte. Die Herzfrequenz (BPM) ist in der Regel relativ zuverlässig, während die Blutsauerstoffmessung (SpO2) stärker schwanken kann. Faktoren wie Fingerposition, Bewegung oder Umgebungslicht haben großen Einfluss auf die Genauigkeit.
Warum zeigt der MAX30102 falsche oder schwankende Werte?
Das ist ein häufiges Problem und liegt meist nicht am Sensor selbst. Typische Ursachen sind:
- Finger liegt nicht ruhig auf dem Sensor
- zu viel Umgebungslicht
- schlechter Kontakt zur Haut
- ungeeignete oder falsch konfigurierte Bibliothek
👉 Tipp: Finger ruhig auflegen und leicht andrücken, ohne den Sensor komplett abzudecken.
Welche I2C-Adresse hat der MAX30102?
Der MAX30102 verwendet standardmäßig die I2C-Adresse: 0x57
Funktioniert der MAX30102 auch mit einem Arduino Nano oder UNO?
Ja, der MAX30102 lässt sich problemlos mit klassischen Arduino-Boards wie dem Arduino Nano oder Arduino UNO betreiben. Wichtig ist lediglich die korrekte Verkabelung über die I2C-Pins (SDA/SCL).
Was ist der Unterschied zwischen MAX30100 und MAX30102?
Der MAX30102 ist der Nachfolger des MAX30100 und bietet einige Verbesserungen, unter anderem:
- stabilere Messwerte
- bessere Signalverarbeitung
- zusätzliche Funktionen wie einen Temperatursensor
Zudem ist der MAX30102 aktuell deutlich verbreiteter und wird von modernen Bibliotheken besser unterstützt.
Warum funktioniert mein MAX30102 Modul nicht?
Ein häufiger Grund ist die Verwendung eines „MAX3010x“-Moduls. Diese sind nicht immer sauber aufgebaut und können Probleme bei der Spannungsversorgung verursachen.
👉 Empfehlung: Ein Modul mit klarer Bezeichnung MAX30102 verwenden.
Kann ich den MAX30102 für medizinische Anwendungen nutzen?
Nein. Der MAX30102 ist kein medizinisch zertifiziertes Gerät. Die gemessenen Werte dienen ausschließlich zu Demonstrations- und Lernzwecken und sollten nicht für medizinische Entscheidungen verwendet werden.
Kann ich die Daten vom MAX30102 weiterverarbeiten?
Ja, die Messwerte lassen sich problemlos weiterverarbeiten, zum Beispiel:
- Darstellung auf einem Display (OLED, LED Matrix)
- Übertragung per Bluetooth oder WiFi (z. B. mit ESP32)
- Speicherung in einer Datenbank oder Cloud
Letzte Aktualisierung am: 07. April 2026











