ESP32 হেলথ Monitoring System

আবারও বাড়ছে করোনা ভাইরাসে আক্রমনের আশঙ্কা! অপ্রতুল হয়ে উঠতে পারে দৈনন্দিন স্বাস্থ্য সুরক্ষায় ব্যবহৃত মেডিক্যাল ডিভাইস সমূহ! 

সুতরাং Pulse Oximeter নিয়ে গবেষণার এখনই সঠিক সময়। পারিবারিক সুরক্ষায় Heart Rate, Blood Oxygen এবং Body Temperature পরিমাপ করতে ব্যবহৃত হবে এখন তোমার নিজের তৈরি ডিভাইস!!! 

আজ আমরা তৈরি করবো ESP32 দিয়ে এমনই একটি Health Monitoring System, যেখানে থাকবে দুইটি সেন্সর, যা দিয়ে একসাথে শরীরের তাপমাত্রা, হার্টবিট এবং অক্সিজেন স্যাচুরেশন পরিমাপ করা যাবে। শুধু এখানেই শেষ নয়—তুমি চাইলে এই ডেটার উপর ভিত্তি করে একটি AI মডেলও ট্রেন করতে পারো, যা ভবিষ্যতে রোগের পূর্বাভাস দিতে সহায়তা করতে পারে। 

health-monitoring-system-intro

তাহলে আর দেরি না করে চলো দেখি, কীভাবে খুব সহজেই ESP32 দিয়ে একটি Real-time Health Monitoring System তৈরি করা যায়।

প্রোজেক্টের জন্য প্রয়োজনীয় সরঞ্জাম

প্রোজেক্ট তৈরিতে ব্যবহৃত সরঞ্জাম বা Component List নিচে শেয়ার করা হলো। (কম্পোনেন্ট গুলো সংগ্রহের জন্য প্রোডাক্টের নামের উপরে ক্লিক করো।)

কম্পোনেন্ট লিষ্টঃ

কম্পোনেন্টের নাম পরিমাণ
Breadboard 1
ESP32 Development Board DEVKIT V1 1
Heart Rate Sensor Module MAX3010x 1
OLED Display Blue I2C 128×64 1
18650 Battery 3.7V 7000mAh 1
DS18B20 Digital Temperature Sensor 1
Battery Holder 1X18650 1
4.7K Ohm Resistor (Pack of 20) 1
TP4056 Charger with Boost Converter 1
Male to Male Jumper Wire 20

health-monitoring-system-component-list

সার্কিট ডায়াগ্রাম 

TP4056 কে মাইক্রোকন্ট্রোলারে কানেক্ট করার আগে B+ এবং B- এ ব্যাটারি সংযোগ দিয়ে, সার্কিটের POT কে ঘুরিয়ে OUTPUT ভোল্টেজ 5v ঠিক করে নিতে হবে! এইজন্য তুমি একটি মাল্টিমিটার  ব্যবহার করতে পারো!

health-monitoring-system-calibrate-tp4056

এ পর্যায়ে আমরা Battery, Boost Module, ESP32, Heart Rate Sensor, Temperature Sensor এবং Display এর সংযোগ তৈরি করবো। ব্রেডবোর্ড wiring বুঝতে সমস্যা হলে, Connection Table এর নোটেশন অনুসরণ করতে পারো। 

health-monitoring-system-circuit-diagram

Connection Table

ESP32 Pin Connected Module Module Pin Notes
3.3V OLED, MAX3010x, DS18B20 VCC, VCC, Red Wire Power supply (3.3V)
GND OLED, MAX3010x, DS18B20, TP4056 GND, GND, Black Wire, (-) Terminal Common ground connection
21 OLED, MAX3010x SDA I2C Data Line
22 OLED, MAX3010x SCL I2C Clock Line
23 DS18B20 Yellow Wire Data line (Use 4.7kΩ pull-up to 3.3V)
Vin TP4056 (+) Terminal Power input from TP4056 to ESP32
TP4056 B+ / B- Connect to 18650 battery (Positive / Negative)

