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
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:
- Control Mode -sensors only (no fan)
- 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:
- how fast the cave heats
- how quickly it cools
- 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
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
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
DHT22 → D2
Relay module (fan) → D5
Light sensor → A0
LCD → I2C pins
PIR → D8 (optional)
Cave Control
This is the sensors-only code.
- Fan stays OFF the whole time.
- It only measures temperature, humidity, light, etc.
- 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
This is the automated cooling code.
- Fan turns on at 24°C
- Fan turns off at ~23°C
- 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
You now have:
- Control (no fan)
- Treatment (fan)
Run R code to compare:
✔ Heating time vs distance
✔ Cooling speed
✔ Stability (temperature oscillation)
Ploting Graph
Plot your findings:
- Line graph (heat time vs distance)
- 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
- It reads temperature, humidity, light, and the toggle switch.
- It turns the fan ON if it gets too warm (24°C).
- It turns the fan OFF when it cools down (23°C).
- A buzzer beeps when the fan turns on or off.
- The LCD screen shows the current temperature and humidity.
- 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:
- DHT11 sensor (temp + humidity)
- Fan relay
- Toggle switch
- PIR (motion sensor)
- Light sensor
- Buzzer
- LCD screen (using I2C)
Temperature rules
- Turn fan ON at 24°C
- Turn fan OFF at 23°C
- This stops the fan from constantly turning on and off.
Reading sensors
Every second the Arduino:
- Reads the temperature
- Reads the humidity
- Reads the light level
- Checks the toggle switch
- Checks the motion sensor
Displaying on the screen
The LCD shows:
- Line 1: Temperature
- 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:
- Read sensors
- Decide if the fan should turn on/off
- Show the numbers on the LCD
- 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
- Reads temperature and humidity from the DHT11 sensor.
- Reads light levels and the toggle switch.
- Turns the fan ON if it gets too warm (24°C).
- Turns the fan OFF after it cools down (23°C).
- Makes a little beep every time the fan turns on or off.
- Shows the numbers on a Grove RGB 16×2 LCD screen.
- 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:
- DHT11 (temp + humidity)
- Fan relay
- Toggle switch
- PIR sensor
- Light sensor
- Buzzer
- LCD (through I2C)
Fan temperature rules
The fan follows two simple rules:
- Turn ON at 24°C
- Turn OFF at 23°C
- This prevents the fan from constantly flicking on and off.
Reading the sensors
Every 1 second, the Arduino:
- Gets the temperature
- Gets the humidity
- Measures the light level
- Checks the toggle switch
- Checks the motion sensor
Showing information on the LCD
The LCD displays:
- Line 1: Temperature
- Line 2: Humidity + toggle switch (“Sw:1” = ON)
How fan control works
When it gets warm:
- The fan turns ON
- A long beep plays
When it cools back down:
- The fan turns OFF
- 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:
- Read all sensors
- Decide whether the fan should be on or off
- Update the LCD
- Send one line of data to the computer
This keeps the “cave” at a stable temperature and logs everything automatically.