Community project

Professional GSR Psychophysiology Module

ESP32
Photo of Professional GSR Psychophysiology Module
Generated with AI

Fernando Contato

Published October 13, 2026

This professional GSR (galvanic skin response) module captures real-time psychophysiological data using a transimpedance amplifier and high-resolution ADC. Built around an ESP32 microcontroller, the system measures skin conductance through reusable Ag/AgCl electrodes, processes the signal with a precision op-amp circuit, and logs data to a microSD card for offline analysis. The module operates independently on a rechargeable lithium battery with USB charging support.

This guide provides a complete wiring diagram, parts list, and step-by-step assembly instructions for building a portable GSR sensor. You'll learn how to construct the isolated power supply, configure the transimpedance amplifier with proper biasing, shield the electrode leads from noise, and deploy the firmware for autonomous data collection without a computer connection.

Wiring diagram

Wiring diagram for Professional GSR Psychophysiology Module

Gather all the parts

QtyComponent
1

ADS1115 16-Bit ADC Module

16-bit four-channel I2C analog-to-digital converter with programmable gain amplifier. Common ADS1115 breakouts expose MCU-facing SDA/SCL pins and four analog inputs for single-ended or differential measurements.

1

MicroSD Card Module

SPI-based microSD card adapter module for SPI-capable microcontrollers. Uses MOSI, MISO, SCK, and CS plus power and ground. Many low-cost modules include a 3.3 V regulator and level shifting for 5 V MCU boards, while bare breakouts should be powered and signalled at 3.3 V.

1

Lithium Ion Battery - 3.7V 2000mAh

3.7V 2000mAh lithium-ion polymer battery with JST-PH connector and protection circuitry. Output ranges from 4.2 V fully charged to 3.0 V cutoff.

1

Adafruit bq25185 USB / DC / Solar Charger with 3.3V Buck Board

bq25185 multi-source LiPoly/LiIon charger combined with a 3.3V buck regulator output, letting a project run directly from the board without a separate regulator.

1

Resistor

100 kΩ, 1% metal-film

Through-hole resistor (current-limiting in series with an LED)

1

Resistor

100 kΩ, 1% metal-film

Through-hole resistor (current-limiting in series with an LED)

1

Resistor

56 kΩ, 1% metal-film

Through-hole resistor (current-limiting in series with an LED)

1

Resistor

10 kΩ, 1% metal-film

Through-hole resistor (current-limiting in series with an LED)

1

MCP6002 dual rail-to-rail op-amp

A two-channel low-voltage amplifier chip that buffers the safe test voltage and converts the tiny skin current into a voltage the ADC can read.

1

Reusable Ag/AgCl snap-electrode pair with insulated leads

Two skin-contact electrodes that connect the low-current measurement circuit to the participant's fingers or palm.

1

100 nF ceramic capacitor

A small capacitor that quiets the 0.5 V reference so movement and digital noise do not dominate the reading.

Assemble it in 6 steps

1. Monte a alimentação isolada

Com a bateria desconectada, ligue BAT+ da bateria ao pino BAT da placa carregadora e ligue BAT− ao GND. Ligue a saída 3V3 da placa carregadora ao pino 3V3 do ESP32, do ADS1115 e do módulo microSD; ligue todos os GND ao mesmo GND. A porta USB da placa carregadora serve apenas para carregar a bateria, nunca para uma sessão de medição.

  • Monte tudo em uma caixa plástica fechada depois de testar.
  • Durante a coleta, mantenha qualquer cabo USB fora do ESP32 e do carregador.
  • Não conecte USB, carregador de tomada ou computador enquanto os eletrodos estiverem presos a uma pessoa; isso elimina a separação por bateria deste protótipo.

2. Ligue o conversor de leitura

Ligue VDD do ADS1115 a 3V3 (alimentação), GND a GND (terra), SDA ao GPIO21 (dados) e SCL ao GPIO22 (dados). Ligue também ADDR ao GND; isso define o endereço que o programa procura.

  • Use fios curtos e mantenha SDA e SCL longe dos cabos dos eletrodos.

3. Monte a referência de meio volt

Coloque o resistor de 56 kΩ entre 3V3 e um ponto vazio da protoboard. Coloque o resistor de 10 kΩ entre esse mesmo ponto e GND. Ligue esse ponto ao pino 1IN+ do MCP6002 e a uma perna do capacitor de 100 nF; a outra perna do capacitor vai ao GND. Ligue 1OUT diretamente a 1IN−. No MCP6002, ligue VDD a 3V3 e VSS a GND.

  • O ponto comum dos dois resistores é a pequena tensão de teste, perto de 0,5 V.
  • Confirme o encaixe do MCP6002: o ponto ou meia-lua do encapsulamento marca o lado do pino 1.
  • Um MCP6002 girado ao contrário pode aquecer ou ser danificado quando a bateria for conectada.