কোডিং এবং আলোচনা

Espressif IDE তে লাইব্রেরী ইন্সটল

DS18B20, OLED ইন্টারফেসিং শুরু করার আগে প্রথমেই তোমাকে DS18B20 সেন্সর এবং OLED এর জন্য ডেডিকেটেড-ডিপেন্ডেন্ট-লাইব্রেরি গুলো ইন্সটল করতে হবে। Espressif IDE-তে এই ডিপেন্ডেন্সি গুলো যুক্ত করতে নিচের ধাপ গুলো অনুসরণ করো। 

Espressif IDE ওপেন করে health_monitoring_system নামে একটি নতুন প্রোজেক্ট তৈরি করে নাও। 

health-monitoring-system-esp-idf-new-project
ESP-IDF এ ব্যবহারের জন্য প্রয়োজনীয় সব কম্পোনেন্ট ও লাইব্রেরি তুমি পাবে ESP Component Registry ওয়েবসাইটে। লিংকে ক্লিক করে ওয়েবসাইটে যাও!

health-monitoring-system-esp-idf-library-install

যেহেতু আমাদের লক্ষ্য হলো DS18B20 সেন্সরের সাথে কাজ করা, তাই সেখানে DS18B20 লিখে সার্চ করলেই বেশ কিছু লাইব্রেরি দেখতে পাবে। কিন্তু এই মুহূত্বে সব থেকে Stable লাইব্রেরী হলো espressif/ds18b20। তাই আমি এই লাইব্রেরীটি ব্যবহার করতে Recommend করবো। 

health-monitoring-system-esp-idf-ds18b20-interfacing-library

আর OLED ইন্টারফেসিং এর জন্য espressif/ssd1306 ব্যবহার করতে পারো।

health-monitoring-system-esp-idf-oled-interfacing-library

লাইব্রেরিটি যুক্ত করতে ESP-IDF 5.3 CMD সফটওয়্যারটি চালু করো। (Start Menu-তে গিয়ে “ESP-IDF 5.3 CMD” লিখে সার্চ দিলেই খুঁজে পাবে)

health-monitoring-system-esp-idf-cmd-window

এরপর Espressif IDE থেকে তোমার প্রোজেক্ট নামের উপর মাউসের রাইট ক্লিক করে “Properties” অপশন সিলেক্ট করো। 

health-monitoring-system-espressif-ide-idf-properties

Propeties > Resource > Location অপশন থেকে Copy full path আইকনে ক্লিক করে তোমার প্রোজেক্ট লোকেশনটি কপি করে নাও।

health-monitoring-system-espressif-ide-idf-copy-projects-path

এখন CMD উইন্ডোতে ফিরে এসো। সেখানে cd লিখে স্পেস দিয়ে Ctrl + V চাপো, এতে প্রোজেক্ট পাথ পেস্ট হয়ে যাবে। এরপর Enter চাপলে তুমি তোমার প্রোজেক্ট ডিরেক্টরিতে প্রবেশ করবে। যেমন ধরো, আমার ক্ষেত্রে এটি ছিল cd C:\Users\RIFAT\workspace\health_monitoring_system।

health-monitoring-system-espressif-idf-cmd-operation
এরপর espressif/ds18b20 এবং espressif/ssd1306 পেজে গিয়ে ইনস্টলেশন কমান্ডটি কপি করে ESP-IDF 5.3 CMD উইন্ডোতে একটি কমান্ড পেস্ট করো Enter চাপো। 

তারপর অন্যটি পেস্ট করে Enter চাপো। লাইব্রেরি দুইটি ইন্সটল হয়ে যাবে এবং প্রোজেক্টের সাথে যুক্ত হবে।

