SparkFun Pulse Oximeter and Heart Rate Sensor

Hello,

I am working with the Sparkfun Bio Sensor Hub using a dual-core ESP32. My current problem is that, most of the time, I can obtain the ir_led and red_led values correctly, but the data stream frequently becomes noisy. I can correlate the start of this noise with the MAX30101 FIFO overflow flag (32-byte FIFO).

How did you implement the logic to retrieve data from the MAX32664 hub? Are you using the MFIO?

I am currently using a fixed 100Hz interval based on the ESP32 clock, as I haven’t been able to get a signal from the MFIO.

Thank you.

It looks like we did :slight_smile: SparkFun_Bio_Sensor_Hub_Library/src/SparkFun_Bio_Sensor_Hub_Library.cpp at master · sparkfun/SparkFun_Bio_Sensor_Hub_Library · GitHub

This is a bit out of my wheelhouse but let me know if you have more Q’s!

Hello!

I’m already using the SparkFun library.

My problem lies precisely in using this library. :slight_smile:

Yes; MFIO is all over the file I linked…its specific recipe is detailed in the code’s comments/notes and implementation pipeline

ESP32 can have some weird clock-stretching issues - does it work as you’d like with a different MCU @ 100Hz?

I have confirmed that my ESP32 is not stretching the I2C (SDA/SCL) clock. By adding an external pull-up resistor, I am now using the MFIO pin to monitor the FIFO buffer status. I’ve observed the pin staying LOW for increasing durations, indicating rising pressure on the buffer. Once the signal remains LOW for 100% of the cycle, a buffer overflow eventually occurs, followed by an automatic FIFO reset by the sensor.

I initially assumed the FIFO operated at a fixed 100 Hz rate, but it appears to adapt based on the data retrieval frequency. Is this the case? Furthermore, how can I reduce the FIFO fill rate? It seems that adding an extra read whenever MFIO is LOW actually exacerbates the issue.

Ok, after poking a bit more: The FIFO does not adapt to your read rate; it fills at the fixed optical sampling rate. MFIO stays low because the library’s readBpm() only fetches one sample per I²C transaction. On an ESP32, doing 15 of those transactions takes longer than the sensor takes to produce 15 samples, so it never catches up. Switch to reading the FIFO count first, then request all waiting samples in a single large requestFrom() . If you only need the latest BPM/SpO₂ value, process the last sample and drop the rest. If the bus is still too slow, lower the MAX30101 sample rate to 50 Hz or 25 Hz via register 0x0A .

Run through the steps below; #2 is the most promising

1. Why “extra reads when MFIO is LOW” makes it worse

MFIO is the MAX32664’s level-sensitive FIFO-interrupt pin. It goes low when the output FIFO count hits the threshold (default 0x0F = 15 samples) and stays low until the host reads enough bytes to drop the count below that threshold.

If he is calling the library’s standard readBpm() or readSensorData() inside a loop whenever MFIO is low, each call performs a separate I²C write-then-read sequence for just one sample. On the ESP32, the Wire/ESP-IDF driver overhead (start conditions, command phase, FreeRTOS scheduling) can easily take 1–3 ms per sample. At 100 Hz the sensor produces a new sample every 10 ms, so if the FIFO already has 15 samples, firing 15 discrete transactions takes ~20–45 ms—during which 2–4 more samples arrive. He never catches up, MFIO never goes high, and eventually the FIFO overflows (bit 4 FifoOutOvrInt or the MAX30101’s own FIFO rolls over).

The “extra read” is the problem, not the solution.


2. Bulk-read the FIFO

Instead of polling MFIO and then calling the high-level single-sample functions, he should use the low-level MAX32664 commands to empty the FIFO in one large I²C block read:

  1. Read the sample count (0xAA 0x12 0x00). The hub returns the number of samples waiting.

  2. Read all samples at once (0xAA 0x12 0x01), requesting count × sampleSize bytes in a single requestFrom(). The MAX32664 advances its internal read pointer by one sample for every sample-size bytes the host clocks out.

  3. Process only the newest sample (the last one in the buffer) and discard the rest, or push the whole batch into a ring buffer for downstream filtering.

This turns N slow transactions into one transaction, which the ESP32 can easily finish well within the 10 ms window.


3. If bulk reads still aren’t enough, reduce the source rate

If the application only needs BPM/SpO₂ (not high-resolution PPG streaming), lower the MAX30101 sample rate so the FIFO grows more slowly:

  • Write to MAX30101 register 0x0A (via the MAX32664 0x40 0x03 passthrough command) to set a slower rate—e.g., 50 Hz or 25 Hz instead of 100 Hz.

  • Alternatively, increase the sample averaging (register 0x0A bits 7:5) so the MAX30101 produces fewer, cleaner samples.


4. ESP32-specific tuning

  • Run I²C at 400 kHz (Fast Mode). The MAX32664 supports it, and it halves transaction time versus 100 kHz.

  • If he is using the ESP-IDF driver directly (as his component registry page suggests), make sure he is not wrapping every byte in a separate transaction. The ESP-IDF i2c_master_read() can handle large buffers efficiently.

  • Consider pinning the sensor-reading task to one core (e.g., Core 0) and keeping WiFi/BLE workload on Core 1, so I²C timing isn’t jittered by RF stack interrupts.


5. Quick diagnostic TEST

Log these three values on every MFIO edge:

  1. numSamples from 0x12 0x00

  2. How many milliseconds MFIO has been low

  3. Whether the MAX32664 status byte (read with 0x00 0x00) shows FifoOutOvrInt (bit 4) set.

If numSamples trends upward cycle-to-cycle, the host is definitely not reading faster than the sensor produces data. Once he switches to bulk reads, he should see numSamples settle at a small, stable number (ideally 0–2) and MFIO become a brief pulse rather than a sustained low.