Загрузка данных
#include <Wire.h>
#include "SSD1306Ascii.h"
#include "SSD1306AsciiWire.h"
#include "MAX30105.h"
#include "heartRate.h"
SSD1306AsciiWire oled;
MAX30105 particleSensor;
// Пины по схеме
const int PIN_INT = 2; // D2 — прерывание MAX30102
const int PIN_BTN = 3; // D3 — кнопка SW2 (к GND)
const int PIN_VBAT = A0; // A0 — делитель R1/R2 (100k / 100k)
// Переменные для расчёта пульса (BPM)
const byte RATE_SIZE = 4;
byte rates[RATE_SIZE];
byte rateSpot = 0;
long lastBeat = 0;
float beatsPerMinute = 0;
int beatAvg = 0;
// Переменные для расчёта SpO2 и оценки АД без перегрузки ОЗУ
const byte SAMPLE_WINDOW = 50;
byte sampleCount = 0;
uint32_t minIR = 0xFFFFFFFF, maxIR = 0;
uint32_t minRed = 0xFFFFFFFF, maxRed = 0;
uint32_t sumIR = 0, sumRed = 0;
int spo2 = 0;
bool validSPO2 = false;
int sysBP = 120;
int diaBP = 80;
// Напряжение аккумулятора
float vbat = 0.0;
int batPercent = 0;
// Управление экранами
byte screenMode = 0; // 0: HR + SpO2, 1: BP (АД), 2: Battery
byte lastScreenMode = 255;
unsigned long lastBtnPress = 0;
unsigned long lastDisplayUpdate = 0;
void setup() {
pinMode(PIN_INT, INPUT_PULLUP);
pinMode(PIN_BTN, INPUT_PULLUP);
Wire.begin();
Wire.setClock(400000L);
// Отключаем внутренние подтяжки 5В на A4/A5 (согласно п. 5 примечаний)
digitalWrite(SDA, LOW);
digitalWrite(SCL, LOW);
oled.begin(&Adafruit128x64, 0x3C);
oled.setFont(System5x7);
oled.clear();
if (!particleSensor.begin(Wire, I2C_SPEED_FAST)) {
oled.println(F("ERR: MAX30102"));
oled.println(F("Check I2C wiring!"));
while (1);
}
particleSensor.setup(60, 4, 2, 100, 411, 4096);
}
void loop() {
// 1. Опрос кнопки SW2 (D3)
if (digitalRead(PIN_BTN) == LOW) {
if (millis() - lastBtnPress > 250) {
screenMode = (screenMode + 1) % 3;
lastBtnPress = millis();
oled.clear();
updateDisplay(particleSensor.getIR() > 50000);
}
}
// 2. Чтение датчика MAX30102
particleSensor.check();
while (particleSensor.available()) {
uint32_t irValue = particleSensor.getFIFOIR();
uint32_t redValue = particleSensor.getFIFORed();
particleSensor.nextSample();
if (irValue > 50000) {
// Детектор пульса
if (checkForBeat(irValue) == true) {
long delta = millis() - lastBeat;
lastBeat = millis();
beatsPerMinute = 60 / (delta / 1000.0);
if (beatsPerMinute < 220 && beatsPerMinute > 40) {
rates[rateSpot++] = (byte)beatsPerMinute;
rateSpot %= RATE_SIZE;
beatAvg = 0;
for (byte x = 0; x < RATE_SIZE; x++) beatAvg += rates[x];
beatAvg /= RATE_SIZE;
}
}
// Накопление минимумов/максимумов на лету (экономит 1.5 КБ ОЗУ!)
if (irValue < minIR) minIR = irValue;
if (irValue > maxIR) maxIR = irValue;
if (redValue < minRed) minRed = redValue;
if (redValue > maxRed) maxRed = redValue;
sumIR += (irValue >> 4);
sumRed += (redValue >> 4);
sampleCount++;
// Каждые 50 выборок вычисляем SpO2 и оценку АД
if (sampleCount >= SAMPLE_WINDOW) {
float acIR = (float)(maxIR - minIR);
float acRed = (float)(maxRed - minRed);
float dcIR = (float)(sumIR / SAMPLE_WINDOW);
float dcRed = (float)(sumRed / SAMPLE_WINDOW);
if (dcIR > 0 && dcRed > 0 && acIR > 20 && acRed > 20) {
// Классическая формула оксиметрии через отношение R
float R = (acRed / dcRed) / (acIR / dcIR);
int calcSpo2 = (int)(110.0 - 25.0 * R);
if (calcSpo2 >= 70 && calcSpo2 <= 100) {
spo2 = constrain(calcSpo2, 80, 99);
validSPO2 = true;
}
}
// Оценка АД по ЧСС и амплитуде пульсовой волны (п. 4 примечаний)
if (beatAvg > 45) {
sysBP = 115 + (beatAvg - 70) * 0.4 + (acIR / 600.0);
diaBP = 75 + (beatAvg - 70) * 0.25 + (acIR / 1200.0);
sysBP = constrain(sysBP, 85, 190);
diaBP = constrain(diaBP, 55, 120);
}
// Сброс для следующего окна измерений
sampleCount = 0;
minIR = 0xFFFFFFFF; maxIR = 0;
minRed = 0xFFFFFFFF; maxRed = 0;
sumIR = 0; sumRed = 0;
}
} else {
beatAvg = 0;
validSPO2 = false;
sampleCount = 0;
minIR = 0xFFFFFFFF; maxIR = 0;
minRed = 0xFFFFFFFF; maxRed = 0;
sumIR = 0; sumRed = 0;
}
}
// 3. Обновление экрана каждые 500 мс
if (millis() - lastDisplayUpdate > 500) {
lastDisplayUpdate = millis();
readBattery();
updateDisplay(particleSensor.getIR() > 50000);
}
}
void readBattery() {
int raw = analogRead(PIN_VBAT);
vbat = (raw * 5.0 / 1023.0) * 2.0;
batPercent = map((long)(vbat * 100), 330, 420, 0, 100);
batPercent = constrain(batPercent, 0, 100);
}
void updateDisplay(bool fingerDetected) {
if (screenMode != lastScreenMode) {
oled.clear();
lastScreenMode = screenMode;
}
oled.set1X();
oled.setCursor(0, 0);
oled.print(F("MODE "));
oled.print(screenMode + 1);
oled.print(F("/3 BAT:"));
if (batPercent < 100) oled.print(F(" "));
oled.print(batPercent);
oled.println(F("%"));
oled.println(F("---------------------"));
if (screenMode == 0) {
oled.println(F("PULSE & SpO2:"));
if (!fingerDetected) {
oled.println();
oled.println(F(" Place finger! "));
oled.println(F(" "));
} else {
oled.println();
oled.print(F("HR: "));
if (beatAvg > 0) { oled.print(beatAvg); oled.println(F(" bpm ")); }
else oled.println(F("--- "));
oled.print(F("SpO2: "));
if (validSPO2) { oled.print(spo2); oled.println(F(" % ")); }
else oled.println(F("calc... "));
}
}
else if (screenMode == 1) {
oled.println(F("BLOOD PRESSURE (EST):"));
if (!fingerDetected || beatAvg == 0) {
oled.println();
oled.println(F(" Waiting pulse... "));
oled.println(F(" "));
} else {
oled.set2X();
oled.setCursor(10, 4);
oled.print(sysBP);
oled.print(F("/"));
oled.print(diaBP);
oled.println(F(" "));
oled.set1X();
oled.setCursor(35, 7);
oled.print(F("mmHg"));
}
}
else if (screenMode == 2) {
oled.println(F("BATTERY STATUS:"));
oled.println();
oled.print(F("Voltage: "));
oled.print(vbat, 2);
oled.println(F(" V "));
oled.print(F("Charge: "));
oled.print(batPercent);
oled.println(F(" % "));
}
}