Несколько уникальных устройств I2C создают помехи

Я пытаюсь создать довольно простую программу, взаимодействующую с датчиком освещенности Adafruit TSL2591, а также < href="https://www.amazon.ca/MAX30102-Detection-Concentration-Compatible-Arduino/dp/B07ZQNC8XP">пульсоксиметр MAX30102. Они оба используют I2C для связи. Я использую рекомендованные библиотеки для обеих версий: Adafruit_TSL2591_Library Версия 1.4.3 и SparkFun_MAX3010x_Sensor_Library Версия 1.1.2 соответственно. Все это работает на Arduino UNO, хотя в ближайшем будущем я перенесу это на NANO. Во всем следующем используется версия 2.0.2 среды разработки Arduino.

Проблема, с которой я столкнулся, заключается в том, что оба сенсора будут отлично работать по отдельности, но при попытке запуска одновременно они блокируются и ничего не выводят.

Пример кода, который работает для датчика освещенности.

Пример кода, который работает для пульсоксиметра.

Я знаю, что моя проводка исправна, потому что датчики работают индивидуально с любой программой, даже если оба датчика подключены одновременно.

Код, который почти работает:

#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 недостаточно SRAM для хранения 100 выборок данных ИК-светодиодов и данных красных светодиодов в 32-битном формате
//Чтобы решить эту проблему, 16-битный старший бит выборочных данных будет усечен. Сэмплы становятся 16-битными данными.
uint16_t irBuffer[100]; //данные инфракрасного светодиодного датчика
uint16_t redBuffer[100];  //данные датчика красного светодиода
#else
uint32_t irBuffer[100]; //данные инфракрасного светодиодного датчика
uint32_t redBuffer[100];  //данные датчика красного светодиода
#endif

int32_t bufferLength; //длина данных
int32_t spo2; //значение SPO2
int8_t validSPO2; //индикатор, показывающий, верен ли расчет SPO2
int32_t heartRate; //значение сердечного ритма
int8_t validHeartRate; //индикатор, показывающий, верен ли расчет сердечного ритма

void setup()
{
  Serial.begin(115200); // инициализируем последовательную связь со скоростью 115200 бит в секунду:

  // Инициализировать датчик
  if (!particleSensor.begin(Wire, I2C_SPEED_FAST, OX_ADDR)) // Использовать порт I2C по умолчанию, скорость 400 кГц
  {
    Serial.println(F("MAX30105 was not found. Please check wiring/power."));
    while (1);
  }

  byte ledBrightness = 60; //Опции: от 0=Выкл. до 255=50 мА
  byte sampleAverage = 4; //Опции: 1, 2, 4, 8, 16, 32
  byte ledMode = 2; //Опции: 1 = только красный, 2 = красный + ИК, 3 = красный + ИК + зеленый
  byte sampleRate = 100; //Опции: 50, 100, 200, 400, 800, 1000, 1600, 3200
  int pulseWidth = 411; //Опции: 69, 118, 215, 411
  int adcRange = 4096; //Опции: 2048, 4096, 8192, 16384

  particleSensor.setup(ledBrightness, sampleAverage, ledMode, sampleRate, pulseWidth, adcRange); //Настройте датчик с этими настройками

  light_sensor_details();
  
  light_sensor_configure();

}