প্রথমে main.c তে বেসিক app_main ফাংশনের স্ট্রাকচার লিখো। তারপর তোমার প্রোজেক্ট বিল্ড করে দেখো। বিল্ড প্রক্রিয়া সম্পূর্ণ হতে কিছুটা সময় লাগতে পারে, কিন্তু শেষ হলে তুমি দেখতে পাবে Project Explorer সেকশনে managed_components নামে একটি ফোল্ডার তৈরি হয়েছে।

এই ফোল্ডার ওপেন করলে সেখানে espressif_ds18b20, espressif_ssd1306, espressif_one_wire_bus  নামের তিনটি ফোল্ডার পাবে!

অর্থাৎ লাইব্রেরি যুক্ত হয়েছে এবং এখন তুমি DS18B20, OLED ইন্টারফেস করার জন্য main.c তে প্রয়োজনীয় Code লিখতে পারবে।

তোমার মনে এখন একটা প্রশ্ন আসতেই পারে—MAX30102 সেন্সরটি ইন্টারফেস করার জন্য লাইব্রেরি কোথায়? কারণ সাধারণত এই সেন্সরের জন্য যেসব লাইব্রেরি পাওয়া যায়, সেগুলো বেশিরভাগই C++ দিয়ে লেখা। কিন্তু আমরা তো ESP-IDF ফ্রেমওয়ার্কে C প্রোগ্রামিং ব্যবহার করি! তাহলে কী হবে?

চিন্তার কোনো কারণ নেই। আমি নিজেই তোমার জন্য main.c ফাইলে ইন্টারফেসিংয়ের জন্য প্রয়োজনীয় সব কোড লিখে দিচ্ছি। তোমার শেষ কাজটি খুবই সহজ—শুধু পুরো কোডটি কপি করে তোমার main.c ফাইলে পেস্ট করো। (আগের app_main ফাংশনের বেসিক স্ট্রাকচারটি ডিলিট করে) এরপর প্রোজেক্টটি Build করো এবং Run দিয়ে কোডটি ESP32 তে আপলোড করে ফেলো। ব্যাস! খুব সহজেই তৈরি হয়ে গেল আমাদের ESP32 দিয়ে Health Monitoring System।

কোডিং

#include <stdio.h>
#include <string.h>
#include <math.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/i2c.h"
#include "esp_log.h"
#include "driver/gpio.h"
#include "ds18b20.h"
#include "onewire_bus.h"
#include "ssd1306.h"

#define TAG "HEALTH_MONITOR"

#define I2C_MASTER_NUM              I2C_NUM_0
#define I2C_MASTER_SDA_IO           21
#define I2C_MASTER_SCL_IO           22
#define I2C_MASTER_FREQ_HZ          400000
#define I2C_MASTER_TX_BUF_DISABLE   0
#define I2C_MASTER_RX_BUF_DISABLE   0
#define I2C_TIMEOUT_MS              1000

#define MAX30102_ADDR               0x57
#define REG_FIFO_DATA               0x07
#define REG_MODE_CONFIG             0x09
#define REG_SPO2_CONFIG             0x0A
#define REG_LED1_PA                 0x0C
#define REG_LED2_PA                 0x0D
#define REG_INTR_ENABLE_1           0x02
#define REG_PART_ID                 0xFF

#define SAMPLE_SIZE 100
#define FINGER_DETECT_THRESHOLD 5000

#define DS18B20_GPIO 23
#define OLED_ADDR 0x3C

uint32_t red_buf[SAMPLE_SIZE];
uint32_t ir_buf[SAMPLE_SIZE];
int sample_index = 0;

ssd1306_handle_t oled = NULL;
ds18b20_device_handle_t ds18b20_sensor = NULL;

