ESP32 & IoT24 October 202619 mins

Build a Cave Microclimate Logger with ESP32-WROVER, Dual SHT35 & SD Storage

Comprehensive step-by-step engineering guide for building an extreme-environment cave microclimate datalogger using ESP32-WROVER, dual SHT35 sensors, and SD card logging.

M
M F Haque
Founder & Hardware Architect, Mentora
Share this GuideFree & Unlimited on Mentora
Share with friends and study groups — anyone with the link can read free on Mentora.
Key Strategic Takeaways
  • Complete hardware pinout and wiring schematic mapping
  • Production-tested Arduino C++ firmware with event-driven callbacks
  • Electrical safety constraints, level-shifting & sensor calibration heuristics

2. Overview & Engineering Objectives

Subterranean karst caves, lava tubes, and abandoned mine shafts represent some of the most punishing environments for electronic instrumentation: constant near-100% relative humidity, dripping mineralized water, and complete absence of Wi-Fi or cellular coverage. This project constructs an ultra-rugged speleological datalogger that tracks microclimate gradients between the cave entrance and deep chambers across multi-month deployments.

Core Engineering Capabilities:

Dual-Zone Gradient Profiling: Interfaces two Sensirion SHT35 industrial sensors simultaneously on addresses 0x44 and 0x45, profiling thermal and humidity lag between external twilight zones and deep subterranean microclimates.

De-Fogging Condensation Bake-Off: Solves the notorious "100% RH saturation trap" by periodically activating the SHT35's internal 33mW heating element to evaporate condensation droplets off the sensing polymer.

Power-Fail Safe SD Storage: Protects FAT32 file tables against corruption by checking battery reserves before every write and executing atomic file.flush() operations.

Multi-Month Battery Autonomy: Spends >99.5% of its lifespan in deep sleep, waking for 1.2 seconds every 15 minutes to sample and log CSV data.

3. Bill of Materials (BOM)

Component

Quantity

Specification / Rating

Est. Cost (INR)

Sourcing / Affiliate Link

ESP32-WROVER-E Dev Board

1

16MB Flash + 8MB PSRAM, Xtensa Dual-Core 240MHz

₹580

Buy on Mentora Store

Sensirion SHT35 Sensor Boards

2

Industrial ±1.5% RH accuracy, I2C interface

₹1,100

Buy on Mentora Store

SPI MicroSD Card Module

1

Standard 3.3V SPI interface with card detect

₹65

Buy on Mentora Store

Industrial 16GB MicroSD Card

1

High-endurance Class 10 / pSLC FAT32 formatted

₹390

Buy on Mentora Store

3.7V 3500mAh 18650 Li-ion Cell

1

High-capacity Panasonic/LG cell

₹240

Buy on Mentora Store

IP68 Underwater Enclosure Box

1

Heavy-duty hermetic case with silicone gasket

₹380

Buy on Mentora Store

PTFE Sintered Sensor Filter Caps

2

Microporous gas-permeable water barrier caps

₹180

Buy on Mentora Store

Total Hardware Cost

~₹2,935

4. Hardware Engineering: The 100% RH Saturation Trap

In subterranean environments, the air frequently reaches 100% Relative Humidity (Dew Point = Air Temperature):

The Problem: Microscopic liquid water droplets condense on the SHT35 capacitive polymer dielectric. Once wetted, surface tension holds liquid water in the micropores, causing the sensor to output a flatlined 100.0% RH reading indefinitely, even if the cave air dries out to 94%!

The Periodic Bake-Off Cycle:

Every 6 hours, firmware commands the SHT35 internal heater ON (sensor.heater(true)).

The element generates heat, warming the sensor die by ~8°C above ambient for 15 seconds to vaporize condensed water droplets.

Firmware deactivates the heater (sensor.heater(false)) and allows the die to cool for 45 seconds before logging valid environmental data.

5. Dual I2C Address Hardware Strapping

