Design of a Dual-Mode Biometric Data Acquisition System Using STM32
System Architecture Overview
This project implements a portable biometric monitoring device centered around the STM32F103C8T6 microcontroller. The system integrates both photoplethysmography (PPG) and electrocardiography (ECG) sensing technologies to provide comprehensive cardiac health monitoring. It supports real-time local visualization via an OLED display, data logging to an SD card, and wireless telemetry using Bluetooth or Wi-Fi modules. The design focuses on low-power operation, signal integrity, and modularity, making it suitable for telemedicine and wearable health applications.
Hardware Implementation
The system architecture is divided into distinct functional blocks: the analog front-end, the digital processing core, the user interface, and connectivity modules. Communication between the MCU and peripherals utilizes standard protocols including I2C, SPI, UART, and ADC.
Key Hardware Components
| Module | Specification | Role |
|---|---|---|
| MCU | STM32F103C8T6 (Cortex-M3, 72MHz) | Central processing, sensor fusion, and power management. |
| PPG Sensor | MAX30102 (I2C Interface) | Non-invasive heart rate and SpO2 acquisition using red/IR LEDs. |
| ECG Sensor | AD8232 (Analog Front-End) | High-gain instrumentation amplifier for chest-strap ECG signal acquisition. |
| Display | 0.96" OLED (128x64, I2C) | Real-time waveform rendering and parameter display. |
| Connectivity | HC-05 (BLE), ESP8266 (Wi-Fi) | Wireless data transmission to mobile apps or cloud servers. |
| Storage | MicroSD Card (SPI interface) | Local archival of raw ECG data and vital signs. |
| Power | Li-Ion 500mAh + TP4056 + AMS1117 | Battery charging circuit and 3.3V voltage regulation. |
Signal Conditioning Circuits
1. Optical Path (MAX30102): The sensor interfaces via I2C (SCL: PB10, SDA: PB11). It is configured in SpO2 mode to simultaneously acquire both Red and Infrared light intensity. The integration time is set to 411µs with a sample rate of 100Hz to optimize power consumption while maintaining signal resolution. An active interrupt pin (PA7) triggers the MCU when the FIFO buffer reaches the water-mark level.
2. Bio-electrical Path (AD8232): This module provides a signal gain of 1000V/V and includes a right-leg drive (RLD) circuit to minimize common-mode noise. The output signal, centered around 0.5V DC bias, is fed into the MCU's ADC1 Channel 0 (PA0). A hardware band-pass filter limits the bandwidth to 0.5Hz - 40Hz to focus on the QRS complex and suppress motion artifacts.
3. Power Management: A TP4056 IC manages the constant-current/constant-voltage charging of the single-cell Li-ion battery. An AMS1117-3.3 LDO regulator steps down the battery voltage to a stable 3.3V rail for all logic circuits. The firmware leverages the STM32's Stop Mode, cutting power to non-esential peripherals during idle periods to extend battery life beyond 72 hours.
Firmware Design (C Language)
The firmware is structured using a modular architecture. It employs a main loop that polls status flags and executes state machine logic for data acquisition, processing, and transmission.
System Main Control Loop
#include "stm32f10x.h"
#include "optical_sensor.h"
#include "ecg_adc.h"
#include "display_drv.h"
#include "comm_uart.h"
#include "file_system.h"
#include "signal_proc.h"
// Device state context
typedef struct {
uint8_t op_mode; // 0=PPG Mode, 1=ECG Mode
uint8_t active_flag;
float bpm_val;
float oxygen_sat;
float ecg_buffer[256];
bool bpm_ready;
} DeviceContext;
int main(void) {
// Peripheral Initialization
HAL_Init();
SystemClock_Config();
OPT_Init(); // Configure MAX30102
ECG_ADC_Init(); // Configure ADC for AD8232
DISP_Init(); // OLED Startup
BLE_Init(); // UART Configuration
FS_Init(); // SD Card Mount
DSP_Init(); // Algorithm coefficients
DeviceContext dev_ctx = {0};
dev_ctx.op_mode = 0; // Default to PPG
dev_ctx.active_flag = 1;
while (1) {
if (dev_ctx.active_flag) {
// Data Acquisition Handler
if (dev_ctx.op_mode == 0) {
// PPG Data Path
OPT_Fetch_Vitals(&dev_ctx.bpm_val, &dev_ctx.oxygen_sat, &dev_ctx.bpm_ready);
} else {
// ECG Data Path
ECG_Capture_Segment(dev_ctx.ecg_buffer, 256);
// Process QRS complex to determine HR
dev_ctx.bpm_val = DSP_Compute_HR_ECG(dev_ctx.ecg_buffer, 256);
dev_ctx.bpm_ready = true;
}
// UI Update
DISP_Render_Stats(dev_ctx.bpm_val, dev_ctx.oxygen_sat, dev_ctx.ecg_buffer, dev_ctx.op_mode);
// Data Logging
FS_Log_Segment(&dev_ctx);
// Wireless Telemetry
if (dev_ctx.bpm_ready) {
BLE_Transmit_Packet(dev_ctx.bpm_val, dev_ctx.oxygen_sat);
}
// Power Saving Strategy
if (Is_Idle_Timeout_Reached()) {
PWR_EnterStopMode();
}
}
}
}
Optical Sensor Driver (MAX30102)
#define OPT_I2C_ADDR 0xAE
// Register Map Definitions
#define REG_MODE_CFG 0x09
#define REG_SPO2_CFG 0x0A
#define REG_FIFO_DATA 0x07
#define REG_LED_PA_1 0x0C
void OPT_Init(void) {
// Soft Reset
I2C_WriteByte(OPT_I2C_ADDR, REG_MODE_CFG, 0x40);
HAL_Delay(10);
// Enable SpO2 Mode (Red + IR)
I2C_WriteByte(OPT_I2C_ADDR, REG_MODE_CFG, 0x03);
// Set Sample Rate: 100sps, Pulse Width: 411us, ADC Range: 4096
I2C_WriteByte(OPT_I2C_ADDR, REG_SPO2_CFG, 0x27);
// LED Current: 7.0mA for both channels
I2C_WriteByte(OPT_I2C_ADDR, REG_LED_PA_1, 0x24);
I2C_WriteByte(OPT_I2C_ADDR, REG_LED_PA_1 + 1, 0x24);
}
void OPT_Fetch_Vitals(float *bpm, float *spo2, bool *status) {
uint8_t rx_buf[6];
uint32_t ir_raw, red_raw;
// Read 3 bytes Red + 3 bytes IR
I2C_ReadBuffer(OPT_I2C_ADDR, REG_FIFO_DATA, rx_buf, 6);
// Reconstruct 18-bit data
red_raw = ((uint32_t)rx_buf[0] << 16) | ((uint32_t)rx_buf[1] << 8) | rx_buf[2];
red_raw &= 0x3FFFF;
ir_raw = ((uint32_t)rx_buf[3] << 16) | ((uint32_t)rx_buf[4] << 8) | rx_buf[5];
ir_raw &= 0x3FFFF;
// Execute processing algorithms
*bpm = DSP_Calc_PPG_HR(ir_raw, red_raw, status);
*spo2 = DSP_Calc_SpO2(ir_raw, red_raw);
}
ECG Analog Front-End Driver
void ECG_ADC_Init(void) {
// Enable GPIOA and ADC1 clocks
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_ADC1, ENABLE);
GPIO_InitTypeDef GPIO_InitStruct = {0};
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AIN;
GPIO_Init(GPIOA, &GPIO_InitStruct);
// ADC Configuration Structure
ADC_InitTypeDef ADC_InitStruct = {0};
ADC_InitStruct.ADC_Mode = ADC_Mode_Independent;
ADC_InitStruct.ADC_ScanConvMode = DISABLE;
ADC_InitStruct.ADC_ContinuousConvMode = DISABLE; // Single conversion per trigger
ADC_InitStruct.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;
ADC_InitStruct.ADC_DataAlign = ADC_DataAlign_Right;
ADC_InitStruct.ADC_NbrOfChannel = 1;
ADC_Init(ADC1, &ADC_InitStruct);
// Configure Channel 0 with 239.5 cycles sampling time for stability
ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_239Cycles5);
ADC_Cmd(ADC1, ENABLE);
// Run Calibration
ADC_ResetCalibration(ADC1);
while(ADC_GetResetCalibrationStatus(ADC1));
ADC_StartCalibration(ADC1);
while(ADC_GetCalibrationStatus(ADC1));
}
void ECG_Capture_Segment(float *buffer, uint16_t count) {
for(uint16_t i = 0; i < count; i++) {
ADC_SoftwareStartConvCmd(ADC1, ENABLE);
while(!ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC));
uint16_t raw_adc = ADC_GetConversionValue(ADC1);
// Convert to Voltage and remove DC offset (0.5V)
float voltage_mv = ((float)raw_adc * 3300.0f / 4095.0f) - 500.0f;
buffer[i] = voltage_mv;
}
}
Signal Processing Algorithms
// Adaptive threshold for QRS detection
#define QRS_AMP_LIMIT 800.0f
#define REFRACTORY_MS 200
float DSP_Compute_HR_ECG(float *data, uint16_t len) {
static uint32_t last_peak_tick = 0;
float local_max = 0;
int16_t peak_idx = -1;
// Scan for maximum peak in the window
for(int i = 2; i < len - 2; i++) {
// Check for peak condition (higher than neighbors)
if(data[i] > data[i-1] && data[i] > data[i+1]) {
if(data[i] > local_max && data[i] > QRS_AMP_LIMIT) {
local_max = data[i];
peak_idx = i;
}
}
}
if(peak_idx != -1) {
uint32_t current_tick = HAL_GetTick();
// Validate refractory period
if((current_tick - last_peak_tick) > REFRACTORY_MS && last_peak_tick != 0) {
uint32_t interval_ms = current_tick - last_peak_tick;
last_peak_tick = current_tick;
// Calculate BPM
return 60000.0f / (float)interval_ms;
}
last_peak_tick = current_tick;
}
return 0.0f; // No valid beat detected
}
System Validation and Optimization
Performance Metrics
| Metric | Target | Validation Method |
|---|---|---|
| Heart Rate Accuracy | ±3 BPM (Resting) | Benchmarked against clinical ECG. |
| SpO2 Accuracy | ±2% (90-100%) | Compared to FDA-approved pulse oximeter. |
| ECG Signal Entegrity | SNR > 20dB | Signal injection test (1mV sine wave). |
| Operating Duration | > 72 Hours | Continuous discharge test at 500mAh. |
Future Enhancements
- Advanced Filtering: Implementation of digital filters (Butterworth/IIR) to replace basic thresholding and improve noise rejection for ECG signals during movement.
- Algorithm Upgrade: Migrating to the Pan-Tompkins algorithm for robust QRS detection in noisy environments.
- Data Compression: Utilizing delta-encoding or Huffman coding on the SD card to maximize storage duration for long-term Holter monitoring.