Arduino + MATLAB Sleep Tracker

A real-time sleep quality monitoring system that uses an Arduino Nano to collect physiological and environmental data during sleep, streams it over serial to MATLAB, and produces live dashboards, a scored sleep report, and personalised recommendations.


Table of Contents

  1. Project Overview
  2. Hardware
  3. Arduino Firmware
  4. MATLAB Application
  5. Live Dashboards
  6. End-of-Session Reports
  7. Scoring Algorithm — Deep Dive
  8. Snore Detection & Analysis
  9. Setup & Usage
  10. Tuning the Thresholds
  11. File Structure

Project Overview

The system monitors five dimensions of sleep quality simultaneously:

Dimension Sensor Used What It Measures
Movement MMA8452Q accelerometer Body restlessness during sleep
Snoring frequency Analogue sound sensor How often snoring events occur
Snoring regularity Sound sensor (derived) Consistency of snoring rhythm (CoV)
Sweating DHT11 humidity sensor Elevated ambient humidity from perspiration
Temperature comfort DHT11 temperature sensor Whether the room is within the ideal sleep temperature range

Data is sampled at approximately 10 Hz (100 ms Arduino loop delay), streamed as CSV over USB serial at 9600 baud, ingested by MATLAB in real time, scored, and visualised across three live figure windows. When the session ends, MATLAB saves a PNG report, a .mat data file, a .csv data file, and a plain-text summary — then opens a final summary dashboard.


Hardware

Arduino Nano

The Arduino Nano acts as the central data acquisition unit. It reads all three sensors on every loop iteration and prints a comma-separated line to the serial port. It is powered via USB, which also carries the serial data stream to the host PC running MATLAB.

DHT11 — Humidity & Temperature Sensor

What it measures: Relative humidity (0–100 %) and ambient temperature (0–50 °C) with ±5 % and ±2 °C accuracy respectively.

Why both channels matter for sleep:

  • Humidity is a proxy for sweating. When a person sweats during sleep, the local air humidity around the body rises. The system flags humidity readings above SWEAT_LEVEL (default 60 %) as a sweating event and penalises the sweat score accordingly.
  • Temperature affects sleep stage progression. Research shows that a skin temperature of approximately 30 °C (corresponding to a room temperature of roughly 20–40 °C depending on bedding) is optimal for deep sleep. The system penalises the temperature score when the reading falls outside TEMP_IDEAL = [20 40].

Sampling rate: The DHT11 is a slow sensor — it can only be read reliably every 2 seconds. The firmware enforces this with a millis() interval (dhtInterval = 2000 ms). Between reads, the previously cached values are re-transmitted so that the CSV stream remains continuous at 10 Hz.

Connection: Data pin → Arduino digital pin 6 (defined as DHT11PIN). Requires the dht11 Arduino library.

MMA8452Q — 3-Axis Accelerometer

What it measures: Linear acceleration along X, Y, and Z axes as raw 12-bit signed integers (range ±2 g in default configuration), communicated over I²C.

Why it matters for sleep: Body movement during sleep is one of the strongest indicators of sleep depth. Frequent, large movements indicate light sleep or waking; sustained stillness indicates deep sleep.

I²C address: 0x1C (the ADDR pin is pulled low). The firmware puts the device into active mode by writing 0x01 to CTRL_REG1 (register 0x2A).

Reading procedure: The firmware requests 6 bytes starting at register 0x01 (OUT_X_MSB). Each axis is reconstructed as a 16-bit signed integer by combining the MSB and LSB bytes.

Connection: SDA → Arduino A4, SCL → Arduino A5 (standard Nano I²C pins). Powered at 3.3 V.

Sound Sensor

What it measures: Ambient sound level as a raw 10-bit ADC value (0–1023) on analogue pin A1. Most common breakout boards use an electret microphone with a simple amplifier and a variable gain potentiometer.

