How to Build an ESP32 Stress Detector Using a MAX30100 Sensor
The MAX30100 continuously measures heart rate and Heart Rate Variability (HRV), while the ESP32's built-in capacitive touch pins serve as a Galvanic Skin Response (GSR) sensor, detecting subtle changes in skin conductance caused by stress. Together, these three physiological signals - heart rate, HRV, and skin conductance - are processed and combined into a live Stress Index Score, streamed directly to your PC over USB Serial.
This project is a practical deep-dive into I2C sensor interfacing, capacitive touch sensing, signal smoothing algorithms, and multi-parameter biometric scoring on the ESP32 platform.

Components Required
- ESP32 30 Pin CP2102 Development Board
- MAX30100 Heart Rate Oxygen Pulse Sensor
- Push Button
- Connecting wires (Jumper wires)
- Breadboard
- Micro-USB Cable
- Laptop or PC (for Serial Monitor / Serial Plotter)
About the Components
ESP32 Development Board
- Dual-core 32-bit processor up to 240 MHz
- Built-in Wi-Fi and Bluetooth connectivity
- 30 GPIO pins with ADC, DAC, PWM, I2C, SPI, and UART support
- 520KB SRAM and 4MB Flash memory
- Breadboard-friendly and easy to program via USB

Role in Project:
The ESP32 is the brain of the Biometric Stress Detector. It reads heart rate and SpO₂ data from the MAX30100 over I2C, simultaneously samples skin conductance through its built-in capacitive touch pins, and runs real-time HRV and stress score calculations. Processed results are streamed to your computer via USB Serial every 500 ms.
MAX30100 Pulse Oximeter and Heart Rate Sensor
ESP32 Touch Pins as GSR Sensor
- Uses ESP32's built-in capacitive touch sensing capability
- Detects changes in skin conductance without external GSR hardware
- Higher skin moisture increases conductivity and changes capacitance readings
- Lower
touchRead()values indicate higher skin conductance - Requires two touch pins in contact with the user's skin
- No additional components needed, reducing cost and complexity
Role in Project:
The ESP32's capacitive touch pins double as a simplified Galvanic Skin Response (GSR) sensor — one of the most widely used physiological markers of stress and emotional arousal. When the user places two fingers on the touch electrodes (jumper wires connected to the touch pins), changes in skin moisture alter the capacitance readings in real time. This data is combined with heart rate and HRV values to compute the overall Stress Index Score, giving the detector a multi-signal foundation rather than relying on a single metric.
How It Works ?
Stress triggers involuntary physiological changes in the body — the same responses that polygraph machines have measured for decades. This project captures three of those signals simultaneously and combines them into a single real-time Stress Index Score.
| Signal | Sensor | Stress Indicator | Weight in Score |
|---|---|---|---|
| Heart Rate (HR) | MAX30100 | HR rises above baseline | 25% |
| Heart Rate Variability (HRV) | MAX30100 (derived) | HRV drops below baseline | 35% |
| Skin Conductance (GSR) | ESP32 Touch Pins | Touch value drops (more sweat) | 40% (level 25% + rate 15%) |

All three signals are measured against a personal baseline captured while the subject is at rest. The composite Stress Index Score (0–100) produces one of three verdicts: Relaxed (below 25), Mild Stress (25–55), or High Stress Detected (above 55).
Finger Placement
All three signals are measured against a personal baseline captured while the subject is at rest. The composite Stress Index Score (0–100) produces one of three verdicts: Relaxed (below 25), Mild Stress (25–55), or High Stress Detected (above 55).
- Index finger — pressed firmly on top of the MAX30100 sensor chip (HR + SpO2)
- Middle finger — pinching the bare metal tip of the jumper wire on GPIO12 (T5)
- Ring finger — pinching the bare metal tip of the jumper wire on GPIO13 (T4)
Alternatively, place the index finger on the MAX30100 with your left hand and hold both jumper wire tips between the thumb and index finger of your right hand. Both placements work equally well.

Circuit Diagram
The MAX30100 connects to the ESP32 over I2C using just four wires. The GSR circuit requires no additional components — two jumper wires plugged into GPIO12 and GPIO13 are all that's needed. The calibration button connects GPIO14 to GND, with the internal pull-up resistor enabled in firmware.


| MAX30100 Pin | ESP32 Pin | Description |
|---|---|---|
| VIN | 3.3V | Power Supply |
| GND | GND | Common Ground |
| SDA | GPIO21 | I2C Data Line |
| SCL | GPIO22 | I2C Clock Line |
| Component | ESP32 Pin | Description |
|---|---|---|
| Jumper wire (Finger 1) | GPIO12 (T5) | GSR contact 1 |
| Jumper wire (Finger 2) | GPIO13 (T4) | GSR contact 2 |
| Push Button | GPIO14 → GND | Calibration trigger |

