.. _arduino_heartrate: ======================== 1.6 Heart Rate Sensor ======================== Read pulse data from the MAX30102 heart rate sensor and calculate BPM. Overview ======== This example interfaces with the MAX30102 optical heart rate sensor over I2C. It reads infrared (IR) reflection data from your fingertip, detects individual heartbeats, and computes a rolling average of beats per minute (BPM). .. note:: This example is based on the open-source ``Example5_HeartRate`` example from the SparkFun MAX3010x sensor library. For the LAFVIN ESP32-S3 Multimedia Kit, we adapted the I2C pin mapping and sensor setup, then added finger-detection debounce, outlier filtering, status reporting, and state reset logic to make the readings more stable for learning and demonstration. What You'll Learn ================= * Using I2C sensor libraries (SparkFun MAX3010x) * Real-time sensor data processing * Beat detection and BPM calculation with validation Before You Begin ================ Complete the setup steps in :ref:`preparation` before continuing. .. note:: Use the recommended Arduino IDE settings in :ref:`arduino_board_settings` unless this lesson says otherwise. Open the example sketch from the code package: ``LAFVIN-ESP32-S3-Multimedia-Kit-main/1.Arduino/6.HeartRate/6.HeartRate.ino`` This example uses the **onboard** MAX30102 sensor. No external wiring is required. ======== ======== Signal ESP32-S3 ======== ======== SDA GPIO 2 SCL GPIO 1 ======== ======== Upload and Test =============== #. Connect the board with a USB Type-C data cable. #. Open ``6.HeartRate.ino`` in Arduino IDE. #. In the **Tools** menu, select ``ESP32S3 Dev Module`` as the board and choose the correct serial port. #. Click **Upload**. #. Open **Serial Monitor** at ``115200`` baud. You should see:: Initializing MAX30102... MAX30102 ready. Place your finger gently on the sensor and keep still. #. Gently place your fingertip on the MAX30102 sensor. After a few seconds, you should see BPM readings:: Finger detected. Measuring heart rate... BPM=72.5, Avg BPM=73, IR(raw)=155000 .. note:: Keep your finger still and apply gentle, consistent pressure. The sensor needs a few seconds to stabilize. If you see ``Signal too weak`` or ``No finger``, adjust your finger position. How the Code Works ================== **Sensor Initialization** The sketch starts I2C on GPIO 2 and GPIO 1, then configures the MAX30102 with the sampling parameters used for this board. .. code-block:: cpp Wire.begin(IIC_SDA, IIC_SCL); if (!particleSensor.begin(Wire, I2C_SPEED_FAST)) { Serial.println("MAX30102/MAX30105 was not found. Please check wiring and power."); while (1) { delay(10); } } particleSensor.setup(ledBrightness, sampleAverage, ledMode, sampleRate, pulseWidth, adcRange); particleSensor.setPulseAmplitudeGreen(0); **Finger Detection** The sketch uses debounce counters so a finger is only considered present or removed after several stable samples. .. code-block:: cpp if (lastIrValue >= FINGER_ON_THRESHOLD) { if (fingerOnCount < FINGER_DEBOUNCE_SAMPLES) { fingerOnCount++; } fingerOffCount = 0; if (!fingerPresent && fingerOnCount >= FINGER_DEBOUNCE_SAMPLES) { handleFingerDetected(); } } else if (lastIrValue <= FINGER_OFF_THRESHOLD) { if (fingerOffCount < FINGER_DEBOUNCE_SAMPLES) { fingerOffCount++; } fingerOnCount = 0; if (fingerPresent && fingerOffCount >= FINGER_DEBOUNCE_SAMPLES) { handleFingerRemoved(); } } **BPM Calculation** Each detected beat is converted into BPM, then checked before it is accepted into the rolling average. .. code-block:: cpp void processHeartRate(uint32_t irValue) { if (!checkForBeat(irValue)) { return; } const long nowMs = millis(); if (lastBeatMs == 0) { lastBeatMs = nowMs; return; } const float bpm = 60.0f / ((nowMs - lastBeatMs) / 1000.0f); lastBeatMs = nowMs; if (!isReasonableBeat(bpm)) { return; } beatsPerMinute = bpm; updateAverageRate((int)(bpm + 0.5f)); } **Status Reporting** The sketch prints different status messages depending on whether no finger is detected, the signal is weak, or stable BPM data is available. .. code-block:: cpp void printStatus() { if (!fingerPresent) { Serial.println("status=No finger"); return; } if (lastIrValue < FINGER_ON_THRESHOLD) { Serial.println("status=Signal too weak"); return; } In the provided sketch: * The green LED is disabled, so the sensor uses the IR channel for heart rate measurement * BPM values outside the valid range are discarded * Readings that differ too much from the rolling average are also rejected * Status information is reported once per second Try This Next ============= * Experiment with the ``FINGER_ON_THRESHOLD`` and ``FINGER_OFF_THRESHOLD`` values * Increase ``RATE_AVG_SIZE`` from 8 to 16 for smoother averaging * Graph the raw IR values in Arduino IDE's **Serial Plotter** to visualize your pulse waveform