Embedded·2024

ECG Heart Rate Bluetooth Monitoring Device

A medical-grade ECG heart rate monitoring device based on Nordic nRF52840, supporting real-time electrocardiogram transmission and arrhythmia detection

clientMedical Technology Company
duration8 months
categoryEmbedded
stack
Nordic nRF52840Bluetooth 5.0C/C++FreeRTOSADS1293Low Power DesignMedical-Grade Algorithms

Project Overview

This project involved developing a wearable ECG heart rate monitoring device for a medical technology company. Built on the Nordic nRF52840 high-performance Bluetooth chip and integrating the professional-grade ADS1293 ECG analog front-end, the device enables 24-hour continuous cardiac monitoring, real-time arrhythmia alerts, and cloud-based data analytics.

Core Technical Challenges

1. Medical-Grade ECG Signal Processing

Challenge:

  • ECG signals are extremely weak (0.5-4mV) and susceptible to noise interference
  • High-precision ADC sampling required (500Hz and above)
  • Real-time signal processing must not interfere with Bluetooth transmission

Solution:

// ADS1293 ECG analog front-end configuration
#define ECG_SAMPLE_RATE    500  // 500Hz sampling rate
#define ECG_LEAD_COUNT     3    // 3-lead electrocardiogram

typedef struct {
    int32_t lead_I;
    int32_t lead_II;
    int32_t lead_III;
    uint32_t timestamp;
} ecg_sample_t;

// ECG digital filter (50Hz notch + 0.5Hz high-pass + 40Hz low-pass)
void ecg_filter_init(ecg_filter_t *filter) {
    // 50Hz notch filter (eliminate power line interference)
    filter->notch_coeff_b[0] = 0.9565;
    filter->notch_coeff_b[1] = -1.9131;
    filter->notch_coeff_b[2] = 0.9565;
    filter->notch_coeff_a[1] = -1.9112;
    filter->notch_coeff_a[2] = 0.9150;

    // Butterworth high-pass filter (eliminate baseline drift)
    filter->hp_cutoff = 0.5;

    // Butterworth low-pass filter (eliminate high-frequency noise)
    filter->lp_cutoff = 40.0;
}

int32_t ecg_apply_filter(ecg_filter_t *filter, int32_t raw_sample) {
    // Notch filtering
    float notch_out = filter->notch_coeff_b[0] * raw_sample +
                      filter->notch_coeff_b[1] * filter->notch_x[0] +
                      filter->notch_coeff_b[2] * filter->notch_x[1] -
                      filter->notch_coeff_a[1] * filter->notch_y[0] -
                      filter->notch_coeff_a[2] * filter->notch_y[1];

    // Update delay line
    filter->notch_x[1] = filter->notch_x[0];
    filter->notch_x[0] = raw_sample;
    filter->notch_y[1] = filter->notch_y[0];
    filter->notch_y[0] = notch_out;

    // High-pass filtering
    float hp_out = apply_butterworth_hp(notch_out, &filter->hp_state);

    // Low-pass filtering
    float lp_out = apply_butterworth_lp(hp_out, &filter->lp_state);

    return (int32_t)lp_out;
}

2. R-Wave Detection and Heart Rate Calculation

Implemented the Pan-Tompkins algorithm for real-time QRS complex detection:

// Pan-Tompkins QRS detection algorithm
typedef struct {
    float derivative_buffer[5];
    float squared_buffer[30];
    float integrated_buffer[30];
    float threshold;
    uint32_t last_qrs_time;
    uint32_t rr_interval_buffer[8];
    uint8_t rr_index;
} qrs_detector_t;

bool qrs_detect(qrs_detector_t *detector, int32_t filtered_sample) {
    // 1. Differentiation (emphasize high slopes)
    float derivative = (2*filtered_sample + detector->derivative_buffer[0]
                       - detector->derivative_buffer[2]
                       - 2*detector->derivative_buffer[3]) / 8.0;

    // 2. Squaring (amplify differences)
    float squared = derivative * derivative;

    // 3. Moving window integration (150ms window)
    float integrated = moving_window_integration(squared, detector->integrated_buffer, 30);

    // 4. Adaptive threshold detection
    if (integrated > detector->threshold) {
        uint32_t current_time = get_timestamp_ms();
        uint32_t rr_interval = current_time - detector->last_qrs_time;

        // Reject excessively short RR intervals (likely noise)
        if (rr_interval > 200) {  // Minimum 200ms (max heart rate 300bpm)
            detector->rr_interval_buffer[detector->rr_index] = rr_interval;
            detector->rr_index = (detector->rr_index + 1) % 8;
            detector->last_qrs_time = current_time;

            // Update adaptive threshold
            update_adaptive_threshold(detector);

            return true;  // R-wave detected
        }
    }

    return false;
}

