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