4. Monte o caminho protegido dos eletrodos

Ligue 1OUT ao primeiro resistor de 100 kΩ. Ligue a outra ponta desse resistor ao fio ELECTRODE_A. Ligue o fio ELECTRODE_B ao pino 2IN−. Ligue 2IN+ ao GND. Coloque o segundo resistor de 100 kΩ entre 2OUT e 2IN−. Por fim, ligue 2OUT ao AIN0 do ADS1115.

  • Deixe os dois resistores de 100 kΩ próximos ao MCP6002, não junto à pele.
  • Trance levemente os dois fios dos eletrodos e mantenha-os afastados do ESP32 e do cartão.
  • Não substitua nem faça ponte no resistor de 100 kΩ que vai ao ELECTRODE_A: ele é a barreira que limita a corrente de teste.

5. Ligue o cartão de dados

Ligue VCC do módulo microSD a 3V3 (alimentação), GND a GND (terra), MOSI ao GPIO23 (dados), MISO ao GPIO19 (dados), SCK ao GPIO18 (relógio) e CS ao GPIO4 (seleção do cartão). Coloque um cartão microSD formatado em FAT32 no módulo antes de ligar a bateria.

  • Use um módulo microSD que aceite sinais e alimentação de 3,3 V.
  • O programa cria GSR000.CSV, depois GSR001.CSV e assim por diante; retire a bateria antes de remover o cartão.
  • Não retire o cartão enquanto a bateria estiver ligada, pois isso pode corromper o arquivo que está sendo gravado.

6. Faça a coleta sem USB

Limpe e seque suavemente dois pontos da mesma mão, prenda os dois eletrodos Ag/AgCl e então ligue somente a bateria. Deixe a pessoa quieta por alguns minutos antes de marcar o início da sessão no seu caderno. Desligue a bateria antes de tirar os eletrodos ou conectar um cabo USB.

  • Faça uma medição com um resistor conhecido de 100 kΩ no lugar da pessoa antes de cada conjunto de sessões.
  • O arquivo CSV é para análise de pesquisa; mantenha junto dele notas sobre participante, condição, horário e qualquer estímulo.
  • Este é um registrador experimental de pesquisa, não um dispositivo médico, diagnóstico ou certificado. Não use em pessoas com marca-passo, implantes eletrônicos, lesão de pele, ou durante procedimentos clínicos.

Review all connections

1. Connections between "battery_1" and "ESP32"

Functionbattery_1ESP32
powerBAT+ → Adafruit bq25185 USB / DC / Solar Charger with 3.3V Buck Board BATEXT
groundBAT-GND

2. Connections between "charger_3v3_1" and "ESP32"

Functioncharger_3v3_1ESP32
groundGNDGND
power3V33V3

3. Connections between "ads1115_1" and "ESP32"

Functionads1115_1ESP32
powerVDD3V3
groundGNDGND
i2cSDAGPIO 21
i2cSCLGPIO 22
groundADDRGND

4. Connections between "microsd_1" and "ESP32"

Functionmicrosd_1ESP32
powerVCC3V3
groundGNDGND
spiMOSIGPIO 23
spiMISOGPIO 19
spiSCKGPIO 18
spiCSGPIO 4

5. Connections between "opamp_mcp6002_1" and "ESP32"

Functionopamp_mcp6002_1ESP32
powerVDD3V3
groundVSSGND
analog1IN- → MCP6002 dual rail-to-rail op-amp 1OUTEXT
analog1OUT → Resistor P1EXT
ground2IN+GND
analog2OUT → ADS1115 16-Bit ADC Module AIN0EXT

6. Connections between "r_ref_top_56k" and "ESP32"

Functionr_ref_top_56kESP32
powerP13V3
digitalP2 → MCP6002 dual rail-to-rail op-amp 1IN+EXT

7. Connections between "r_ref_bottom_10k" and "ESP32"

Functionr_ref_bottom_10kESP32
digitalP1 → MCP6002 dual rail-to-rail op-amp 1IN+EXT
groundP2GND

8. Connections between "c_ref_100nf" and "ESP32"

Functionc_ref_100nfESP32
analogP1 → MCP6002 dual rail-to-rail op-amp 1IN+EXT
groundP2GND

9. Connections between "r_source_100k" and "ESP32"

Functionr_source_100kESP32
digitalP2 → Reusable Ag/AgCl snap-electrode pair with insulated leads ELECTRODE_AEXT

10. Connections between "electrodes_agcl_1" and "ESP32"

Functionelectrodes_agcl_1ESP32
analogELECTRODE_B → MCP6002 dual rail-to-rail op-amp 2IN-EXT

11. Connections between "r_feedback_100k" and "ESP32"

