Несколько уникальных устройств 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? Есть ли простое решение, о котором я просто не знаю? Или это отдельная проблема с моим кодом?
Большое спасибо за любую помощь!
Изменить: изменено название
@Jackson V, 👍2
1 ответ
Лучший ответ:
Оказывается, проблема была в ограниченной динамической памяти. MAX30105 требует много места для своих вычислений, и добавление чего-либо еще, будь то еще один датчик или даже программное последовательное соединение, приведет к его сбою. Даже использование Serial.println()
делает его чрезмерным. Она стала нестабильной после заполнения около 89 %, или около 250 байт пустой динамической памяти.
В итоге я использовал плату с 32u4, чтобы решить проблему с памятью, и после этого она отлично заработала.
Это не имело никакого отношения к I2C.
- Последовательная связь между несколькими устройствами (или ардуино)
- Соединение I2C с модулем камеры MT9D111, странные результаты после записи регистров через i2C
- Как узнать частоту дискретизации?
- Что такое Serial.begin(9600)?
- Использовать все контакты как цифровые входы/выходы
- Float печатается только 2 десятичных знака после запятой
- Arduino как USB HID
- Отправка и получение различных типов данных через I2C в Arduino