The Sensirion SHT35 breakout has an address select pin (ADDR):

Sensor 1 (Entrance Zone): Tie ADDR to GND to I2C Address 0x44.

Sensor 2 (Deep Chamber): Tie ADDR to 3V3 to I2C Address 0x45.

Both sensors connect in parallel to the hardware I2C bus (GPIO 21 SDA and GPIO 22 SCL) without requiring bus multiplexers.

6. Real-World Troubleshooting FAQs

Q1: Why does the SD card corrupt its directory structure after a few weeks?

Root Cause: Writing during battery exhaustion. When the 18650 cell drops below 3.0V, the write voltage to the flash NAND gates becomes unstable, corrupting the FAT allocation table.

Fix: Measure battery voltage via a 100kΩ/100kΩ divider on GPIO 35 (ADC1). If V_bat < 3.25V, halt all SD writes and put the ESP32 into emergency deep sleep.

Q2: Why does the SPI bus hang when the SD card is initialized alongside I2C?

Root Cause: Pin cross-talk. The ESP32 hardware SPI uses GPIO 18 (SCK), GPIO 19 (MISO), GPIO 23 (MOSI), and GPIO 5 (CS). Keep these completely separated from the I2C bus lines on GPIO 21 (SDA) and GPIO 22 (SCL).

7. Mentora Monetization & Student Support Hooks

🛒 Official Mentora Speleological Datalogger Kit: ESP32-WROVER board, 2x SHT35 sensors with sintered PTFE filter caps, SPI MicroSD shield, industrial 16GB card, and IP68 case for ₹3,499.

⚡ ₹49 Mentora Token Vault: Instantly unlock the circular CSV wear-leveling datalogger firmware, subterranean humidity psychrometric analysis script, and low-power battery discharge curve model.

🛠️ ₹299 1-on-1 Mentora Live Video Debugging: 30-minute session to configure SPI FAT32 cluster synchronizations, tune automated SHT35 heater de-fogging cycles, and optimize deep sleep power budgets for 6-month unattended cave deployments.


Hardware Pinout & Wiring Connections

Hardware Pinout & Wiring Connection Guide

Project 25: Cave Microclimate Environmental Logger with Dual SHT35 & SD Datalogging

Microcontroller: ESP32-WROVER-E (with 8MB PSRAM)Microclimate Sensors: Dual Sensirion SHT35 Industrial Probes (Entrance: 0x44, Deep Cave: 0x45)Storage Subsystem: SPI MicroSD Card Adapter (FAT32 Power-Fail Safe Logging)Power Stage: 3.7V 3500mAh 18650 Li-ion Cell + 2:1 Battery Divider on ADC1 GPIO 35

1. Complete Fritzing Pin Connection Mapping Table

Table A: Dual SHT35 Sensors & MicroSD Shield to ESP32-WROVER-E

Component

Component Pin

ESP32 Pin

Wire Color

Bus Signal / Function

Operational Constraints & Notes

Both SHT35 Probes

VIN

3V3 Rail

Red

3.3V DC Sensor Supply

Powered strictly from 3.3V.

Both SHT35 Probes

GND

GND Rail

Black

Ground Return

Common system ground plane.

Both SHT35 Probes

SDA

GPIO 21

Blue

Shared I2C Data Line

Hardware I2C SDA line.

Both SHT35 Probes

SCL

GPIO 22

Yellow

Shared I2C Clock Line

Hardware I2C SCL clock line.

Sensor 1 (Entrance)

ADDR

GND Rail

Brown

Hardware Address Select

TIE TO GND to lock address to 0x44.

Sensor 2 (Deep Cave)

ADDR

3V3 Rail

Red

Hardware Address Select

TIE TO 3V3 to lock address to 0x45.

MicroSD Module

VCC

3V3 Rail

Red (Thick)

SD Logic & Flash Power

Use 3.3V compatible SPI microSD breakout.

MicroSD Module

GND

GND Rail

Black (Thick)

