An ESP-IDF component for the DFRobot Gravity: MAX30102 PPG Heart Rate and Oximeter Sensor (SEN0518). Provides a clean C API for reading heart rate (BPM), blood oxygen saturation (SpO2), and die temperature over I2C.
⚠️ This is NOT a generic MAX30102 driver. It is written specifically for the DFRobot Gravity SEN0518 module. That module pairs the MAX30102 chip with an onboard MCU that runs the full PPG signal processing pipeline (peak detection, DC removal, SpO2 ratio calculation) and exposes the already-computed results via its own I2C register map. If you wire a bare MAX30102 chip to this and expect it to work, it won't — the register layout is completely different.
- Read SpO2 (%) and heart rate (BPM) from precomputed sensor registers
- Read die temperature from the sensor
- Controlled measurement sessions via
neoPPG_start_collect/neoPPG_end_collect - Device ID verification on init to catch wiring mistakes early
- Minimal footprint — pure C, no heap-heavy abstractions
- ESP-IDF (uses the legacy
i2cdriver — compatible with ESP-IDF v4.x and v5.x) - DFRobot Gravity Heart Rate and SpO2 Sensor (MAX30102-based module, I2C)
Connect the sensor module to your ESP32:
| Sensor Pin | ESP32 Pin |
|---|---|
| VCC | 3.3V or 5V |
| GND | GND |
| SDA | I2C_MASTER_SDA_IO (defined in neoPPG.h) |
| SCL | I2C_MASTER_SCL_IO (defined in neoPPG.h) |
The default I2C address is 0x57 (DFRobot SEN0518 default).
Clone or copy this component into your project's components/ directory:
cd your_project/components
git clone https://github.com/neobitlab/neoPPG.gitBefore building, set your pin assignments in include/neoPPG.h:
#define I2C_MASTER_SCL_IO 22
#define I2C_MASTER_SDA_IO 21
#define I2C_MASTER_NUM I2C_NUM_0
#define I2C_MASTER_FREQ_HZ 100000
#define DEVICE_ADDRESS 0x57 // DFRobot SEN0518 default#include "neoPPG.h"
void app_main(void) {
neoPPG_handle_t sensor = {0};
// Initialize I2C and verify device ID
ESP_ERROR_CHECK(neoPPG_init(&sensor));
// Start a measurement session
ESP_ERROR_CHECK(neoPPG_start_collect(&sensor));
// Wait for the sensor to acquire data
vTaskDelay(pdMS_TO_TICKS(4000));
// Read SpO2 and BPM
ESP_ERROR_CHECK(neoPPG_measure(&sensor));
if (sensor.readings.SPO2 != -1 && sensor.readings.BPM != -1) {
printf("SpO2: %d%% | BPM: %d\n",
sensor.readings.SPO2,
sensor.readings.BPM);
} else {
printf("No valid reading — make sure finger is on sensor.\n");
}
// Read die temperature
float temp;
ESP_ERROR_CHECK(neoPPG_get_temperature(&sensor, &temp));
printf("Die temp: %.2f °C\n", temp);
// End measurement session
ESP_ERROR_CHECK(neoPPG_end_collect(&sensor));
}// Initialize I2C master and verify the sensor is reachable at the expected address.
// Returns ESP_ERR_NOT_FOUND if the device doesn't respond (check wiring and I2C address).
esp_err_t neoPPG_init(neoPPG_handle_t *dev);// Tell the sensor module to start collecting PPG data
esp_err_t neoPPG_start_collect(neoPPG_handle_t *dev);
// Stop collection
esp_err_t neoPPG_end_collect(neoPPG_handle_t *dev);// Read SpO2 and BPM from the sensor registers.
// Results are stored in dev->readings.SPO2 and dev->readings.BPM.
// Returns -1 for a field if the sensor reports no valid reading.
esp_err_t neoPPG_measure(neoPPG_handle_t *dev);
// Read the sensor module's die temperature in °C
esp_err_t neoPPG_get_temperature(neoPPG_handle_t *dev, float *temperature);typedef struct {
i2c_port_t i2c_port;
uint8_t device_addr;
struct {
int32_t SPO2; // Blood oxygen saturation (%), or -1 if invalid
int32_t BPM; // Heart rate (beats per minute), or -1 if invalid
} readings;
} neoPPG_handle_t;- The sensor module needs a few seconds to stabilize after
neoPPG_start_collect. A 3–5 second delay before callingneoPPG_measureis typical. - Invalid readings (
-1) usually mean no finger is detected or the contact is poor. - This component initializes its own I2C driver. If you're sharing the I2C bus with other peripherals, you'll need to either refactor the init or ensure
i2c_driver_installis only called once.
Inspired by DFRobot_BloodOxygen_S by DFRobot.