Simulating Cave Temperature Stability Using an Arduino Microclimate System

by skylartooks13 in Design > Software

21 Views, 0 Favorites, 0 Comments

Simulating Cave Temperature Stability Using an Arduino Microclimate System

IMG_5488.jpeg
IMG_5492.jpeg
IMG_5490.jpeg
IMG_2068.jpeg

This project uses a single “artificial cave” box to test how caves might respond to future warming conditions. Caves are normally stable environments used by bats and other wildlife, but global warming is beginning to push cave temperatures higher.

To explore this, I built one cave chamber and tested it in two modes:

  1. Control Mode -sensors only (no fan)
  2. Treatment Mode -automated cooling mode (fan turns on at 24°C)

Each mode uses its own Arduino code. By running the same physical cave box with two different codes, I compared:

  1. how fast the cave heats
  2. how quickly it cools
  3. whether automated airflow improves stability

This system uses an Arduino UNO with temperature, humidity, light, and motion sensors, plus a relay-controlled fan. It creates a simple and low-cost model of cave microclimates.

Supplies

IMG_5489.jpeg
IMG_5486.jpeg
IMG_2071.jpeg
IMG_2070.jpeg
IMG_2074.jpeg
IMG_2072.jpeg

One plastic container (your “cave”)

Arduino UNO

DHT22 temp/humidity sensor

Light sensor

PIR sensor (optional)

Relay module

Cooling fan (5V or 12V)

Lamp (heat source)

16×2 LCD (I2C)

Laptop with Arduino IDE

Wires, tape, scissors

•Ultrasonic mist humidifier (operated manually , not connected to Arduino due to incompatible cord attachment)

Making the Cave

IMG_5485 2.jpeg

Use one shoebox.

Cut small holes for the DHT22, light sensor, and fan airflow.

Mount sensors inside.

Keep the fan outside blowing into the cave

Connections and Set Up

IMG_5491.jpeg
IMG_2076.jpeg

DHT22 → D2

Relay module (fan) → D5

Light sensor → A0

LCD → I2C pins

PIR → D8 (optional)

Cave Control

This is the sensors-only code.

  1. Fan stays OFF the whole time.
  2. It only measures temperature, humidity, light, etc.
  3. Run a heating trial: move the heat source to 0, 3, 6 inches and record time to reach 36°C.

Save your data as Cave_Control.csv.

Cave Treatment

IMG_5488 2.jpeg

This is the automated cooling code.

  1. Fan turns on at 24°C
  2. Fan turns off at ~23°C
  3. Run the heating and cooling trials again using the same distances (0, 3, 6 inches)

Save your data as Cave_Treatment.csv.

Comparing the Two Treatments

IMG_5487 2.jpeg
IMG_2071 2.jpeg
IMG_2070 2.jpeg

You now have:

  1. Control (no fan)
  2. Treatment (fan)

Run R code to compare:

✔ Heating time vs distance

✔ Cooling speed

✔ Stability (temperature oscillation)

Ploting Graph

Screenshot 2025-11-30 at 1.45.01 AM.png

Plot your findings:

  1. Line graph (heat time vs distance)
  2. Boxplot (heat vs cool time by condition)

These graphs go on your poster and into the Instructable.

Coding Break Down for Control

Cave Microclimate — TREATMENT control (No Mist)

- Stable LCD

- DHT11 on D2

- Fan relay on D5

- Toggle on D3 (INPUT_PULLUP)

- PIR on D8

- Light A0, Spare A2

- Buzzer/LED on D6



#include <Wire.h>

#include "rgb_lcd.h"

#include "DHT.h"

#include <math.h>


rgb_lcd lcd;


#define PIN_DHT 2

#define DHTTYPE DHT11

#define PIN_TOGGLE 3

#define PIN_FAN 5

#define PIN_PIR 8

#define PIN_LIGHT A0

#define PIN_A2 A2

#define PIN_BUZZER 6


// Relay configuration

const bool RELAY_ACTIVE_LOW = true;

const bool RELAY_WIRED_NC = true;


// Fan thresholds (°C)

const float TEMP_HIGH_C = 24.0; // ON at/above

const float TEMP_LOW_C = 23.0; // OFF at/under


// Cadence

unsigned long tRead = 0, tLCD = 0;

const unsigned long READ_MS = 1000, LCD_MS = 1000;


// State

DHT dht(PIN_DHT, DHTTYPE);

float t_dht = NAN, rh = NAN;

int lightRaw = -1, a2Raw = -1;

bool fanOn = false, lastFan = false;


// Helpers