uint16_t calculate_heart_rate(qrs_detector_t *detector) {
    // Calculate average RR interval
    uint32_t avg_rr = 0;
    for (int i = 0; i < 8; i++) {
        avg_rr += detector->rr_interval_buffer[i];
    }
    avg_rr /= 8;

    // Heart rate (bpm) = 60000 / RR interval (ms)
    uint16_t heart_rate = (avg_rr > 0) ? (60000 / avg_rr) : 0;

    return heart_rate;
}

3. Arrhythmia Detection Algorithm

// Arrhythmia types
typedef enum {
    RHYTHM_NORMAL,           // Normal sinus rhythm
    RHYTHM_BRADYCARDIA,      // Bradycardia (<60 bpm)
    RHYTHM_TACHYCARDIA,      // Tachycardia (>100 bpm)
    RHYTHM_IRREGULAR,        // Irregular rhythm (excessive RR interval variability)
    RHYTHM_AFIB,             // Suspected atrial fibrillation
    RHYTHM_PVC              // Premature ventricular contraction
} cardiac_rhythm_t;

cardiac_rhythm_t detect_arrhythmia(qrs_detector_t *detector, uint16_t heart_rate) {
    // 1. Bradycardia detection
    if (heart_rate < 60) {
        return RHYTHM_BRADYCARDIA;
    }

    // 2. Tachycardia detection
    if (heart_rate > 100) {
        return RHYTHM_TACHYCARDIA;
    }

    // 3. RR interval variability analysis (detect atrial fibrillation)
    float rr_variance = calculate_rr_variance(detector->rr_interval_buffer, 8);
    float rr_mean = calculate_rr_mean(detector->rr_interval_buffer, 8);
    float cv = rr_variance / rr_mean;  // Coefficient of variation

    if (cv > 0.15) {  // Coefficient of variation > 15%
        // Further analysis for atrial fibrillation
        bool is_afib = afib_classifier(detector);
        if (is_afib) {
            return RHYTHM_AFIB;
        }
        return RHYTHM_IRREGULAR;
    }

    // 4. Premature contraction detection (sudden RR shortening followed by compensatory pause)
    if (detect_premature_contraction(detector)) {
        return RHYTHM_PVC;
    }

    return RHYTHM_NORMAL;
}

4. Low-Power Design (7-Day Battery Life)

Power Optimization Strategy:

// Power management configuration
#define ECG_CONTINUOUS_MODE     0  // Continuous monitoring mode (high power)
#define ECG_SMART_MODE          1  // Smart monitoring mode (power saving)

typedef struct {
    uint8_t mode;
    uint16_t sampling_rate;      // Current sampling rate
    uint16_t ble_interval;       // Bluetooth connection interval
    bool motion_detected;        // Motion detection flag
} power_config_t;

void optimize_power_consumption(power_config_t *config) {
    // 1. Dynamic sampling rate adjustment
    if (config->motion_detected) {
        // During exercise: reduce sampling rate to save computation
        config->sampling_rate = 250;  // 250Hz (still meets medical standards)
    } else {
        // At rest: normal sampling rate
        config->sampling_rate = 500;  // 500Hz
    }

    // 2. Dynamic Bluetooth connection interval adjustment
    if (config->mode == ECG_SMART_MODE) {
        // Normal rhythm: extend Bluetooth interval
        config->ble_interval = 1000;  // Update once per second
    } else {
        // Anomaly detected: shorten interval for real-time alerts
        config->ble_interval = 100;   // 100ms update
    }

    // 3. Peripheral power saving
    nrf_gpio_cfg_sense_input(MOTION_SENSOR_PIN,
                             NRF_GPIO_PIN_PULLUP,
                             NRF_GPIO_PIN_SENSE_LOW);

    // 4. Enable nRF52 DC/DC converter (40% power reduction)
    NRF_POWER->DCDCEN = 1;
}

// FreeRTOS task priority configuration
void create_rtos_tasks(void) {
    // Highest priority: ECG sampling (cannot miss samples)
    xTaskCreate(ecg_sampling_task, "ECG_SAMP", 512, NULL, 5, NULL);

    // High priority: signal processing and QRS detection
    xTaskCreate(ecg_processing_task, "ECG_PROC", 1024, NULL, 4, NULL);

    // Medium priority: Bluetooth data transmission
    xTaskCreate(ble_transmit_task, "BLE_TX", 512, NULL, 3, NULL);

    // Low priority: storage and logging
    xTaskCreate(data_logging_task, "LOG", 256, NULL, 2, NULL);
}

5. Bluetooth 5.0 High-Speed Transmission

// BLE ECG service definition (custom UUID)
#define BLE_UUID_ECG_SERVICE            0x181D  // Health Thermometer Service Base
#define BLE_UUID_ECG_REALTIME_CHAR      0x2A1C  // Custom ECG Realtime Data
#define BLE_UUID_ECG_STATS_CHAR         0x2A1D  // Custom ECG Statistics

// ECG real-time data characteristic (supports Notification)
static ble_gatts_char_handles_t ecg_realtime_handles;