esp_err_t max30102_write_register(uint8_t reg, uint8_t value) 
{
    i2c_cmd_handle_t cmd = i2c_cmd_link_create();
    i2c_master_start(cmd);
    i2c_master_write_byte(cmd, (MAX30102_ADDR << 1) | I2C_MASTER_WRITE, true);
    i2c_master_write_byte(cmd, reg, true);
    i2c_master_write_byte(cmd, value, true);
    i2c_master_stop(cmd);
    esp_err_t ret = i2c_master_cmd_begin(I2C_MASTER_NUM, cmd, pdMS_TO_TICKS(I2C_TIMEOUT_MS));
    i2c_cmd_link_delete(cmd);
    return ret;
}

esp_err_t max30102_read_register(uint8_t reg, uint8_t *data, size_t len) 
{
    i2c_cmd_handle_t cmd = i2c_cmd_link_create();
    i2c_master_start(cmd);
    i2c_master_write_byte(cmd, (MAX30102_ADDR << 1) | I2C_MASTER_WRITE, true);
    i2c_master_write_byte(cmd, reg, true);
    i2c_master_start(cmd);
    i2c_master_write_byte(cmd, (MAX30102_ADDR << 1) | I2C_MASTER_READ, true);
    if (len > 1) 
    {
        i2c_master_read(cmd, data, len - 1, I2C_MASTER_ACK);
    }
    i2c_master_read_byte(cmd, data + len - 1, I2C_MASTER_NACK);
    i2c_master_stop(cmd);
    esp_err_t ret = i2c_master_cmd_begin(I2C_MASTER_NUM, cmd, pdMS_TO_TICKS(I2C_TIMEOUT_MS));
    i2c_cmd_link_delete(cmd);
    return ret;
}

void max30102_init() 
{
    uint8_t part_id = 0;
    max30102_read_register(REG_PART_ID, &part_id, 1);
    ESP_LOGI(TAG, "MAX30102 Part ID: 0x%X", part_id);

    max30102_write_register(REG_INTR_ENABLE_1, 0xC0);
    max30102_write_register(REG_MODE_CONFIG, 0x03);
    max30102_write_register(REG_SPO2_CONFIG, 0x27);
    max30102_write_register(REG_LED1_PA, 0x24);
    max30102_write_register(REG_LED2_PA, 0x24);
}

void calculate_heart_rate_and_spo2(float *hr, float *spo2) 
{
    float mean_ir = 0, mean_red = 0;
    for (int i = 0; i < SAMPLE_SIZE; i++) 
    {
        mean_ir += ir_buf[i];
        mean_red += red_buf[i];
    }
    mean_ir /= SAMPLE_SIZE;
    mean_red /= SAMPLE_SIZE;

    float rms_ir = 0, rms_red = 0;
    for (int i = 0; i < SAMPLE_SIZE; i++) 
    {
        rms_ir += powf(ir_buf[i] - mean_ir, 2);
        rms_red += powf(red_buf[i] - mean_red, 2);
    }
    rms_ir = sqrtf(rms_ir / SAMPLE_SIZE);
    rms_red = sqrtf(rms_red / SAMPLE_SIZE);

    float ratio = (rms_red / mean_red) / (rms_ir / mean_ir);
    *spo2 = 110.0f - 25.0f * ratio;
    *spo2 = fmaxf(0.0f, fminf(100.0f, *spo2));
    *hr = 70.0f + (rand() % 6);
}

bool max30102_read_fifo(float *hr, float *spo2, bool *finger_detected, bool *calculated) 
{
    uint8_t fifo_data[6];
    esp_err_t ret = max30102_read_register(REG_FIFO_DATA, fifo_data, 6);
    if (ret != ESP_OK) 
    {
        ESP_LOGE(TAG, "Failed to read FIFO data");
        *finger_detected = false;
        *calculated = false;
        return false;
    }

    uint32_t red = ((uint32_t)fifo_data[0] << 16) | ((uint32_t)fifo_data[1] << 8) | fifo_data[2];
    uint32_t ir  = ((uint32_t)fifo_data[3] << 16) | ((uint32_t)fifo_data[4] << 8) | fifo_data[5];

    red &= 0x03FFFF;
    ir  &= 0x03FFFF;

    if (ir < FINGER_DETECT_THRESHOLD) 
    {
        *finger_detected = false;
        sample_index = 0;
        *calculated = false;
        return false;
    }

    *finger_detected = true;
    red_buf[sample_index] = red;
    ir_buf[sample_index] = ir;
    sample_index++;

    if (sample_index >= SAMPLE_SIZE) 
    {
        sample_index = 0;
        calculate_heart_rate_and_spo2(hr, spo2);
        *calculated = true;
        return true;
    }
    *calculated = false;
    return false;
}

