Блок с датойБлок с временемБлок с возрастом сайта
Mr.ALB

    Анатолий Беляев (aka Mr.ALB). Персональный сайт

    Да пребудут с вами Силы СВЕТА!

     

    Ардуино (Arduino). #30

    Простенький пульсоксиметр. Измеряет пульс и сатурацию (насыщенность крови кислородом) по кончику пальца.

    Подразделы



    ПульсОксиметр на MAX-30100

    Модуль датчика MAX-30100 позволяет измерять количество ударов сердца в минуту и сатурацию (насыщенность крови кислородом) используя два светодиода: красный и инфракрасный.

    Появился у меня такой интересный модуль с датчиком MAX-30100, поискал примеры в Интернете, в основном они под новые датчики, но нашёл и под этот.

    Запустил пример, вроде бы тоже для начинающих, но там оказалось много недочётов и ошибок. Пришлось всё переделать. Потом добавить небольшой OLED дисплейчик с управлением на I2C. В итоге можете сами посмотреть, что получилось.

    Вся схема соединений описана в самом скетче, собственно ничего сложного. И модуль с датчиком и дисплей подключаются по двум проводам протокола I2C. Для лучшей работы и провод с SCL и с SDA желательно подтянуть к +3,3В через резисторы 4,7кОм.

    Питание и модуля датчика и дисплея от напряжения +3,3В. Поэтому если использовать Arduino Pro Mini, то понадобится стабилизатор на это напряжение. Хорошим выбором служит стабилизатор КМОП 7533 с низким током потребления в режиме покоя.


    Скетч

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

    Для дисплея вставил код его проверки по знакоместам (строки 095...117). Можно согласно комментария его изменять и смотреть как выводится информация на ваш дисплей, но для этого, соответственно, требуется раскомментировать строчку 031.

    Если раскомментировать строку 029, то можно симулировать измеренное значение, для просмотра как оно выводится на дисплей. Значения симуляции смотрите/изменяйте в строках 250...254.

    /**********************************************************
      2022-07-22 Mr.ALB Повторение устройства
      Источник: https://arduinoplus.ru/arduino-max30100/
    
      2022-07-22 Mr.ALB Исправление недочётов и ошибок, 
      проверка работы:
      - В зависимости от положения пальца может сильно завирать
      Измеряет 35 bpm/93%, Реально по промышленному 56 bpm/98%
      Ошибка +21 bpm/+5%
      Палец надо держать на расстоянии 1...2 мм от датчика
      - Добавил коррекцию для ударов сердца и для %SpO2 
      - Добавил OLED дисплей для автономного устройства
      - Добавил условную компиляцию, для автономного устройства
      с отключением Serial
    
      Скетч использует 20362 байт на Arduino Uno без Serial
      Архив: https://disk.yandex.ru/d/sEJNMexM9dQOng
     *********************************************************/
    #include <Wire.h>
    #include <MAX30100.h>
    #include <MAX30100_BeatDetector.h>
    #include <MAX30100_Filters.h>
    #include <MAX30100_PulseOximeter.h>
    #include <MAX30100_Registers.h>
    #include <MAX30100_SpO2Calculator.h>
    #include <Adafruit_GFX.h>
    #include <Adafruit_SSD1306.h>
    
    //#define SIMULATION
    //#define SERIAL
    //#define TEST_OLED
    
    #define  OLED_RESET 4
    // Создадим объект дисплея
    Adafruit_SSD1306 oled(OLED_RESET);
    
    #define REPORTING_PERIOD_MS 500
    
    // Создадим объект пульсоксиметра
    PulseOximeter pox;
    
    const int numReadings = 10;
    float filterweight = 0.5;
    uint32_t tsLastReport = 0;
    uint32_t last_beat = 0;
    byte readIndex = 0;
    byte average_beat = 0;
    byte average_SpO2 = 0;
    bool calculation_complete = false;
    bool calculating = false;
    bool initialized = false;
    byte beat = 0;
    
    byte correct_beat = 0;
    byte correct_SpO2 = 3;
    
    /************************************
       Прототипы функций
     ***********************************/
    void onBeatDetected();
    void viewBeat();
    void initial_display();
    void display_calculating(byte);
    void display_values();
    void calculate_average(byte, byte);
    
    /*************************
       Функция настройки>
     ************************/
    void setup()
    {
    #ifdef SERIAL
      Serial.begin(9600);
    #endif
    
      pox.begin();  // Старт пульоксиметра
      
      // Установка вызова функции определения ударов сердца
      pox.setOnBeatDetectedCallback(onBeatDetected);
    
      // Инициализация дисплея
      oled.begin(SSD1306_SWITCHCAPVCC, 0x3C);
      oled.setTextColor(WHITE);
      oled.display();
      delay(2000);
      
      oled.clearDisplay();
      oled.setTextSize(2);
      oled.setCursor(10, 16);
      oled.print("MAX-30100");
      oled.setTextSize(1);
      oled.setCursor(20, 48);
      oled.print("Pulse-Oximeter");
    
    #ifdef TEST_OLED
      // Тест экрана
      // Знакоместо: 6(ш) на 8(в) пикселей
      // setTextSize(1): 21 колонок в 8 строк
      // setTextSize(2): 10 колонок в 4 строки
      oled.setTextSize(2);
      byte count = 0;
      for (byte j = 0; j < 4; j++)
      {
        // Если setTextSize(1), то
        // i < 122
        for (byte i = 0; i < 120; i += 12)
        {
          // Если setTextSize(1), то
          // j * 8
          oled.setCursor(i, j * 16);
          oled.print(count++);
          if (count > 9)count = 0;
        }
      }
      oled.display();       // Покажем
      while(1);
    #endif
    
      oled.display();       // Покажем
    }
    
    /*****************************
       Функция основного цикла
     ****************************/
    void loop()
    {
      pox.update();
      if
      (
        (millis() - tsLastReport > REPORTING_PERIOD_MS) &&
        !calculation_complete
      )
      {
        calculate_average(pox.getHeartRate(), pox.getSpO2());
        tsLastReport = millis();
      }
    
      if ((millis() - last_beat > 5000))
      {
        calculation_complete = false;
        average_beat = 0;
        average_SpO2 = 0;
        initial_display();
      }
    }
    
    /*************************************
       Функция определения ударов сердца
     ************************************/
    // Calls back when pulse is detected
    // Перезванивает при обнаружении импульса
    void onBeatDetected() 
    {
      viewBeat();
      last_beat = millis();
    }
    
    /**********************************
       Функция ударов сердца
     *********************************/
    void viewBeat()
    {
      beat = !beat;
    #ifdef SERIAL  
      if (!beat)
        Serial.print("_");
      else
        Serial.print("^");
    #endif    
    }
    
    /************************************
       Функция начального сообщения
     ************************************/
    void initial_display()
    {
      if (!initialized)
      {
        viewBeat();
        initialized = true;
        
    #ifdef SERIAL
        Serial.println("");
        Serial.println(F("MAX30100 Pulse-Oximeter"));
        Serial.println(F("***********************************"));
        Serial.println(F("  Place your finger on the sensor"));
        Serial.println(F("***********************************"));
    #endif
    
        oled.clearDisplay();
        oled.setTextSize(1);
        oled.setCursor(32, 24);
        oled.print("Finger out");
        oled.display();
        oled.clearDisplay();
      }
    }
    
    /**********************************
       Функция показа измерения
     *********************************/
    void display_calculating(byte j)
    {
      viewBeat();
      oled.setTextSize(2);
      oled.setCursor(8, 16);
      oled.print("Measuring");
      oled.setCursor(0, 36);
      
    #ifdef SERIAL
      Serial.println("");
      Serial.print(F("Measuring "));
    #endif
    
      for (byte i = 0; i < j; i++)
      {
    #ifdef SERIAL
        Serial.print(".");
    #endif    
        oled.print(".");
      }
      oled.display();
    }
    
    
    /**************************************
       Функция показа измеренных значений
     *************************************/
    void display_values()
    {
      if (average_SpO2 > 100)
        average_SpO2 = 100;
      
    #ifdef SERIAL
      Serial.println("");
      Serial.print(F("Pulse = "));
      Serial.print(average_beat);
      Serial.print(F(" bpm, "));
      Serial.print(F(" SpO2 = "));
      Serial.print(average_SpO2);
      Serial.println(F("%"));
      Serial.println("");
    #endif
    
      oled.clearDisplay();  // Сотрём информацию с дисплея
      byte col;             // Положение слева
      byte row = 16;        // Положение строки
      byte sizeF = 3;       // Размер шрифта
    
    #ifdef SIMULATION
      // Симуляция значений для настройки
      average_beat = 108;
      average_SpO2 = 97;
    #endif
    
      // Настройка позиционирования
      if (average_beat >= 100 && average_SpO2 == 100)
      {
        sizeF = 2;
        col = 12;
      }
      if (average_beat < 100 && average_SpO2 < 100)
        col = 16;
      if
      (
        (average_beat >= 100 && average_SpO2 < 100) ||
        (average_beat < 100 && average_SpO2 == 100)
      )
        col = 4;
        
      // Вывод на дисплей измеренных значений
      oled.setCursor(col, row);
      oled.setTextSize(sizeF);
      oled.print(average_beat);
      oled.setTextSize(1);
      oled.print("bpm ");
      oled.setTextSize(sizeF);
      oled.print(average_SpO2);
      oled.setTextSize(1);
      oled.print("%");
      oled.display();       // Покажем
    }
    
    /******************************************
       Функция вычисления среднего значения
     *****************************************/
    void calculate_average(byte beat, byte SpO2)
    {
      if
      (
        !calculation_complete && beat > 30 &&
        beat < 220 &&
        SpO2 > 50
      )
      {
        // Усреднение значение измерения
        average_beat = filterweight * (beat) +
                       (1 - filterweight ) * average_beat;
        average_SpO2 = filterweight * (SpO2) +
                       (1 - filterweight ) * average_SpO2;
        readIndex++;
        // Показ процесса измерения
        display_calculating(readIndex);
      }
    
      // Переставил сюда, было до предыдущего if
      if (readIndex == numReadings)
      {
        calculation_complete = true;
        calculating = false;
        initialized = false;
        readIndex = 0;
        
        // Коррекция значений
        // Коэфф. коррекции при калибровке
        average_beat += correct_beat;
        average_SpO2 += correct_SpO2;
        display_values(); // Показ измеренных значений
      }
    }
    

    Макет пульсоксиметра

    Данный проект не подразумевал изготовление какой-то определённой конструкции, поэтому собирался на макетной плате, в которую вставил модуль датчика и модуль дисплея. Проводами-перемычками подключил к Arduino Uno.

    Однако в любой момент можно сделать и небольшое автономное устройство, так как на дисплее выводится вся необходимая информация для удобного использования этого пульсоксиметра.

    Автономный приборчик, думаю, лучше всего собирать на Arduino Pro Mini.

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

    Макет пульсоксиметра на MAX-30100. Начальная заставка
    Pic 1. Макет пульсоксиметра на MAX-30100. Начальная заставка

    Далее выводится надпись Finger out – что указывает на отсутствие пальца на датчике.

    Макет пульсоксиметра на MAX-30100. Нет пальца
    Pic 2. Макет пульсоксиметра на MAX-30100. Нет пальца

    Если приложить палец к датчику, то при его обнаружении на дисплее выводится надпись Measuring – измерение. Далее снизу этой надписи выводятся точки каждого измерения. В скетче установлено 10 измерений – выводится 10 точек.

    Макет пульсоксиметра на MAX-30100. Измерение
    Pic 3. Макет пульсоксиметра на MAX-30100. Измерение

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

    Макет пульсоксиметра на MAX-30100. Измерение
    Pic 4. Макет пульсоксиметра на MAX-30100. Измерение

    Когда определён результат, то на дисплее выводятся згачения пульса и сатурации. Замечу, что от разного положения пальца могут быть разные результаты, поэтому стоит произвести измерение несколько раз, подобрав оптимальное положение пальца относительно датчика.

    Макет пульсоксиметра на MAX-30100. Результат
    Pic 5. Макет пульсоксиметра на MAX-30100. Результат

    В общем приборчик показывает результаты совпадающие с промышленным пульсоксиметром.

    Макет пульсоксиметра на MAX-30100. Результат
    Pic 6. Макет пульсоксиметра на MAX-30100. Результат


    Версия v1.1. Слишком много отнимает ресурсов библиотека Adafruit_SSD1306.h, изменил скетч для работы через библиотеку от Gyver-а GyverOLED.h. Код скетча сократился почти в два раза и живенько так всё работает. Скетч в том же архиве. В нём применена условная компиляция, т.е. можно включить любую библиотеку и посмотреть результаты работы.

    Приложение

    Материалы для повторения:

    Анатолий Беляев.

    . Mr.ALB
    Предыдущая страница Страница 31 Далее