2

I'm trying to make a fairly simple program that interfaces with the Adafruit TSL2591 light sensor as well as a MAX30102 pulse oximeter. They both use I2C for communication. I'm using the recommended libraries for both, Adafruit_TSL2591_Library Version 1.4.3 and SparkFun_MAX3010x_Sensor_Library Version 1.1.2 respectively. This is all running on an Arduino UNO, although I will be transferring it to a NANO in the near future. All of the following is using Version 2.0.2 of the Arduino IDE.

The issue I'm running into is that both sensors will work perfectly fine in isolation, but when trying to run at the same time it locks up and outputs nothing.

Example code that works for the Light sensor.

Example code that works for the pulse oximeter.

I know my wiring is good because the sensors work individually with either program even when both sensors are plugged in simultaneously.

The code that almost works:

#include <Wire.h>
#include "MAX30105.h"
#include "spo2_algorithm.h"
#include <Adafruit_Sensor.h>
#include "Adafruit_TSL2591.h"

MAX30105 particleSensor;

const byte LUX_ADDR = 0x29;
const byte OX_ADDR = 0x57;

Adafruit_TSL2591 tsl = Adafruit_TSL2591(2591);

#define MAX_BRIGHTNESS 255

#if defined(__AVR_ATmega328P__) || defined(__AVR_ATmega168__)
//Arduino Uno doesn't have enough SRAM to store 100 samples of IR led data and red led data in 32-bit format
//To solve this problem, 16-bit MSB of the sampled data will be truncated. Samples become 16-bit data.
uint16_t irBuffer[100]; //infrared LED sensor data
uint16_t redBuffer[100];  //red LED sensor data
#else
uint32_t irBuffer[100]; //infrared LED sensor data
uint32_t redBuffer[100];  //red LED sensor data
#endif

int32_t bufferLength; //data length
int32_t spo2; //SPO2 value
int8_t validSPO2; //indicator to show if the SPO2 calculation is valid
int32_t heartRate; //heart rate value
int8_t validHeartRate; //indicator to show if the heart rate calculation is valid

void setup()
{
  Serial.begin(115200); // initialize serial communication at 115200 bits per second:

  // Initialize sensor
  if (!particleSensor.begin(Wire, I2C_SPEED_FAST, OX_ADDR)) //Use default I2C port, 400kHz speed
  {
    Serial.println(F("MAX30105 was not found. Please check wiring/power."));
    while (1);
  }

  byte ledBrightness = 60; //Options: 0=Off to 255=50mA
  byte sampleAverage = 4; //Options: 1, 2, 4, 8, 16, 32
  byte ledMode = 2; //Options: 1 = Red only, 2 = Red + IR, 3 = Red + IR + Green
  byte sampleRate = 100; //Options: 50, 100, 200, 400, 800, 1000, 1600, 3200
  int pulseWidth = 411; //Options: 69, 118, 215, 411
  int adcRange = 4096; //Options: 2048, 4096, 8192, 16384

  particleSensor.setup(ledBrightness, sampleAverage, ledMode, sampleRate, pulseWidth, adcRange); //Configure sensor with these settings

  light_sensor_details();
  
  light_sensor_configure();

}

void loop()
{
  bufferLength = 100; //buffer length of 100 stores 4 seconds of samples running at 25sps

  //read the first 100 samples, and determine the signal range
  for (byte i = 0 ; i < bufferLength ; i++)
  {
    while (particleSensor.available() == false) //do we have new data?
      particleSensor.check(); //Check the sensor for new data

    redBuffer[i] = particleSensor.getRed();
    irBuffer[i] = particleSensor.getIR();
    particleSensor.nextSample(); //We're finished with this sample so move to next sample

    Serial.print(F("red="));
    Serial.print(redBuffer[i], DEC);
    Serial.print(F(", ir="));
    Serial.println(irBuffer[i], DEC);
  }

  //calculate heart rate and SpO2 after first 100 samples (first 4 seconds of samples)
  maxim_heart_rate_and_oxygen_saturation(irBuffer, bufferLength, redBuffer, &spo2, &validSPO2, &heartRate, &validHeartRate);

  //Continuously taking samples from MAX30102.  Heart rate and SpO2 are calculated every 1 second
  while (1)
  {
    //dumping the first 25 sets of samples in the memory and shift the last 75 sets of samples to the top
    for (byte i = 25; i < 100; i++)
    {
      redBuffer[i - 25] = redBuffer[i];
      irBuffer[i - 25] = irBuffer[i];
    }

    //take 25 sets of samples before calculating the heart rate.
    for (byte i = 75; i < 100; i++)
    {
      while (particleSensor.available() == false) //do we have new data?
        particleSensor.check(); //Check the sensor for new data

      redBuffer[i] = particleSensor.getRed();
      irBuffer[i] = particleSensor.getIR();
      particleSensor.nextSample(); //We're finished with this sample so move to next sample

      //send samples and calculation result to terminal program through UART
      Serial.print(F("red="));
      Serial.print(redBuffer[i], DEC);
      Serial.print(F(", ir="));
      Serial.print(irBuffer[i], DEC);

      Serial.print(F(", HR="));
      Serial.print(heartRate, DEC);

      Serial.print(F(", HRvalid="));
      Serial.print(validHeartRate, DEC);

      Serial.print(F(", SPO2="));
      Serial.print(spo2, DEC);

      Serial.print(F(", SPO2Valid="));
      Serial.println(validSPO2, DEC);
    }

    //After gathering 25 new samples recalculate HR and SP02
    maxim_heart_rate_and_oxygen_saturation(irBuffer, bufferLength, redBuffer, &spo2, &validSPO2, &heartRate, &validHeartRate);
  
    light_sensor_read();
  
  }
}