Code Explanation
Libraries and Pin Definitions
The firmware uses Wire.h for I2C communication and MAX30100_PulseOximeter.h from the MAX30100lib library for sensor management. The two capacitive touch pins (for GSR) and the calibration button pin are defined at the top. Several baseline variables are declared to store the user's calm-state reference values, which are captured during the calibration phase.
GSR Reading with Smoothing
The readGSR() function captures skin conductance by averaging the two ESP32 capacitive touch pins. Because raw touch readings are highly susceptible to physical noise (like finger micro-movements), an exponential moving average (low-pass filter) is applied. By combining 80% of the previous smoothed value with 20% of the new raw reading, minor jitters are ignored, allowing only genuine sweat-driven conductance changes to pass through to the scoring logic.
HRV Calculation
Heart Rate Variability (HRV) is derived from the heart rate using a mathematical approximation of RMSSD (Root Mean Square of Successive Differences). The RR interval (milliseconds between beats) is computed from the BPM, and the last 10 successive differences are tracked. A stressed state typically triggers the sympathetic nervous system, causing HRV to drop rapidly as heartbeats become unnaturally rigid.
$$RMSSD = \sqrt{\frac{1}{N} \sum_{i=1}^{N} (RR_i - RR_{i-1})^2}$$
Stress Scoring
The stressScore() function computes a weighted composite score (0 to 100) based on four distinct biometric sub-signals: HR delta (25%), HRV delta (35%), absolute GSR delta (25%), and GSR rate of change (15%). The rate-of-change component is the most sensitive metric—it catches immediate, sharp conductance spikes that occur before the absolute levels have shifted significantly. Each sub-score is clamped to 0–100 before weighting.
Baseline Calibration
Because everyone's resting physiology is different, pressing the button triggers a non-blocking 3-second calibration window. It gathers biometric data every 300ms, averages it, and establishes a stable resting baseline. All subsequent stress scores are calculated as deviations from this saved baseline, which eliminates inter-person differences in resting HR, HRV, and skin conductance.
Main Loop - Reading and Verdict Output
The main loop calls pox.update() continuously at the top to keep the MAX30100 I2C processing alive without blocking delays. Every 500ms, it evaluates the sensor values, computes the stress score (if a baseline exists), and prints the result. The output format is natively compatible with both the Serial Monitor (readable text) and the Serial Plotter (graphs automatically).
How to Use
- Upload the firmware and open Serial Monitor at 115200 baud.
- Place your index finger firmly on the MAX30100 chip. Wait for a stable HR reading.
- Pinch the two jumper wire metal tips with your middle and ring finger of the same hand.
- Stay calm and press the calibration button. Hold completely still for 3 seconds until the baseline confirmation message appears.
- Ask a few neutral control questions first ("What is your name?") to ensure the score stays near zero.
- Ask the test questions and watch the Score column and verdict update in real time.
To visualize all signals as live graphs, switch from Serial Monitor to Tools → Serial Plotter in Arduino IDE. HR, HRV, GSR, and Score will each render as a separate trace.
GSR RANGE COMPARISON (DRY VS SWEATY FINGER)
Tuning the Thresholds
Before testing on others, run this quick calibration sketch to understand your ESP32's specific capacitive touch baseline:
| State | Typical touchRead value |
|---|---|
| Not touching | 60 – 80 |
| Dry finger, relaxed | 20 – 40 |
| Normal hold, calm | 10 – 25 |
| Stressed or sweating | 3 – 12 |
If your personal calm-to-stressed delta is smaller than 15 points, increase GSR sensitivity by lowering the / 20.0 divisor in the stressScore() function. If the score triggers "HIGH STRESS" too easily from normal breathing or motion, raise the divisor to require a stronger physiological reaction.
Result
The ESP32 successfully operates as a three-signal biometric stress detector using only its built-in capacitive touch pins and an I2C MAX30100 sensor. The system establishes a personalized baseline in 3 seconds, outputs a live composite stress score every 500ms, and delivers a real-time, three-level verdict (CALM, ANXIOUS, or HIGH STRESS DETECTED) over USB Serial. It achieves this with zero external circuitry beyond the sensor module, a push button, and two jumper wires.

In the final prototype, the exponential low-pass filter on the GSR inputs successfully eliminates micro-motion noise while preserving genuine conductance spikes. The HRV (RMSSD) drop component provides the most reliable long-duration indicator of stress, while the GSR rate-of-change metric catches immediate sympathetic nervous system responses within 1–2 seconds of a question or stimulus. Working in tandem, these core signals cover the same physiological channels monitored by professional polygraph systems.
Checkout the full tutorial :
Complete Code
The following ESP32 Arduino sketch implements the complete pocket stress detector firmware. It integrates MAX30100 heart rate and SpO2 processing, HRV derivation, capacitive touch GSR sensing with exponential smoothing, and a non-blocking 3-second baseline calibration. Using these inputs, the algorithm calculates a weighted composite stress score and pushes the live data to the Serial Monitor and Serial Plotter every 500ms.
