Загрузка данных
#include <Wire.h>
#include "SSD1306Ascii.h"
#include "SSD1306AsciiWire.h"
#include "MAX30105.h"
#include "heartRate.h"
#include "spo2_algorithm.h"
// Инициализация лёгкого текстового дисплея SSD1306 (занимает всего ~50 байт ОЗУ!)
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 BUFFER_SIZE = 50;
uint16_t irBuffer[BUFFER_SIZE];
uint16_t redBuffer[BUFFER_SIZE];
int32_t spo2 = 0;
int8_t validSPO2 = 0;
int32_t heartRateAlgo = 0;
int8_t validHeartRate = 0;
byte sampleIndex = 0;
// Оценка АД
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() {
Serial.begin(115200);
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 по адресу 0x3C
oled.begin(&Adafruit128x64, 0x3C);
oled.setFont(System5x7);
oled.clear();
// Инициализация MAX30102 (адрес 0x57)
if (!particleSensor.begin(Wire, I2C_SPEED_FAST)) {
oled.println("ERR: MAX30102");
oled.println("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;
}
}
irBuffer[sampleIndex] = (uint16_t)(irValue >> 2);
redBuffer[sampleIndex] = (uint16_t)(redValue >> 2);
sampleIndex++;
if (sampleIndex >= BUFFER_SIZE) {
sampleIndex = 0;
maxim_heart_rate_and_oxygen_saturation(
irBuffer, BUFFER_SIZE, redBuffer,
&spo2, &validSPO2, &heartRateAlgo, &validHeartRate
);
if (beatAvg > 45) {
uint16_t minIR = 65535, maxIR = 0;
for (byte i = 0; i < BUFFER_SIZE; i++) {
if (irBuffer[i] < minIR) minIR = irBuffer[i];
if (irBuffer[i] > maxIR) maxIR = irBuffer[i];
}
uint16_t amplitude = maxIR - minIR;
sysBP = 115 + (beatAvg - 70) * 0.4 + (amplitude / 150.0);
diaBP = 75 + (beatAvg - 70) * 0.25 + (amplitude / 300.0);
sysBP = constrain(sysBP, 85, 190);
diaBP = constrain(diaBP, 55, 120);
}
}
} else {
beatAvg = 0;
validSPO2 = 0;
sampleIndex = 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("MODE ");
oled.print(screenMode + 1);
oled.print("/3 BAT:");
if (batPercent < 100) oled.print(" ");
oled.print(batPercent);
oled.println("%");
oled.println("---------------------");
if (screenMode == 0) {
oled.println("PULSE & SpO2:");
if (!fingerDetected) {
oled.println();
oled.println(" Place finger! ");
oled.println(" ");
} else {
oled.println();
oled.print("HR: ");
if (beatAvg > 0) { oled.print(beatAvg); oled.println(" bpm "); }
else oled.println("--- ");
oled.print("SpO2: ");
if (validSPO2 && spo2 > 70 && spo2 <= 100) { oled.print(spo2); oled.println(" % "); }
else oled.println("calc... ");
}
}
else if (screenMode == 1) {
oled.println("BLOOD PRESSURE (EST):");
if (!fingerDetected || beatAvg == 0) {
oled.println();
oled.println(" Waiting pulse... ");
oled.println(" ");
} else {
oled.set2X();
oled.setCursor(10, 4);
oled.print(sysBP);
oled.print("/");
oled.print(diaBP);
oled.println(" ");
oled.set1X();
oled.setCursor(35, 7);
oled.print("mmHg");
}
}
else if (screenMode == 2) {
oled.println("BATTERY STATUS:");
oled.println();
oled.print("Voltage: ");
oled.print(vbat, 2);
oled.println(" V ");
oled.print("Charge: ");
oled.print(batPercent);
oled.println(" % ");
}
}