void ble_ecg_service_init(void) {
    ble_uuid_t ble_uuid;
    ble_gatts_char_md_t char_md;
    ble_gatts_attr_t attr_char_value;

    // Set characteristic properties
    memset(&char_md, 0, sizeof(char_md));
    char_md.char_props.notify = 1;  // Enable Notification
    char_md.char_props.read = 1;

    // Set CCCD (Client Characteristic Configuration Descriptor)
    ble_gatts_attr_md_t cccd_md;
    memset(&cccd_md, 0, sizeof(cccd_md));
    BLE_GAP_CONN_SEC_MODE_SET_OPEN(&cccd_md.read_perm);
    BLE_GAP_CONN_SEC_MODE_SET_OPEN(&cccd_md.write_perm);
    cccd_md.vloc = BLE_GATTS_VLOC_STACK;
    char_md.p_cccd_md = &cccd_md;

    // Add characteristic
    ble_uuid.type = BLE_UUID_TYPE_BLE;
    ble_uuid.uuid = BLE_UUID_ECG_REALTIME_CHAR;

    memset(&attr_char_value, 0, sizeof(attr_char_value));
    attr_char_value.p_uuid = &ble_uuid;
    attr_char_value.max_len = 20;  // Maximum 20 bytes (BLE 4.2)
    attr_char_value.init_len = 0;

    sd_ble_gatts_characteristic_add(ecg_service_handle,
                                     &char_md,
                                     &attr_char_value,
                                     &ecg_realtime_handles);
}

// Bluetooth 5.0 extended data length (maximum 251 bytes)
void enable_ble_data_length_extension(void) {
    ble_opt_t opt;
    opt.common_opt.conn_evt_ext.enable = 1;
    sd_ble_opt_set(BLE_COMMON_OPT_CONN_EVT_EXT, &opt);

    // Set PHY to 2Mbps (Bluetooth 5.0)
    ble_gap_phys_t phys = {
        .tx_phys = BLE_GAP_PHY_2MBPS,
        .rx_phys = BLE_GAP_PHY_2MBPS
    };
    sd_ble_gap_phy_update(conn_handle, &phys);
}

// Real-time ECG data transmission (compressed format)
void send_ecg_data_via_ble(ecg_sample_t *samples, uint8_t count) {
    uint8_t buffer[244];  // BLE 5.0 maximum payload
    uint16_t offset = 0;

    // Packet header
    buffer[offset++] = 0xEC;  // Magic byte
    buffer[offset++] = count;  // Sample count

    // Differential encoding compression (reduce data volume)
    int32_t prev_value = 0;
    for (uint8_t i = 0; i < count; i++) {
        int16_t diff = (samples[i].lead_I - prev_value) >> 2;  // Divide by 4 to reduce precision
        buffer[offset++] = (diff >> 8) & 0xFF;
        buffer[offset++] = diff & 0xFF;
        prev_value = samples[i].lead_I;
    }

    // Send Notification
    ble_gatts_hvx_params_t hvx_params;
    memset(&hvx_params, 0, sizeof(hvx_params));
    hvx_params.handle = ecg_realtime_handles.value_handle;
    hvx_params.type = BLE_GATT_HVX_NOTIFICATION;
    hvx_params.offset = 0;
    hvx_params.p_len = &offset;
    hvx_params.p_data = buffer;

    sd_ble_gatts_hvx(conn_handle, &hvx_params);
}

Project Results

Technical Metrics

  • ECG sampling precision: 24-bit ADC, 500Hz sampling rate
  • R-wave detection: Real-time QRS complex detection via Pan-Tompkins algorithm
  • Heart rate measurement range: 30-250 bpm
  • Bluetooth transmission: Bluetooth 5.0 2Mbps PHY

Innovation Highlights

  1. Medical-grade signal quality: TI ADS1293 professional ECG analog front-end
  2. Real-time arrhythmia alerts: Built-in detection for 5 types of cardiac anomalies with instant push notifications to mobile app
  3. Low-power design: Smart power-saving algorithms dynamically adjust sampling rate and Bluetooth connection interval
  4. Cloud data analysis: Integrated cloud model providing health data analytics

Technology Stack

Hardware Platform:

  • Nordic nRF52840 (ARM Cortex-M4F 64MHz)
  • TI ADS1293 (3-lead ECG analog front-end)
  • InvenSense ICM-20948 (9-axis motion sensor)
  • 300mAh lithium polymer battery

Firmware Development:

  • C/C++ low-level driver development
  • FreeRTOS real-time operating system
  • Nordic SDK 17.1.0
  • SoftDevice S140 Bluetooth protocol stack

Development Tools:

  • Segger Embedded Studio
  • nRF Connect SDK
  • J-Link debugger
  • Logic analyzer

Medical Certifications:

  • IEC 60601-1 Electrical Safety Standard
  • IEC 60601-2-27 ECG Equipment Specific Standard
  • ISO 13485 Medical Device Quality Management System

Project Duration: August 2022 - March 2023 Technical Domains: Embedded Systems, Medical Electronics, Bluetooth Communication, Digital Signal Processing

$ ls projects/embedded/

More work in Embedded.