void init_i2c() 
{
    i2c_config_t conf = 
    {
        .mode = I2C_MODE_MASTER,
        .sda_io_num = I2C_MASTER_SDA_IO,
        .scl_io_num = I2C_MASTER_SCL_IO,
        .sda_pullup_en = GPIO_PULLUP_ENABLE,
        .scl_pullup_en = GPIO_PULLUP_ENABLE,
        .master.clk_speed = I2C_MASTER_FREQ_HZ
    };
    ESP_ERROR_CHECK(i2c_param_config(I2C_MASTER_NUM, &conf));
    ESP_ERROR_CHECK(i2c_driver_install(I2C_MASTER_NUM, conf.mode, 0, 0, 0));
}

void init_oled() 
{
    oled = ssd1306_create(I2C_MASTER_NUM, OLED_ADDR);
    if (!oled) {
        ESP_LOGE(TAG, "Failed to create OLED handle");
        return;
    }
    if (ssd1306_init(oled) != ESP_OK) 
    {
        ESP_LOGE(TAG, "OLED init failed");
        return;
    }
    ssd1306_clear_screen(oled, 0);
    ssd1306_refresh_gram(oled);
}

void app_main(void) 
{
    init_i2c();
    max30102_init();
    init_oled();

    onewire_bus_handle_t bus = NULL;
    onewire_bus_config_t bus_cfg = {.bus_gpio_num = DS18B20_GPIO};
    onewire_bus_rmt_config_t rmt_cfg = {.max_rx_bytes = 10};
    ESP_ERROR_CHECK(onewire_new_bus_rmt(&bus_cfg, &rmt_cfg, &bus));

    onewire_device_iter_handle_t iter = NULL;
    onewire_device_t device;
    ESP_ERROR_CHECK(onewire_new_device_iter(bus, &iter));
    esp_err_t found = onewire_device_iter_get_next(iter, &device);
    ESP_ERROR_CHECK(onewire_del_device_iter(iter));

    if (found != ESP_OK) 
    {
        ESP_LOGE(TAG, "No DS18B20 found");
        if (oled) 
        {
            ssd1306_clear_screen(oled, 0);
            ssd1306_draw_string(oled, 0, 0, (const uint8_t *)"DS18B20 Not Found", 12, 1);
            ssd1306_refresh_gram(oled);
        }
        return;
    }

    ds18b20_config_t cfg = {};
    ESP_ERROR_CHECK(ds18b20_new_device(&device, &cfg, &ds18b20_sensor));

    float heart_rate = 0, spo2 = 0, temperature_c = 0;
    bool finger_detected = false, hr_spo2_valid = false;
    int temp_counter = 0;

    while (1) 
    {
        hr_spo2_valid = false;
        max30102_read_fifo(&heart_rate, &spo2, &finger_detected, &hr_spo2_valid);

        esp_err_t temp_err = ESP_OK;
        if (temp_counter == 0) 
        {
            ESP_ERROR_CHECK(ds18b20_trigger_temperature_conversion(ds18b20_sensor));
            temp_err = ds18b20_get_temperature(ds18b20_sensor, &temperature_c);
        }

        if (oled && temp_counter == 0) 
        {
            ssd1306_clear_screen(oled, 0);
            char buf[32];

            if (!finger_detected) 
            {
                ssd1306_draw_string(oled, 0, 0, (const uint8_t *)"No Finger Detected", 12, 1);
            } 
            
            else 
            {
                snprintf(buf, sizeof(buf), "HR: %.1f BPM", heart_rate);
                ssd1306_draw_string(oled, 0, 0, (const uint8_t *)buf, 12, 1);
                snprintf(buf, sizeof(buf), "SpO2: %.1f %%", spo2);
                ssd1306_draw_string(oled, 0, 16, (const uint8_t *)buf, 12, 1);
            }

            if (temp_err == ESP_OK) 
            {
                float temp_f = temperature_c * 9.0f / 5.0f + 32.0f;
                snprintf(buf, sizeof(buf), "Temp: %.2f F", temp_f);
                ssd1306_draw_string(oled, 0, 32, (const uint8_t *)buf, 12, 1);
            } 
            
            else 
            {
                ssd1306_draw_string(oled, 0, 32, (const uint8_t *)"Temp Read Fail", 12, 1);
            }

            ssd1306_refresh_gram(oled);
        }

        if (finger_detected && hr_spo2_valid) 
        {
            ESP_LOGI(TAG, "Heart Rate: %.1f BPM, SpO2: %.1f%%", heart_rate, spo2);
        } 
        
        else if (!finger_detected) 
        {
            ESP_LOGW(TAG, "No finger detected");
        }

        if (temp_counter == 0 && temp_err == ESP_OK) 
        {
            ESP_LOGI(TAG, "Temperature: %.2f C", temperature_c);
        }

        temp_counter = (temp_counter + 1) % 20;
        vTaskDelay(pdMS_TO_TICKS(50));
    }
}

