Smart Patient Health Monitoring System using Arduino | Oximeter, BPM & Fever Alarm

 Smart Patient Health Monitoring System using Arduino | Oximeter, BPM & Fever Alarm

Arduino Code Copy Below

---------------------------------------


#include <Wire.h> 

const int TEMP_PIN = 2;   

const int BUZZER_PIN = 3; 

#define OLED_ADDR 0x3C

#define MAX30102_ADDR 0x57


// Extended Font Map (Supports Space, :, -, 0-9, A-Z, e, m, p, °, %)

const uint8_t PROGMEM font_map[][5] = {

  {0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x24, 0x00, 0x00}, {0x08, 0x08, 0x08, 0x08, 0x08}, // Space(0), :(1), -(2)

  {0x3E, 0x51, 0x49, 0x45, 0x3E}, {0x00, 0x42, 0x7F, 0x40, 0x00}, {0x42, 0x61, 0x51, 0x49, 0x46}, // 0(3), 1(4), 2(5)

  {0x21, 0x41, 0x45, 0x4B, 0x31}, {0x18, 0x14, 0x12, 0x7F, 0x10}, {0x27, 0x45, 0x45, 0x45, 0x39}, // 3(6), 4(7), 5(8)

  {0x3C, 0x4A, 0x49, 0x49, 0x30}, {0x01, 0x71, 0x09, 0x05, 0x03}, {0x36, 0x49, 0x49, 0x49, 0x36}, // 6(9), 7(10), 8(11)

  {0x06, 0x49, 0x49, 0x29, 0x1E}, {0x7E, 0x11, 0x11, 0x11, 0x7E}, {0x7F, 0x49, 0x49, 0x49, 0x36}, // 9(12), A(13), B(14)

  {0x3E, 0x41, 0x41, 0x41, 0x22}, {0x7F, 0x41, 0x41, 0x41, 0x22}, {0x7F, 0x49, 0x49, 0x49, 0x41}, // C(15), D(16), E(17)

  {0x7F, 0x09, 0x09, 0x09, 0x01}, {0x3E, 0x41, 0x49, 0x49, 0x3A}, {0x7F, 0x08, 0x08, 0x08, 0x7F}, // F(18), G(19), H(20)

  {0x00, 0x41, 0x7F, 0x41, 0x00}, {0x20, 0x40, 0x41, 0x3F, 0x01}, {0x7F, 0x08, 0x14, 0x22, 0x41}, // I(21), J(22), K(23)

  {0x7F, 0x40, 0x40, 0x40, 0x40}, {0x7F, 0x02, 0x0C, 0x02, 0x7F}, {0x7F, 0x04, 0x08, 0x10, 0x7F}, // L(24), M(25), N(26)

  {0x3E, 0x41, 0x41, 0x41, 0x3E}, {0x7F, 0x09, 0x09, 0x09, 0x06}, {0x3E, 0x41, 0x51, 0x21, 0x5E}, // O(27), P(28), Q(29)

  {0x7F, 0x09, 0x19, 0x29, 0x46}, {0x46, 0x49, 0x49, 0x49, 0x31}, {0x01, 0x01, 0x7F, 0x01, 0x01}, // R(30), S(31), T(32)

  {0x3F, 0x40, 0x40, 0x40, 0x3F}, {0x1F, 0x20, 0x40, 0x20, 0x1F}, {0x7F, 0x20, 0x18, 0x20, 0x7F}, // U(33), V(34), W(35)

  {0x63, 0x14, 0x08, 0x14, 0x63}, {0x07, 0x08, 0x70, 0x08, 0x07}, {0x61, 0x51, 0x49, 0x45, 0x43}, // X(36), Y(37), Z(38)

  {0x38, 0x54, 0x54, 0x54, 0x18}, {0x7C, 0x04, 0x18, 0x04, 0x78}, {0x7C, 0x14, 0x14, 0x14, 0x08}, // e(39), m(40), p(41)

  {0x02, 0x05, 0x02, 0x00, 0x00}, {0x23, 0x13, 0x08, 0x64, 0x62}                                  // °(42), %(43)

};