Why it matters: Snoring produces characteristic bursts of loud sound. The system uses a configurable threshold (SNORE_THRESHOLD, default 700 out of 1023) to detect snore events, tracks their timing, and analyses the interval pattern to assess breathing regularity.

Connection: Analogue output → Arduino A1 (SOUND_SENSOR_PIN). Powered at 5 V.


Arduino Firmware

Dependencies

  • dht11.h — DHT11 sensor library (install via Arduino Library Manager or manually)
  • Wire.h — Built-in I²C library (standard with Arduino IDE)

Pin Assignments

Pin Purpose
D6 DHT11 data
A1 Sound sensor analogue output
A4 (SDA) MMA8452Q I²C data
A5 (SCL) MMA8452Q I²C clock

Initialisation

In setup(), the firmware:

  1. Opens serial at 9600 baud.
  2. Starts the I²C bus with Wire.begin().
  3. Sets A1 as input.
  4. Wakes the MMA8452Q from standby by writing active mode to CTRL_REG1.
  5. Prints the CSV header line Hum,Temp,X,Y,Z,Sound — MATLAB detects this string to confirm the connection is live.

Main Loop & Serial Protocol

Every 100 ms (delay(100) at the end of loop()), the firmware:

  1. Checks if 2 seconds have elapsed since the last DHT11 read; if so, it reads fresh humidity and temperature, otherwise it reuses cached values.
  2. Reads the sound sensor with analogRead(SOUND_SENSOR_PIN).
  3. Reads 6 bytes from the MMA8452Q and reconstructs X, Y, Z as 16-bit integers.
  4. Prints a comma-separated line:
<humidity>,<temperature>,<X>,<Y>,<Z>,<sound>

followed by a newline (\n). MATLAB uses this newline as the terminator character.


MATLAB Application

The MATLAB script (sleep_monitor_dashboard.m) is the brain of the system. It handles serial I/O, signal processing, scoring, visualisation, and report generation.

Configuration Parameters

All user-tunable parameters sit at the top of the script for easy adjustment:

Parameter Default Description
COM_PORT 'COM12' Serial port the Arduino is connected to
BAUD_RATE 9600 Must match the Arduino firmware
PREALLOC 10000 Number of samples to pre-allocate in memory
MOVE_THRESHOLD 1500 IMU deviation (EMA-filtered) above this = movement event
SNORE_THRESHOLD 700 Sound ADC value (0–1023) above this = snore burst
SNORE_GAP 15 Minimum samples between consecutive snore detections (debounce)
SWEAT_LEVEL 60 Humidity % above this = sweating penalty
TEMP_IDEAL [20 40] °C range considered comfortable for sleep
IREG_WIN 10 Number of recent snore intervals used for regularity calculation
WINDOW_SEC 30 Rolling time window (seconds) for snore interval analysis

Serial Connection & Handshake

