I have SparkFun Pulse Oximeter and Heart Rate Sensor connected to a nrf52dK board.
I am able to communicate, but I can’t seem to get the correct data that i can parse. IR and RED LED values seem ok, but algorythm output outputs zero, except for SpO2 which always outputs a number that is always increasing by 0.1%
[621107] IR=10621 RED=12548 HR=0.0 bpm Conf=0% SpO2=23.6% Status=12
[621227] IR=10621 RED=12548 HR=0.0 bpm Conf=0% SpO2=23.6% Status=12
[621347] IR=10621 RED=12548 HR=0.0 bpm Conf=0% SpO2=23.6% Status=12
[621467] IR=10625 RED=12555 HR=0.0 bpm Conf=0% SpO2=23.6% Status=68
[621587] IR=10625 RED=12555 HR=0.0 bpm Conf=0% SpO2=23.6% Status=68
[621707] IR=10625 RED=12555 HR=0.0 bpm Conf=0% SpO2=23.6% Status=68
[621828] IR=10625 RED=12555 HR=0.0 bpm Conf=0% SpO2=23.6% Status=68
[621948] IR=10625 RED=12555 HR=0.0 bpm Conf=0% SpO2=23.6% Status=68
[622068] IR=10621 RED=12555 HR=0.0 bpm Conf=0% SpO2=23.6% Status=124
[622188] IR=10621 RED=12555 HR=0.0 bpm Conf=0% SpO2=23.6% Status=124
[622308] IR=10621 RED=12555 HR=0.0 bpm Conf=0% SpO2=23.6% Status=124
[622428] IR=10621 RED=12555 HR=0.0 bpm Conf=0% SpO2=23.6% Status=124
[622548] IR=10621 RED=12555 HR=0.0 bpm Conf=0% SpO2=23.6% Status=124
[622668] IR=10621 RED=12552 HR=0.0 bpm Conf=0% SpO2=23.6% Status=-76
[622782] IR=10621 RED=12552 HR=0.0 bpm Conf=0% SpO2=23.6% Status=-76
[622902] IR=10621 RED=12552 HR=0.0 bpm Conf=0% SpO2=23.6% Status=-76
[623016] IR=10621 RED=12552 HR=0.0 bpm Conf=0% SpO2=23.6% Status=-76
[623136] IR=10625 RED=12554 HR=0.0 bpm Conf=0% SpO2=23.6% Status=-20
[623256] IR=10625 RED=12554 HR=0.0 bpm Conf=0% SpO2=23.6% Status=-20
[623377] IR=10625 RED=12554 HR=0.0 bpm Conf=0% SpO2=23.6% Status=-20
[623497] IR=10625 RED=12554 HR=0.0 bpm Conf=0% SpO2=23.6% Status=-20
[623617] IR=10625 RED=12554 HR=0.0 bpm Conf=0% SpO2=23.6% Status=-20
[623737] IR=10625 RED=12555 HR=0.0 bpm Conf=0% SpO2=23.7% Status=36
[623857] IR=10625 RED=12555 HR=0.0 bpm Conf=0% SpO2=23.7% Status=36
[623977] IR=10625 RED=12555 HR=0.0 bpm Conf=0% SpO2=23.7% Status=36
[624097] IR=10625 RED=12555 HR=0.0 bpm Conf=0% SpO2=23.7% Status=36
[624217] IR=10625 RED=12555 HR=0.0 bpm Conf=0% SpO2=23.7% Status=36
[624337] IR=10626 RED=12558 HR=0.0 bpm Conf=0% SpO2=23.7% Status=92
[624457] IR=10626 RED=12558 HR=0.0 bpm Conf=0% SpO2=23.7% Status=92
[624577] IR=10626 RED=12558 HR=0.0 bpm Conf=0% SpO2=23.7% Status=92
[624697] IR=10626 RED=12558 HR=0.0 bpm Conf=0% SpO2=23.7% Status=92
[624817] IR=10626 RED=12558 HR=0.0 bpm Conf=0% SpO2=23.7% Status=92
[624930] FIFO count reported = 77
[625047] IR=10625 RED=12558 HR=0.0 bpm Conf=0% SpO2=23.7% Status=-108
[625167] IR=10625 RED=12558 HR=0.0 bpm Conf=0% SpO2=23.7% Status=-108
[625281] IR=10625 RED=12558 HR=0.0 bpm Conf=0% SpO2=23.7% Status=-108
[625402] IR=10627 RED=12552 HR=0.0 bpm Conf=0% SpO2=23.7% Status=-52
[625515] IR=10627 RED=12552 HR=0.0 bpm Conf=0% SpO2=23.7% Status=-52
[625636] IR=10627 RED=12552 HR=0.0 bpm Conf=0% SpO2=23.7% Status=-52
[625756] IR=10627 RED=12552 HR=0.0 bpm Conf=0% SpO2=23.7% Status=-52
[625876] IR=10627 RED=12552 HR=0.0 bpm Conf=0% SpO2=23.7% Status=-52
[625996] IR=10622 RED=12559 HR=0.0 bpm Conf=0% SpO2=23.8% Status=4
[626116] IR=10622 RED=12559 HR=0.0 bpm Conf=0% SpO2=23.8% Status=4
[626236] IR=10622 RED=12559 HR=0.0 bpm Conf=0% SpO2=23.8% Status=4
[626356] IR=10622 RED=12559 HR=0.0 bpm Conf=0% SpO2=23.8% Status=4
[626476] IR=10625 RED=12554 HR=0.0 bpm Conf=0% SpO2=23.8% Status=60
[626596] IR=10625 RED=12554 HR=0.0 bpm Conf=0% SpO2=23.8% Status=60
[626716] IR=10625 RED=12554 HR=0.0 bpm Conf=0% SpO2=23.8% Status=60
[626836] IR=10625 RED=12554 HR=0.0 bpm Conf=0% SpO2=23.8% Status=60
[626956] IR=10625 RED=12554 HR=0.0 bpm Conf=0% SpO2=23.8% Status=60
[627076] IR=10625 RED=12554 HR=0.0 bpm Conf=0% SpO2=23.8% Status=60
[627196] IR=10627 RED=12552 HR=0.0 bpm Conf=0% SpO2=23.8% Status=116
[627316] IR=10627 RED=12552 HR=0.0 bpm Conf=0% SpO2=23.8% Status=116
[627436] IR=10627 RED=12552 HR=0.0 bpm Conf=0% SpO2=23.8% Status=116
[627550] IR=10627 RED=12552 HR=0.0 bpm Conf=0% SpO2=23.8% Status=116
[627670] IR=10623 RED=12550 HR=0.0 bpm Conf=0% SpO2=23.8% Status=-84
[627784] IR=10623 RED=12550 HR=0.0 bpm Conf=0% SpO2=23.8% Status=-84
[627905] IR=10623 RED=12550 HR=0.0 bpm Conf=0% SpO2=23.8% Status=-84
[628025] IR=10623 RED=12550 HR=0.0 bpm Conf=0% SpO2=23.8% Status=-84
[628145] IR=10623 RED=12554 HR=0.0 bpm Conf=0% SpO2=23.8% Status=-28
[628265] IR=10623 RED=12554 HR=0.0 bpm Conf=0% SpO2=23.8% Status=-28
[628385] IR=10623 RED=12554 HR=0.0 bpm Conf=0% SpO2=23.8% Status=-28
[628505] IR=10623 RED=12554 HR=0.0 bpm Conf=0% SpO2=23.8% Status=-28
[628625] IR=10623 RED=12554 HR=0.0 bpm Conf=0% SpO2=23.8% Status=-28
[628746] IR=10623 RED=12554 HR=0.0 bpm Conf=0% SpO2=23.9% Status=28
[628866] IR=10623 RED=12554 HR=0.0 bpm Conf=0% SpO2=23.9% Status=28
[628986] IR=10623 RED=12554 HR=0.0 bpm Conf=0% SpO2=23.9% Status=28
[629106] IR=10623 RED=12554 HR=0.0 bpm Conf=0% SpO2=23.9% Status=28
[629226] IR=10623 RED=12554 HR=0.0 bpm Conf=0% SpO2=23.9% Status=28
[629346] IR=10624 RED=12555 HR=0.0 bpm Conf=0% SpO2=23.9% Status=84
[629466] IR=10624 RED=12555 HR=0.0 bpm Conf=0% SpO2=23.9% Status=84
[629586] IR=10624 RED=12555 HR=0.0 bpm Conf=0% SpO2=23.9% Status=84
[629706] IR=10624 RED=12555 HR=0.0 bpm Conf=0% SpO2=23.9% Status=84
[629826] IR=10624 RED=12555 HR=0.0 bpm Conf=0% SpO2=23.9% Status=84
[629946] IR=10621 RED=12552 HR=0.0 bpm Conf=0% SpO2=23.9% Status=-116
[630060] IR=10621 RED=12552 HR=0.0 bpm Conf=0% SpO2=23.9% Status=-116
[630180] IR=10621 RED=12552 HR=0.0 bpm Conf=0% SpO2=23.9% Status=-116
[630294] IR=10621 RED=12552 HR=0.0 bpm Conf=0% SpO2=23.9% Status=-116
[630415] IR=10618 RED=12554 HR=0.0 bpm Conf=0% SpO2=23.9% Status=-60
[630535] IR=10618 RED=12554 HR=0.0 bpm Conf=0% SpO2=23.9% Status=-60
[630655] IR=10618 RED=12554 HR=0.0 bpm Conf=0% SpO2=23.9% Status=-60
[630775] IR=10618 RED=12554 HR=0.0 bpm Conf=0% SpO2=23.9% Status=-60
[630895] IR=10618 RED=12554 HR=0.0 bpm Conf=0% SpO2=23.9% Status=-60
[631015] IR=10625 RED=12556 HR=0.0 bpm Conf=0% SpO2=23.9% Status=-4
[631135] IR=10625 RED=12556 HR=0.0 bpm Conf=0% SpO2=23.9% Status=-4
[631255] IR=10625 RED=12556 HR=0.0 bpm Conf=0% SpO2=23.9% Status=-4
[631375] IR=10625 RED=12556 HR=0.0 bpm Conf=0% SpO2=23.9% Status=-4
[631495] IR=10625 RED=12556 HR=0.0 bpm Conf=0% SpO2=23.9% Status=-4
[631615] IR=10625 RED=12551 HR=0.0 bpm Conf=0% SpO2=24.0% Status=52
[631736] IR=10625 RED=12551 HR=0.0 bpm Conf=0% SpO2=24.0% Status=52
[631856] IR=10625 RED=12551 HR=0.0 bpm Conf=0% SpO2=24.0% Status=52
[631976] IR=10625 RED=12551 HR=0.0 bpm Conf=0% SpO2=24.0% Status=52
[632096] IR=10625 RED=12551 HR=0.0 bpm Conf=0% SpO2=24.0% Status=52
[632216] IR=10623 RED=12553 HR=0.0 bpm Conf=0% SpO2=24.0% Status=108
[632336] IR=10623 RED=12553 HR=0.0 bpm Conf=0% SpO2=24.0% Status=108
[632456] IR=10623 RED=12553 HR=0.0 bpm Conf=0% SpO2=24.0% Status=108
[632570] IR=10623 RED=12553 HR=0.0 bpm Conf=0% SpO2=24.0% Status=108
[632690] IR=10627 RED=12551 HR=0.0 bpm Conf=0% SpO2=24.0% Status=-92
[632804] IR=10627 RED=12551 HR=0.0 bpm Conf=0% SpO2=24.0% Status=-92
[632924] IR=10627 RED=12551 HR=0.0 bpm Conf=0% SpO2=24.0% Status=-92
[633044] IR=10627 RED=12551 HR=0.0 bpm Conf=0% SpO2=24.0% Status=-92
[633164] IR=10627 RED=12551 HR=0.0 bpm Conf=0% SpO2=24.0% Status=-92
[633284] IR=10622 RED=12556 HR=0.0 bpm Conf=0% SpO2=24.0% Status=-36
[633404] IR=10622 RED=12556 HR=0.0 bpm Conf=0% SpO2=24.0% Status=-36
[633525] IR=10622 RED=12556 HR=0.0 bpm Conf=0% SpO2=24.0% Status=-36
[633645] IR=10622 RED=12556 HR=0.0 bpm Conf=0% SpO2=24.0% Status=-36
[633765] IR=10622 RED=12556 HR=0.0 bpm Conf=0% SpO2=24.0% Status=-36
[633885] IR=10625 RED=12551 HR=0.0 bpm Conf=0% SpO2=24.1% Status=20
[634005] IR=10625 RED=12551 HR=0.0 bpm Conf=0% SpO2=24.1% Status=20
[634125] IR=10625 RED=12551 HR=0.0 bpm Conf=0% SpO2=24.1% Status=20
[634238] FIFO count reported = 71
[634355] IR=10625 RED=12551 HR=0.0 bpm Conf=0% SpO2=24.1% Status=20
[634475] IR=10625 RED=12551 HR=0.0 bpm Conf=0% SpO2=24.1% Status=76
[634595] IR=10625 RED=12551 HR=0.0 bpm Conf=0% SpO2=24.1% Status=76
[634715] IR=10625 RED=12551 HR=0.0 bpm Conf=0% SpO2=24.1% Status=76
[634829] IR=10625 RED=12551 HR=0.0 bpm Conf=0% SpO2=24.1% Status=76
[634949] IR=10623 RED=12556 HR=0.0 bpm Conf=0% SpO2=24.1% Status=-124
[635063] IR=10623 RED=12556 HR=0.0 bpm Conf=0% SpO2=24.1% Status=-124
[635183] IR=10623 RED=12556 HR=0.0 bpm Conf=0% SpO2=24.1% Status=-124
[635303] IR=10623 RED=12556 HR=0.0 bpm Conf=0% SpO2=24.1% Status=-124
[635424] IR=10625 RED=12549 HR=0.0 bpm Conf=0% SpO2=24.1% Status=-68
[635544] IR=10625 RED=12549 HR=0.0 bpm Conf=0% SpO2=24.1% Status=-68
[635664] IR=10625 RED=12549 HR=0.0 bpm Conf=0% SpO2=24.1% Status=-68
[635784] IR=10625 RED=12549 HR=0.0 bpm Conf=0% SpO2=24.1% Status=-68
[635904] IR=10625 RED=12549 HR=0.0 bpm Conf=0% SpO2=24.1% Status=-68
[636024] IR=10625 RED=12549 HR=0.0 bpm Conf=0% SpO2=24.1% Status=-68
[636144] IR=10626 RED=12556 HR=0.0 bpm Conf=0% SpO2=24.1% Status=-12
[636265] IR=10626 RED=12556 HR=0.0 bpm Conf=0% SpO2=24.1% Status=-12
[636385] IR=10626 RED=12556 HR=0.0 bpm Conf=0% SpO2=24.1% Status=-12
[636505] IR=10626 RED=12556 HR=0.0 bpm Conf=0% SpO2=24.1% Status=-12
[636625] IR=10623 RED=12553 HR=0.0 bpm Conf=0% SpO2=24.2% Status=44
[636745] IR=10623 RED=12553 HR=0.0 bpm Conf=0% SpO2=24.2% Status=44
[636865] IR=10623 RED=12553 HR=0.0 bpm Conf=0% SpO2=24.2% Status=44
[636985] IR=10623 RED=12553 HR=0.0 bpm Conf=0% SpO2=24.2% Status=44
[637105] IR=10623 RED=12553 HR=0.0 bpm Conf=0% SpO2=24.2% Status=44
[637225] IR=10625 RED=12550 HR=0.0 bpm Conf=0% SpO2=24.2% Status=100
[637339] IR=10625 RED=12550 HR=0.0 bpm Conf=0% SpO2=24.2% Status=100
[637459] IR=10625 RED=12550 HR=0.0 bpm Conf=0% SpO2=24.2% Status=100
[637573] IR=10625 RED=12550 HR=0.0 bpm Conf=0% SpO2=24.2% Status=100
[637693] IR=10626 RED=12552 HR=0.0 bpm Conf=0% SpO2=24.2% Status=-100
[637813] IR=10626 RED=12552 HR=0.0 bpm Conf=0% SpO2=24.2% Status=-100
[637934] IR=10626 RED=12552 HR=0.0 bpm Conf=0% SpO2=24.2% Status=-100
[638054] IR=10626 RED=12552 HR=0.0 bpm Conf=0% SpO2=24.2% Status=-100
[638174] IR=10626 RED=12552 HR=0.0 bpm Conf=0% SpO2=24.2% Status=-100
[638294] IR=10626 RED=12552 HR=0.0 bpm Conf=0% SpO2=24.2% Status=-100
[638415] IR=10621 RED=12555 HR=0.0 bpm Conf=0% SpO2=24.2% Status=-44
[638535] IR=10621 RED=12555 HR=0.0 bpm Conf=0% SpO2=24.2% Status=-44
[638655] IR=10621 RED=12555 HR=0.0 bpm Conf=0% SpO2=24.2% Status=-44
[638775] IR=10621 RED=12555 HR=0.0 bpm Conf=0% SpO2=24.2% Status=-44
[638895] IR=10622 RED=12552 HR=0.0 bpm Conf=0% SpO2=24.3% Status=12
[639015] IR=10622 RED=12552 HR=0.0 bpm Conf=0% SpO2=24.3% Status=12
/*
* main.c
* MAX30101 via MAX32664 bridge (application-mode sequences per MAX32664 app note Table 9)
*
* - Zephyr OS
* - MFIO = 18, RSTN = 19 (change to match board)
* - I2C node = i2c0 (change as needed)
*
* Key behavior:
* - For families 0x40/0x41/0x42/0x43/0x10/0x12 use write → STOP → k_busy_wait(CMD_DELAY_US) → read(status [+ payload])
* - Probe attrs, chunked dump, configure output mode, set FIFO threshold, enable sensor, enable algorithm, read status, read FIFO count, read FIFO data
*
* Tuning:
* - CMD_DELAY_US: vendor recommends ~60 us; set larger (120..500) when debugging
* - CHUNK_DUMP: small chunk size (8..24) is safe
*/
#include <zephyr/kernel.h>
#include <zephyr/device.h>
#include <zephyr/devicetree.h>
#include <zephyr/drivers/i2c.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/sys/printk.h>
#include <stdint.h>
#include <stdarg.h>
#include <string.h>
#include <errno.h>
#define I2C_DEV DEVICE_DT_GET(DT_NODELABEL(i2c0))
#define GPIO_DEV DEVICE_DT_GET(DT_NODELABEL(gpio0))
/* MAX32664 families / host commands */
#define FAMILY_STATUS 0x00 /* status read/write probe family (we use 0x00 probe as in some examples) */
#define FAMILY_DEVICE_MODE 0x02
#define FAMILY_BOOT_VER 0xFF
#define FAMILY_OUTPUT_MODE 0x10
#define FAMILY_OUTPUT_QRY 0x11
#define FAMILY_READ_FIFO_COUNT 0x12 /* 0x12 index 0x00 → num samples; index 0x01 → read fifo data */
#define FAMILY_AFE_WRITE 0x40
#define FAMILY_AFE_READ 0x41
#define FAMILY_AFE_ATTR 0x42
#define FAMILY_AFE_DUMP 0x43
#define FAMILY_ALGO_ENABLE 0x52
#define HUB_ADDR 0x55
/* MAX30101 index in bridge */
#define IDX_MAX30101 0x03
/* pins (adapt if necessary) */
#define MFIO_PIN 18
#define RSTN_PIN 19
/* timing constants */
#define CMD_DELAY_US 120 /* start conservative; vendor ~60us */
#define BOOT_POST_MS 600
#define POST_AFE_WRITE_MS 300
/* stability and fetch tuning */
#define STABLE_COUNT 6
#define STABLE_GAP_MS 8
#define FETCH_SLACK_MS 60
#define FETCH_RETRY_COUNT 6
#define FETCH_RETRY_DELAY_MS 10
/* dump */
#define MAX_REGS 36
#define MAX_DUMP_BYTES (MAX_REGS * 2)
#define DUMP_CHUNK 12
/* MAX30101 registers */
#define R_FIFO_WRPTR 0x04
#define R_FIFO_OVF 0x05
#define R_FIFO_RDPTR 0x06
#define R_FIFO_DATA 0x07
#define R_FIFO_CONF 0x08
#define R_MODE_CONF 0x09
#define R_SPO2_CONF 0x0A
#define R_LED1_PA 0x0C
#define R_LED2_PA 0x0D
/* parse helper */
static inline uint32_t parse24(const uint8_t *b) {
return ((((uint32_t)b[0] << 16) | ((uint32_t)b[1] << 8) | b[2]) & 0x03FFFF);
}
static inline uint16_t be16(const uint8_t *p) {
return (uint16_t)(((uint16_t)p[0] << 8) | p[1]); // big-endian → host
}
static inline int16_t be16s(const uint8_t *p) { // if you later parse accel
return (int16_t)(((uint16_t)p[0] << 8) | p[1]);
}
/* devices */
static const struct device *i2c;
static const struct device *gpio_dev;
/* MFIO DataReady */
static struct gpio_callback mfio_cb;
static volatile bool data_ready_flag;
static void mfio_isr(const struct device *dev, struct gpio_callback *cb, uint32_t pins) {
ARG_UNUSED(dev); ARG_UNUSED(cb); ARG_UNUSED(pins);
data_ready_flag = true;
}
/* logging */
static void tprint(const char *fmt, …) {
va_list ap; va_start(ap, fmt);
printk("[%u] ", (unsigned)k_uptime_get());
vprintk(fmt, ap);
va_end(ap);
}
/* minimal helpers */
static inline int min_i(int a, int b) { return (a < b) ? a : b; }
static void maybe_recover_bus(void) {
if (i2c) (void)i2c_recover_bus(i2c);
k_msleep(5);
}
/* Core vendor flow: write → STOP → CMD_DELAY → read(status [+payload])
* tx_buf: Family, Index, optional write-data
* expected_payload_len: number of payload bytes expected (not counting status)
* out_status: optional pointer to status byte
* out_payload: buffer for payload bytes
* returns 0 on success (status==0x00), negative on transport error or nonzero status
*/
static int vendor_write_then_read(const uint8_t *tx_buf, size_t tx_len,
uint8_t *out_payload, size_t expected_payload_len,
uint8_t *out_status)
{
int rc;
if (!i2c) return -ENODEV;
rc = i2c_write(i2c, tx_buf, tx_len, HUB_ADDR);
if (rc) {
tprint(“i2c_write failed rc=%d\n”, rc);
maybe_recover_bus();
return rc;
}
/\* STOP generated; wait CMD_DELAY_US for bridge to prepare status/payload \*/
k_busy_wait(CMD_DELAY_US);
size_t rx_len = 1 + expected_payload_len;
if (expected_payload_len > 64) return -EINVAL;
uint8_t rx_buf[1 + 64];
rc = i2c_read(i2c, rx_buf, rx_len, HUB_ADDR);
if (rc) {
tprint(“i2c_read failed rc=%d\n”, rc);
maybe_recover_bus();
return rc;
}
uint8_t st = rx_buf[0];
if (out_status) *out_status = st;
if (st != 0x00) {
tprint(“bridge status=0x%02X raw:”, st);
int to = min_i((int)rx_len, 16);
for (int i = 0; i < to; ++i) printk(" %02X", rx_buf[i]);
printk(“\n”);
return -EIO;
}
if (expected_payload_len && out_payload) memcpy(out_payload, &rx_buf[1], expected_payload_len);
return 0;
}
/* status-only probe using vendor flow: write Family=0x00, Index=0x00 (works as minimal probe) */
static int vendor_read_status(uint8_t *status_out) {
uint8_t tx[2] = { 0x00, 0x00 };
return vendor_write_then_read(tx, sizeof(tx), NULL, 0, status_out);
}
/* stability polling: require STABLE_COUNT consecutive 0x00 status reads */
static int wait_for_stable_zero(void) {
int stable = 0;
uint8_t st = 0xFF;
for (int i = 0; i < STABLE_COUNT * 10; ++i) {
int rc = vendor_read_status(&st);
if (rc == 0 && st == 0x00) {
stable++;
if (stable >= STABLE_COUNT) return 0;
k_msleep(STABLE_GAP_MS);
continue;
}
stable = 0;
maybe_recover_bus();
k_msleep(STABLE_GAP_MS);
}
tprint(“stability wait failed last_status=0x%02X\n”, st);
return -ETIMEDOUT;
}
/* high-level vendor fetch: writes fam+idx(+opt_data), ensures stability, sleeps slack, then performs final vendor_write_then_read */
static int stability_checked_vendor_fetch(uint8_t fam, uint8_t idx,
const uint8_t *opt_data, size_t opt_len,
uint8_t *out_payload, size_t out_len)
{
int rc = wait_for_stable_zero();
if (rc) return rc;
k_msleep(FETCH_SLACK_MS);
uint8_t tx[4 + 64];
size_t tx_len = 0;
tx[tx_len++] = fam;
tx[tx_len++] = idx;
if (opt_data && opt_len) {
memcpy(&tx[tx_len], opt_data, opt_len);
tx_len += opt_len;
}
for (int attempt = 1; attempt <= FETCH_RETRY_COUNT; ++attempt) {
uint8_t st = 0xFF;
rc = vendor_write_then_read(tx, tx_len, out_payload, out_len, &st);
if (rc == 0) return 0;
tprint(“fetch attempt %d failed rc=%d status=0x%02X; retrying\n”, attempt, rc, st);
maybe_recover_bus();
k_msleep(FETCH_RETRY_DELAY_MS * attempt);
}
return -EIO;
}
/* AFE helpers built on vendor fetch */
static int read_afe_attrs(int idx, uint8_t attrs[2]) {
return stability_checked_vendor_fetch(FAMILY_AFE_ATTR, idx, NULL, 0, attrs, 2);
}
static int read_afe_reg(int idx, uint8_t reg, uint8_t *val) {
uint8_t arg[1] = { reg };
return stability_checked_vendor_fetch(FAMILY_AFE_READ, idx, arg, 1, val, 1);
}
static int write_afe_reg(int idx, uint8_t reg, uint8_t val) {
uint8_t w[2] = { reg, val };
/\* write returns only status \*/
return stability_checked_vendor_fetch(FAMILY_AFE_WRITE, idx, w, 2, NULL, 0);
}
static int read_fifo_count(void) {
uint8_t out[1];
int rc = stability_checked_vendor_fetch(FAMILY_READ_FIFO_COUNT, 0x00, NULL, 0, out, 1);
return (rc == 0) ? out[0] : -1;
}
static int read_fifo_data_chunk(uint8_t *buf, size_t len) {
/\* family 0x12 index 0x01 returns FIFO data; write index 0x01 with no opt_data, then read status + len payload \*/
return stability_checked_vendor_fetch(FAMILY_READ_FIFO_COUNT, 0x01, NULL, 0, buf, len);
}
/* Boot helper: drive MFIO as boot selection then release */
static void hard_reset_hub_once(bool mfio_high) {
if (!gpio_dev) return;
gpio_pin_configure(gpio_dev, MFIO_PIN, GPIO_OUTPUT);
gpio_pin_configure(gpio_dev, RSTN_PIN, GPIO_OUTPUT);
gpio_pin_set(gpio_dev, MFIO_PIN, mfio_high ? 1 : 0);
tprint(“boot: set MFIO=%d\n”, mfio_high ? 1 : 0);
k_msleep(5);
gpio_pin_set(gpio_dev, RSTN_PIN, 0);
tprint(“boot: assert RSTN=0\n”);
k_msleep(10);
gpio_pin_set(gpio_dev, RSTN_PIN, 1);
tprint(“boot: release RSTN=1\n”);
k_msleep(BOOT_POST_MS);
gpio_pin_configure(gpio_dev, MFIO_PIN, GPIO_INPUT | GPIO_PULL_UP);
}
/* Wait for MFIO DataReady with timeout (ms) */
static bool wait_data_ready_ms(int timeout_ms) {
int elapsed = 0;
const int step = 10;
while (elapsed < timeout_ms) {
if (data_ready_flag) { data_ready_flag = false; return true; }
k_msleep(step);
elapsed += step;
}
return false;
}
int main(void) {
i2c = I2C_DEV;
gpio_dev = GPIO_DEV;
if (!device_is_ready(i2c)) {
printk(“I2C not ready\n”);
return 0;
}
tprint(“i2c ready: %s\n”, i2c->name);
if (device_is_ready(gpio_dev)) tprint(“gpio ready: %s\n”, gpio_dev->name);
maybe_recover_bus();
/\* Boot into app mode (MFIO=1 => application) \*/
hard_reset_hub_once(true);
tprint(“Boot: application mode\n”);
/\* Configure MFIO IRQ (DataReady) \*/
if (device_is_ready(gpio_dev)) {
gpio_pin_configure(gpio_dev, MFIO_PIN, GPIO_INPUT | GPIO_PULL_UP);
gpio_init_callback(&mfio_cb, mfio_isr, BIT(MFIO_PIN));
gpio_add_callback(gpio_dev, &mfio_cb);
gpio_pin_interrupt_configure(gpio_dev, MFIO_PIN, GPIO_INT_EDGE_TO_ACTIVE);
tprint(“MFIO DataReady IRQ configured\n”);
}
/\* Probe indices 0..7 to find MAX30101 \*/
int detected = -1;
uint8_t attrs[2] = { 0xFF, 0xFF };
tprint(“Probing indices for MAX30101 attributes\n”);
for (int idx = 0; idx < 8; ++idx) {
int rc = read_afe_attrs(idx, attrs);
tprint(“probe idx=%d rc=%d attrs=%02X %02X\n”, idx, rc, attrs[0], attrs[1]);
if (rc == 0 && attrs[0] != 0x00) { detected = idx; break; }
}
int afe_idx = (detected >= 0) ? detected : IDX_MAX30101;
tprint(“using afe_idx=%d\n”, afe_idx);
/\* Read a couple of single registers to confirm comms \*/
k_msleep(POST_AFE_WRITE_MS);
uint8_t modev = 0xFF;
int rc = read_afe_reg(afe_idx, R_MODE_CONF, &modev);
tprint(“R_MODE_CONF rc=%d val=0x%02X\n”, rc, modev);
uint8_t fifo_conf = 0xFF;
rc = read_afe_reg(afe_idx, R_FIFO_CONF, &fifo_conf);
tprint(“R_FIFO_CONF rc=%d val=0x%02X\n”, rc, fifo_conf);
/\* Do chunked dump if attributes were valid \*/
if (attrs[1] != 0xFF && rc == 0) {
int regs = attrs[1];
int total_bytes = regs * 2;
tprint(“attempting chunked dump regs=%d total=%d\n”, regs, total_bytes);
uint8_t dump[MAX_DUMP_BYTES];
memset(dump, 0, sizeof(dump));
int off = 0;
while (off < total_bytes) {
int want = min_i(DUMP_CHUNK, total_bytes - off);
int d = stability_checked_vendor_fetch(FAMILY_AFE_DUMP, afe_idx, NULL, 0, &dump[off], want);
if (d == 0) {
off += want;
continue;
}
tprint(“dump chunk failed rc=%d off=%d want=%d — falling back to per-register reads\n”, d, off, want);
/\* fallback: read regs individually for this chunk \*/
int reg_start = off / 2;
int reg_end = reg_start + (want / 2);
for (int r = reg_start; r < reg_end; ++r) {
uint8_t v;
if (read_afe_reg(afe_idx, (uint8_t)r, &v) == 0) {
dump[off + (r - reg_start) * 2 + 0] = (uint8_t)r;
dump[off + (r - reg_start) * 2 + 1] = v;
} else {
tprint(“per-reg read failed reg=%d\n”, r);
}
}
off += want;
}
if (off == total_bytes) {
tprint(“dump collected:\n”);
for (int i = 0; i < total_bytes; ++i) {
printk("%02X ", dump[i]);
if ((i & 0x0F) == 0x0F) printk(“\n”);
}
printk(“\n”);
}
} else {
tprint(“skipping dump: attrs invalid or single-reads failed\n”);
}
/\* Configure per Table 9: set output mode to both raw+algo and FIFO threshold \*/
/\* Example: write Family=0x10 Index=0x00 WriteByte=0x03 to set output to both sensor+algo
\* The vendor flow for these families uses write->STOP->CMD_DELAY->read(status).
\*/
uint8_t tx_outmode[3] = { FAMILY_OUTPUT_MODE, 0x00, 0x03 };
(void)vendor_write_then_read(tx_outmode, 3, NULL, 0, NULL);
tprint(“Set output mode to both (requested)\n”);
uint8_t tx_thresh[3] = { FAMILY_OUTPUT_MODE, 0x01, 0x0F }; /* set FIFO almost-full threshold = 0x0F */
(void)vendor_write_then_read(tx_thresh, 3, NULL, 0, NULL);
tprint(“Set FIFO threshold to 0x0F\n”);
/\* Enable MAX30101 sensor (FAMILY 0x44 index 0x03 with write 0x01) \*/
uint8_t tx_enable_sensor[4] = { 0x44, 0x03, 0x01, 0x00 }; /* some firmwares accept 3 bytes; vendor_write_then_read will accept opt_len */
/\* Use stability flow for enabling: wait stable then write \*/
(void)stability_checked_vendor_fetch(0x44, 0x03, (uint8_t\[\]){0x01}, 1, NULL, 0);
tprint(“Enabled MAX30101\n”);
/\* Enable algorithm WHRM / MaximFast (Family 0x52, index 0x02 with 0x01) \*/
(void)stability_checked_vendor_fetch(FAMILY_ALGO_ENABLE, 0x02, (uint8_t\[\]){0x01}, 1, NULL, 0);
tprint(“Enabled WHRM/MaximFast algorithm\n”);
/\* Poll status (Family=0x00 probe) to see DataRdyInt, then read FIFO count and data per app note \*/
tprint(“Waiting for data (DataRdyInt)…\n”);
for (;
{
/\* prefer MFIO IRQ if available \*/
if (!wait_data_ready_ms(2000)) {
/\* fallback: poll status \*/
uint8_t s = 0xFF;
if (vendor_read_status(&s) != 0) { k_msleep(100); continue; }
if ((s & 0x08) == 0) { k_msleep(100); continue; } /* DataRdyInt bit (example: bit 3) */
}
/\* Read FIFO count (family 0x12 index 0x00) \*/
uint8_t fifo_cnt = 0;
if (stability_checked_vendor_fetch(FAMILY_READ_FIFO_COUNT, 0x00, NULL, 0, &fifo_cnt, 1) != 0) {
tprint(“Failed to read FIFO count\n”);
k_msleep(100);
continue;
}
tprint(“FIFO count reported = %u\n”, fifo_cnt);
if (fifo_cnt == 0) { k_msleep(20); continue; }
/\* Read FIFO data: family 0x12 index 0x01 returns block of sample bytes.
\* Per app note, each sample is 3 bytes IR + 3 bytes RED + algorithm bytes depending on mode.
\* Here we read raw 6-byte samples one at a time using vendor flow (write 0x12,0x01 then read 1+6)
\*/
const int leds = 3; // IR + RED (add more if you enable LED3)
const bool accel = false; // true if you enable the accel
const bool sample_counter = false; // true if you select 0x05/0x06/0x07
const int sensor_bytes = leds*3 + (accel ? 6 : 0);
const int algo_bytes = 6; // WHRM/MaximFast
const int sample_bytes = sensor_bytes + algo_bytes + (sample_counter ? 1 : 0);
for (int i = 0; i < fifo_cnt; ++i) {
uint8_t s[32]; // big enough for typical configs
if (stability_checked_vendor_fetch(FAMILY_READ_FIFO_COUNT, 0x01, NULL, 0, s, sample_bytes) != 0) {
tprint(“FIFO read failed\n”);
break;
}
size_t o = 0;
if (sample_counter) { uint8_t sc = s[o++]; (void)sc; } // optional
// RAW PPG (big-endian 24-bit; mask to 18 bits for MAX30101)
uint32_t led1 = parse24(&s[o]); o += 3; // usually IR
uint32_t led2 = parse24(&s[o]); o += 3; // usually RED
// if you enabled LED3: uint32_t led3 = parse24(&s[o]); o += 3;
// if accel enabled: int16_t ax = (int16_t)be16(&s[o]); o+=2; … (ay, az)
// ALGORITHM (big-endian)
uint16_t hr10 = be16(&s[o]); o += 2; // HR in 0.1 bpm
uint8_t conf = s[o++];
uint16_t spo210 = be16(&s[o]); o += 2; // SpO2 in 0.1 %
int8_t status = (int8_t)s[o++];
tprint(“IR=%lu RED=%lu HR=%u.%u bpm Conf=%u%% SpO2=%u.%u%% Status=%d\n”,
(unsigned long)led1, (unsigned long)led2,
hr10/10, hr10%10, conf, spo210/10, spo210%10, status);
}
}
return 0;
}