Ground Reference

Common system ground.

MicroSD Module

CS

GPIO 5

Orange

SPI Chip Select

Active-LOW SD card select line.

MicroSD Module

MOSI

GPIO 23

Green

SPI Master Out Slave In

Hardware SPI data out.

MicroSD Module

MISO

GPIO 19

Blue

SPI Master In Slave Out

Hardware SPI data in.

MicroSD Module

SCK

GPIO 18

Yellow

SPI Serial Clock

Hardware SPI clock line.

2. Circuit Connection Schematic Diagram (ASCII Art)

+-------------------------------------------------------------+

| ESP32-WROVER-E |

| |

| [ 3V3 ] [ GND ] [G21] [G22] [G5] [G23] [G19] [G18] |

+----+--------+---------+-----+-------+----+-----+-----+---+--+

textcode snippet
|        |         |     |       |    |     |     |

+3.3V | GND | SDA | SCL | CS |MOSI| MISO| SCK |

Rail | Rail | Line |Line | | | | |

=======+========+=========+=====+=======+====+=====+=====+======

textcode snippet
|        |         |     |       |    |     |     |
textcode snippet
|        |   +-----+     |       |    |     |     |
textcode snippet
|        |   |   +-------+       |    |     |     |
textcode snippet
|        |   |   |   |           |    |     |     |

+--+--------+---+---+--++ | | | |

textcode snippet
| VIN GND ADDR  SDA SCL |           |    |     |     |
textcode snippet
| SHT35 ENTRANCE (0x44) |           |    |     |     |
textcode snippet
|  (ADDR -> GND)        |           |    |     |     |

+-----------------------+ | | | |

textcode snippet
|        |                       |    |     |     |

+--+--------+---+---+---+ | | | |

textcode snippet
| VIN GND ADDR  SDA SCL |<----------+    |     |     |
textcode snippet
| SHT35 DEEP CAVE(0x45) |                |     |     |
textcode snippet
|  (ADDR -> 3V3)        |                |     |     |

+-----------------------+ | | |

textcode snippet
|        |                            |     |     |

+--+--------+----------------------------+-----+-----+---+--+

| VCC GND CS MOSI MISO SCK |

| SPI MicroSD SHIELD |

+-----------------------------------------------------------+

3. Golden Engineering Rules for Subterranean Loggers

PTFE Sintered Sensor Protection:

Always house both SHT35 sensors inside sintered porous PTFE filter caps. These caps permit water vapor diffusion while physically blocking liquid droplets, mud, and bat guano aerosols from reaching the sensor silicon.

Atomic MicroSD Card Sync:

Always call logFile.flush() and unmount the card with SD.end() before entering deep sleep. If the ESP32 loses power while holding unwritten data in SRAM, the SD card's master boot record will be corrupted.

I2C & SPI Bus Separation:

The ESP32 hardware I2C bus (GPIO 21/22) and hardware VSPI bus (GPIO 5/18/19/23) are completely independent silicon peripherals. Maintaining this routing eliminates bus lockup conflicts.


Complete Tested Arduino C++ Firmware

``cpp

/*

  • ==============================================================================
  • Project: Cave Microclimate Environmental Logger with Dual SHT35 & SD Storage
  • Microcontroller: ESP32-WROVER-E (Xtensa Dual-Core with 8MB PSRAM)
  • Author: Mentora Maker Lab (Spark Autonomous Content Intelligence)
  • License: MIT

*

  • Hardware Peripherals:
  • - Dual SHT35 Precision Temp & Humidity Sensors:
  • Sensor 1 (Entrance Zone): Address 0x44 (ADDR -> GND)
  • Sensor 2 (Deep Chamber) : Address 0x45 (ADDR -> 3V3)
  • Shared I2C: SDA -> GPIO 21, SCL -> GPIO 22
  • - MicroSD Card Adapter (SPI Bus):
  • CS -> GPIO 5, MOSI -> GPIO 23, MISO -> GPIO 19, SCK -> GPIO 18
  • - Battery Divider (100k + 100k): Signal -> GPIO 35 (ADC1 Channel 7)
  • - Power: 3.7V 3500mAh 18650 Li-ion Battery (15-Minute Deep Sleep Interval)
  • ==============================================================================

*/

