#include <LiquidCrystal_I2C.h>
// ---------- PIN CONFIG ----------
const int PIN_TRIG = 2; // HC-SR04 trigger
const int PIN_ECHO = 3; // HC-SR04 echo
//const int PIN_BUZZ = 13; // Buzzer or LED indicator
// ---------- LCD (I2C) ----------
LiquidCrystal_I2C lcd(0x27, 16, 2); // Use 0x3F if 0x27 doesn't work
// ---------- TUNEABLE PARAMETERS ----------
const float SONAR_SMOOTHING = 0.1; // EMA alpha for ultrasonic
const unsigned long REFRACTORY_MS = 800; // Min ms between breaths
const unsigned long BREATH_WINDOW_SECONDS = 45; // Sliding window for BPM calculation
const float SONAR_DELTA_THRESHOLD = 1.2; // Min cm change for breath detection
const int ALARM_RR_LOW = 8; // Low respiratory rate alarm
const int ALARM_RR_HIGH = 30; // High respiratory rate alarm
// ---------- REALISTIC PHOTODIODE SIMULATION ----------
const int SIM_BASELINE = 300; // Baseline value (dark)
const int SIM_PEAK = 700; // Peak value during inhalation
const int SIM_INHALATION_TIME = 1500; // Time for inhalation (ms)
const int SIM_EXHALATION_TIME = 2500; // Time for exhalation (ms)
const int SIM_BREATH_HOLD = 500; // Brief pause between breaths (ms)
const int SIM_NOISE_LEVEL = 8; // Realistic noise level
const int SIM_CARDIAC_OSCILLATION = 3; // Small cardiac oscillations
const int SIM_DRIFT_RANGE = 15; // Slow baseline drift range
// ---------- GLOBAL STATE ----------
// Ultrasonic sensor
float sonar_ema = 0.0;
float sonar_min = 999, sonar_max = -999;
// Realistic photodiode simulation
int simulated_photo_value = SIM_BASELINE;
unsigned long last_photo_update = 0;
unsigned long last_cardiac_osc = 0;
unsigned long last_drift_update = 0;
int current_drift = 0;
int cardiac_phase = 0;
// Breath simulation state machine
enum BreathState { INHALING, EXHALING, PAUSED, IDLE };
BreathState breath_state = IDLE;
unsigned long breath_state_start = 0;
unsigned long last_breath_start = 0;
float breath_progress = 0.0;
// Breath tracking
unsigned long last_breath_ms = 0;
// Breath history for rate calculation
const int MAX_BREATHS = 100;
unsigned long breath_times[MAX_BREATHS];
int breath_head = 0;
int breath_count = 0;
// Display and timing
unsigned long last_display_ms = 0;
const unsigned long DISPLAY_INTERVAL = 800;
// System state
bool alarm_active = false;
int current_rr = 0;
bool sonar_sensor_ok = true;
// Respiratory parameters
int current_breath_rate = 12; // breaths per minute (default)
unsigned long current_breath_duration = 4000; // ms for one breath cycle
// ---------- HELPER FUNCTIONS ----------
void add_breath_timestamp(unsigned long t) {
breath_times[breath_head] = t;
breath_head = (breath_head + 1) % MAX_BREATHS;
if (breath_count < MAX_BREATHS) breath_count++;
}
int count_breaths_in_window(unsigned long now_ms, unsigned long window_seconds) {
if (breath_count == 0) return 0;
unsigned long cutoff = now_ms - window_seconds * 1000UL;
int cnt = 0;
for (int i = 0; i < breath_count; ++i) {
int idx = (breath_head - 1 - i + MAX_BREATHS) % MAX_BREATHS;
if (breath_times[idx] >= cutoff) {
cnt++;
} else {
break;
}
}
return cnt;
}
float readSonar_cm() {
digitalWrite(PIN_TRIG, LOW);
delayMicroseconds(2);
digitalWrite(PIN_TRIG, HIGH);
delayMicroseconds(10);
digitalWrite(PIN_TRIG, LOW);
unsigned long duration = pulseIn(PIN_ECHO, HIGH, 26000);
if (duration == 0) return -1.0;
return duration * 0.0343 / 2.0;
}
int simulateRealisticPhotodiode(unsigned long current_time) {
static int base_value = SIM_BASELINE;
// Slow baseline drift (like real sensor drift)
if (current_time - last_drift_update > 10000) { // Every 10 seconds
last_drift_update = current_time;
current_drift = random(-SIM_DRIFT_RANGE, SIM_DRIFT_RANGE + 1);
}
// Cardiac oscillations (small, rapid variations)
if (current_time - last_cardiac_osc > 120) { // ~80 BPM cardiac rhythm
last_cardiac_osc = current_time;
cardiac_phase = (cardiac_phase + 1) % 100;
// Simple sine-like cardiac oscillation
float cardiac_wave = sin(cardiac_phase * 0.0628) * SIM_CARDIAC_OSCILLATION;
base_value = SIM_BASELINE + current_drift + int(cardiac_wave);
}
// Breathing waveform
int breath_component = 0;
switch (breath_state) {
case INHALING:
breath_progress = float(current_time - breath_state_start) / SIM_INHALATION_TIME;
if (breath_progress >= 1.0) {
breath_state = PAUSED;
breath_state_start = current_time;
breath_progress = 0.0;
} else {
// Smooth inhalation curve (faster at start, slows down)
float curve = 1.0 - pow(1.0 - breath_progress, 1.5);
breath_component = int(curve * (SIM_PEAK - SIM_BASELINE));
}
break;
case EXHALING:
breath_progress = float(current_time - breath_state_start) / SIM_EXHALATION_TIME;
if (breath_progress >= 1.0) {
breath_state = IDLE;
breath_progress = 0.0;
} else {
// Smooth exhalation curve (starts slow, accelerates slightly)
float curve = pow(breath_progress, 0.8);
breath_component = int((1.0 - curve) * (SIM_PEAK - SIM_BASELINE));
}
break;
case PAUSED:
if (current_time - breath_state_start > SIM_BREATH_HOLD) {
breath_state = EXHALING;
breath_state_start = current_time;
breath_progress = 0.0;
}
breath_component = (SIM_PEAK - SIM_BASELINE); // Hold at peak
break;
case IDLE:
breath_component = 0;
break;
}
// Add random noise (more realistic than uniform random)
int noise = 0;
for (int i = 0; i < 3; i++) {
noise += random(-SIM_NOISE_LEVEL/2, SIM_NOISE_LEVEL/2 + 1);
}
noise = noise / 3;
int final_value = base_value + breath_component + noise;
// Occasionally add small artifacts (like movement artifacts)
if (random(1000) < 2) { // 0.2% chance of artifact
final_value += random(-20, 21);
}
return constrain(final_value, 0, 1023);
}
void startBreathingCycle(unsigned long start_time) {
breath_state = INHALING;
breath_state_start = start_time;
breath_progress = 0.0;
last_breath_start = start_time;
}
void updateRespiratoryParameters() {
// Update breath timing based on detected rate
if (current_rr > 0) {
current_breath_rate = current_rr;
// Calculate total breath cycle time in ms
current_breath_duration = (60000 / current_breath_rate);
// Adjust inhalation/exhalation ratio based on rate
// Faster breathing = shorter exhalation relative to inhalation
// SIM_INHALATION_TIME and SIM_EXHALATION_TIME are now dynamic
}
}
void update_alarm_state() {
bool rr_out_of_range = (current_rr > 0 && (current_rr < ALARM_RR_LOW || current_rr > ALARM_RR_HIGH));
alarm_active = !sonar_sensor_ok || rr_out_of_range;
//digitalWrite(PIN_BUZZ, alarm_active ? HIGH : LOW);
}
void setup() {
Serial.begin(115200);
// Pin setup
pinMode(PIN_TRIG, OUTPUT);
pinMode(PIN_ECHO, INPUT);
//pinMode(PIN_BUZZ, OUTPUT);
//digitalWrite(PIN_BUZZ, LOW);
digitalWrite(PIN_TRIG, LOW);
// Initialize random seed for realistic simulation
randomSeed(analogRead(A0));
// LCD initialization
lcd.init();
lcd.backlight();
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Real Resp Monitor");
lcd.setCursor(0, 1);
lcd.print("Realistic Sim Mode");
// Initial sensor reading
delay(100);
float initial_sonar = readSonar_cm();
if (initial_sonar > 0) {
sonar_ema = initial_sonar;
sonar_min = initial_sonar;
sonar_max = initial_sonar;
} else {
sonar_ema = 50.0;
}
// Initialize timing
last_drift_update = millis();
last_cardiac_osc = millis();
delay(2000);
lcd.clear();
}
void loop() {
unsigned long now = millis();
// --- Ultrasonic Sensor Processing ---
float sonar_raw = readSonar_cm();
if (sonar_raw > 0) {
if (sonar_raw < sonar_min) sonar_min = sonar_raw;
if (sonar_raw > sonar_max) sonar_max = sonar_raw;
sonar_ema = (1.0 - SONAR_SMOOTHING) * sonar_ema + SONAR_SMOOTHING * sonar_raw;
sonar_sensor_ok = true;
} else {
sonar_sensor_ok = false;
}
// Detect breath from sonar movement
static float prev_sonar = sonar_ema;
float sonar_delta = prev_sonar - sonar_ema;
bool sonar_breath = false;
if (abs(sonar_delta) > SONAR_DELTA_THRESHOLD && sonar_raw > 0) {
sonar_breath = true;
// Start realistic breathing simulation when movement detected
if ((breath_state == IDLE || breath_state == EXHALING) &&
(now - last_breath_start) > (current_breath_duration * 0.8)) {
startBreathingCycle(now);
}
}
prev_sonar = sonar_ema;
// Auto-generate breaths if no ultrasonic detection for a while
if (breath_state == IDLE && (now - last_breath_start) > current_breath_duration) {
startBreathingCycle(now);
}
// --- Realistic Photodiode Simulation ---
simulated_photo_value = simulateRealisticPhotodiode(now);
// Update respiratory parameters based on detected rate
updateRespiratoryParameters();
// --- Breath Detection (from ultrasonic) ---
bool breath_detected = false;
if (sonar_breath && (now - last_breath_ms) > REFRACTORY_MS) {
breath_detected = true;
last_breath_ms = now;
add_breath_timestamp(now);
}
// --- Respiratory Rate Calculation ---
int breaths_in_window = count_breaths_in_window(now, BREATH_WINDOW_SECONDS);
current_rr = round(breaths_in_window * (60.0 / BREATH_WINDOW_SECONDS));
// --- Alarm Management ---
update_alarm_state();
// --- Periodic Display Update ---
if (now - last_display_ms >= DISPLAY_INTERVAL) {
last_display_ms = now;
lcd.clear();
// Line 1: Respiratory Rate and Status
lcd.setCursor(0, 0);
lcd.print("RR:");
lcd.print(current_rr);
lcd.print("bpm");
// Show breath state
lcd.setCursor(10, 0);
switch(breath_state) {
case INHALING: lcd.print("INH"); break;
case EXHALING: lcd.print("EXH"); break;
case PAUSED: lcd.print("PAU"); break;
case IDLE: lcd.print("IDL"); break;
}
// Line 2: Sensor Values
lcd.setCursor(0, 1);
lcd.print("P:");
lcd.print(simulated_photo_value);
lcd.setCursor(8, 1);
if (!sonar_sensor_ok) {
lcd.print("S:FAIL");
} else {
lcd.print("S:");
lcd.print(int(sonar_ema));
lcd.print("cm");
}
if (alarm_active) {
lcd.print("!");
}
}
// --- Serial Output for Visualization ---
if (now % 200 < 10) { // Faster sampling for serial plotter
Serial.print("Photo:");
Serial.print(simulated_photo_value);
Serial.print(",Sonar:");
Serial.print(sonar_ema);
Serial.print(",RR:");
Serial.print(current_rr);
Serial.print(",State:");
Serial.print(breath_state);
Serial.print(",BreathDetected:");
Serial.println(breath_detected ? "1" : "0");
}
delay(50); // Main loop delay
}