MATLAB opens the serial port with serialport(), sets the terminator to LF (line feed, matching Arduino's println), and enters a 30-attempt handshake loop that reads lines until it finds one containing the string 'Hum' — the CSV header sent by the Arduino on startup. A pause(2) before the loop gives the Arduino time to reset after the serial port opens.

Data Buffers

All sensor channels and computed metrics are stored in pre-allocated nan-filled row vectors of length PREALLOC. If the session runs longer than expected and idx exceeds the pre-allocated size, each buffer is extended by another PREALLOC samples using MATLAB's dynamic array growth. Storing nan rather than zero ensures that averaging and plotting functions correctly ignore uninitialised elements.

The buffers are:

Buffer Content
t_buf Sample index (x-axis for all plots)
hum_buf Raw humidity readings
temp_buf Raw temperature readings
ax_buf, ay_buf, az_buf Raw accelerometer axes
snd_buf Raw sound ADC readings
mag_buf Computed IMU deviation from EMA baseline
stage_buf Estimated sleep stage (0 = Deep, 1 = Light, 2 = Awake)
total_buf Overall sleep score (0–100) per sample
ms_buf Movement sub-score
ss_buf Snore frequency sub-score
rs_buf Snore regularity sub-score
ws_buf Sweat sub-score
ts_buf Temperature sub-score

Signal Processing

IMU Deviation (Movement Detection)

Raw 3-axis accelerometer values are first combined into a scalar magnitude:

mag = sqrt(X² + Y² + Z²)

A slow exponential moving average (EMA) tracks the baseline resting magnitude using a 0.98/0.02 weighting:

imu_baseline = 0.98 × imu_baseline + 0.02 × mag

The deviation from baseline is then:

deviation = |mag − imu_baseline|

This approach filters out the constant gravity component and slow positional drift, responding only to sudden changes caused by movement. Any deviation above MOVE_THRESHOLD increments the cumulative move_count.

Rolling Window Analysis

All sub-scores are computed over a rolling window of the most recent 100 samples (win = max(1, idx-99):idx), making the live score reflect current sleep state rather than the entire session average.

Sleep Stage Estimation

A simple three-level stage classifier runs on every sample:

Condition Stage Label
deviation > 2 × MOVE_THRESHOLD 2 Awake
deviation > MOVE_THRESHOLD OR sound > SNORE_THRESHOLD 1 Light sleep
Otherwise 0 Deep sleep

This is displayed as a staircase plot in Figure 2 and stored in stage_buf.

Scoring Engine

Five sub-scores (each 0–100) are computed each sample and blended into a weighted total:

Total = round(0.30×MS + 0.20×SS + 0.20×RS + 0.15×WS + 0.15×TS)
Score Weight Formula
Movement Score (MS) 30% max(0, 100 × (1 − 2.5 × move_rate))
Snore Frequency Score (SS) 20% max(0, 100 × (1 − 3.0 × snore_rate))
Snore Regularity Score (RS) 20% max(0, 100 × (1 − 2 × CoV))
Sweat Score (WS) 15% max(0, 100 × (1 − sweat_penalty))
Temperature Score (TS) 15% max(0, 100 × (1 − temp_penalty))

Where:

  • move_rate = fraction of the last 100 samples with movement above threshold
  • snore_rate = fraction of the last 100 samples with sound above threshold
  • CoV = coefficient of variation of snore intervals (standard deviation / mean) over the last IREG_WIN intervals in the 30-second rolling window
  • sweat_penalty = max(0, (humidity − SWEAT_LEVEL) / (100 − SWEAT_LEVEL))
  • temp_penalty = max(0, (temp − TEMP_IDEAL(2)) / 10) + max(0, (TEMP_IDEAL(1) − temp) / 10)

Snore Detection & Analysis

Snore detection uses a combined threshold and debounce approach:

  1. A snore burst is registered when sound > SNORE_THRESHOLD AND at least SNORE_GAP samples (15 by default) have passed since the last detection. This prevents a single prolonged snore from being counted multiple times.
  2. Each snore event records both its sample index (snore_times) and wall-clock time (snore_wall_times).
  3. At each sample, events older than WINDOW_SEC seconds are pruned from both arrays, keeping only the most recent 30-second window.
  4. The inter-burst intervals are computed as diff(snore_times).
  5. The Coefficient of Variation (CoV) of the most recent IREG_WIN intervals is computed as σ/μ. A CoV of 0 means perfectly regular snoring; a high CoV means chaotic, irregular breathing.
  6. The Regularity Score maps CoV to 0–100: max(0, 100 × (1 − 2 × CoV)), reaching zero at CoV ≥ 0.5.

The snore interval pattern is visualised in real time in Figure 2, with a mean line and ±1 SD dotted bounds drawn dynamically.


Live Dashboards

Figure 1 — Raw Sensor Feed

A 3×2 tiled layout showing the raw streaming data for all six channels:

Tile Channel Colour
Top-left Humidity (%) Blue
Top-right Temperature (°C) Orange
Mid-left Accelerometer X Green
Mid-right Accelerometer Y Yellow
Bottom-left Accelerometer Z Purple
Bottom-right Sound (ADC) Red

Each panel auto-scales its x-axis to the current sample count. This figure is primarily a diagnostic view to confirm the sensors are producing sensible values.

Figure 2 — Sleep Analytics

A 2×2 tiled layout of derived signals:

  • Movement Activity (top-left): The EMA-filtered IMU deviation with a dashed yellow threshold line. Spikes above the line represent detected movements.
  • Snore Interval Pattern (top-right): A rolling scatter/line plot of snore inter-burst intervals within the last 30 seconds, with a yellow mean line and orange dotted ±1 SD bounds. The CoV and regularity score are printed in the top-left corner of the panel.
  • Sweat Level (bottom-left): Raw humidity with a dashed red sweating threshold line.
  • Sleep Stage Estimate (bottom-right): A staircase plot with y-axis labels Deep (0), Light (1), Awake (2).

Figure 3 — Live Sleep Score Gauge

A dedicated full-screen window showing:

  • A semicircular gauge with red/amber/green colour bands (0–33/33–67/67–100) and a white needle pointing to the current total score. The score number is displayed in large text at the centre.
  • A quality label (POOR / FAIR / GREAT) below the gauge.
  • Cumulative movement and snore burst counts in the bottom corners.
  • A horizontal bar chart at the bottom showing all five sub-scores side by side with colour-coded bars.
  • A prominent red STOP button to end the session gracefully.

The gauge and bar chart are redrawn every 5 samples (mod(idx,5) == 0) to balance responsiveness with performance.


End-of-Session Reports

When the main loop exits (Stop button, figure closed, or Ctrl+C), MATLAB calls saveSleepReport() followed by runSleepSummary().

Sleep Session Report Figure

An 8-panel tiled figure (4×2) showing the full session history:

  • Panels 1–6: Time-series of all five sub-scores plus the overall score. Each panel includes the raw noisy signal (faint) and a 50-sample moving average (bright), plus dashed 75 and 50 threshold lines.
  • Panel 7: Raw sound signal with snore burst markers as red scatter dots.
  • Panel 8: Full-session snore interval pattern with mean line, ±1 SD bounds, and the full-session CoV annotated.

This figure is rendered with Visible','off' (invisible) so it can be saved to PNG without appearing on screen, then closed.

Sleep Summary Dashboard

A rich analytics dashboard showing:

  • Header bar with title and generation timestamp.
  • Four stat pills: average score, volatility (standard deviation of score), first-half vs second-half trend, and total sample count.
  • Panel A — Score Gauge: A re-rendered semicircular gauge with the session-average score, colour-coded verdict (EXCELLENT / GOOD / FAIR / POOR / VERY POOR), and three quality pills showing what percentage of the session was Great (≥75), Fair (50–74), and Poor (<50).
  • Panel B — Score Over Time: A smoothed overall score time-series with shaded green/amber/red quality bands and a trend annotation.
  • Panel C — Sub-Score Breakdown: A horizontal bar chart of the five session-average sub-scores, with a gold star marking the best and an orange triangle marking the weakest.
  • Panel D — Sparklines: Five mini time-series (one per sub-score) with colour-coded background (green/amber/red) based on whether the score passed 75 or 50.
  • Panel E — Recommendations: Up to four personalised tips generated by the scoring logic, each with a numbered icon. Tips are only generated for dimensions that scored below 60.

Saved Files

All output files use a SleepReport_YYYY-MM-DD_HH-MM-SS base name:

File Contents
SleepReport_<timestamp>.png Sleep Session Report figure (150 dpi)
SleepReport_<timestamp>.mat MATLAB workspace data (all score buffers, snore data)
SleepReport_<timestamp>.csv Tabular data: Sample, TotalScore, MovementScore, SnoreFreqScore, SnoreRegScore, SweatScore, TempScore
SleepReport_<timestamp>_Dashboard.png Sleep Summary Dashboard figure (150 dpi)
SleepReport_<timestamp>_Summary.txt Plain-text verdict, sub-scores, and tips

Scoring Algorithm — Deep Dive

Movement Score

move_rate  = (samples with deviation > MOVE_THRESHOLD) / 100
MS = max(0, 100 × (1 − 2.5 × move_rate))

A move_rate of 40% (40 out of 100 recent samples with movement) drives the score to zero. The 2.5 multiplier means the score reaches zero at 40% movement density.

Snore Frequency Score

snore_rate = (samples with sound > SNORE_THRESHOLD) / 100
SS = max(0, 100 × (1 − 3.0 × snore_rate))

The 3.0 multiplier is more aggressive — the score hits zero at ~33% snoring density, reflecting that sustained loud snoring is more disruptive than the equivalent amount of movement.

Snore Regularity Score

CoV = std(recent_intervals) / mean(recent_intervals)
RS = max(0, 100 × (1 − 2 × CoV))

If fewer than 3 intervals are available, RS defaults to 100 (no penalty for insufficient data). The regularity score captures the difference between rhythmic, periodic snoring (likely normal) and erratic, arrhythmic snoring (potentially indicative of obstructive sleep apnoea).

Sweat Score

sweat_penalty = max(0, (humidity − 60) / 40)
WS = max(0, 100 × (1 − sweat_penalty))

Humidity at exactly 60% incurs no penalty; at 100% the penalty is 1.0 (score = 0).

Temperature Score

temp_penalty = max(0, (temp − 40) / 10) + max(0, (20 − temp) / 10)
TS = max(0, 100 × (1 − temp_penalty))

Temperatures between 20 °C and 40 °C incur no penalty. Outside this range, the penalty rises by 10% per degree of deviation, reaching zero at 10 °C above or below the boundary.


Setup & Usage

Hardware wiring summary:

DHT11       → D6 (data), 5V, GND
MMA8452Q    → A4 (SDA), A5 (SCL), 3.3V, GND
Sound sensor→ A1 (AOUT), 5V, GND

Step-by-step:

  1. Wire the three sensors to the Arduino Nano as above.
  2. Install the dht11 library in the Arduino IDE.
  3. Open the Arduino .ino file, verify DHT11PIN, MMA8452_ADDRESS, and SOUND_SENSOR_PIN match your wiring, and upload to the Nano.
  4. Open a Serial Monitor briefly to confirm lines like 45.00,22.00,1024,-512,16384,230 are appearing, then close it.
  5. In MATLAB, set COM_PORT at the top of the script to match the port shown in Device Manager (Windows) or /dev/tty... (Mac/Linux).
  6. Run the script. Three full-screen figure windows will open.
  7. Place the sensor assembly near (not on) the sleeping person.
  8. When the sleep session ends, press the STOP button in Figure 3. Reports are saved automatically to the MATLAB working directory.

Tuning the Thresholds

If you see this problem Adjust this
Too many false movement detections (e.g. from vibrations) Increase MOVE_THRESHOLD
Genuine movements not detected Decrease MOVE_THRESHOLD
Snores not being detected (quiet sleeper) Decrease SNORE_THRESHOLD; also increase mic gain via the potentiometer on the sensor board
Ambient noise triggering false snore events Increase SNORE_THRESHOLD
Same snore being counted multiple times Increase SNORE_GAP
Sleep in a hot environment triggering sweat penalty unfairly Increase SWEAT_LEVEL
Temperature score always low Widen TEMP_IDEAL, e.g. [15 45]

File Structure

project/
│
├── sleep_monitor_dashboard.m     ← MATLAB acquisition, analysis & reporting
├── sleep_tracker.ino             ← Arduino firmware
│
└── (generated at runtime)
    ├── SleepReport_<timestamp>.png
    ├── SleepReport_<timestamp>.mat
    ├── SleepReport_<timestamp>.csv
    ├── SleepReport_<timestamp>_Dashboard.png
    └── SleepReport_<timestamp>_Summary.txt

Built With

Share this project:

Updates