В связи с текущей пандемией коронавируса Covid-19 резко возрос спрос на такие электронные устройства как бесконтактные термометры и пульсоксиметры (Pulse-Oximeters). В настоящее время существует достаточно много различающихся по принципу действия пульсоксиметров и на нашем сайте мы уже рассматривали измеритель пульса на Ардуино, но в этой статье мы рассмотрим создание на основе платы Arduino и датчике MAX30100 пульсоксиметра, который по принципу действия и отображения получаемых результатов будет максимально приближен к пульсоксиметрам промышленного изготовления, которые продаются в магазинах. Для этого мы и применили в данном проекте датчик MAX30100 и OLED дисплей. Также мы постарались сделать наш пульсоксиметр максимально компактным, в связи с чем мы спроектировали его на основе платы Arduino Nano.
Также вы можете посмотреть на нашем сайте все проекты, связанные с медициной и, в частности, с проблематикой коронавируса.
Примечание: рассматриваемый в данной статье проект пульсоксиметра представлен в образовательных целях, не рекомендуем его использовать для лечения по настоящему (особенно тяжело) больных людей – используйте для этой цели сертифицированные пульсоксиметры.
Датчик MAX30100
Датчик MAX30100 позволяет производить измерение уровня насыщения крови кислородом (сатурация кислорода в крови, SpO2) и пульса (сердечного ритма) и передавать эту информацию микроконтроллеру по интерфейсу I2C. Таким образом, датчик содержит две интегрированные в него функции – мониторинг пульса и измерение уровня насыщения кислородом крови в неинвазивной форме.
Датчик содержит фотодетекторы и оптические элементы, в которых производится модуляция излучения красного или зеленого светодиодов импульсами. Ток светодиодов можно настроить в диапазоне от 0 до 50mA. На следующем рисунке представлен внешний вид датчика MAX30100.
Датчик работает с напряжениями в диапазоне от 1.8V до 5.5V. Подтягивающие резисторы для контактов интерфейса I2C включены в состав датчика.
OLED дисплей
OLED дисплеи с каждым годом находят все больше применения в современной электронике. В нашем проекте пульсоксиметра мы также решили использовать OLED дисплей поскольку все пульсоксиметры промышленного изготовления в качестве устройства отображения информации используют OLED дисплей. Мы использовали OLED дисплей с диагональю 1.3 дюйма, работающий по интерфейсу I2C. Дисплей поддерживает разрешение 128x32 пикселов и содержит управляющий чип (драйвер) SSD1306. Внешний вид такого дисплея показан на следующем рисунке.
Все проекты с использованием подобного дисплея на нашем сайте вы можете посмотреть по следующей ссылке.
Технические характеристики OLED дисплея SSD1306:
- драйвер микросхемы OLED: SSD1306;
- разрешение: 128 x 64;
- угол зрения: >160°;
- входное напряжение: 3.3V ~ 6V;
- цвет пикселов: синий;
- диапазон рабочих температур: -30°C ~ 70°C.
Необходимые компоненты
- Плата Arduino Nano (купить на AliExpress).
- Два подтягивающих резистора для интерфейса I2C сопротивлением 4,7 кОм (купить на AliExpress).
- Датчик MAX30100 (купить на AliExpress).
- OLED дисплей SSD1306 с разрешением 128x32 (128x64) (купить на AliExpress).
- Источник питания с напряжением 5V, обеспечивающим ток не менее 300mA.
Схема проекта
Схема пульсоксиметра на основе платы Arduino и датчике MAX30100 представлена на следующем рисунке.
Как видите, схема очень проста – контакты A5 и A4 платы Arduino Nano соединены с контактами интерфейса I2C (SDA и SCL) датчика MAX30100 и OLED дисплея с использованием двух подтягивающих резисторов.
Объяснение программы для Arduino
Полный код программы приведен в конце статьи, здесь же мы кратко рассмотрим его основные фрагменты.
Вначале кода программы нам необходимо подключить заголовочные файлы всех используемых библиотек, а их у нас достаточно много: MAX30100 Pulse oximeter – для работы с датчиком MAX30100, Wire.h – для работы с интерфейсом I2C, Adafruit_GFX.h – для отображения анимации на дисплее, Adafruit_SSD1306.h – для работы с драйвером дисплея, Fonts/FreeSerif9pt7b.h – для работы со шрифтами.
1 2 3 4 5 |
#include <Wire.h> #include "MAX30100_PulseOximeter.h" #include <Adafruit_GFX.h> #include <Adafruit_SSD1306.h> #include <Fonts/FreeSerif9pt7b.h> |
Также используем переменную (дефиницию) ENABLE_MAX30100, которая будет использоваться для установки бита доступности (enable bit) модуля MAX30100 в 1. Также устанавливается ширина (SCREEN_WIDTH) и высота (SCREEN_HEIGHT) OLED дисплея.
1 2 3 |
#define ENABLE_MAX30100 1 #define SCREEN_WIDTH 128 // OLED display width, in pixels #define SCREEN_HEIGHT 32 //64 // OLED display height, in pixels |
Также запрограммируем функцию onBeatDetected(), которая будет вызываться при обнаружении пульса.
1 2 3 4 5 |
void onBeatDetected() { Serial.println("Beat!"); heart_beat(&xPos); } |
В функции setup() в нашем проекте осуществляются следующие действия:
- Активация драйвера дисплея для работы по шине I2C.
- Очистка экрана дисплея, установка размера и цвета текста, установка позиции курсора.
- Вывод на экран дисплея приветственного сообщения "Pulse Oximeter".
- Установка позиции для анимации пульса.
- Инициализация датчика MAX30100.
- Установка позиции курсора для вывода значения пульса и уровня насыщения крови кислородом (spo2).
Рассмотрим код каждой из этих операций по отдельности.
Активация драйвера дисплея для работы по шине I2C.
1 2 3 4 5 |
// SSD1306_SWITCHCAPVCC = generate display voltage from 3.3V internally if (!display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS)) { Serial.println(F("SSD1306 allocation failed")); for (;;); // Don't proceed, loop forever } |
Очистка экрана дисплея, установка размера и цвета текста, установка позиции курсора.
1 2 3 4 |
display.clearDisplay(); display.setTextSize(1); display.setTextColor(WHITE); display.setCursor(20, 18); |
Вывод на экран дисплея приветственного сообщения "Pulse Oximeter".
1 |
display.print("Pulse OxiMeter"); |
Установка позиции для анимации пульса.
1 2 3 4 5 6 7 |
int temp1 = 0; int temp2 = 40; int temp3 = 80; heart_beat(&temp1); heart_beat(&temp2); heart_beat(&temp3); xPos = 0; |
Инициализация датчика MAX30100.
1 2 3 4 5 6 7 8 9 10 |
#if ENABLE_MAX30100 // Initialize the PulseOximeter instance // Failures are generally due to an improper I2C wiring, missing power supply // or wrong target chip if (!pox.begin()) { Serial.println("FAILED"); for (;;); } else { Serial.println("SUCCESS"); } |
Установка тока светодиодов в датчике MAX30100 и задание функции вызова при обнаружении пульса.
1 2 3 |
pox.setIRLedCurrent(MAX30100_LED_CURR_7_6MA); // Register a callback for the beat detection pox.setOnBeatDetectedCallback(onBeatDetected); |
Получение значения пульса (BPM) и уровня насыщения крови кислородом (SPO2).
1 |
pox.getHeartRate(); and pox.getSpO2(); |
Переходим к самому интересному - тестированию работы проекта.
Тестирование работы пульсоксиметра (Pulse Oximeter)
Схему проекта мы собрали на компактной предварительно сформированной печатной плате (Vero board). После сборки аппаратной части проекта и загрузки программу в плату Arduino можно приступать к тестированию работы проекта. Без отсутствия данных (палец не прислонен к датчику) на экране OLED дисплея показываются нулевые показания.
Когда мы прислонили палец, на экране отобразился уровень насыщенности кислорода в крови (SPO2) 97% и частота пульса, равная 82 ударам в минуту.
Исходный код программы (скетча)
Комментарии к коду программу кратко поясняют суть отдельных команд.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 |
/* File Name: pulse-oxymeter.ino Created on: 28-Jan-2021 Author: Noyel Seth (noyelseth@gmail.com) */ #include <Wire.h> #include "MAX30100_PulseOximeter.h" #include <Adafruit_GFX.h> #include <Adafruit_SSD1306.h> #include <Fonts/FreeSerif9pt7b.h> #define ENABLE_MAX30100 1 #define SCREEN_WIDTH 128 // ширина OLED дисплея в пикселах #define SCREEN_HEIGHT 32 //64 // высота OLED дисплея в пикселах // объявления для дисплея SSD1306, подключенного по интерфейсу I2C (контакты SDA, SCL) // контакты для интерфейса I2C определены библиотекой Wire. // On an arduino UNO: A4(SDA), A5(SCL) // On an arduino MEGA 2560: 20(SDA), 21(SCL) // On an arduino LEONARDO: 2(SDA), 3(SCL), ... #define OLED_RESET -1 // 4 // сбрасываем контакт # (или -1 если расшариваем контакт сброса Arduino) #define SCREEN_ADDRESS 0x3c ///< See datasheet for Address; 0x3D for 128x64, 0x3C for 128x32 Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET); #if ENABLE_MAX30100 #define REPORTING_PERIOD_MS 5000 // PulseOximeter is the higher level interface to the sensor // it offers: // * beat detection reporting // * heart rate calculation // * SpO2 (oxidation level) calculation PulseOximeter pox; #endif uint32_t tsLastReport = 0; int xPos = 0; // Callback (registered below) fired when a pulse is detected (вызывается когда обнаруживается импульс) void onBeatDetected() { Serial.println("Beat!"); heart_beat(&xPos); } void setup() { Serial.begin(115200); Serial.println("SSD1306 128x64 OLED TEST"); // SSD1306_SWITCHCAPVCC = generate display voltage from 3.3V internally if (!display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS)) { Serial.println(F("SSD1306 allocation failed")); for (;;); // не продолжаем дальше, бесконечный цикл } // Show initial display buffer contents on the screen -- // the library initializes this with an Adafruit splash screen. //display.display(); display.clearDisplay(); display.setTextSize(1); display.setTextColor(WHITE); display.setCursor(20, 18); // отображаем статический текст display.print("Pulse OxiMeter"); int temp1 = 0; int temp2 = 40; int temp3 = 80; heart_beat(&temp1); heart_beat(&temp2); heart_beat(&temp3); xPos = 0; display.display(); delay(2000); // пауза 2 секунды display.cp437(true); display.clearDisplay(); Serial.print("Initializing pulse oximeter.."); #if ENABLE_MAX30100 // инициализируем объект пульсоксиметра (PulseOximeter) // сбои обычно происходят из-за неправильного подключения I2C, не подачи питания // или неправильного чипа назначения if (!pox.begin()) { Serial.println("FAILED"); for (;;); } else { Serial.println("SUCCESS"); } // ток по умолчанию для IR LED (инфракрасного светодиода) составляет 50mA, но он может быть изменен // by uncommenting the following line. Check MAX30100_Registers.h for all the // available options. pox.setIRLedCurrent(MAX30100_LED_CURR_7_6MA); // Register a callback for the beat detection pox.setOnBeatDetectedCallback(onBeatDetected); display_data(0, 0); #endif } void loop() { #if ENABLE_MAX30100 // Make sure to call update as fast as possible pox.update(); int bpm = 0; int spo2 = 0; // Asynchronously dump heart rate and oxidation levels to the serial // For both, a value of 0 means "invalid" if (millis() - tsLastReport > REPORTING_PERIOD_MS) { //Serial.print("Heart rate:"); bpm = pox.getHeartRate(); spo2 = pox.getSpO2(); Serial.println(bpm); //Serial.print("bpm / SpO2:"); Serial.println(spo2); //Serial.println("%"); tsLastReport = millis(); display_data(bpm, spo2); } #endif drawLine(&xPos); } void display_data(int bpm, int spo2) { display.fillRect(0, 18, 127, 15, SSD1306_BLACK); display.setTextSize(1); display.setTextColor(WHITE); display.setCursor(0, 18); // Display static text display.print("PRbpm "); display.print(bpm); display.display(); display.setTextSize(1); display.setTextColor(WHITE); display.setCursor(64, 18); // Display static text display.print("%Sp02 "); display.println(spo2); display.display(); } void drawLine(int *x_pos) { // Draw a single pixel in white display.drawPixel(*x_pos, 8, SSD1306_WHITE); display.drawPixel((*x_pos)++, 8, SSD1306_WHITE); display.drawPixel((*x_pos)++, 8, SSD1306_WHITE); display.drawPixel((*x_pos)++, 8, SSD1306_WHITE); display.drawPixel((*x_pos), 8, BLACK); // ----- //Serial.println(*x_pos); display.fillRect(*x_pos, 0, 31, 16, SSD1306_BLACK); display.display(); delay(1); if (*x_pos >= SCREEN_WIDTH) { *x_pos = 0; } } void heart_beat(int *x_pos) { /************************************************/ //display.clearDisplay(); display.fillRect(*x_pos, 0, 30, 15, SSD1306_BLACK); // Draw a single pixel in white display.drawPixel(*x_pos + 0, 8, SSD1306_WHITE); display.drawPixel(*x_pos + 1, 8, SSD1306_WHITE); display.drawPixel(*x_pos + 2, 8, SSD1306_WHITE); display.drawPixel(*x_pos + 3, 8, SSD1306_WHITE); display.drawPixel(*x_pos + 4, 8, BLACK); // ----- //display.display(); //delay(1); display.drawPixel(*x_pos + 5, 7, SSD1306_WHITE); display.drawPixel(*x_pos + 6, 6, SSD1306_WHITE); display.drawPixel(*x_pos + 7, 7, SSD1306_WHITE); // .~. //display.display(); //delay(1); display.drawPixel(*x_pos + 8, 8, SSD1306_WHITE); display.drawPixel(*x_pos + 9, 8, SSD1306_WHITE); // -- //display.display(); //delay(1); /******************************************/ display.drawPixel(*x_pos + 10, 8, SSD1306_WHITE); display.drawPixel(*x_pos + 10, 9, SSD1306_WHITE); display.drawPixel(*x_pos + 11, 10, SSD1306_WHITE); display.drawPixel(*x_pos + 11, 11, SSD1306_WHITE); //display.display(); //delay(1); /******************************************/ display.drawPixel(*x_pos + 12, 10, SSD1306_WHITE); display.drawPixel(*x_pos + 12, 9, SSD1306_WHITE); display.drawPixel(*x_pos + 12, 8, SSD1306_WHITE); display.drawPixel(*x_pos + 12, 7, SSD1306_WHITE); //display.display(); //delay(1); display.drawPixel(*x_pos + 13, 6, SSD1306_WHITE); display.drawPixel(*x_pos + 13, 5, SSD1306_WHITE); display.drawPixel(*x_pos + 13, 4, SSD1306_WHITE); display.drawPixel(*x_pos + 13, 3, SSD1306_WHITE); //display.display(); //delay(1); display.drawPixel(*x_pos + 14, 2, SSD1306_WHITE); display.drawPixel(*x_pos + 14, 1, SSD1306_WHITE); display.drawPixel(*x_pos + 14, 0, SSD1306_WHITE); display.drawPixel(*x_pos + 14, 0, SSD1306_WHITE); //display.display(); //delay(1); /******************************************/ display.drawPixel(*x_pos + 15, 0, SSD1306_WHITE); display.drawPixel(*x_pos + 15, 1, SSD1306_WHITE); display.drawPixel(*x_pos + 15, 2, SSD1306_WHITE); display.drawPixel(*x_pos + 15, 3, SSD1306_WHITE); //display.display(); //delay(1); display.drawPixel(*x_pos + 15, 4, SSD1306_WHITE); display.drawPixel(*x_pos + 15, 5, SSD1306_WHITE); display.drawPixel(*x_pos + 16, 6, SSD1306_WHITE); display.drawPixel(*x_pos + 16, 7, SSD1306_WHITE); //display.display(); //delay(1); display.drawPixel(*x_pos + 16, 8, SSD1306_WHITE); display.drawPixel(*x_pos + 16, 9, SSD1306_WHITE); display.drawPixel(*x_pos + 16, 10, SSD1306_WHITE); display.drawPixel(*x_pos + 16, 11, SSD1306_WHITE); //display.display(); //delay(1); display.drawPixel(*x_pos + 17, 12, SSD1306_WHITE); display.drawPixel(*x_pos + 17, 13, SSD1306_WHITE); display.drawPixel(*x_pos + 17, 14, SSD1306_WHITE); display.drawPixel(*x_pos + 17, 15, SSD1306_WHITE); //display.display(); //delay(1); display.drawPixel(*x_pos + 18, 15, SSD1306_WHITE); display.drawPixel(*x_pos + 18, 14, SSD1306_WHITE); display.drawPixel(*x_pos + 18, 13, SSD1306_WHITE); display.drawPixel(*x_pos + 18, 12, SSD1306_WHITE); //display.display(); //delay(1); display.drawPixel(*x_pos + 19, 11, SSD1306_WHITE); display.drawPixel(*x_pos + 19, 10, SSD1306_WHITE); display.drawPixel(*x_pos + 19, 9, SSD1306_WHITE); display.drawPixel(*x_pos + 19, 8, SSD1306_WHITE); //display.display(); //delay(1); /****************************************************/ display.drawPixel(*x_pos + 20, 8, SSD1306_WHITE); display.drawPixel(*x_pos + 21, 8, SSD1306_WHITE); //display.display(); //delay(1); /****************************************************/ display.drawPixel(*x_pos + 22, 7, SSD1306_WHITE); display.drawPixel(*x_pos + 23, 6, SSD1306_WHITE); display.drawPixel(*x_pos + 24, 6, SSD1306_WHITE); display.drawPixel(*x_pos + 25, 7, SSD1306_WHITE); //display.display(); //delay(1); /************************************************/ display.drawPixel(*x_pos + 26, 8, SSD1306_WHITE); display.drawPixel(*x_pos + 27, 8, SSD1306_WHITE); display.drawPixel(*x_pos + 28, 8, SSD1306_WHITE); display.drawPixel(*x_pos + 29, 8, SSD1306_WHITE); display.drawPixel(*x_pos + 30, 8, SSD1306_WHITE); // ----- *x_pos = *x_pos + 30; display.display(); delay(1); } |
Добрый день, есть ли у вас структурная схема и электрическая-принципиальная для данного пульсоксиметра с пояснениями?
Добрый вечер. К сожалению нет, все что есть изложено в статье.
Как резисторы подключить по схеме этой?
Ну вроде на схеме показано куда их нужно подключить. В чем конкретно вопрос?
Захотел собрать Вашу схему пульсокиметра,но! Компиляция останавливается на 240 строке и все.пишет:
"hulsoksimetr^240:3:error:expected ";"before "display"
exit stanus 1 и все.а жаль хотел бы собрать.
В 236 строке была пропущена ";" в конце строки, исправил опечатку, теперь подобной ошибки возникать не должно