#include <Wire.h>

#include <SPI.h>

#include <SD.h>

#include <Adafruit_SHT31.h>

// -----------------------------------------------------------------------------

// USER CONFIGURATION & PIN ASSIGNMENTS

// -----------------------------------------------------------------------------

#define SDCSPIN 5

#define BATTERYADCPIN 35

#define STATUSLEDPIN 2

#define SLEEPINTERVALSEC 900ULL // Log microclimate every 15 minutes (900s)

#define LOGFILENAME "/cavelog.csv"

// De-Fogging Heater Cycle (Every 24 wakeups = 6 Hours)

#define HEATERCYCLEINTERVAL 24

AdafruitSHT31 shtEntrance = AdafruitSHT31();

AdafruitSHT31 shtDeep = AdafruitSHT31();

// RTC Persistent Memory across deep sleep reboots

RTCDATAATTR int logCycleCounter = 0;

// -----------------------------------------------------------------------------

// BATTERY VOLTAGE CHECKER (POWER-FAIL PROTECTION)

// -----------------------------------------------------------------------------

float readBatteryVoltage() {

long sum = 0;

for (int i = 0; i < 16; i++) {

sum += analogRead(BATTERYADCPIN);

delay(2);

}

float pinVolts = (sum / 16.0 / 4095.0) * 3.3;

return pinVolts * 2.0; // 2:1 resistive divider

}

// -----------------------------------------------------------------------------

// PERIODIC SENSOR DE-FOGGING CYCLE (CONDENSATION BAKE-OFF)

// -----------------------------------------------------------------------------

void executeCondensationBakeOff() {

Serial.println("[DE-FOG] Subterranean 100% RH bake-off cycle triggered.");

Serial.println("[DE-FOG] Engaging SHT35 internal heaters for 12 seconds...");

shtEntrance.heater(true);

shtDeep.heater(true);

delay(12000); // 12-second heating pulse

shtEntrance.heater(false);

shtDeep.heater(false);

Serial.println("[DE-FOG] Heaters deactivated. Cooling sensor polymer for 30s...");

delay(30000); // Allow sensor die to cool back to ambient

}

// -----------------------------------------------------------------------------

// SD CARD CSV LOGGER (ATOMIC WRITE & FLUSH)

// -----------------------------------------------------------------------------

bool appendLogToSD(float t1, float rh1, float t2, float rh2, float vbat) {

if (!SD.begin(SDCSPIN)) {

Serial.println("[ERROR] MicroSD card initialization failed!");

return false;

}

// Create CSV header if file doesn't exist

if (!SD.exists(LOG_FILENAME)) {

File newFile = SD.open(LOGFILENAME, FILEWRITE);

if (newFile) {

newFile.println("Cycle,EntranceTempC,EntranceRHPct,DeepTempC,DeepRHPct,Battery_V");

newFile.close();

}

}

File logFile = SD.open(LOGFILENAME, FILEAPPEND);

if (!logFile) {

Serial.println("[ERROR] Failed opening log file for writing!");

SD.end();

return false;

}

// Format CSV entry

String entry = String(logCycleCounter) + ",";

entry += String(t1, 2) + "," + String(rh1, 1) + ",";

entry += String(t2, 2) + "," + String(rh2, 1) + ",";

entry += String(vbat, 2);

logFile.println(entry);

logFile.flush(); // WAITS UNTIL FLASH SECTORS ARE PHYSICALLY COMMITTED!

logFile.close();

SD.end(); // Unmount safely to conserve power and prevent corruption

Serial.println("[SD] Log entry written and flushed: " + entry);

return true;

}

// -----------------------------------------------------------------------------

