Using Two MAX30102 Sensors on Arduino MKR Zero Without a Multiplexer

Hi everyone,

I'm trying to read data from two MAX30102 sensors on an Arduino MKR Zero, but I’m running into several issues. I’d like a solution without using a multiplexer (if there’s no way around it, I can consider one). Here’s what I’ve tried so far: Hardware Setup

Sensor Bus SDA SCL VIN GND
MAX30102 #1 Wire (main I²C) D11 D12 5(3.3)V GND
MAX30102 #2 Wire1 (SERCOM3) D0 (PA22) D1 (PA23) 5(3.3) V GND

I defined a second I²C bus using SERCOM3 on the SAMD21, following the MKR Zero documentation: #include <Wire.h>

#include "wiring_private.h"
#include "MAX30105.h"

#define SDA1_PIN 0
#define SCL1_PIN 1

TwoWire Wire1(&sercom3, SDA1_PIN, SCL1_PIN);
void SERCOM3_Handler() { Wire1.onService(); }

void setup() {
  Serial.begin(115200);
  while (!Serial);

  // Main I2C bus
  Wire.begin();
  Wire.setClock(400000);

  // Configure SERCOM pins
  pinPeripheral(SDA1_PIN, PIO_SERCOM_ALT);
  pinPeripheral(SCL1_PIN, PIO_SERCOM_ALT);

  // Secondary I2C bus
  Wire1.begin();
  Wire1.setClock(400000);

  // Initialize sensors
  if (sensor1.begin(Wire, I2C_SPEED_STANDARD)) {
      Serial.println("Sensor 1 found");
  } else {
      Serial.println("Sensor 1 not found");
  }

  if (sensor2.begin(Wire1, I2C_SPEED_STANDARD)) {
      Serial.println("Sensor 2 found");
  } else {
      Serial.println("Sensor 2 not found");
  }
}

Observations

  • The first sensor on Wire works perfectly: readings are correct and consistent.

  • The second sensor on Wire1 is never detected.

  • I tried both PIO_SERCOM and PIO_SERCOM_ALT.

  • I verified wiring, common GND, and power.

  • Sensor #1: SDA → D11, SCL → D12, VIN → 5(3.3)V, GND → GND
    Sensor #2: SDA → D0, SCL → D1, VIN → 5(3.3)V, GND → GND I also tried an I²C scan on the second sensor.

  • With VCC at 5 V (same as the first sensor), it does not start scanning.

  • With VCC at 3.3 V (from the MKR Zero), scanning starts but still does not detect the address. Problems Encountered

  1. Both MAX30102s have the same I²C address (0x57), so they cannot coexist on the same bus simultaneously.
  2. Extra pins on the MKR Zero (D0/D1) do not have internal pull-ups, so the secondary bus is unstable.
  3. SoftWire cannot handle the speed required by the sensors.
  4. Even with SERCOM, the second bus does not respond. Question

Is there a reliable way to get two MAX30102 sensors working on the MKR Zero without using a multiplexer?
Has anyone successfully used SERCOM or other techniques to bypass the address conflict, or are we forced to use a multiplexer for simultaneous readings?

From what I understand, the way you're attempting this should work. I don't know why it isn't. With 2x i2c ports, no multiplexer should be necessary.

So you added external pull-ups, right?

You mean the second bus? And what was the result?

PIO_SERCOM definitely seems corerct so me so that it goes to peripheral C which means PA22 --> SERCOM 3.00 and PA23 -> SERCOM 3.01. Otherwise they would be at SERCOM 5.

In reference to https://github.com/arduino/ArduinoCore-samd/blob/1.8.14/variants/mkrzero/variant.cpp.

Your code does not compile

'sensor1' was not declared in this scope

Can you please post the actual code you are trying to use.

Oh, the pinPeripheralis definitely nullified by Wire1.begin() because it does a pinPeripheral call in there too.

You need to change that in your variant.cpp, changing it away from the PIO_DIGITAL to PIO_SERCOM.

I changed the variant.cpp and now it reads sensor 2 (wire1) while sensor 1 on the main wire does not (before it was the opposite: it read the one on the main wire and not the one on wire1). I tried changing 3 MAX30102 sensors on the wire but none of the three work (I hope they are not defective).
this is my full sketch :

#include <Wire.h>
#include "wiring_private.h"
#include "MAX30105.h"
#include "heartRate.h"
#include "spo2_algorithm.h"
#include <SD.h>
#include <SPI.h>