// Global Tracking Variables

float current_temp_c = 0.0;

float current_temp_f = 0.0;

unsigned long last_temp_sample = 0;

unsigned long last_beat_time = 0;


// Signal Filtering Elements

float filtered_ir = 0;

const float alpha = 0.75; 

int bpm_queue[12]; // Queue size for 10 second window

int bpm_count = 0;


// Raw SpO2 Component Signal Caps

long red_max = 0, red_min = 999999;

long ir_max = 0, ir_min = 999999;


// State Machine Setup

enum SystemState { NO_FINGER, STABILIZING, DISPLAY_FIXED };

SystemState current_state = NO_FINGER;

unsigned long state_start_time = 0;

int final_locked_bpm = 0;

int final_locked_spo2 = 0;


// --- 1-WIRE PROTOCOL (DS18B20) ---

void ow_unselect() { pinMode(TEMP_PIN, INPUT); }

void ow_select() { pinMode(TEMP_PIN, OUTPUT); digitalWrite(TEMP_PIN, LOW); }


uint8_t ow_reset() {

  noInterrupts();

  ow_select(); 

  delayMicroseconds(480); 

  ow_unselect(); 

  delayMicroseconds(70);

  uint8_t presence = digitalRead(TEMP_PIN); 

  interrupts();

  delayMicroseconds(410);

  return presence; 

}


void ow_write_bit(uint8_t bit) {

  noInterrupts();

  ow_select();

  if (bit) { 

    delayMicroseconds(10); 

    ow_unselect(); 

    interrupts(); 

    delayMicroseconds(55); 

  } else { 

    delayMicroseconds(65); 

    ow_unselect(); 

    interrupts(); 

    delayMicroseconds(5); 

  }

}


uint8_t ow_read_bit() {

  noInterrupts();

  ow_select(); 

  delayMicroseconds(3); 

  ow_unselect(); 

  delayMicroseconds(10);

  uint8_t bit = digitalRead(TEMP_PIN); 

  interrupts();

  delayMicroseconds(53);

  return bit;

}


void ow_write_byte(uint8_t data) {

  for (int i = 0; i < 8; i++) { ow_write_bit(data & 0x01); data >>= 1; }

}


uint8_t ow_read_byte() {

  uint8_t data = 0;

  for (int i = 0; i < 8; i++) { if (ow_read_bit()) data |= (1 << i); }

  return data;

}


void request_ds18b20_temperature() {

  if (ow_reset() == 0) { ow_write_byte(0xCC); ow_write_byte(0x44); }

}


void read_ds18b20_temperature() {

  if (ow_reset() == 0) {

    ow_write_byte(0xCC); ow_write_byte(0xBE);

    uint8_t low_byte = ow_read_byte(); uint8_t high_byte = ow_read_byte();

    int16_t raw = (high_byte << 8) | low_byte;

    current_temp_c = (float)raw / 16.0;

    current_temp_f = (current_temp_c * 9.0 / 5.0) + 32.0;

  }

}


// --- MAX30102 REGISTER HANDLING ---

void max30102_write_reg(uint8_t reg, uint8_t value) {

  Wire.beginTransmission(MAX30102_ADDR); Wire.write(reg); Wire.write(value); Wire.endTransmission();

}


uint8_t max30102_read_reg(uint8_t reg) {

  Wire.beginTransmission(MAX30102_ADDR); Wire.write(reg);

  if (Wire.endTransmission(false) != 0) return 0;

  Wire.requestFrom(MAX30102_ADDR, 1);

  return Wire.available() ? Wire.read() : 0;

}


