Промышленное производство
Промышленный Интернет вещей | Промышленные материалы | Техническое обслуживание и ремонт оборудования | Промышленное программирование |
home  MfgRobots >> Промышленное производство >  >> Manufacturing Technology >> Производственный процесс

Самодельный точный датчик оксиметра: ИК, Arduino, LCD и руководство по компонентам

Компоненты и материалы

ИК-излучатель, 18° Длина волны 805–1000 нм ×1
Фотодиод LPT80A Широкодиапазонный, должен принимать красный (600 нм) и ИК (940 нм) сигналы ×1
Красный светодиод 5 мм Длина волны около 600 нм ×1
Резистор 10 кОм ×1
Резистор 330 Ом ×2
I2C LCD-дисплей 16×2 (DFRobot или аналог) Для вывода значений ×1
Arduino UNO ×1
Перемычки (набор) ×1
Высокойяркостный белый светодиод Опционально, для улучшения стабильности измерений ×1

Необходимые инструменты

Приложения и онлайн-сервисы

О проекте

В период изоляции я собрал оксиметр из имеющихся деталей. Ведь оксиметр по сути состоит из двух светодиодов и фотодиода.

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

Насыщение кислородом и COVID-19

Мы узнали, что одним из способов оценки дыхательной недостаточности является измерение кислорода в крови. Это делает пульсоксиметр — неинвазивный прибор, надеваемый на палец. Нормальное насыщение (SpO₂) — 95% и выше. Падение ниже 90% при кашле и температуре — повод для беспокойства. Если каждый мейкер сможет собрать оксиметр, раннее обнаружение инфекции станет доступнее, а люди смогут принимать взвешенные решения об обращении к врачу.

Сначала разбираемся с датчиком пульса

Начался проект с модуля KY-039 (ИК-светодиод + фотодиод + два резистора). Пальцем перекрывается луч, и пульсация крови меняет поглощение света. Сигнал с фотодиода считывается через вывод S.

Если нет KY-039, датчик легко собрать самостоятельно из пары светодиодов, фотодиода и трёх резисторов.

Измерение пульса по пикам сигнала

Сигнал зашумлен и слаб. Используется скользящее усреднение и детектирование восходящих фронтов (rise_threshold). Найдя пики, считаем их количество или интервалы для расчёта BPM (ударов в минуту).

Сборка оксиметра (модификация KY-039)

Кровь по-разному поглощает красный (~600 нм) и ИК (~950 нм) свет. Сравнивая показания, можно найти SpO₂. В KY-039 был только ИК-светодиод — добавил красный, отключил ИК от питания и подключил оба к разным пинам Arduino через резисторы 330 Ом.

Схему можно собрать с нуля — это просто два светодиода, фотодиод и три резистора.

Поочерёдно включаем ИК и красный светодиоды, считываем сигнал с одного фотодиода. На графике видно, что ИК-сигнал ниже красного. Важно правильно положить палец: светодиоды к ногтю, фотодиод снизу. Не двигайте пальцем и не меняйте освещение — даже тень искажает данные.

Как рассчитывается SpO₂%

SpO₂ — функция параметра R (по статье из Политехника Милано):

R = ((REDmax - REDmin) / REDmin) / ((IRmax - IRmin) / IRmin)

Нужна калибровка для связи R и SpO₂. Определяем период пульса (мс), делим на частоту дискретизации (40 мс — 20 мс на каждый светодиод), анализируем последние L отсчётов для поиска минимумов и максимумов. R пересчитывается каждый удар.

Калибровка: от R к SpO₂%

Зависимость приближается прямой: SpO₂ = K × R + M. Для нахождения K и M нужен эталонный оксиметр. Два замер: нормальное дыхание и задержка дыхания (SpO₂ падает, R растёт). Решаем систему уравнений. После калибровки прибор показывает BPM и SpO₂. Значения выводятся на LCD, если найдено ≥5 стабильных периодов (разброс ≤10%). Параметр c — число стабильных замеров.

Улучшение: устранение влияния внешнего света

Измерения сильно зависели от освещения. Добавил третий (белый) светодиод, который постоянно светит на палец, а весь сенсор закрыл непрозрачной тканью. Результаты стали стабильнее. Пришлось перекалибровать — теперь прибор быстро и точно показывает BPM и SpO₂%.