// ARDUINO SETUP (WAKE -> SAMPLE -> LOG -> SLEEP)

// -----------------------------------------------------------------------------

void setup() {

Serial.begin(115200);

delay(100);

logCycleCounter++;

Serial.println("\n========================================================");

Serial.printf(" MENTORA CAVE MICROCLIMATE DATALOGGER (CYCLE #%d)\n", logCycleCounter);

Serial.println("========================================================");

pinMode(STATUSLEDPIN, OUTPUT);

digitalWrite(STATUSLEDPIN, HIGH);

pinMode(BATTERYADCPIN, INPUT);

analogReadResolution(12);

analogSetAttenuation(ADC_11db);

// 1. Evaluate Battery Reserve

float batteryVolts = readBatteryVoltage();

Serial.printf("[BATTERY] Measured Voltage: %.2fV\n", batteryVolts);

if (batteryVolts < 3.25) {

Serial.println("[LOCKOUT] Battery critically low (< 3.25V)!");

Serial.println("[LOCKOUT] Halting SD card writes to prevent FAT32 corruption. Sleeping...");

digitalWrite(STATUSLEDPIN, LOW);

espsleepenabletimerwakeup(7200ULL * 1000000ULL); // 2-hour sleep

espdeepsleep_start();

}

// 2. Initialize Dual I2C SHT35 Sensors

Wire.begin(21, 22);

Wire.setClock(100000);

delay(20);

bool okEntrance = shtEntrance.begin(0x44);

bool okDeep = shtDeep.begin(0x45);

if (!okEntrance) Serial.println("[WARNING] Entrance SHT35 (0x44) not responding!");

if (!okDeep) Serial.println("[WARNING] Deep Chamber SHT35 (0x45) not responding!");

// Execute periodic de-fogging bake-off if scheduled

if (logCycleCounter % HEATERCYCLEINTERVAL == 0 && okEntrance && okDeep) {

executeCondensationBakeOff();

}

// 3. Sample Microclimate Data

float tEntrance = okEntrance ? shtEntrance.readTemperature() : 0.0;

float rhEntrance = okEntrance ? shtEntrance.readHumidity() : 0.0;

float tDeep = okDeep ? shtDeep.readTemperature() : 0.0;

float rhDeep = okDeep ? shtDeep.readHumidity() : 0.0;

Serial.printf("[ENTRANCE 0x44] Temp: %5.2f C | Humidity: %5.1f%%\n", tEntrance, rhEntrance);

Serial.printf("[DEEP CAVE 0x45] Temp: %5.2f C | Humidity: %5.1f%%\n", tDeep, rhDeep);

// 4. Log to MicroSD Card

appendLogToSD(tEntrance, rhEntrance, tDeep, rhDeep, batteryVolts);

digitalWrite(STATUSLEDPIN, LOW);

// 5. Enter Deep Sleep

Serial.printf("[SYSTEM] Entering deep sleep for %llu seconds...\n\n", SLEEPINTERVALSEC);

espsleepenabletimerwakeup(SLEEPINTERVALSEC * 1000000ULL);

espdeepsleep_start();

}

void loop() {

}

``

Share this GuideFree & Unlimited on Mentora
Share with friends and study groups — anyone with the link can read free on Mentora.
Microcontroller Hub: ESP32-WROVER-E (Xtensa Dual-Core with 8MB PSRAM)Precision Climate Sensors: Dual Industrial Sensirion SHT35 Probes ($\pm 1.5% \text{ RH}, \pm 0.1^\circ\text{C}$ Accuracy, I2C: 0x44 Entrance Zone & 0x45 Deep Chamber)Mass Storage: SPI MicroSD Card Data-Logging Shield with Power-Fail Safe FAT32 File FlushingSpeleological Engineering: Periodic On-Chip Condensation Bake-Off De-Fogging Cycle, Sub-20µA Battery Deep SleepTarget Search Intent: "100% relative humidity condensation saturation SHT35 fix", "MicroSD SPI bus contention with sensor I2C ESP32", "SD card FAT32 file corrupt on low battery fix", "Subterranean cave climate datalogger" Token-Gated Asset

