System Specifications
| Parameter | Specification | Component |
|---|---|---|
| MCU + BLE | nRF52840 (Cortex-M4F, 64 MHz) | Nordic Semiconductor |
| PPG / SpO2 | Dual-LED (red + IR), 18-bit ADC | MAX30102 |
| ECG | Single-lead, instrumentation amp | AD8232 |
| IMU | 6-axis (accel + gyro) | LSM6DSO |
| Display | 0.96″ OLED 128×64 | SSD1306 (I2C) |
| Battery | 150 mAh LiPo, BQ25180 charger | USB-C charging |
| Target Life | 7 days continuous HR, 12h ECG | Power management |
| Enclosure | IP67, medical-grade silicone band | 38 mm watch form |
Hardware Architecture
flowchart TD
A["150mAh LiPo"] --> B["BQ25180
Charger IC"]
B --> C["TPS62740
1.8V Buck"]
C --> D["nRF52840
BLE 5.0 SoC"]
D -->|I2C| E["MAX30102
PPG/SpO2"]
D -->|ADC| F["AD8232
ECG AFE"]
D -->|SPI| G["LSM6DSO
6-axis IMU"]
D -->|I2C| H["SSD1306
OLED Display"]
D --> I["BLE 5.0
Smartphone App"]
D --> J["QSPI Flash
4MB Log Storage"]
style D fill:#3B9797,color:#fff
style E fill:#BF092F,color:#fff
PPG & SpO2 Signal Chain
/* MAX30102 PPG/SpO2 driver — I2C interface on nRF52840
Configures dual-LED mode for simultaneous HR + SpO2 */
#include <stdint.h>
#define MAX30102_ADDR 0x57
#define REG_FIFO_WR_PTR 0x04
#define REG_FIFO_DATA 0x07
#define REG_MODE_CONFIG 0x09
#define REG_SPO2_CONFIG 0x0A
#define REG_LED1_PA 0x0C /* Red LED current */
#define REG_LED2_PA 0x0D /* IR LED current */
typedef struct {
uint32_t red_raw; /* Red LED ADC (18-bit) */
uint32_t ir_raw; /* IR LED ADC (18-bit) */
float heart_rate; /* BPM from peak detection */
float spo2; /* SpO2 percentage */
uint8_t confidence; /* Signal quality (0-100) */
} ppg_reading_t;
/* SpO2 calibration curve (empirical):
* R = (AC_red / DC_red) / (AC_ir / DC_ir)
* SpO2 = 110.0 - 25.0 * R
* Valid for R ∈ [0.4, 1.0] → SpO2 ∈ [85%, 100%]
*/
/* Configuration for continuous HR + SpO2 mode:
* - Sample rate: 100 Hz (50 Hz per LED)
* - ADC range: 16384 (18-bit)
* - LED pulse width: 411 µs (best SNR)
* - Red LED: 6.4 mA, IR LED: 6.4 mA
* - Average: 4 samples (effective 25 Hz output)
*/
ECG Analog Front-End
# ECG signal processing — R-peak detection and heart rate
# Simulates AD8232 output digitized by nRF52840 ADC (12-bit)
import numpy as np
# ECG parameters
sample_rate_hz = 250 # AD8232 → nRF52840 ADC
duration_sec = 10
n_samples = sample_rate_hz * duration_sec
# Simulate ECG-like signal (simplified PQRST complex)
t = np.arange(n_samples) / sample_rate_hz
heart_rate_bpm = 72
beat_period = 60.0 / heart_rate_bpm
# Generate synthetic ECG with R-peaks
ecg = np.zeros(n_samples)
for beat_time in np.arange(0, duration_sec, beat_period):
idx = int(beat_time * sample_rate_hz)
if idx + 20 < n_samples:
ecg[idx + 5] = -0.15 # Q wave
ecg[idx + 8] = 1.0 # R peak
ecg[idx + 11] = -0.3 # S wave
ecg[idx + 18] = 0.2 # T wave
# Simple R-peak detection (threshold-based)
threshold = 0.5
r_peaks = []
refractory = int(0.2 * sample_rate_hz) # 200ms refractory period
last_peak = -refractory
for i in range(1, n_samples - 1):
if ecg[i] > threshold and ecg[i] > ecg[i-1] and ecg[i] > ecg[i+1]:
if (i - last_peak) > refractory:
r_peaks.append(i)
last_peak = i
# Calculate heart rate from R-R intervals
rr_intervals = np.diff(r_peaks) / sample_rate_hz
hr_values = 60.0 / rr_intervals
print("ECG Signal Processing Results")
print("=" * 50)
print(f"Sample rate: {sample_rate_hz} Hz")
print(f"Duration: {duration_sec} sec")
print(f"R-peaks detected: {len(r_peaks)}")
print(f"Mean R-R interval: {np.mean(rr_intervals):.3f} sec")
print(f"Mean heart rate: {np.mean(hr_values):.1f} BPM")
print(f"HR std deviation: {np.std(hr_values):.2f} BPM")
print(f"HRV (SDNN): {np.std(rr_intervals)*1000:.1f} ms")
Power Budget Analysis
# Wearable power budget — 150 mAh battery life estimation
# nRF52840 + MAX30102 + AD8232 + SSD1306 OLED
import numpy as np
components = {
"nRF52840 (sleep)": {"current_uA": 1.5, "duty": 0.90},
"nRF52840 (active)": {"current_uA": 3000, "duty": 0.05},
"nRF52840 (BLE TX)": {"current_uA": 5500, "duty": 0.02},
"nRF52840 (ADC ECG)": {"current_uA": 800, "duty": 0.03},
"MAX30102 (HR mode)": {"current_uA": 600, "duty": 1.00},
"MAX30102 (SpO2 burst)": {"current_uA": 1200, "duty": 0.00},
"AD8232 (ECG standby)": {"current_uA": 170, "duty": 0.95},
"AD8232 (ECG active)": {"current_uA": 170, "duty": 0.05},
"LSM6DSO (low power)": {"current_uA": 12, "duty": 1.00},
"SSD1306 (off)": {"current_uA": 1, "duty": 0.95},
"SSD1306 (display on)": {"current_uA": 8000, "duty": 0.05},
"QSPI Flash (standby)": {"current_uA": 5, "duty": 0.99},
"QSPI Flash (write)": {"current_uA": 15000, "duty": 0.01},
"TPS62740 quiescent": {"current_uA": 0.36, "duty": 1.00},
"BQ25180 quiescent": {"current_uA": 0.5, "duty": 1.00},
}
battery_mah = 150
efficiency = 0.88 # Buck converter efficiency
print("Wearable Health Monitor — Power Budget")
print("=" * 60)
print(f"{'Component':<28} {'Avg µA':>8} {'Duty':>6}")
print("-" * 60)
total_ua = 0
for name, spec in components.items():
avg = spec["current_uA"] * spec["duty"]
total_ua += avg
print(f"{name:<28} {avg:>8.1f} {spec['duty']:>5.0%}")
system_ua = total_ua / efficiency
battery_hours = (battery_mah * 1000) / system_ua
battery_days = battery_hours / 24
print("-" * 60)
print(f"{'Total (load)':<28} {total_ua:>8.1f} µA")
print(f"{'System (w/ regulator)':<28} {system_ua:>8.1f} µA")
print(f"{'Battery capacity':<28} {battery_mah:>8} mAh")
print(f"{'Estimated battery life':<28} {battery_hours:>8.1f} hours")
print(f"{' ':<28} {battery_days:>8.1f} days")
Sensor Configuration Planner
Wearable Sensor Planner
Configure sensor modes and sampling parameters. Download as Word, Excel, or PDF.
Conclusion
This wearable integrates optical (MAX30102 PPG/SpO2), electrical (AD8232 ECG), and inertial (LSM6DSO IMU) sensing with BLE 5.0 connectivity on a power budget targeting 7+ days on a 150 mAh cell. The design demonstrates the critical balance between measurement fidelity and energy efficiency in medical wearables.
Next Capstone
In Capstone 8: Autonomous Robot Platform, we’ll build a mobile robotics platform with motor control, LIDAR navigation, and real-time path planning on an embedded SoC.