Код (oximeter-diy-ver-0.92.ino)

/* * an oximeter diy. v.0.92 (minor fixes) * by hacking a ky-039 heartbeat sensor or using an infrared led * a red led and a photodiode. * https://hackaday.io/project/170752-oximeter-do-it-yourself */ 
#include <Wire.h> 
#include <LiquidCrystal_I2C.h> 
#define maxperiod_siz 80 
#define measures 10 
#define samp_siz 4 
#define rise_threshold 3 
LiquidCrystal_I2C lcd(0x3F, 16, 2); 
int T = 20; 
int sensorPin = A1; 
int REDLed = 3; 
int IRLed = 4; 
byte sym[3][8] = { 
 { B00000, B01010, B11111, B11111, B01110, B00100, B00000, B00000 },
 { B00000, B00000, B00000, B11000, B00100, B01000, B10000, B11100 },
 { B00000, B00100, B01010, B00010, B00100, B00100, B00000, B00100 } 
}; 
void setup() { 
 Serial.begin(9600); 
 Serial.flush(); 
 pinMode(sensorPin,INPUT); 
 pinMode(REDLed,OUTPUT); 
 pinMode(IRLed,OUTPUT); 
 lcd.init(); 
 lcd.backlight(); 
 digitalWrite(REDLed,LOW); 
 digitalWrite(IRLed,LOW); 
 for(int i=0;i<8;i++) lcd.createChar(i, sym[i]); 
} 
void loop () { 
 bool finger_status = true; 
 float readsIR[samp_siz], sumIR,lastIR, reader, start; 
 float readsRED[samp_siz], sumRED,lastRED; 
 int period, samples; 
 period=0; 
 samples=0; 
 int samplesCounter = 0; 
 float readsIRMM[maxperiod_siz],readsREDMM[maxperiod_siz]; 
 int ptrMM =0; 
 for (int i = 0; i < maxperiod_siz; i++) { 
 readsIRMM[i] = 0;readsREDMM[i]=0; 
 } 
 float IRmax=0; float IRmin=0; float REDmax=0; float REDmin=0; 
 double R=0; 
 float measuresR[measures]; 
 int measuresPeriods[measures]; 
 int m = 0; 
 for (int i = 0; i < measures; i++) { 
 measuresPeriods[i]=0; measuresR[i]=0; 
 } 
 int ptr; float beforeIR; bool rising; int rise_count; int n; long int last_beat; 
 for (int i = 0; i < samp_siz; i++) { 
 readsIR[i] = 0; readsRED[i]=0; 
 } 
 sumIR = 0; sumRED=0; ptr = 0; 
 while(1) { 
 digitalWrite(REDLed,LOW); digitalWrite(IRLed,HIGH); 
 n = 0; start = millis(); reader = 0.; 
 do { reader += analogRead (sensorPin); n++; } while (millis() < start + T); 
 reader /= n; 
 sumIR -= readsIR[ptr]; sumIR += reader; readsIR[ptr] = reader; lastIR = sumIR / samp_siz; 
 digitalWrite(REDLed,HIGH); digitalWrite(IRLed,LOW); 
 n = 0; start = millis(); reader = 0.; 
 do { reader += analogRead (sensorPin); n++; } while (millis() < start + T); 
 reader /= n; 
 sumRED -= readsRED[ptr]; sumRED += reader; readsRED[ptr] = reader; lastRED = sumRED / samp_siz; 
 readsIRMM[ptrMM]=lastIR; readsREDMM[ptrMM]=lastRED; ptrMM++; ptrMM %= maxperiod_siz; samplesCounter++; 
 if(samplesCounter>=samples){ 
 samplesCounter =0; IRmax = 0; IRmin=1023; REDmax = 0; REDmin=1023; 
 for(int i=0;i<maxperiod_siz;i++) { 
 if( readsIRMM[i]> IRmax) IRmax = readsIRMM[i]; 
 if( readsIRMM[i]>0 && readsIRMM[i]< IRmin ) IRmin = readsIRMM[i]; readsIRMM[i] =0; 
 if( readsREDMM[i]> REDmax) REDmax = readsREDMM[i]; 
 if( readsREDMM[i]>0 && readsREDMM[i]< REDmin ) REDmin = readsREDMM[i]; readsREDMM[i] =0; 
 } 
 R = ( (REDmax-REDmin) / REDmin) / ( (IRmax-IRmin) / IRmin ) ; 
 } 
 if (lastRED < lastIR) { 
 if(finger_status==true) { finger_status = false; lcd.clear(); lcd.setCursor(0,0); lcd.print("No finger?"); } 
 } else { 
 if(finger_status==false) { lcd.clear(); finger_status = true; lcd.setCursor(10,0); lcd.print("c="); lcd.setCursor(0,0); lcd.print("bpm"); lcd.setCursor(0,1); lcd.print("SpO"); lcd.write(1); lcd.setCursor(10,1); lcd.print("R="); } 
 } 
 float avR = 0; int avBPM=0; 
 if (finger_status==true){ 
 if (lastIR > beforeIR) { 
 rise_count++; 
 if (!rising && rise_count > rise_threshold) { 
 lcd.setCursor(3,0); lcd.write( 0 ); 
 rising = true; 
 measuresR[m] = R; 
 measuresPeriods[m] = millis() - last_beat; 
 last_beat = millis(); 
 int period = 0; 
 for(int i =0; i<measures; i++) period += measuresPeriods[i]; 
 period = period / measures; 
 samples = period / (2*T); 
 int avPeriod = 0; int c = 0; 
 for(int i =1; i<measures; i++) { 
 if ( (measuresPeriods[i] < measuresPeriods[i-1] * 1.1) && (measuresPeriods[i] > measuresPeriods[i-1] / 1.1) ) { 
 c++; avPeriod += measuresPeriods[i]; avR += measuresR[i]; 
 } } 
 m++; m %= measures; 
 lcd.setCursor(12,0); lcd.print(String(c)+" "); 
 avBPM = 60000 / ( avPeriod / c) ; avR = avR / c ; 
 lcd.setCursor(12,1); 
 if(c==0) lcd.print(" "); else lcd.print(String(avR) + " "); 
 if(c > 4) { 
 int SpO2 = -19 * R + 112; 
 lcd.setCursor(4,0); 
 if(avBPM > 40 && avBPM <220) lcd.print(String(avBPM)+" "); 
 lcd.setCursor(4,1); 
 if(SpO2 > 70 && SpO2 <150) lcd.print(" " + String(SpO2) +"% "); 
 } else { 
 if(c <3) { lcd.setCursor(3,0); lcd.write( 2 ); lcd.setCursor(4,1); lcd.write( 2 ); } } 
 } } else { rising = false; rise_count = 0; lcd.setCursor(3,0);lcd.print(" "); } 
 beforeIR = lastIR; 
 } 
 Serial.print(lastIR); Serial.print(","); Serial.print(lastRED); Serial.println(); 
 ptr++; ptr %= samp_siz; 
 } 
}

Схемы

Модифицированный KY-039 или схема с нуля (ИК-светодиод, красный светодиод, фотодиод, 3 резистора). Файл Fritzing: oximeter-diy_oW9ZI5zQtJ.fzz.

Самодельный точный датчик оксиметра: ИК, Arduino, LCD и руководство по компонентам

Производственный процесс

  1. Схема экономии энергии на пассивном ИК-датчике (PIR): проектирование, работа и применение
  2. Какая длина волны LiDAR будет доминировать в автомобильных приложениях?
  3. Построение DIY инфракрасного датчика движения для Raspberry Pi — пошаговое руководство
  4. Управление светодиодом с PIR-датчиком движения на Raspberry Pi
  5. Построение низкобюджетного датчика влажности почвы на Raspberry Pi для умного полива
  6. Тестирование датчика температуры DS18B20 на Raspberry Pi
  7. Создайте домашний монитор температуры на Raspberry Pi с MCP9808, InfluxDB и Grafana
  8. Sensor Telemetry 2.0.1 — продвинутый мониторинг температуры с Azure IoT Hub
  9. Датчик эмоций и ЭЭГ: низкобюджетное железо для детекции эмоций в реальном времени
  10. Точное инфракрасное температурное измерение для цветозависимых процессов