static inline void buzz(uint16_t ms){ digitalWrite(PIN_BUZZER, HIGH); delay(ms); digitalWrite(PIN_BUZZER, LOW); }

static inline void print16(const char* s){ int i=0; while(s[i] && i<16){ lcd.print(s[i++]); } while(i++<16) lcd.print(' '); }

static inline void driveRelay(bool energize){

if (RELAY_ACTIVE_LOW) digitalWrite(PIN_FAN, energize ? LOW : HIGH);

else digitalWrite(PIN_FAN, energize ? HIGH : LOW);

}

void setFan(bool on){

// If wired NC, coil energize is inverted relative to desired fan state

bool energize = on ^ RELAY_WIRED_NC;

driveRelay(energize);

fanOn = on; // record physical state

}


void setup(){

pinMode(PIN_FAN, OUTPUT); setFan(false);

pinMode(PIN_TOGGLE, INPUT_PULLUP);

pinMode(PIN_PIR, INPUT);

pinMode(PIN_BUZZER, OUTPUT); digitalWrite(PIN_BUZZER, LOW);


Serial.begin(9600);


Wire.begin();

Wire.setClock(25000);


dht.begin(); delay(1500);


lcd.begin(16,2);

lcd.setRGB(0,120,60);

lcd.setCursor(0,0); print16("MODE: TREATMENT");

delay(3);

lcd.setCursor(0,1); print16("Auto control");

buzz(80); delay(400);


Serial.println("----- Cave Treatment v7.4 (Grove RGB LCD) -----");

Serial.print("Config ACTIVE_LOW="); Serial.print(RELAY_ACTIVE_LOW ? "true" : "false");

Serial.print(" WIRED_NC="); Serial.println(RELAY_WIRED_NC ? "true" : "false");

Serial.println("Time(ms) Temp Hum Light A2 Fan Sw PIR [energize pin]");

}


void readSensors(){

float h = dht.readHumidity();

float t = dht.readTemperature();

if (!isnan(h)) rh = h;

if (!isnan(t)) t_dht = t;


long sL=0; for(int i=0;i<8;i++){ sL += analogRead(PIN_LIGHT); delay(2); }

lightRaw = sL/8;

a2Raw = analogRead(PIN_A2);

}


void controlLogic(){

lastFan = fanOn;

if (!isnan(t_dht)){

if (!fanOn && t_dht >= TEMP_HIGH_C) setFan(true);

else if (fanOn && t_dht <= TEMP_LOW_C) setFan(false);

}

if (fanOn != lastFan) buzz(fanOn ? 120 : 60);

}


void updateLCD(){

char line0[17], line1[17];


if (isnan(t_dht)) snprintf(line0, sizeof(line0), "Temp --.- C");

else {

int t10 = (int)round(t_dht * 10.0f);

snprintf(line0, sizeof(line0), "Temp %2d.%1d C", t10/10, abs(t10%10));

}


int sw = (digitalRead(PIN_TOGGLE)==LOW) ? 1 : 0;

if (isnan(rh)) snprintf(line1, sizeof(line1), "Hum -- pct Sw:%d", sw);

else snprintf(line1, sizeof(line1), "Hum %3d pct Sw:%d", (int)round(rh), sw);


lcd.setCursor(0,0); print16(line0); delay(3);

lcd.setCursor(0,1); print16(line1); delay(3);

}


void loop(){

unsigned long now = millis();


if (now - tRead >= READ_MS){

tRead = now;

readSensors();

controlLogic();


bool energize = fanOn ^ RELAY_WIRED_NC;

Serial.print("Time(ms)="); Serial.print(now);

Serial.print(" Temp="); Serial.print(isnan(t_dht)?-999:t_dht,1); Serial.print("C");

Serial.print(" Hum="); Serial.print(isnan(rh)?-999:rh,0); Serial.print("%");

Serial.print(" Light="); Serial.print(lightRaw);

Serial.print(" A2="); Serial.print(a2Raw);

Serial.print(" Fan="); Serial.print(fanOn ? "ON" : "OFF");

Serial.print(" Sw="); Serial.print(digitalRead(PIN_TOGGLE)==LOW ? "ON" : "OFF");

Serial.print(" PIR="); Serial.print(digitalRead(PIN_PIR));

Serial.print(" [energize="); Serial.print(energize ? "1" : "0");

Serial.print(" pin="); Serial.print(digitalRead(PIN_FAN));

Serial.println("]");

}


if (now - tLCD >= LCD_MS){

tLCD = now;

updateLCD();

}

}



