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, CE medical certified

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.

The product has passed CE medical device certification and has sold over 50,000 units in the European market.

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 accuracy: 99.2% (validated against MIT-BIH database)
  • Heart rate measurement range: 30-250 bpm, error +/-2 bpm
  • Battery life: 7 days continuous monitoring (300mAh lithium battery)
  • Bluetooth transmission latency: < 50ms (Bluetooth 5.0 2Mbps PHY)
  • Water resistance: IP67

Business Results

  • Passed CE medical device certification (MDD 93/42/EEC Class IIa)
  • Awarded 2023 Taiwan Excellence Award
  • European market sales: 50,000+ units
  • Medical professional rating: 4.6/5.0
  • Helped client secure EUR 2M in venture capital funding

Innovation Highlights

  1. Medical-grade signal quality: TI ADS1293 professional ECG front-end, achieving clinical-standard signal quality
  2. Real-time arrhythmia alerts: Built-in detection for 5 types of cardiac anomalies with instant push notifications to mobile app
  3. Extended battery life: Smart power-saving algorithms enable 7-day continuous monitoring
  4. Cloud AI analysis: Integrated cloud deep learning model providing personalized health insights

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

Client Testimonial

"The BASHCAT team demonstrates exceptional firmware development expertise, particularly in medical signal processing. They not only met all technical specifications but also proactively optimized battery life, significantly boosting product competitiveness. Most importantly, they are highly experienced with medical certification processes and helped us successfully obtain CE certification."

Product Director, German Medical Technology Company


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

$ ls projects/embedded/

More work in Embedded.