void light_sensor_details () {

  sensor_t sensor;
  tsl.getSensor(&sensor);

  delay(500);
  
}

void light_sensor_configure () {

  delay(200);
  tsl.setGain(TSL2591_GAIN_MED); 
  delay(200);
  tsl.setTiming(TSL2591_INTEGRATIONTIME_100MS);
  delay(200);
  tsl2591Gain_t gain = tsl.getGain();
  
}

void light_sensor_read() {

  uint32_t lum = tsl.getFullLuminosity();
  uint16_t ir, full;
  ir = lum >> 16;
  full = lum & 0xFFFF;

  Serial.print(F("Lux: ")); Serial.println(tsl.calculateLux(full, ir), 6);
}

The pulse oximeter part will function correctly with the last two lines of setup() and the last line of loop() commented. light_sensor_details();,light_sensor_configure(); and light_sensor_read(); respectively. The output with these two lines commented is as follows:

red=4229, ir=3967
red=4530, ir=3790
red=3629, ir=4104
red=3563, ir=5211
red=4555, ir=9076
red=7091, ir=16753
red=13068, ir=23695
red=10753, ir=11273
...
red=31031, ir=48271, HR=-999, HRvalid=0, SPO2=-999, SPO2Valid=0
red=31044, ir=48283, HR=-999, HRvalid=0, SPO2=-999, SPO2Valid=0
red=31017, ir=48289, HR=-999, HRvalid=0, SPO2=-999, SPO2Valid=0
red=31027, ir=48306, HR=-999, HRvalid=0, SPO2=-999, SPO2Valid=0
red=31022, ir=48291, HR=-999, HRvalid=0, SPO2=-999, SPO2Valid=0
red=31016, ir=48271, HR=-999, HRvalid=0, SPO2=-999, SPO2Valid=0
red=30997, ir=48294, HR=-999, HRvalid=0, SPO2=-999, SPO2Valid=0
red=30981, ir=48268, HR=-999, HRvalid=0, SPO2=-999, SPO2Valid=0
red=30966, ir=48289, HR=-999, HRvalid=0, SPO2=-999, SPO2Valid=0
red=30981, ir=48293, HR=-999, HRvalid=0, SPO2=-999, SPO2Valid=0
...

(An expected output given my finger wasn't on the sensor for this.)

If I uncomment the 2nd last line of setup() then it still manages to show the red and ir values but never starts to show the calculated values. Instead it will occasionally (unpredictably) show chunks of invalid characters, like so:

Going further and uncommenting the other two lines simply locks up and prints nothing to the serial monitor.

Also to clarify the three light sensor related functions do work in their own file, I'll include it here for the sake of completeness:

#include <Wire.h>
#include <Adafruit_Sensor.h>
#include "Adafruit_TSL2591.h"

Adafruit_TSL2591 tsl = Adafruit_TSL2591(2591);

void setup()
{
  Serial.begin(115200); // initialize serial communication at 115200 bits per second:

  light_sensor_details();
  light_sensor_configure();
}

void loop()
{
  light_sensor_read();
}

void light_sensor_details () {

  sensor_t sensor;
  tsl.getSensor(&sensor);

  delay(500);
  
}

void light_sensor_configure () {

  tsl.setGain(TSL2591_GAIN_MED); 
  tsl.setTiming(TSL2591_INTEGRATIONTIME_300MS);

  tsl2591Gain_t gain = tsl.getGain();
  
}

void light_sensor_read() {

  uint32_t lum = tsl.getFullLuminosity();
  uint16_t ir, full;
  ir = lum >> 16;
  full = lum & 0xFFFF;

  Serial.print(F("Lux: ")); Serial.println(tsl.calculateLux(full, ir), 6);
}

Running this outputs:

...
Lux: 41.283641
Lux: 36.879783
Lux: 10.549278
Lux: 2.826355
Lux: 11.158865
Lux: 1.489472
Lux: 1.713600
Lux: 31.461915
Lux: 48.357471
Lux: 50.827312
...

I think the issue is something to do with the I2C bus, but because both sensors use their own libraries for communication I can't use the online examples for manually starting and stopping communication with the Wire library. I can't find anyone else online with this issue or any examples of multiple I2C devices that I'm able to make use of (to my understanding).

Am I on the right track with it being an I2C problem? Is there a simple fix that I'm simply unaware of? Or is there a separate issue with my code?

Many thanks for any help!

Edit: Changed Title

1 Answer 1

3

It turns out the issue was with limited dynamic memory. The MAX30105 requires a lot of space for its calculations and adding anything else, whether that be another sensor or even a software serial connection, would crash it. Even using Serial.println() put it over the edge. It became unstable beyond around 89% full, or about 250 bytes of empty dynamic memory.

I ended up using a 32u4 equipped board to solve the memory issue and it worked perfectly after that.

It had nothing to do with I2C.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.