ezgif-537466593956e125.mp4
A real-time medical device prototype that uses Deep Learning (TinyML) to detect sleep apnea events from raw ECG signals directly on a microcontroller. This project demonstrates the end-to-end pipeline from training a 1D-CNN in Python to deploying optimized C code on bare-metal hardware.
-
Goal: Classify 60-second ECG windows as "Apnea" or "Normal" in real-time.
-
Hardware: STM32 Nucleo-F446RE (ARM Cortex-M4 @ 180MHz).
-
Latency:
$<$ 130 ms per inference. -
Model Size: ~13KB parameters < 1KB Stack usage).
-
Performance: < 89% Confidence on Apnea events (post-optimization).
-
Input: PhysioNet ECG Data (Processed in Python).
-
Model: Trained .h5 Model converted via STM32Cube.AI.
-
Firmware: Generated C Code running on STM32 Firmware.
-
Output: Real-Time Inference triggering an LED Indicator.
I designed a lightweight 1D Convolutional Neural Network (CNN) optimized for embedded deployment.
-
Input: 6000 samples (60 seconds @ 100Hz).
-
Layers: 3x Conv1D blocks with MaxPooling, followed by GlobalAveragePooling and a Dense Sigmoid output.
-
Dataset: PhysioNet Apnea-ECG Database (v1.0.0).
Problem: Initial training yielded 99% accuracy on paper but failed in hardware testing. The model was "shy," predicting a low probability 27% for both Apnea and Normal inputs.
Root Cause: Severe class imbalance (the dataset had far more healthy minutes than apnea minutes). The model minimized error by safely guessing "Normal" constantly.
Solution:
-
Class Weighting: Implemented
sklearn.class_weightduring training to heavily penalize missed Apnea events. -
Smart Scanning: Developed a custom "Scanner Script" (
apnea_helper.py) to mine the dataset for high-confidence "Gold Standard" windows for Hardware-in-the-Loop validation.
-
Used STM32Cube.AI (X-CUBE-AI) to quantize and convert the Keras model into optimized C code.
-
Integrated the model into a bare-metal C application (
main.c). -
Hardware Logic:
-
Input: Serialized ECG windows injected via firmware (HIL Testing).
-
Output: If Probability
$>$ 0.5, trigger LD2 (Green LED).
-
.
|-- Core/
| |-- Src/
| | |-- main.c # Main application loop (Inference logic)
| | `-- apnea_ai.c # Wrapper for X-CUBE-AI functions
| `-- Inc/
| `-- one_window.h # Header file containing Test Data
|-- Python_Training/
| |-- train_apnea_small.py # Training script with Class Weighting
| |-- apnea_helper.py # Scanner script
| `-- requirements.txt # Python dependencies
|-- SleepApneaDetection.ioc # STM32CubeMX Configuration
`-- README.md| Metric | Value |
|---|---|
| Inference Time | 128.75 ms |
| CPU Load | ~12% |
| Flash Usage | ~177 KB |
| Stack Usage | 912 Bytes |
UART logs demonstrating the discrimination capability of the retrained model:
Injecting APNEA Data... Apnea Prob: 0.8939 [LED ON]
Injecting NORMAL Data... Apnea Prob: 0.1956 [LED OFF]
-
Steven Reynoso
-
LinkedIn: linkedin.com/in/stevenreynoso123
-
GitHub: github.com/StevenReynoso
License: MIT License. Dataset provided by PhysioNet (ODC-BY 1.0).