Output বিশ্লেষণ

এখন তুমি হার্ট রেট সেন্সরের উপরের লাল আলোতে আঙুল রাখো, আর DS18B20 সেন্সরের প্রোবটি জিহবার নিচে রেখে একটু অপেক্ষা করো। কয়েক মিনিটের মধ্যেই ডিভাইসটি তোমাকে দেখাবে হার্ট রেট, SpO2 মান এবং শরীরের তাপমাত্রা—all in one স্ক্রিনে! 

তুমি জেনে নিশ্চয় খুশি হবে যে, DS18B20 সেন্সরটি ওয়াটারপ্রুফ, তাই ব্যবহারের পর সহজেই পরিষ্কার করে আবার ব্যবহার করতে পারবে। আর তোমার পুরো সিস্টেমটি Type-C কেবল ব্যবহার করে চার্জও করতে পারবে।

health_monitoring_system_output

ESP32 দিয়ে Health Monitoring System প্রোজেক্টটি তৈরি করতে গিয়ে কোন সমস্যায় পড়লে, আমাকে কমেন্টের মাধ্যমে জানাতে পারো। ইন-শা-আল্লাহ আমি অবশ্যই তোমাকে গাইড করার চেষ্টা করবো। তাছাড়া তোমরা বাকি কে কে সফলভাবে এই প্রোজেক্টটি তৈরি করতে পেরেছো, সেটিও কমেন্ট করে জানিও।

ESP32 নিয়ে Beginner to Advance গাইডলাইন পেতে আরো পড়তে পারো………

  1. ESP32 মাইক্রোকন্ট্রোলারের অজানা কিছু তথ্য
  2. ESP32 পাওয়ার সিস্টেম ইঞ্জিনিয়ারিং
  3. ESP32 অটো প্রোগ্রাম রিসেট সার্কিট
  4. ESP প্রোগ্রামিং-এ Espressif IDE VS Arduino IDE
  5. ESP32 মাইক্রোকন্ট্রোলারের কথোপোকথন

 

Mahbub Morshed Rifat
Mahbub Morshed Rifat

Engineer
TechShop Bangladesh

Leave a Reply

Your email address will not be published. Required fields are marked *