Hello,
I am trying to integrate a ProtoCentral MAX30001 ECG/BioZ breakout board with an nRF52840 DK using the nRF Connect SDK and the ProtoCentral Zephyr driver:
https://github.com/Protocentral/protocentral_max30001_zephyr_driver
My environment is:
- Board: nRF52840 DK
- nRF Connect SDK: v3.3.1
- Zephyr: v4.3.99
- Target:
nrf52840dk/nrf52840 - MAX30001 communication: SPI Mode 0, 1 MHz
- Breakout supply: 3 V from the nRF52840 DK
My wiring is:
- MAX30001 SCK → P1.15
- MAX30001 MOSI → P1.13
- MAX30001 MISO → P1.14
- MAX30001 CS → P1.12
- MAX30001 INT1 → P1.10
- MAX30001 VCC → 3.3 V
- MAX30001 GND → GND
- FCLK is not connected because the breakout has an onboard clock source
- INT2 is not connected
SPI communication appears to work correctly. I can read the device ID consistently:
MAX30001 ID: 52 00 00
MAX30001 detected, ID=0x52
The original Zephyr driver only accepted 0x54, so I changed the ID check to accept both 0x52 and 0x54:
if ((chip_id[0] != 0x52) &&
(chip_id[0] != 0x54)) {
LOG_ERR("MAX30001 not found, ID=0x%02X", chip_id[0]);
return -ENODEV;
}
The sensor initializes, ECG and R-to-R detection start, and I can read heart-rate values. However, the results are very unstable. My actual heart rate is approximately 76 BPM, but the output jumps between values such as:
HR = 58 BPM
HR = 119 BPM
HR = 60 BPM
HR = 187 BPM
HR = 33 BPM
HR = 170 BPM
HR waiting...
Sometimes the same value is printed repeatedly. Looking at the driver, SENSOR_CHAN_HR appears to return the cached lastHR value, even if a new RRINT event has not occurred:
case SENSOR_CHAN_HR:
val->val1 = data->lastHR;
break;
The HR calculation in the driver is:
data->lastRRI = (uint16_t)(max30001_rtor >> 10) * 8;
data->lastHR = (uint16_t)(60 * 1000 / data->lastRRI);
Therefore, I am not sure how the application should distinguish a new heartbeat from a previously cached HR value.
I initially used the overlay configuration from the driver README:
max30001: max30001@0 {
compatible = "maxim,max30001";
status = "okay";
reg = <0>;
spi-max-frequency = <1000000>;
intb-gpios = <&gpio1 10 GPIO_ACTIVE_LOW>;
ecg-enabled;
rtor-enabled;
ecg-gain = <3>;
ecg-invert;
};
According to the binding file, ecg-gain = <3> means 160 V/V. I suspect this may be amplifying mains noise and motion artifacts. I am now planning to test this configuration:
max30001: max30001@0 {
compatible = "maxim,max30001";
status = "okay";
reg = <0>;
spi-max-frequency = <1000000>;
intb-gpios = <&gpio1 10 GPIO_ACTIVE_LOW>;
ecg-enabled;
rtor-enabled;
ecg-gain = <0>;
};
This removes ecg-invert, disables BioZ, and selects 20 V/V ECG gain.
I am using three electrodes with the following placement:
- LA: below the left collarbone
- RA: below the right collarbone
- RL: lower-right abdomen
The raw ECG waveform is also quite noisy and appears to contain significant mains-frequency interference. The R-to-R detector may therefore be interpreting noise or motion artifacts as R peaks.
I would appreciate help with the following questions:
- Is
0x52a valid expected device ID for this ProtoCentral MAX30001 breakout revision? - What ECG gain and polarity settings are recommended for an initial three-electrode heart-rate test?
- Should internal R-bias be enabled or disabled when using the breakout’s LA, RA, and RL connections?
- Are
CNFG_RTOR1 = 0x3FC600and the driver’s default RTOR settings suitable for this setup? - How should a Zephyr application determine whether a new R-to-R event occurred instead of repeatedly reading the cached
lastHRvalue? - Should INT1 generate a GPIO interrupt for every new R-to-R event, or is polling the STATUS register every 8 ms acceptable?
- Has this driver been tested with nRF Connect SDK v3.3.1 / Zephyr v4.3.99? The repository mentions testing with Zephyr 3.5 and 3.6.
- Are there any known issues in this driver that could cause incorrect RTOR or ECG FIFO decoding?
The SPI bus and chip detection appear to be working, so I believe the remaining problem is related to ECG signal quality, register configuration, or the driver’s handling of new R-to-R events.
Thank you for any guidance.