// -----------------------------
// Secondo bus I2C (SERCOM3) su pin D0/D1 (PA22/PA23)
// -----------------------------
#define SDA1_PIN 0  // D0 → PA22
#define SCL1_PIN 1  // D1 → PA23
TwoWire Wire1(&sercom3, SDA1_PIN, SCL1_PIN);
void SERCOM3_Handler() { Wire1.onService(); }

// -----------------------------
// Sensori MAX30102
// -----------------------------
MAX30105 sensor1;
MAX30105 sensor2;
bool sensor1Ready = false;
bool sensor2Ready = false;

// -----------------------------
// SD card
// -----------------------------
File logfile;

// -----------------------------
// Buffer e variabili
// -----------------------------
const int bufferLength = 100;
uint32_t irBuffer1[bufferLength];
uint32_t redBuffer1[bufferLength];
uint32_t irBuffer2[bufferLength];
uint32_t redBuffer2[bufferLength];

int32_t spo2_1, spo2_2;
int8_t validSPO2_1, validSPO2_2;
int32_t heartRate1, heartRate2;
int8_t validHeartRate1, validHeartRate2;

// =======================================================
void setup() {
  Serial.begin(115200);
  while (!Serial);
  delay(1000);
  Serial.println("== Avvio doppio MAX30102 + SD (400 kHz) ==");

  // ---- SD card ----
  if (!SD.begin(SDCARD_SS_PIN)) {
    Serial.println("⚠️ Nessuna SD trovata, continuo senza salvataggio.");
  } else {
    logfile = SD.open("bio.csv", FILE_WRITE);
    if (logfile) {
      logfile.println("IR1,RED1,BPM1,SpO2_1,IR2,RED2,BPM2,SpO2_2");
      logfile.flush();
      Serial.println("💾 SD pronta, salvataggio attivo.");
    } else {
      Serial.println("⚠️ Errore apertura bio.csv, continuo senza salvataggio.");
    }
  }

  // ---- Primo bus I2C (Wire) ----
  Wire.begin();
  Wire.setClock(400000);

  // ---- Secondo bus I2C (Wire1 / SERCOM3) ----
  pinPeripheral(SDA1_PIN, PIO_SERCOM);   // <<--- cambiato
  pinPeripheral(SCL1_PIN, PIO_SERCOM);   // <<--- cambiato

  Wire1.begin();
  Wire1.setClock(400000);

  // ---- Inizializzazione sensori ----
  if (sensor1.begin(Wire, I2C_SPEED_STANDARD)) {
    sensor1.setup();
    sensor1Ready = true;
    Serial.println("✅ Sensore 1 inizializzato (Wire).");
  } else {
    Serial.println("❌ Sensore 1 non trovato.");
  }

  if (sensor2.begin(Wire1, I2C_SPEED_STANDARD)) {
    sensor2.setup();
    sensor2Ready = true;
    Serial.println("✅ Sensore 2 inizializzato (Wire1 su D0/D1).");
  } else {
    Serial.println("❌ Sensore 2 non trovato su Wire1.");
  }

  if (!sensor1Ready && !sensor2Ready) {
    Serial.println("❌ Nessun sensore trovato. Controlla collegamenti!");
    while (1);
  }

  Serial.println("Setup completato.\n");
}

// =======================================================
void loop() {
  if (sensor1Ready) {
    for (int i = 0; i < bufferLength; i++) {
      while (!sensor1.available()) sensor1.check();
      redBuffer1[i] = sensor1.getRed();
      irBuffer1[i] = sensor1.getIR();
      sensor1.nextSample();
    }
    maxim_heart_rate_and_oxygen_saturation(
      irBuffer1, bufferLength, redBuffer1,
      &spo2_1, &validSPO2_1,
      &heartRate1, &validHeartRate1
    );
  }

  if (sensor2Ready) {
    for (int i = 0; i < bufferLength; i++) {
      while (!sensor2.available()) sensor2.check();
      redBuffer2[i] = sensor2.getRed();
      irBuffer2[i] = sensor2.getIR();
      sensor2.nextSample();
    }
    maxim_heart_rate_and_oxygen_saturation(
      irBuffer2, bufferLength, redBuffer2,
      &spo2_2, &validSPO2_2,
      &heartRate2, &validHeartRate2
    );
  }

  if (sensor1Ready) {
    Serial.print("S1 -> HR: "); Serial.print(heartRate1); Serial.print(" bpm, SpO2: "); Serial.println(spo2_1);
  }
  if (sensor2Ready) {
    Serial.print("S2 -> HR: "); Serial.print(heartRate2); Serial.print(" bpm, SpO2: "); Serial.println(spo2_2);
  }

  if (logfile && (sensor1Ready || sensor2Ready)) {
    for (int i = 0; i < bufferLength; i++) {
      if (sensor1Ready) {
        logfile.print(irBuffer1[i]); logfile.print(","); logfile.print(redBuffer1[i]); logfile.print(",");
        logfile.print(heartRate1); logfile.print(","); logfile.print(spo2_1);
      }
      logfile.print(",");
      if (sensor2Ready) {
        logfile.print(irBuffer2[i]); logfile.print(","); logfile.print(redBuffer2[i]); logfile.print(",");
        logfile.print(heartRate2); logfile.print(","); logfile.print(spo2_2);
      }
      logfile.println();
    }
    logfile.flush();
  }

  Serial.println("-----------------------------------");
  delay(1000);
}

