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:
-
Read the sample count (0xAA 0x12 0x00). The hub returns the number of samples waiting.
-
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.
-
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:
-
numSamples from 0x12 0x00
-
How many milliseconds MFIO has been low
-
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.