void max30102_init() {

  max30102_write_reg(0x09, 0x40); delay(100); 

  max30102_write_reg(0x09, 0x03); 

  max30102_write_reg(0x0A, 0x27); 

  max30102_write_reg(0x0C, 0x24); 

  max30102_write_reg(0x0D, 0x24); 

  max30102_write_reg(0x08, 0x10); 

  max30102_write_reg(0x04, 0x00); max30102_write_reg(0x05, 0x00); max30102_write_reg(0x06, 0x00);

}


void process_max30102() {

  uint8_t read_ptr = max30102_read_reg(0x06); uint8_t write_ptr = max30102_read_reg(0x04);

  int num_samples = (int)write_ptr - (int)read_ptr;

  if (num_samples < 0) num_samples += 32;

  

  if (num_samples > 0) {

    Wire.beginTransmission(MAX30102_ADDR); Wire.write(0x07);

    if (Wire.endTransmission(false) != 0) return;

    

    Wire.requestFrom(MAX30102_ADDR, 6);

    if (Wire.available() >= 6) {

      long raw_red = 0; long raw_ir = 0;

      raw_red |= (long)Wire.read() << 16; raw_red |= (long)Wire.read() << 8; raw_red |= Wire.read(); raw_red &= 0x03FFFF;

      raw_ir |= (long)Wire.read() << 16;  raw_ir |= (long)Wire.read() << 8;  raw_ir |= Wire.read();  raw_ir &= 0x03FFFF;


      if (raw_ir > 30000) { 

        if (current_state == NO_FINGER) {

          current_state = STABILIZING;

          state_start_time = millis();

          bpm_count = 0;

          red_max = 0; red_min = 999999;

          ir_max = 0; ir_min = 999999;

        }


        if (current_state == STABILIZING) {

          if (raw_red > red_max) red_max = raw_red;

          if (raw_red < red_min && raw_red > 10000) red_min = raw_red;

          if (raw_ir > ir_max) ir_max = raw_ir;

          if (raw_ir < ir_min && raw_ir > 10000) ir_min = raw_ir;

        }


        filtered_ir = (alpha * raw_ir) + ((1.0 - alpha) * filtered_ir);

        long delta = raw_ir - filtered_ir;


        if (delta > 25 && (millis() - last_beat_time > 500)) {

          long duration = millis() - last_beat_time;

          last_beat_time = millis();

          

          int calculated_bpm = 60000 / duration;

          if (calculated_bpm >= 55 && calculated_bpm <= 150) {

            if (current_state == STABILIZING && bpm_count < 12) {

              bpm_queue[bpm_count] = calculated_bpm;

              bpm_count++;

            }

          }

        }


        // Strict 10-Second Wait for all vitals to stabilize

        if (current_state == STABILIZING && (millis() - state_start_time >= 10000)) {

          

          if (bpm_count > 0) {

            long total = 0;

            for (int i = 0; i < bpm_count; i++) total += bpm_queue[i];

            final_locked_bpm = total / bpm_count;

          } else {

            final_locked_bpm = -1; 

          }


          long red_ac = red_max - red_min;

          long ir_ac = ir_max - ir_min;

          if (ir_ac > 0 && red_min > 0 && ir_min > 0 && red_ac > 0) {

            float float_red_ac = (float)red_ac / (float)red_min;

            float float_ir_ac = (float)ir_ac / (float)ir_min;

            float ratio = float_red_ac / float_ir_ac;

            

            int calculated_spo2 = 105 - (18 * ratio); 

            if (calculated_spo2 > 100) calculated_spo2 = 100;

            if (calculated_spo2 < 75) calculated_spo2 = 75; 

            final_locked_spo2 = calculated_spo2;

          } else {

            final_locked_spo2 = -1;

          }

          current_state = DISPLAY_FIXED;

        }

      } else { 

        current_state = NO_FINGER;

        final_locked_bpm = 0;

        final_locked_spo2 = 0;

        bpm_count = 0;

      }

    }

  }

}


// --- SH1106 OLED FUNCTIONS ---

void oled_command(uint8_t cmd) { Wire.beginTransmission(OLED_ADDR); Wire.write(0x00); Wire.write(cmd); Wire.endTransmission(); }