What the code controls

  1. It reads temperature, humidity, light, and the toggle switch.
  2. It turns the fan ON if it gets too warm (24°C).
  3. It turns the fan OFF when it cools down (23°C).
  4. A buzzer beeps when the fan turns on or off.
  5. The LCD screen shows the current temperature and humidity.
  6. Every second, it sends all the data to the Serial Monitor so I can graph it later.

What each part of the code means

Pin setup

This tells the Arduino which pin each part is plugged into:

  1. DHT11 sensor (temp + humidity)
  2. Fan relay
  3. Toggle switch
  4. PIR (motion sensor)
  5. Light sensor
  6. Buzzer
  7. LCD screen (using I2C)

Temperature rules

  1. Turn fan ON at 24°C
  2. Turn fan OFF at 23°C
  3. This stops the fan from constantly turning on and off.

Reading sensors

Every second the Arduino:

  1. Reads the temperature
  2. Reads the humidity
  3. Reads the light level
  4. Checks the toggle switch
  5. Checks the motion sensor

Displaying on the screen

The LCD shows:

  1. Line 1: Temperature
  2. Line 2: Humidity + toggle switch state (“Sw:1” means ON)

Controlling the fan

If the temperature reaches the high limit, the fan turns on.

When it cools down enough, the fan turns off.


What the loop does

Every second:

  1. Read sensors
  2. Decide if the fan should turn on/off
  3. Show the numbers on the LCD
  4. Send the data to the Serial Monitor

Coding Break Down for Treatment

Cave Microclimate — TREATMENT

- Stable LCD (ASCII-only, slow I2C, no lcd.clear in loop)

- DHT11 on D2

- Fan relay on D5

- Toggle on D3 (INPUT_PULLUP)

- PIR on D8

- Light A0, Spare A2

- Buzzer/LED on D6



#include <Wire.h>

#include "rgb_lcd.h"

#include "DHT.h"

#include <math.h>


rgb_lcd lcd;


#define PIN_DHT 2

#define DHTTYPE DHT11

#define PIN_TOGGLE 3

#define PIN_FAN 5

#define PIN_PIR 8

#define PIN_LIGHT A0

#define PIN_A2 A2

#define PIN_BUZZER 6


// Relay configuration

const bool RELAY_ACTIVE_LOW = true;

const bool RELAY_WIRED_NC = true;


// Fan thresholds (°C)

const float TEMP_HIGH_C = 24.0; // ON at/above

const float TEMP_LOW_C = 23.0; // OFF at/under


// Cadence

unsigned long tRead = 0, tLCD = 0;

const unsigned long READ_MS = 1000, LCD_MS = 1000;


// State

DHT dht(PIN_DHT, DHTTYPE);

float t_dht = NAN, rh = NAN;

int lightRaw = -1, a2Raw = -1;

bool fanOn = false, lastFan = false;


// Helpers

static inline void buzz(uint16_t ms){ digitalWrite(PIN_BUZZER, HIGH); delay(ms); digitalWrite(PIN_BUZZER, LOW); }

static inline void print16(const char* s){ int i=0; while(s[i] && i<16){ lcd.print(s[i++]); } while(i++<16) lcd.print(' '); }

static inline void driveRelay(bool energize){

if (RELAY_ACTIVE_LOW) digitalWrite(PIN_FAN, energize ? LOW : HIGH);

else digitalWrite(PIN_FAN, energize ? HIGH : LOW);

}

void setFan(bool on){

// If wired NC, coil energize is inverted relative to desired fan state

bool energize = on ^ RELAY_WIRED_NC;

driveRelay(energize);

fanOn = on; // record physical state

}


void setup(){

pinMode(PIN_FAN, OUTPUT); setFan(false);

pinMode(PIN_TOGGLE, INPUT_PULLUP);

pinMode(PIN_PIR, INPUT);

pinMode(PIN_BUZZER, OUTPUT); digitalWrite(PIN_BUZZER, LOW);


Serial.begin(9600);


Wire.begin();

Wire.setClock(25000);


dht.begin(); delay(1500);


lcd.begin(16,2);

lcd.setRGB(0,120,60);

lcd.setCursor(0,0); print16("MODE: TREATMENT");

delay(3);

lcd.setCursor(0,1); print16("Auto control");

buzz(80); delay(400);


Serial.println("----- Cave Treatment v7.4 (Grove RGB LCD) -----");

Serial.print("Config ACTIVE_LOW="); Serial.print(RELAY_ACTIVE_LOW ? "true" : "false");

Serial.print(" WIRED_NC="); Serial.println(RELAY_WIRED_NC ? "true" : "false");

Serial.println("Time(ms) Temp Hum Light A2 Fan Sw PIR [energize pin]");

}


