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
- Medical-grade signal quality: TI ADS1293 professional ECG front-end, achieving clinical-standard signal quality
- Real-time arrhythmia alerts: Built-in detection for 5 types of cardiac anomalies with instant push notifications to mobile app
- Extended battery life: Smart power-saving algorithms enable 7-day continuous monitoring
- 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