void oled_init() {

  delay(100); oled_command(0xAE); oled_command(0x20); oled_command(0x02); oled_command(0xA1); oled_command(0xC8); oled_command(0xAF); oled_clear();

}


void oled_clear() {

  for (uint8_t page = 0; page < 8; page++) {

    oled_command(0xB0 + page); oled_command(0x02); oled_command(0x10);

    for (uint8_t col = 0; col < 128; col++) {  Wire.beginTransmission(OLED_ADDR); Wire.write(0x40); Wire.write(0x00); Wire.endTransmission(); }

  }

}


int get_font_index(char c) {

  if (c == ' ') return 0; if (c == ':') return 1; if (c == '-') return 2;

  if (c >= '0' && c <= '9') return 3 + (c - '0');

  if (c >= 'A' && c <= 'Z') return 13 + (c - 'A');

  if (c == 'e') return 39; if (c == 'm') return 40; if (c == 'p') return 41; 

  if (c == '~') return 42; if (c == '%') return 43;

  return 0;

}


void oled_print_string(const char* str, uint8_t page, uint8_t col) {

  col += 2; oled_command(0xB0 + page); oled_command(col & 0x0F); oled_command(0x10 | ((col >> 4) & 0x0F));

  while (*str) {

    int idx = get_font_index(*str); Wire.beginTransmission(OLED_ADDR); Wire.write(0x40);

    for (uint8_t i = 0; i < 5; i++) { Wire.write(pgm_read_byte(&(font_map[idx][i]))); }

    Wire.write(0x00); Wire.endTransmission(); str++;

  }

}


void oled_print_string_2x(const char* str, uint8_t page, uint8_t col) {

  uint8_t original_col = col + 2;

  for (uint8_t p = 0; p < 2; p++) {

    uint8_t current_col = original_col;

    oled_command(0xB0 + page + p);

    oled_command(current_col & 0x0F);

    oled_command(0x10 | ((current_col >> 4) & 0x0F));

    const char* s = str;

    while (*s) {

      int idx = get_font_index(*s);

      Wire.beginTransmission(OLED_ADDR);

      Wire.write(0x40);

      for (uint8_t i = 0; i < 5; i++) {

        uint8_t col_data = pgm_read_byte(&(font_map[idx][i]));

        uint8_t scaled_data = 0;

        for (uint8_t b = 0; b < 4; b++) {

          if (p == 0) {

            if (col_data & (1 << b)) scaled_data |= (3 << (b * 2));

          } else {

            if (col_data & (1 << (b + 4))) scaled_data |= (3 << (b * 2));

          }

        }

        Wire.write(scaled_data);

        Wire.write(scaled_data);

      }

      Wire.write(0x00); Wire.write(0x00);

      Wire.endTransmission();

      s++;

    }

  }

}


// --- SYSTEM INITIALIZATION & RUNTIME LOOP ---

void setup() {

  Wire.begin();

  #if defined(WIRE_HAS_TIMEOUT)

    Wire.setWireTimeout(3000, true); 

  #endif

  

  pinMode(BUZZER_PIN, OUTPUT); digitalWrite(BUZZER_PIN, LOW);


  // --- STARTUP DIAGNOSTIC: 3 BEEP TEST ---

  for (int i = 0; i < 3; i++) {

    digitalWrite(BUZZER_PIN, HIGH);

    delay(150); // Buzzer ON for 150ms

    digitalWrite(BUZZER_PIN, LOW);

    delay(150); // Buzzer OFF for 150ms

  }

  

  oled_init(); max30102_init();

  

  oled_print_string("SYSTEM ACTIVE", 0, 24); delay(1000); oled_clear();

  request_ds18b20_temperature();

}