void loop()
{
  bufferLength = 100; // длина буфера 100 хранит 4 секунды сэмплов, работающих со скоростью 25sps

  //читаем первые 100 выборок и определяем диапазон сигнала
  for (byte i = 0 ; i < bufferLength ; i++)
  {
    while (particleSensor.available() == false) //есть ли у нас новые данные?
      particleSensor.check(); //Проверяем датчик на наличие новых данных

    redBuffer[i] = particleSensor.getRed();
    irBuffer[i] = particleSensor.getIR();
    particleSensor.nextSample(); //Мы закончили с этим образцом, поэтому переходим к следующему образцу

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

  //рассчитать частоту сердечных сокращений и SpO2 после первых 100 образцов (первые 4 секунды образцов)
  maxim_heart_rate_and_oxygen_saturation(irBuffer, bufferLength, redBuffer, &spo2, &validSPO2, &heartRate, &validHeartRate);

  //Постоянно берем образцы с MAX30102. ЧСС и SpO2 рассчитываются каждую 1 секунду
  while (1)
  {
    //сбрасываем первые 25 наборов сэмплов в память и сдвигаем последние 75 наборов сэмплов наверх
    for (byte i = 25; i < 100; i++)
    {
      redBuffer[i - 25] = redBuffer[i];
      irBuffer[i - 25] = irBuffer[i];
    }

    //возьмем 25 наборов образцов перед расчетом частоты сердечных сокращений.
    for (byte i = 75; i < 100; i++)
    {
      while (particleSensor.available() == false) //есть ли у нас новые данные?
        particleSensor.check(); //Проверяем датчик на наличие новых данных

      redBuffer[i] = particleSensor.getRed();
      irBuffer[i] = particleSensor.getIR();
      particleSensor.nextSample(); //Мы закончили с этим образцом, поэтому переходим к следующему образцу

      //отправляем образцы и результат расчета в терминальную программу через 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);
    }

    //После сбора 25 новых образцов пересчитываем HR и 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);
}

Пульсоксиметр будет работать правильно, если последние две строки setup() и последняя строка loop() будут прокомментированы. light_sensor_details();,light_sensor_configure(); и light_sensor_read(); соответственно. Вывод с комментариями этих двух строк выглядит следующим образом:

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
...

(Ожидаемый результат, учитывая, что мой палец не был на датчике для этого.)

Если я раскомментирую 2-ю последнюю строку setup(), тогда ему все равно удастся отобразить значения красного и ir, но никогда не начнут показывать вычисленные значения. Вместо этого время от времени (непредсказуемо) будут отображаться фрагменты недопустимых символов, например:

Двигаясь дальше и раскомментируя две другие строки, мы просто блокируемся и ничего не выводим на последовательный монитор.

Кроме того, чтобы уточнить, что три функции, связанные с датчиком освещенности, работают в отдельном файле, я включу его сюда для полноты картины:

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

Adafruit_TSL2591 tsl = Adafruit_TSL2591(2591);

void setup()
{
  Serial.begin(115200); // инициализируем последовательную связь со скоростью 115200 бит в секунду:

  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);
}

Выполнение этих выходных данных:

...
Люкс: 41.283641
Люкс: 36.879783
Люкс: 10.549278
Люкс: 2.826355
Люкс: 11.158865
Люкс: 1.489472
Люкс: 1.713600
Люкс: 31.461915
Люкс: 48.357471
Люкс: 50.827312
...

Я думаю, что проблема связана с шиной I2C, но поскольку оба датчика используют свои собственные библиотеки для связи, я не могу использовать онлайн-примеры для ручного запуска и остановки связи с Wire. библиотека. Я не могу найти кого-либо еще в Интернете с этой проблемой или какие-либо примеры нескольких устройств I2C, которые я могу использовать (насколько я понимаю).

Правильно ли я понимаю, что это проблема I2C? Есть ли простое решение, о котором я просто не знаю? Или это отдельная проблема с моим кодом?

Большое спасибо за любую помощь!

Изменить: изменено название

, 👍2


1 ответ


Лучший ответ:

3

Оказывается, проблема была в ограниченной динамической памяти. MAX30105 требует много места для своих вычислений, и добавление чего-либо еще, будь то еще один датчик или даже программное последовательное соединение, приведет к его сбою. Даже использование Serial.println() делает его чрезмерным. Она стала нестабильной после заполнения около 89 %, или около 250 байт пустой динамической памяти.

В итоге я использовал плату с 32u4, чтобы решить проблему с памятью, и после этого она отлично заработала.

Это не имело никакого отношения к I2C.

,