Mhm, makes no sense. I2C / Wire1 operation on SERCOM3 should not disturb the previously working SERCOM2 / Wire.

Also nothing else uses SERCOM3.

Exactly. I'm starting to suspect that I may have burned some sensors because during my tests I set both to 5V instead of 3.3V, but I don't know because I have the module that has internal pull-ups that regulate it (but I don't know if this only applies to the standard SDA and SCL, so I set it to 3.3V). However, before, when I put them together (even with 3.3V), the first sensor on the main wire started, but now that the second one starts (wire 1), the first one doesn't start anymore (wire)

Actually I'd advise you do undo all changes in the variant.cpp or variant.h that you might have done, you can in fact use pinPeripheral to change the mode, but it has to be after you called Wire1.begin(). You do it before.

This is mentioned in the documentation https://learn.adafruit.com/using-atsamd21-sercom-to-add-more-spi-i2c-serial-ports/creating-a-new-wire.

Maybe you have some other modification that prevent it from working right now. So a clean uninstall and install of the SAMD core in the Arduino IDE board manager might be best.

#include <Wire.h>
#include "wiring_private.h"
#include "MAX30105.h"
#include "heartRate.h"
#include "spo2_algorithm.h"
#include <SD.h>
#include <SPI.h>

// -----------------------------
// Secondo bus I2C (SERCOM3) su pin D0/D1 (PA22/PA23)
// -----------------------------
#define SDA1_PIN 0  // D0 → PA22
#define SCL1_PIN 1  // D1 → PA23
TwoWire Wire1(&sercom3, SDA1_PIN, SCL1_PIN);
void SERCOM3_Handler() { Wire1.onService(); }

// -----------------------------
// Sensori MAX30102
// -----------------------------
MAX30105 sensor1;
MAX30105 sensor2;
bool sensor1Ready = false;
bool sensor2Ready = false;

// -----------------------------
// SD card
// -----------------------------
File logfile;

// -----------------------------
// Buffer e variabili
// -----------------------------
const int bufferLength = 100;
uint32_t irBuffer1[bufferLength];
uint32_t redBuffer1[bufferLength];
uint32_t irBuffer2[bufferLength];
uint32_t redBuffer2[bufferLength];

int32_t spo2_1, spo2_2;
int8_t validSPO2_1, validSPO2_2;
int32_t heartRate1, heartRate2;
int8_t validHeartRate1, validHeartRate2;

// =======================================================
void setup() {
  Serial.begin(115200);
  while (!Serial);
  delay(1000);
  Serial.println("== Avvio doppio MAX30102 + SD (400 kHz) ==");

  // ---- SD card ----
  if (!SD.begin(SDCARD_SS_PIN)) {
    Serial.println("⚠️ Nessuna SD trovata, continuo senza salvataggio.");
  } else {
    logfile = SD.open("bio.csv", FILE_WRITE);
    if (logfile) {
      logfile.println("IR1,RED1,BPM1,SpO2_1,IR2,RED2,BPM2,SpO2_2");
      logfile.flush();
      Serial.println("💾 SD pronta, salvataggio attivo.");
    } else {
      Serial.println("⚠️ Errore apertura bio.csv, continuo senza salvataggio.");
    }
  }

  // ---- Primo bus I2C (Wire) ----
  Wire.begin();
  Wire.setClock(400000);

  // ---- Secondo bus I2C (Wire1 / SERCOM3) ----
  Wire1.begin();
  Wire1.setClock(400000);
  pinPeripheral(SDA1_PIN, PIO_SERCOM);   // <<--- cambiato
  pinPeripheral(SCL1_PIN, PIO_SERCOM);   // <<--- cambiato

  // ---- Inizializzazione sensori ----
  if (sensor1.begin(Wire, I2C_SPEED_STANDARD)) {
    sensor1.setup();
    sensor1Ready = true;
    Serial.println("✅ Sensore 1 inizializzato (Wire).");
  } else {
    Serial.println("❌ Sensore 1 non trovato.");
  }

  if (sensor2.begin(Wire1, I2C_SPEED_STANDARD)) {
    sensor2.setup();
    sensor2Ready = true;
    Serial.println("✅ Sensore 2 inizializzato (Wire1 su D0/D1).");
  } else {
    Serial.println("❌ Sensore 2 non trovato su Wire1.");
  }

  if (!sensor1Ready && !sensor2Ready) {
    Serial.println("❌ Nessun sensore trovato. Controlla collegamenti!");
    while (1);
  }

  Serial.println("Setup completato.\n");
}

// =======================================================
void loop() {
  if (sensor1Ready) {
    for (int i = 0; i < bufferLength; i++) {
      while (!sensor1.available()) sensor1.check();
      redBuffer1[i] = sensor1.getRed();
      irBuffer1[i] = sensor1.getIR();
      sensor1.nextSample();
    }
    maxim_heart_rate_and_oxygen_saturation(
      irBuffer1, bufferLength, redBuffer1,
      &spo2_1, &validSPO2_1,
      &heartRate1, &validHeartRate1
    );
  }

  if (sensor2Ready) {
    for (int i = 0; i < bufferLength; i++) {
      while (!sensor2.available()) sensor2.check();
      redBuffer2[i] = sensor2.getRed();
      irBuffer2[i] = sensor2.getIR();
      sensor2.nextSample();
    }
    maxim_heart_rate_and_oxygen_saturation(
      irBuffer2, bufferLength, redBuffer2,
      &spo2_2, &validSPO2_2,
      &heartRate2, &validHeartRate2
    );
  }

  if (sensor1Ready) {
    Serial.print("S1 -> HR: "); Serial.print(heartRate1); Serial.print(" bpm, SpO2: "); Serial.println(spo2_1);
  }
  if (sensor2Ready) {
    Serial.print("S2 -> HR: "); Serial.print(heartRate2); Serial.print(" bpm, SpO2: "); Serial.println(spo2_2);
  }

  if (logfile && (sensor1Ready || sensor2Ready)) {
    for (int i = 0; i < bufferLength; i++) {
      if (sensor1Ready) {
        logfile.print(irBuffer1[i]); logfile.print(","); logfile.print(redBuffer1[i]); logfile.print(",");
        logfile.print(heartRate1); logfile.print(","); logfile.print(spo2_1);
      }
      logfile.print(",");
      if (sensor2Ready) {
        logfile.print(irBuffer2[i]); logfile.print(","); logfile.print(redBuffer2[i]); logfile.print(",");
        logfile.print(heartRate2); logfile.print(","); logfile.print(spo2_2);
      }
      logfile.println();
    }
    logfile.flush();
  }

  Serial.println("-----------------------------------");
  delay(1000);
}

i changed the variant.cpp and i reset it to default. I put the PInPeriperhal after wire1.begin but change nothing.
The snsor1 started but the sensor2 not. I also set the clock to 100KHz instead of 400KHz but didn't work. I had also put a delay between sensor1 and sensor2 . I don't understand why only the sensor1 started

#include <Wire.h>
#include "wiring_private.h"
#include "MAX30105.h"
#include "heartRate.h"
#include "spo2_algorithm.h"
#include <SD.h>
#include <SPI.h>


// Secondo bus I2C (SERCOM3) su pin D0/D1 (PA22/PA23)

#define SDA1_PIN 0  // D0 → PA22
#define SCL1_PIN 1  // D1 → PA23
TwoWire Wire1(&sercom3, SDA1_PIN, SCL1_PIN);
void SERCOM3_Handler() { Wire1.onService(); }


// Sensori MAX30102

MAX30105 sensor1;
MAX30105 sensor2;
bool sensor1Ready = false;
bool sensor2Ready = false;


// SD card
File logfile ;

// Buffer e variabili
const int bufferLength = 128;
uint32_t irBuffer1[bufferLength];
uint32_t redBuffer1[bufferLength];
uint32_t irBuffer2[bufferLength];
uint32_t redBuffer2[bufferLength];

int32_t spo2_1, spo2_2;
int8_t validSPO2_1, validSPO2_2;
int32_t heartRate1, heartRate2;
int8_t validHeartRate1, validHeartRate2;

// =======================================================
void setup() {
  Serial.begin(115200);
  while (!Serial);
  delay(1000);
  Serial.println("== Avvio doppio MAX30102 + SD (400 kHz) ==");

  // ---- SD card ----
  if (!SD.begin(SDCARD_SS_PIN)) {
    Serial.println(" Nessuna SD trovata, continuo senza salvataggio.");
  } else {
    logfile = SD.open("bio.csv", FILE_WRITE);
    if (logfile) {
      logfile.println("IR1,RED1,BPM1,SpO2_1,IR2,RED2,BPM2,SpO2_2");
      logfile.flush();
      Serial.println(" SD pronta, salvataggio attivo.");
    } else {
      Serial.println(" Errore apertura bio.csv, continuo senza salvataggio.");
    }
  }

  // ---- Primo bus I2C (Wire) ----
  Wire.begin();
  Wire.setClock(400000);
  delay(100);
  // ---- Secondo bus I2C (Wire1 / SERCOM3) ----
  Wire1.begin();
  Wire1.setClock(400000);
  pinPeripheral(SDA1_PIN, PIO_SERCOM);   // <-- usiamo PIO_SERCOM
  pinPeripheral(SCL1_PIN, PIO_SERCOM);   // <-- usiamo PIO_SERCOM
  delay (100) ;
  // ---- Inizializzazione sensori ----
  if (sensor1.begin(Wire, I2C_SPEED_STANDARD)) {
    sensor1.setup();
    sensor1Ready = true;
    Serial.println(" Sensore 1 inizializzato (Wire).");
  } else {
    Serial.println(" Sensore 1 non trovato.");
  }

  if (sensor2.begin(Wire1, I2C_SPEED_STANDARD)) {
    sensor2.setup();
    sensor2Ready = true;
    Serial.println(" Sensore 2 inizializzato (Wire1 su D0/D1).");
  } else {
    Serial.println(" Sensore 2 non trovato su Wire1.");
  }

  if (!sensor1Ready && !sensor2Ready) {
    Serial.println(" Nessun sensore trovato. Controlla collegamenti!");
    while (1);
  }

  Serial.println("Setup completato.\n");
}

// =======================================================
void loop() {
  if (sensor1Ready) {
    for (int i = 0; i < bufferLength; i++) {
      while (!sensor1.available()) sensor1.check();
      redBuffer1[i] = sensor1.getRed();
      irBuffer1[i] = sensor1.getIR();
      sensor1.nextSample();
    }
    maxim_heart_rate_and_oxygen_saturation(
      irBuffer1, bufferLength, redBuffer1,
      &spo2_1, &validSPO2_1,
      &heartRate1, &validHeartRate1
    );
  }

  if (sensor2Ready) {
    for (int i = 0; i < bufferLength; i++) {
      while (!sensor2.available()) sensor2.check();
      redBuffer2[i] = sensor2.getRed();
      irBuffer2[i] = sensor2.getIR();
      sensor2.nextSample();
    }
    maxim_heart_rate_and_oxygen_saturation(
      irBuffer2, bufferLength, redBuffer2,
      &spo2_2, &validSPO2_2,
      &heartRate2, &validHeartRate2
    );
  }

  if (sensor1Ready) {
    Serial.print("S1 -> HR: "); Serial.print(heartRate1); Serial.print(" bpm, SpO2: "); Serial.println(spo2_1);
  }
  if (sensor2Ready) {
    Serial.print("S2 -> HR: "); Serial.print(heartRate2); Serial.print(" bpm, SpO2: "); Serial.println(spo2_2);
  }

  if (logfile && (sensor1Ready || sensor2Ready)) {
    for (int i = 0; i < bufferLength; i++) {
      if (sensor1Ready) {
        logfile.print(irBuffer1[i]); logfile.print(","); logfile.print(redBuffer1[i]); logfile.print(",");
        logfile.print(heartRate1); logfile.print(","); logfile.print(spo2_1);
      }
      logfile.print(",");
      if (sensor2Ready) {
        logfile.print(irBuffer2[i]); logfile.print(","); logfile.print(redBuffer2[i]); logfile.print(",");
        logfile.print(heartRate2); logfile.print(","); logfile.print(spo2_2);
      }
      logfile.println();
    }
    logfile.flush();
  }

  Serial.println("-----------------------------------");
  delay(1000);
}