void loop() {

  process_max30102();

  

  if (millis() - last_temp_sample >= 1000) {

    read_ds18b20_temperature(); 

    request_ds18b20_temperature(); 

    

    char bpm_buff[16];

    char spo2_buff[16];

    char header_buff[24];

    

    // 1. Process State UI Strings

    if (current_state == STABILIZING) {

      int seconds_left = 10 - ((millis() - state_start_time) / 1000);

      if (seconds_left < 0) seconds_left = 0;

      

      char sec_str[4]; itoa(seconds_left, sec_str, 10);

      strcpy(bpm_buff, "WT "); strcat(bpm_buff, sec_str); strcat(bpm_buff, "s ");

      strcpy(spo2_buff, "WT "); strcat(spo2_buff, sec_str); strcat(spo2_buff, "s ");

      

      strcpy(header_buff, "READING VITALS ");

    } 

    else if (current_state == DISPLAY_FIXED) {

      if (final_locked_bpm > 0 && final_locked_spo2 > 0) {

        itoa(final_locked_bpm, bpm_buff, 10); strcat(bpm_buff, "  ");

        itoa(final_locked_spo2, spo2_buff, 10); strcat(spo2_buff, "% ");

      } else {

        strcpy(bpm_buff, "ERR ");

        strcpy(spo2_buff, "ERR ");

      }


      // Check temperature and map output exactly

      if (current_temp_c >= 38.0) {

        strcpy(header_buff, "FEVER DETECTED ");

      } else {

        strcpy(header_buff, "NORMAL         ");

      }

    } 

    else {

      // Idle NO_FINGER state

      strcpy(bpm_buff, "--- ");

      strcpy(spo2_buff, "--- ");

      strcpy(header_buff, "PLACE FINGER   ");

    }


    // 2. Render OLED Sections

    // Header Row Notification (Standard 1x scale)

    oled_print_string(header_buff, 0, 16);

    

    // Heart Rate Block (2x scale, fills pages 2 and 3)

    oled_print_string_2x("HR:", 2, 4); 

    oled_print_string_2x(bpm_buff, 2, 48);


    // Blood Oxygen Block (2x scale, fills pages 4 and 5)

    oled_print_string_2x("O2:", 4, 4); 

    oled_print_string_2x(spo2_buff, 4, 48);


    // Temperature Row (Standard 1x scale on page 6)

    char temp_combined[24];

    char c_buff[8]; dtostrf(current_temp_c, 4, 1, c_buff);

    char f_buff[8]; dtostrf(current_temp_f, 4, 1, f_buff);

    strcpy(temp_combined, c_buff); strcat(temp_combined, "~C  ");

    strcat(temp_combined, f_buff);  strcat(temp_combined, "~F ");

    

    oled_print_string("T:  ", 6, 4); 

    oled_print_string(temp_combined, 6, 40);


    // Flush I2C registers instantly

    max30102_write_reg(0x04, 0x00); max30102_write_reg(0x05, 0x00); max30102_write_reg(0x06, 0x00);

    last_temp_sample = millis();


    // Safety Trigger Outputs (Activates buzzer if vitals are dangerous during FIXED state)

    if (current_state == DISPLAY_FIXED && (current_temp_c >= 38.0 || final_locked_bpm > 130 || final_locked_spo2 < 90)) { 

      digitalWrite(BUZZER_PIN, HIGH); 

    } else { 

      digitalWrite(BUZZER_PIN, LOW); 

    }

  }

}


Components Buying Link
---------------------------------------

Buy Arduino uno - https://amzn.to/3RfS29I

Buy OLED Display SH 1106 - https://amzn.to/44uT3xB

Buy Heart / BPM sensor - https://amzn.to/4vruo80

Buy Temperature sensor (fever) -
https://amzn.to/4w9ISLi

Buy Buzzer - https://amzn.to/4w4V8fW

Buy Resistor(mandatory 4.7 killohms) - https://amzn.to/4f1tXwp

Buy Breadboard jumper - https://amzn.to/3TjWEfz

Buy Wire cutter - https://amzn.to/3SZymHD

Buy Wire Stripper - https://amzn.to/4eZPaXD















Previous Post Next Post