Mini Weather Station with ESP8266, DHT22 & DS3231 RTC
01 Overview
We are building a Mini weather station to provide real-time environment information for a localized area, room or office. It continuously senses ambient temperature and humidity, providing immediate and accurate readings on an OLED display. Additionally, it has a precise real-time clock (RTC) that makes sure that time and date information is available even when the gadget loses power. It also has the potential to grow into an advanced smart home or IoT application in the future.
02 Hardware and Software Components
Gather everything below before you start.
Hardware Components
| Component | Description |
|---|---|
| ESP8266 NodeMCU | Wi-Fi-capable microcontroller board used as the main controller |
| DHT22 Temperature and Humidity Sensor | Measures ambient temperature and humidity |
| DS3231 RTC Module | Keeps accurate time and date even when power is lost |
| LCD I2C Module | 20x4 character display used in Version 2 of the build |
| OLED Display | 0.96" display used in Version 1 of the build |
| Jumper Wires | Used to connect all modules to the NodeMCU |
| Breadboard | Used for prototyping the circuit |
Software Tools
| Software | Version / Details |
|---|---|
| Arduino IDE | Used to write and upload the firmware |
03 Application Discussion
Here is what each component does and why it is part of this project.
ESP8266
The NodeMCU V1.0 (ESP8266) is a small IoT development platform that includes a Wi-Fi-capable microcontroller, a USB interface both for power and programming, a voltage regulator, a few GPIO pins that can be used to connect sensors and devices, a single ADC for analog input, built-in flash memory, and an integrated antenna for wireless communication.
DHT22
The DHT22 measures temperature and humidity with good accuracy and low power. It uses a single digital pin, making it easy to connect to a microcontroller like an Arduino or ESP8266. The DHT22 has three main parts: the humidity sensing component, the temperature sensing component, and a signal processing component that produces calibrated digital output. The DHT22 is often used in weather stations, smart homes, HVAC systems, and science/educational projects for monitoring the environment.
DS3231 RTC
The DS3231 is a digital real-time clock module that keeps accurate time and date even when the rest of the circuit loses power, thanks to its onboard battery backup. It communicates over I2C, making it simple to wire alongside other I2C devices such as the OLED or LCD display, and it is commonly used in weather stations, smart homes, HVAC systems, and educational projects that need reliable timekeeping.
LCD
A 20x4 LCD is a display module that can show 20 characters per line for a total of 4 lines, which makes it useful for applications that require more information to be displayed at once. It has a built-in HD44780-compatible controller, and uses a backlight which enhances visibility in low-light environments. It has an included I2C interface module so only two data lines for communication (SDA and SCL) together with Power and Ground are needed, which simplifies wiring connection.
OLED
The 0.96 inch OLED is a small and low-power display that can be used to display text, graphic images, or sensory data. This display uses organic light-emitting diode (OLED) technology, meaning each pixel emits its own light without needing a backlight. This also means it provides lots of contrast and rich colors, with low power, and wide viewing angles. It uses the SSD1306 driver chip that controls the operation of each of the pixels. It uses either I2C or serial (SPI) to communicate with the microcontroller (NodeMCU or Arduino). I2C is typically what is used, as it requires less wiring.
04 Hardware Setup
Wire the components to the board using the diagrams and tables below. There are two versions of this build: Version 1 uses the OLED display, and Version 2 uses the LCD display.
Version 1 (OLED)
| ESP8266 Pin | Connects To |
|---|---|
| 3V3 | DHT VCC |
| GND | DHT GND |
| D4 | DHT DATA PIN |
| D2 (GPIO4) | RTC SDA |
| D1 (GPIO5) | RTC SCL |
| 3V3 | RTC VCC |
| GND | RTC GND |
| D2 (GPIO4) | OLED SDA |
| D1 (GPIO5) | OLED SCL |
| 3V3 | OLED VCC |
| GND | OLED GND |
Summary:
- D1 (GPIO5): SCL for both OLED and RTC
- D2 (GPIO4): SDA for both OLED and RTC
- D4 (GPIO2): Data for DHT22 sensor
- 3V3 (or Vin): Power supply for all modules
- GND: Common ground for all modules
Version 2 (LCD)
| ESP8266 Pin | Connects To |
|---|---|
| 3V3 | DHT VCC |
| GND | DHT GND |
| D4 | DHT DATA PIN |
| D2 (GPIO4) | RTC SDA |
| D1 (GPIO5) | RTC SCL |
| 3V3 | RTC VCC |
| GND | RTC GND |
| D2 (GPIO4) | LCD SDA |
| D1 (GPIO5) | LCD SCL |
| 3V3 | LCD VCC |
| GND | LCD GND |
Summary:
- D1 (GPIO5): SCL for both LCD and RTC
- D2 (GPIO4): SDA for both LCD and RTC
- D4 (GPIO2): Data for DHT22 sensor
- 3V3 (or Vin): Power supply for all modules
- GND: Common ground for all modules
05 Software Setup
Since we make 2 versions of the project using the LCD and the OLED, we set it up in 2 different ways. Open the Arduino IDE, install the required libraries for your chosen version (Wire, Adafruit GFX, Adafruit SSD1306, DHT, and RTClib for Version 1 OLED; or Wire, LiquidCrystal_I2C, DHT, and RTClib for Version 2 LCD), then copy the matching sketch from the Code section below and upload it to your NodeMCU.
06 Code
Copy the sketch below for the version you are building. Read the Code Breakdown section to understand what each part does.
Version 1 Project Code (OLED)
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <DHT.h>
#include <RTClib.h>
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define DHTPIN D4
#define DHTTYPE DHT22
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1);
DHT dht(DHTPIN, DHTTYPE);
RTC_DS3231 rtc;
const unsigned char temp_icon[] PROGMEM = {
0x1c,0x00,0x22,0x02,0x2b,0x05,0x2a,0x02,0x2b,0x38,0x2a,0x60,0x2b,0x40,
0x2a,0x40,0x2a,0x60,0x49,0x38,0x9c,0x80,0xae,0x80,0xbe,0x80,0x9c,0x80,
0x41,0x00,0x3e,0x00
};
const unsigned char hum_icon[] PROGMEM = {
0x04,0x00,0x04,0x00,0x0c,0x00,0x0e,0x00,0x1e,0x00,0x1f,0x00,0x3f,0x80,
0x3f,0x80,0x7e,0xc0,0x7f,0x40,0xff,0x60,0xff,0xe0,0x7f,0xc0,0x7f,0xc0,
0x3f,0x80,0x0f,0x00
};
const unsigned char heat_index_icon[] PROGMEM = {
0x01,0x00,0x21,0x08,0x10,0x10,0x03,0x80,0x8c,0x62,0x48,0x24,0x10,0x10,
0x10,0x10,0x10,0x10,0x48,0x24,0x8c,0x62,0x03,0x80,0x10,0x10,0x21,0x08,
0x01,0x00,0x00,0x00
};
void setup() {
Serial.begin(115200);
Wire.begin(D2, D1);
display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
display.clearDisplay();
dht.begin();
if (!rtc.begin()) {
display.setCursor(0, 0);
display.setTextSize(1);
display.setTextColor(SSD1306_WHITE);
display.println("RTC Error!");
display.display();
while (true);
}
delay(1500);
}
void loop() {
float h = dht.readHumidity();
float t = dht.readTemperature();
float hi = dht.computeHeatIndex(t, h, false);
DateTime now = rtc.now();
if (isnan(h) || isnan(t)) {
display.clearDisplay();
display.setCursor(0, 0);
display.println("Sensor Error!");
display.display();
delay(2000);
return;
}
char timeBuf[9], dateBuf[11];
sprintf(timeBuf, "%02d:%02d:%02d", now.hour(), now.minute(), now.second());
sprintf(dateBuf, "%02d/%02d/%d", now.month(), now.day(), now.year());
display.clearDisplay();
display.setTextColor(SSD1306_WHITE);
display.setTextSize(1);
display.setCursor(0, 0);
display.println("Mini");
display.println("Weather");
display.println("Station");
display.setCursor(SCREEN_WIDTH - 6 * strlen(dateBuf), 0);
display.print(dateBuf);
display.setCursor(SCREEN_WIDTH - 6 * strlen(timeBuf), 9);
display.print(timeBuf);
display.drawBitmap(0, 30, temp_icon, 16, 16, SSD1306_WHITE);
display.setCursor(19, 34);
display.print("T:");
display.print(t, 1); display.cp437(true); display.write(167); display.print("C");
display.drawBitmap(0, 48, hum_icon, 11, 16, SSD1306_WHITE);
display.setCursor(18, 50);
display.print("H:");
display.print(h, 0); display.print("%");
display.drawBitmap(65, 48, heat_index_icon, 15, 16, SSD1306_WHITE);
display.setCursor(83, 50);
display.print("HI:");
display.print(hi, 0);
display.write(167); display.print("C");
display.display();
delay(1000);
}
Version 2 Project Code (LCD)
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include <DHT.h>
#include <RTClib.h>
#define DHTPIN D4
#define DHTTYPE DHT22
DHT dht(DHTPIN, DHTTYPE);
LiquidCrystal_I2C lcd(0x27, 20, 4);
RTC_DS3231 rtc;
char daysOfTheWeek[7][4] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
void setup() {
Serial.begin(115200);
Serial.println("NodeMCU Mini Weather Station with RTC Starting...");
Serial.println("-------------------------------------------------");
lcd.init();
lcd.backlight();
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Weather Station");
lcd.setCursor(0, 1);
lcd.print("Loading sensors...");
delay(1000);
dht.begin();
Serial.println("DHT sensor initialized.");
Serial.print("Initializing RTC... ");
if (!rtc.begin()) {
Serial.println("FAILED! Couldn't find RTC.");
Serial.println("Please check RTC wiring (SDA to D2, SCL to D1, VCC/GND)");
Serial.println("Also ensure the RTC module has a battery.");
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("RTC ERROR!");
lcd.setCursor(0, 1);
lcd.print("Check wiring/battery!");
while (1);
}
Serial.println("SUCCESS! RTC found.");
if (rtc.lostPower()) {
Serial.println("RTC lost power, time needs to be set!");
lcd.setCursor(0, 2);
lcd.print("RTC Lost Power!");
lcd.setCursor(0, 3);
lcd.print("Setting Time...");
delay(2000);
Serial.print("Setting RTC to compile time: ");
DateTime compiled = DateTime(F(__DATE__), F(__TIME__));
rtc.adjust(compiled);
Serial.print(compiled.year(), DEC);
Serial.print("/");
Serial.print(compiled.month(), DEC);
Serial.print("/");
Serial.print(compiled.day(), DEC);
Serial.print(" ");
Serial.print(compiled.hour(), DEC);
Serial.print(":");
Serial.print(compiled.minute(), DEC);
Serial.print(":");
Serial.print(compiled.second(), DEC);
Serial.println();
lcd.clear();
lcd.setCursor(0,0);
lcd.print("RTC Set! Re-upload");
lcd.setCursor(0,1);
lcd.print("with line commented");
delay(5000);
} else {
Serial.println("RTC is running and time is preserved.");
}
Serial.println("Setup complete.");
lcd.clear();
}
void loop() {
delay(2000);
DateTime now = rtc.now();
if (now.year() < 2024) {
Serial.println("RTC is not providing valid time (likely stuck on default).");
Serial.println("Re-check setup errors or wiring.");
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("RTC NOT WORKING!");
lcd.setCursor(0, 1);
lcd.print("Check Serial/Wiring");
delay(5000);
return;
}
float h = dht.readHumidity();
float t = dht.readTemperature();
float f = dht.readTemperature(true);
if (isnan(h) || isnan(t) || isnan(f)) {
Serial.println("Error: Failed to read from DHT sensor!");
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("DHT Sensor Error!");
lcd.setCursor(0, 1);
lcd.print("Check wiring!");
delay(3000);
return;
}
float hif = dht.computeHeatIndex(f, h);
float hic = dht.computeHeatIndex(t, h, false);
lcd.clear();
lcd.setCursor(0, 0);
char dateBuffer[11];
sprintf(dateBuffer, "%04d/%02d/%02d", now.year(), now.month(), now.day());
lcd.print(dateBuffer);
lcd.setCursor(0, 1);
char timeBuffer[11];
sprintf(timeBuffer, "%02d:%02d:%02d %s",
now.hour(), now.minute(), now.second(), daysOfTheWeek[now.dayOfTheWeek()]);
lcd.print(timeBuffer);
lcd.setCursor(0, 2);
lcd.print("T:");
lcd.print(t, 1);
lcd.print((char)223);
lcd.print("C (");
lcd.print(f, 1);
lcd.print((char)223);
lcd.print("F)");
lcd.setCursor(0, 3);
lcd.print("H:");
lcd.print(h, 1);
lcd.print(" %");
lcd.print(" HI:");
lcd.print(hic, 1);
lcd.print((char)223);
lcd.print("C");
Serial.print("Local Time: ");
Serial.print(now.year(), DEC);
Serial.print("/");
Serial.print(now.month(), DEC);
Serial.print("/");
Serial.print(now.day(), DEC);
Serial.print(" (");
Serial.print(daysOfTheWeek[now.dayOfTheWeek()]);
Serial.print(") ");
Serial.print(now.hour(), DEC);
Serial.print(":");
Serial.print(now.minute(), DEC);
Serial.print(":");
Serial.print(now.second(), DEC);
Serial.println();
Serial.print("Humidity: ");
Serial.print(h);
Serial.print(" %\t");
Serial.print("Temperature: ");
Serial.print(t);
Serial.print(" *C (");
Serial.print(f);
Serial.print(" *F)\t");
Serial.print("Heat index: ");
Serial.print(hic);
Serial.print(" *C (");
Serial.print(hif);
Serial.println(" *F)");
Serial.println("----------------------------------------");
}
07 Code Breakdown
Here is what each part of the code does.
Libraries
| Library | Purpose |
|---|---|
| Wire.h | Handles I2C communication |
| Adafruit_GFX.h / Adafruit_SSD1306.h | Drives the OLED display (Version 1) |
| LiquidCrystal_I2C.h | Drives the 20x4 LCD over I2C (Version 2) |
| DHT.h | Operates the DHT22 temperature and humidity sensor |
| RTClib.h | Operates the DS3231 real-time clock module |
Key Functions
setup()
This function runs once when the device starts. It sets up the display, sensor, and RTC. It also checks if the RTC is connected and working.
loop()
Continuously runs after setup. It reads temperature and humidity, gets the current date and time, calculates the heat index, and shows the values on the screen.
dht.readTemperature() and dht.readHumidity()
Gets live values from the DHT22. If it fails, it shows an error message.
dht.computeHeatIndex()
Computes how hot it actually feels by combining temperature and humidity.
rtc.now()
Reads the current time and date from the RTC for display on the screen. For this to return valid values, the DS3231 must have its time set at least once, otherwise it will output its default fallback date.
08 Testing and Calibration
During testing, we initially connected the power source to the 5V pin, but the system did not work properly. We found that the ESP8266 module (NodeMCU) is designed to run on 3.3V, not 5V.
- To fix the issue, we transferred the power source from the 5V pin to the 3V3 (3.3V) pin on the ESP8266, and the device worked correctly.
- After fixing the power issue, we tested the DHT22 sensor and RTC to make sure they displayed temperature, humidity, and time correctly.
- We also checked if the display (OLED or LCD) was showing values clearly and updated in real-time.
- Minor adjustments were done to sensor reading intervals and display formatting for better visibility and accuracy.
- Make sure all components are properly connected and not loose.
- Ensure power supply is stable to avoid flickering or resetting.
09 System Demonstration
Mini Weather Station with OLED
Mini Weather Station with LCD
Video Demonstration
10 Conclusion
This tutorial demonstrates real-time environmental monitoring. First, the serial communication, I2C connections, OLED screen, DHT22 sensor, and RTC module are initialized. To identify and notify in the event that the RTC or sensor fails, it incorporates error handling. After obtaining temperature and humidity readings from the DHT22 sensor, the code computes the heat index, which is a measure of perceived heat. It simultaneously gets the current time and date from the RTC. Temperature, humidity, and heat index readings, along with a corresponding custom-drawn icon, are displayed on the OLED display along with a formatted mini weather station title and the current date and time. In addition to temperature and heat index readings, the Celsius degree symbol is shown. In a small and easy-to-use format, the display shows current weather and time information, updating every second.
11 References
- ESP32/ESP8266: DHT Temperature and Humidity Readings in OLED Display
- Weather station with OLED and WiFi - using ESP8266, OLED SSD1306, and sensors DHT11 or SHTC3
- IoT Made Simple: Home Weather Station With NodeMCU and OLED
12 Project Authors
- Glydelle Arellano
- Arcel Alfaro
Quality Checked By
- Elijah Joseph Orias
- Lukie-Mar Lonzaga
- Miles Darren Bagnol