void readSensors(){

float h = dht.readHumidity();

float t = dht.readTemperature();

if (!isnan(h)) rh = h;

if (!isnan(t)) t_dht = t;


long sL=0; for(int i=0;i<8;i++){ sL += analogRead(PIN_LIGHT); delay(2); }

lightRaw = sL/8;

a2Raw = analogRead(PIN_A2);

}


void controlLogic(){

lastFan = fanOn;

if (!isnan(t_dht)){

if (!fanOn && t_dht >= TEMP_HIGH_C) setFan(true);

else if (fanOn && t_dht <= TEMP_LOW_C) setFan(false);

}

if (fanOn != lastFan) buzz(fanOn ? 120 : 60);

}


void updateLCD(){

char line0[17], line1[17];


if (isnan(t_dht)) snprintf(line0, sizeof(line0), "Temp --.- C");

else {

int t10 = (int)round(t_dht * 10.0f);

snprintf(line0, sizeof(line0), "Temp %2d.%1d C", t10/10, abs(t10%10));

}


int sw = (digitalRead(PIN_TOGGLE)==LOW) ? 1 : 0;

if (isnan(rh)) snprintf(line1, sizeof(line1), "Hum -- pct Sw:%d", sw);

else snprintf(line1, sizeof(line1), "Hum %3d pct Sw:%d", (int)round(rh), sw);


lcd.setCursor(0,0); print16(line0); delay(3);

lcd.setCursor(0,1); print16(line1); delay(3);

}


void loop(){

unsigned long now = millis();


if (now - tRead >= READ_MS){

tRead = now;

readSensors();

controlLogic();


bool energize = fanOn ^ RELAY_WIRED_NC;

Serial.print("Time(ms)="); Serial.print(now);

Serial.print(" Temp="); Serial.print(isnan(t_dht)?-999:t_dht,1); Serial.print("C");

Serial.print(" Hum="); Serial.print(isnan(rh)?-999:rh,0); Serial.print("%");

Serial.print(" Light="); Serial.print(lightRaw);

Serial.print(" A2="); Serial.print(a2Raw);

Serial.print(" Fan="); Serial.print(fanOn ? "ON" : "OFF");

Serial.print(" Sw="); Serial.print(digitalRead(PIN_TOGGLE)==LOW ? "ON" : "OFF");

Serial.print(" PIR="); Serial.print(digitalRead(PIN_PIR));

Serial.print(" [energize="); Serial.print(energize ? "1" : "0");

Serial.print(" pin="); Serial.print(digitalRead(PIN_FAN));

Serial.println("]");

}


if (now - tLCD >= LCD_MS){

tLCD = now;

updateLCD();

}

}


What the code controls

  1. Reads temperature and humidity from the DHT11 sensor.
  2. Reads light levels and the toggle switch.
  3. Turns the fan ON if it gets too warm (24°C).
  4. Turns the fan OFF after it cools down (23°C).
  5. Makes a little beep every time the fan turns on or off.
  6. Shows the numbers on a Grove RGB 16×2 LCD screen.
  7. Sends one full line of data every second to the Serial Monitor so I can make graphs later.


What each section of the code means

Pin setup

This tells the Arduino where everything is plugged in:

  1. DHT11 (temp + humidity)
  2. Fan relay
  3. Toggle switch
  4. PIR sensor
  5. Light sensor
  6. Buzzer
  7. LCD (through I2C)

Fan temperature rules

The fan follows two simple rules:

  1. Turn ON at 24°C
  2. Turn OFF at 23°C
  3. This prevents the fan from constantly flicking on and off.

Reading the sensors

Every 1 second, the Arduino:

  1. Gets the temperature
  2. Gets the humidity
  3. Measures the light level
  4. Checks the toggle switch
  5. Checks the motion sensor

Showing information on the LCD

The LCD displays:

  1. Line 1: Temperature
  2. Line 2: Humidity + toggle switch (“Sw:1” = ON)

How fan control works

When it gets warm:

  1. The fan turns ON
  2. A long beep plays

When it cools back down:

  1. The fan turns OFF
  2. A short beep plays

The code also handles the relay wiring (active-LOW, NC) so the fan works correctly


What the main loop does

Every second:

  1. Read all sensors
  2. Decide whether the fan should be on or off
  3. Update the LCD
  4. Send one line of data to the computer

This keeps the “cave” at a stable temperature and logs everything automatically.