// Respiratory monitoring with HC-SR04 + Simulated Photodiode + I2C LCD
// Arduino UNO compatible
// Photodiode simulation based on ultrasonic movement
#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
// ---------- PHOTODIODE SIMULATION PARAMETERS ----------
const int SIM_BASELINE = 500; // Baseline value for simulated photodiode
const int SIM_AMPLITUDE = 80; // Amplitude of breathing signal
const int SIM_BREATH_DURATION = 3000; // Duration of simulated breath in ms
const int SIM_NOISE_LEVEL = 5; // Random noise level
// ---------- GLOBAL STATE ----------
// Ultrasonic sensor
float sonar_ema = 0.0;
float sonar_min = 999, sonar_max = -999;
// Simulated photodiode
int simulated_photo_value = SIM_BASELINE;
bool is_breathing_cycle = false;
unsigned long breath_start_time = 0;
unsigned long last_photo_update = 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;
// ---------- 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() {
// Trigger pulse with proper timing
digitalWrite(PIN_TRIG, LOW);
delayMicroseconds(2);
digitalWrite(PIN_TRIG, HIGH);
delayMicroseconds(10);
digitalWrite(PIN_TRIG, LOW);
// Read echo with timeout (26ms = ~4.5m max distance)
unsigned long duration = pulseIn(PIN_ECHO, HIGH, 26000);
if (duration == 0) return -1.0; // Timeout or no echo
// Convert to cm (sound travels ~343 m/s = 0.0343 cm/µs)
return duration * 0.0343 / 2.0;
}
int simulatePhotodiodeValue(unsigned long current_time) {
if (!is_breathing_cycle) {
// Return baseline with small random variations
return SIM_BASELINE + random(-SIM_NOISE_LEVEL, SIM_NOISE_LEVEL + 1);
}
// Calculate progress through breathing cycle (0.0 to 1.0)
float progress = (float)(current_time - breath_start_time) / SIM_BREATH_DURATION;
if (progress >= 1.0) {
// Breathing cycle complete
is_breathing_cycle = false;
return SIM_BASELINE + random(-SIM_NOISE_LEVEL, SIM_NOISE_LEVEL + 1);
}
// Create a smooth breathing waveform (sine wave)
float waveform = sin(progress * 3.14159); // 0 to PI radians
int simulated_value = SIM_BASELINE + (waveform * SIM_AMPLITUDE);
// Add some random noise
simulated_value += random(-SIM_NOISE_LEVEL, SIM_NOISE_LEVEL + 1);
// Constrain to reasonable ADC range
return constrain(simulated_value, 0, 1023);
}
void startBreathingSimulation(unsigned long start_time) {
is_breathing_cycle = true;
breath_start_time = start_time;
}
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 photodiode simulation
randomSeed(analogRead(A0));
// LCD initialization
lcd.init();
lcd.backlight();
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Respiratory Monitor");
lcd.setCursor(0, 1);
lcd.print("SIM Photo Mode");
// Initial sensor reading for EMA
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; // Default fallback
}
delay(2000);
lcd.clear();
}
void loop() {
unsigned long now = millis();
// --- Ultrasonic Sensor Processing ---
float sonar_raw = readSonar_cm();
if (sonar_raw > 0) { // Valid reading
// Update min/max
if (sonar_raw < sonar_min) sonar_min = sonar_raw;
if (sonar_raw > sonar_max) sonar_max = sonar_raw;
// EMA smoothing
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; // Positive when moving closer
bool sonar_breath = false;
if (abs(sonar_delta) > SONAR_DELTA_THRESHOLD && sonar_raw > 0) {
sonar_breath = true;
// Start photodiode simulation when movement detected
if (!is_breathing_cycle && (now - last_breath_ms) > REFRACTORY_MS) {
startBreathingSimulation(now);
}
}
prev_sonar = sonar_ema;
// --- Simulated Photodiode Processing ---
simulated_photo_value = simulatePhotodiodeValue(now);
// Detect breath from simulated photodiode (for demonstration)
static int prev_sim_photo = simulated_photo_value;
int sim_delta = simulated_photo_value - prev_sim_photo;
bool sim_breath = false;
// Detect rising edge in simulated signal
if (sim_delta > 10 && is_breathing_cycle) {
sim_breath = true;
}
prev_sim_photo = simulated_photo_value;
// --- Breath Detection (Primary: Ultrasonic, Secondary: Simulated Photo) ---
bool breath_detected = false;
if ((sonar_breath || sim_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
lcd.setCursor(0, 0);
lcd.print("RR:");
lcd.print(current_rr);
lcd.print(" bpm");
if (alarm_active) {
lcd.print(" ALARM!");
}
// Line 2: Sensor Status and Values
lcd.setCursor(0, 1);
if (!sonar_sensor_ok) {
lcd.print("Sonar:FAIL");
} else {
lcd.print("S:");
lcd.print(int(sonar_ema));
lcd.print("cm");
// Show simulated photodiode value
lcd.print(" P:");
lcd.print(simulated_photo_value);
}
}
// --- Serial Output for Debugging ---
if (now % 2000 < 50) { // Every ~2 seconds
Serial.print("Sonar: ");
Serial.print(sonar_ema);
Serial.print("cm Delta: ");
Serial.print(sonar_delta);
Serial.print(" | Sim Photo: ");
Serial.print(simulated_photo_value);
Serial.print(" | RR: ");
Serial.print(current_rr);
Serial.print(" bpm | Breathing: ");
Serial.print(is_breathing_cycle ? "YES" : "NO");
Serial.print(" | Alarm: ");
Serial.println(alarm_active ? "ON" : "OFF");
}
delay(50); // Main loop delay
}