Functionr_feedback_100kESP32
digitalP1 → MCP6002 dual rail-to-rail op-amp 2OUTEXT
digitalP2 → MCP6002 dual rail-to-rail op-amp 2IN-EXT

Deploy the firmware

#include <Arduino.h>
#include <Wire.h>
#include <SPI.h>
#include <SD.h>
#include <Adafruit_ADS1X15.h>
#include <WiFi.h>

constexpr int I2C_SDA_PIN = 21;
constexpr int I2C_SCL_PIN = 22;
constexpr int SD_CS_PIN = 4;
constexpr int SD_MOSI_PIN = 23;
constexpr int SD_MISO_PIN = 19;
constexpr int SD_SCK_PIN = 18;
constexpr float TEST_REFERENCE_V = 0.500f;
constexpr float FEEDBACK_RESISTOR_OHM = 100000.0f;
constexpr uint32_t SAMPLE_INTERVAL_MS = 100;

Adafruit_ADS1115 ads;
File logFile;
uint32_t lastSampleMs = 0;

[[noreturn]] void stopWithError(const char *message) {
  Serial.println(message);
  while (true) {
    delay(1000);
  }
}

String nextLogName() {
  for (int i = 0; i < 1000; ++i) {
    char candidate[16];
    snprintf(candidate, sizeof(candidate), "/GSR%03d.CSV", i);
    if (!SD.exists(candidate)) {
      return String(candidate);
    }
  }
  return String("/GSR999.CSV");
}

float readTiaVolts(int16_t &rawOut) {
  int32_t total = 0;
  constexpr int COUNT = 4;
  for (int i = 0; i < COUNT; ++i) {
    total += ads.readADC_SingleEnded(0);
  }
  rawOut = static_cast<int16_t>(total / COUNT);
  // ADS1115 at GAIN_TWO: one count represents 0.0625 mV.
  return static_cast<float>(rawOut) * 0.0000625f;
}

void logSample() {
  int16_t raw = 0;
  const float tiaVolts = readTiaVolts(raw);
  // I = Vout / Rf.  The equal 100 kOhm series resistor means the skin
  // voltage is approximately 0.500 V - Vout.
  const float skinVolts = TEST_REFERENCE_V - tiaVolts;
  float conductanceUS = NAN;
  if (tiaVolts >= 0.0f && skinVolts > 0.010f) {
    const float currentA = tiaVolts / FEEDBACK_RESISTOR_OHM;
    conductanceUS = (currentA / skinVolts) * 1000000.0f;
  }

  const uint32_t now = millis();
  logFile.print(now);
  logFile.print(',');
  logFile.print(raw);
  logFile.print(',');
  logFile.print(tiaVolts, 6);
  logFile.print(',');
  if (isnan(conductanceUS)) {
    logFile.println("NaN");
  } else {
    logFile.println(conductanceUS, 4);
  }
  logFile.flush();

  Serial.print(now);
  Serial.print(" ms, ");
  Serial.print(tiaVolts, 6);
  Serial.print(" V, ");
  if (isnan(conductanceUS)) {
    Serial.println("out of range");
  } else {
    Serial.print(conductanceUS, 4);
    Serial.println(" uS");
  }
}

void setup() {
  Serial.begin(115200);
  WiFi.mode(WIFI_OFF);

  Wire.begin(I2C_SDA_PIN, I2C_SCL_PIN);
  if (!ads.begin(0x48, &Wire)) {
    stopWithError("ADS1115 not found; disconnect the battery before checking its SDA and SCL wires.");
  }
  ads.setGain(GAIN_TWO);
  ads.setDataRate(RATE_ADS1115_128SPS);

  SPI.begin(SD_SCK_PIN, SD_MISO_PIN, SD_MOSI_PIN, SD_CS_PIN);
  if (!SD.begin(SD_CS_PIN, SPI, 1000000)) {
    stopWithError("microSD card not found; disconnect the battery before checking the card and its wires.");
  }

  const String filename = nextLogName();
  logFile = SD.open(filename.c_str(), FILE_WRITE);
  if (!logFile) {
    stopWithError("Could not create the CSV file on the microSD card.");
  }
  logFile.println("time_ms,ads_counts,tia_volts,conductance_uS");
  logFile.flush();
  Serial.print("Logging to ");
  Serial.println(filename);
}

void loop() {
  const uint32_t now = millis();
  if (now - lastSampleMs >= SAMPLE_INTERVAL_MS) {
    lastSampleMs = now;
    logSample();
  }
}

Remix this project

Make it yours in one click

Open a full copy of this project in your own Schematik workspace — diagram, code, parts, and assembly steps included. Swap the sensor, add features, or redesign the whole thing with AI. The author's original stays untouched.

Open in Schematik