Build a Cave Microclimate Logger with ESP32-WROVER, Dual SHT35 & SD Storage Firmware & Schematics Vault

Download verified Arduino C++ firmware, pinout table, and BOM for Build a Cave Microclimate Logger with ESP32-WROVER, Dual SHT35 & SD Storage.

Access Fee
₹49₹199

Free Tutorial vs. Project Vault: The tutorial and educational theory above are 100% free. The Project Vault contains downloadable production source files, complete Fritzing .fzz circuits, laser vector files, and 1-on-1 mentor debugging sessions.

ESP32_Cave_Microclimate_Logger_Dual_SHT35.ino(Locked Asset)
Token-Gated
#include<Wire.h>
#include<Adafruit_Sensor.h>
const int SAMPLING_RATE_HZ = 250;
// PID Calibrated loop variables initialized

void setup() { Wire.begin(21, 22); calibrateIMUGyroscope(); }

void loop() { float pitch = readSensorPitch(); float roll = readSensorRoll(); }

float calculatePID(float error, float kp, float ki, float kd) { ... }

void transmitTelemetryPackage(uint8_t *packet, size_t length) { ... }

1 Code Snippets & 0 Schematics Locked

Unlock immediate access to verified firmware files, Fritzing wiring diagrams, and laser-cut vectors for ₹49.

Fritzing & CAD Schematics Pack.FZZ & .PDF

Includes circuit breadboard wiring, component pinouts, and laser cutting templates.

Stuck on Hardware or Code? Book a 1-on-1 Video SessionLive Mentor

Screen-share or show your breadboard to M. F. Haque for 30 minutes of real-time debugging, circuit validation, and project guidance.

Peer Review & Rewards

Review This Build & Claim a Free ₹49 Token

Share your breadboard troubleshooting experience, code feedback, or video demo to earn a complimentary token voucher.

5/5 - Outstanding Build

Have a Reward Voucher?

Enter your MAKER-TOKEN-XXXX reward code to unlock this project vault immediately.

Ambassador Program

Earn ₹50 Per Friend You Refer

Get your custom referral link. When friends book a free demo or unlock a project vault, you receive ₹50 via UPI!

Frequently Asked Questions

Why does the SD card corrupt its directory structure after a few weeks?

Root Cause: Writing during battery exhaustion. When the 18650 cell drops below 3.0V, the write voltage to the flash NAND gates becomes unstable, corrupting the FAT allocation table. Fix: Measure battery voltage via a 100kΩ/100kΩ divider on GPIO 35 (ADC1). If $V_{bat} < 3.25\text{V}$, halt all SD writes and put the ESP32 into emergency deep sleep.

Why does the SPI bus hang when the SD card is initialized alongside I2C?

Root Cause: Pin cross-talk. The ESP32 hardware SPI uses GPIO 18 (SCK), GPIO 19 (MISO), GPIO 23 (MOSI), and GPIO 5 (CS). Keep these completely separated from the I2C bus lines on GPIO 21 (SDA) and GPIO 22 (SCL).

Live Evening Cohort • Interactive Micro-Cohort (Max 6–8 Kids)

Learn this in our Junior Tech: Game Design & Scratch

Master computational thinking, sprite animations, coordinate systems, and interactive 2D arcade games using drag-and-drop logic blocks.

Tuesday & Thursday • 5:30 PM – 6:30 PM IST • Monthly After-School Pass: ₹800/mo ₹1,500
Topics:#ESP32#ESP32 & IoT#Circuit Wiring#Arduino Code#DIY Electronics#ESP32 cave microclimate logger#SHT35 100% humidity condensation heater#Dual SHT35 I2C address wiring#MicroSD FAT32 corruption protection#Speleological environmental monitoring