Build a Smart Weather Station with ESP32 and DHT11 OLED Display
Introduction
I’ve always been slightly obsessed with knowing the exact temperature of my office. Not the outside weather — I can open a window for that — but the microclimate around my desk, where the Wi‑Fi router and the afternoon sun conspire to turn summer afternoons into a slow roast. A few months ago I threw together a bare DHT11 dangling from my ESP32, just spitting readings to the serial monitor. It worked, but staring at a wall of text in the Arduino IDE got old fast. I wanted a glanceable display, something that would sit quietly in the corner and tell me if I needed to crack a window before my 3D printer filament turned into spaghetti.
So I built a proper ESP32 weather station with a tiny 0.96″ OLED that shows temperature and humidity in real time. This article is the exact walkthrough I wish I had when I started — wiring hiccups, library headaches, and the “oh, that pin is input‑only?” moments all included. By the end you’ll have your own smart weather station ESP32, a neat little desk buddy that’s surprisingly addictive to glance at.
What You’ll Need
No need to raid a component catalog; this project is built on three inexpensive modules and some jumper wires.
- ESP32-DevKit board:
Buy it here
(ESP32 Development Board Module)
Any ESP32 dev board will do. I used a generic DOIT ESP32 DEVKIT V1, but a LOLIN32 or a NodeMCU‑32S works the same. - 0.96″ OLED display (SSD1306)
The classic 128×64 pixel I²C version. Make sure it’s the 4‑pin variant (VCC, GND, SCL, SDA). The SPI ones are faster but messier to wire. - DHT11 Module
These usually come on a small PCB with a built‑in pull‑up resistor and a filtering capacitor, which saves you a breadboard nightmare. If you only have the bare 4‑pin sensor, you’ll need a 4.7kΩ–10kΩ resistor between VCC and the data line, but honestly, the module costs pennies and spares your sanity. - Breadboard and jumper wires (male‑to‑female if you’re skipping the breadboard and plugging directly into the dev board)
- Micro USB cable for programming and power
That’s it. No extra resistors or capacitors required when using the module variants. I learned this the hard way after bodging a pull‑up resistor onto a bare DHT11 with tape and bad intentions — don’t be like me.
Wiring It All Together
Both the OLED and the DHT11 talk over I²C (the DHT11 actually uses its own one‑wire protocol, but I’m putting them side‑by‑side on the same bus for clarity). The ESP32 has dedicated I²C pins, but you can use almost any GPIO via the Wire library. I stuck with the defaults because, well, why fight the hardware?
Here’s the exact pin‑to‑pin mapping:
| ESP32 Pin | OLED Pin | DHT11 Module Pin | Notes |
|---|---|---|---|
| 3.3V | VCC | VCC (+) | Power both from the 3.3V rail. The OLED is 3.3V logic; the DHT11 is tolerant, but 3.3V keeps it safe. |
| GND | GND | GND (-) | Common ground. |
| GPIO21 | SDA | — | I²C data line. Some OLED boards label it “DATA”. |
| GPIO22 | SCL | — | I²C clock line. Sometimes labeled “CLK”. |
| GPIO4 | — | OUT (or DATA) | DHT11 data pin. You can use any GPIO; I chose 4 because it’s close to ground and the 3.3V pin on my board. |
A photo would be great here, but since I can only paint with words, imagine the OLED and DHT11 crouched on opposite sides of the breadboard, all four jumper wires bridging neatly to the ESP32. The OLED’s VCC and GND are often beside each other, but double‑check: I once reversed them and turned my display into a pocket heater. The thing still works, but it’s now a permanent cautionary tale.
If your OLED has a different pinout (some have VCC and GND swapped, or SDA/SCL on opposite sides), look closely at the silkscreen — the label “GND” is your north star.
Setting Up the Arduino IDE
Before we can make the ESP32 weather station smart, we need the Arduino IDE to recognize the board and talk to it. I’m assuming you already have the Arduino IDE installed (if not, grab it from arduino.cc). Then:
Add the ESP32 board manager URL
Go to File > Preferences, and in the “Additional Boards Manager URLs” field, paste:https://dl.espressif.com/dl/package_esp32_index.json
Click OK.Install the ESP32 core
Go to Tools > Board > Boards Manager, search for “ESP32” by Espressif Systems, and install the latest version. This can take a few minutes — go make a coffee.Select your board
From Tools > Board, pick your specific ESP32 dev board. I use “DOIT ESP32 DEVKIT V1”. If your board isn’t listed, “ESP32 Dev Module” usually works as a catch‑all.Install the required libraries
Open the Library Manager (Sketch > Include Library > Manage Libraries) and search for and install:- Adafruit SSD1306 by Adafruit (for the OLED)
- Adafruit GFX by Adafruit (dependency of the SSD1306 library)
- DHT sensor library by Adafruit (works with DHT11, DHT22 and friends)
- Adafruit Unified Sensor (another dependency — the DHT library will complain if it’s missing)
After installation, close and reopen the IDE to be absolutely sure everything takes.
That’s the IDE ESP32 setup sorted. Now the fun part.
The Code
I’ve written a single sketch that reads the DHT11 and pushes the temperature and humidity to the OLED. It’s not a work of art, but it’s battle‑tested and it works. I usually keep a copy of this in my back pocket for whenever I need a quick environmental monitor.
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <DHT.h>
// OLED display settings
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define OLED_RESET -1
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
// DHT sensor settings
#define DHTPIN 4 // GPIO4 connects to DHT11 data pin
#define DHTTYPE DHT11 // DHT 11
DHT dht(DHTPIN, DHTTYPE);
void setup() {
Serial.begin(115200);
// Initialize the OLED
if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) { // Address 0x3C for most 128x64 OLEDs
Serial.println(F("SSD1306 allocation failed"));
for(;;); // Halt and let user know something's wrong
}
display.clearDisplay();
display.setTextSize(1);
display.setTextColor(SSD1306_WHITE);
// Initialize the DHT sensor
dht.begin();
Serial.println(F("DHT11 and OLED ready."));
}
void loop() {
// Wait a couple of seconds between measurements
delay(2000);
// Reading temperature and humidity takes about 250ms!
float humidity = dht.readHumidity();
float temperature = dht.readTemperature(); // Celsius by default
// Check if any reads failed and exit early (try again on next loop)
if (isnan(humidity) || isnan(temperature)) {
Serial.println(F("Failed to read from DHT sensor!"));
return;
}
// Display results on the serial monitor
Serial.print(F("Humidity: "));
Serial.print(humidity);
Serial.print(F("% Temperature: "));
Serial.print(temperature);
Serial.println(F("°C"));
// Update the OLED
display.clearDisplay();
display.setCursor(0, 10);
display.setTextSize(2);
display.print(F("Temp: "));
display.print(temperature, 1); // one decimal place
display.println(F(" C"));
display.setCursor(0, 40);
display.setTextSize(2);
display.print(F("Humi: "));
display.print(humidity, 1);
display.println(F(" %"));
display.display();
}
What’s happening here?
- The
Adafruit_SSD1306object is instantiated with the I²C address0x3C. Most generic 128×64 OLEDs use this address, but some use0x3D. If your display stays blank, run an I²C scanner sketch to verify — I’ll cover that in the troubleshooting section. - The DHT sensor is read once every two seconds. The library blocks for about 250ms during a read, so don’t call it faster than that or you’ll risk getting nonsense values.
- The
isnan()check is critical because the DHT11 sometimes returnsNaNwhen the reading fails — often due to a loose wire or electrical noise. Without this, your OLED would show “Temp: nan C” and everything would look broken. - The display is cleared and rewritten each loop. It’s not elegant, but it avoids ghosting.
If you want Fahrenheit, replace dht.readTemperature() with dht.readTemperature(true). I’m a Celsius person, but the code is flexible.
Uploading and Testing Your ESP32 Weather Station
Connect your ESP32 via USB, select the correct COM port under Tools > Port, and hit the upload arrow. If the IDE starts complaining about “waiting for packet header” or the upload hangs:
- Hold down the BOOT (or IO0) button on the ESP32 board while you click upload, and release it when the IDE says “Connecting…”. On some boards, that’s the only way to convince them into flashing mode.
- Try a different USB cable. I’ve wasted hours with charge‑only cables that look identical to data cables. If Windows doesn’t ping a driver install, suspect the cable.
Once uploaded, the OLED should light up with the temperature and humidity. If it’s blank, don’t panic — jump to the next section. Assuming it worked, you now have a fully functional smart weather station ESP32 that sits quietly on your desk, updating every two seconds.
Pro tip: because the DHT11’s accuracy is about ±2°C and ±5% RH, don’t take the numbers as gospel. I treat mine as a “how comfortable is this room?” indicator, not a scientific instrument.
Common Pitfalls and How to Fix Them
I’ve run into every single one of these while building various incarnations of this project. Here’s how to unstick yourself.
OLED display is blank, but the serial monitor shows readings
First, check that you’re using the right I²C address. Open a fresh sketch and run an I²C scanner (there are plenty online — just search “Arduino I²C scanner”). It will print the address of any device connected to the SDA/SCL pins. If it finds 0x3C, then your wiring is fine. If it finds 0x3D, change the 0x3C in the display.begin() line. If it finds nothing, double‑check your SDA/SCL connections. I once spent an evening convinced my OLED was dead, only to realise I’d swapped SDA and SCL. Swapping them back brought it to life instantly.
Also, some OLEDs struggle to power on if the contrast is all the way down, but that’s rare with the SSD1306. The SSD1306_SWITCHCAPVCC init takes care of that.
DHT11 returns NaN (or readings are all zeros)
This is the most annoying failure mode because it’s intermittent. First, verify the data pin is correct and that the module has power. Test with a multimeter between VCC and GND — you should see 3.3V. If you’re using a bare DHT11 without the pull‑up resistor, add a 4.7kΩ between data and VCC. Even with the module, a loose jumper wire can cause spurious NaN readings. Try reseating everything.
Sometimes the DHT11 is just out to ruin your afternoon. In that case, add a small (100nF) capacitor between VCC and GND as close to the sensor as possible to filter noise. I’ve also had success simply inserting a delay(100) after dht.begin() in setup, giving the sensor a moment to stabilise.
Board not uploading / “Failed to connect” errors
We already touched on the BOOT button trick and cable quality, but also check that you’ve selected the right board and port. If the IDE is stuck on “Connecting….” for more than 10 seconds, something is fundamentally wrong. On Windows, you might need to install CP210x or CH340 drivers, depending on your board’s USB‑to‑serial chip. I keep a spare NodeMCU just to test drivers because driver dance is the worst.
Erratic or slow‑to‑update values
The DHT11’s 2‑second minimum read interval is a hard limit. If your loop runs faster, you’ll get stale or impossible numbers. My code has a delay(2000), which is safe. If you need faster updates, switch to a DHT22 — it can be read every 1 second and is more accurate.
Next Steps and Upgrades
A tiny standalone temperature display is great, but your ESP32 weather station is crying out for more. Here’s where you can take it.
Connect it to your home network
The ESP32 has WiFi built in, so adding NTP time syncing and an MQTT client is trivial. I’ve sent the same DHT11 data to an MQTT broker and then into Home Assistant, pretty much following the approach I described in my monitoring guide with ESPHome. The combination turns this into a proper IoT sensor node.Upgrade to a DHT22 (or better)
The DHT22 gives you ±0.5°C accuracy and works over a wider humidity range. The code change is literally swappingDHTTYPE DHT22. If you want a single‑chip solution, a BME280 adds barometric pressure, which is brilliant for predicting rain — something I will eventually get around to, I keep telling myself.Add deep sleep for battery operation
With a couple of additional wires and a deep‑sleep sketch, you can run this on a Li‑Poly battery for months. The 0.96″ OLED draws about 20mA, so turn it off between readings.Build a dashboard with Grafana
If you push your data to InfluxDB or Prometheus, you can create beautiful dashboards and even set up alerts when your office temperature exceeds 30°C. I did exactly that and then added custom alert messages, as detailed in my Grafana alerting article. The moment my ESP32 weather station told me the room was too hot for my 3D printer, I felt unreasonably proud.Integrate with Zigbee2MQTT
Not directly applicable to this build, but if you’re looking to extend your smart home sensor network, check out how I added Aqara temperature sensors via Zigbee2MQTT. It’s a great complement to a DIY station.
FAQ
Can I use a DHT22 instead of the DHT11?
Absolutely. The wiring is identical, and you just change #define DHTTYPE DHT22 in the code. The DHT22 is more accurate and refreshes faster, making your ESP32 weather station a lot more responsive.
Why is my OLED display blank even though the sketch uploaded fine?
Almost always an I²C address mismatch. Run an I²C scanner sketch to find your display’s address, then update the 0x3C (or 0x3D) in the display.begin() line. Also check SDA/SCL swap.
What if the DHT11 returns ‘nan’ all the time?
Check the wiring and ensure the module receives 3.3V. If using a bare sensor, add a 4.7kΩ pull‑up resistor between VCC and the data pin. Sometimes the sensor just needs a short delay before the first read. Replacing the DHT11 is cheap — I always keep a spare because I’ve killed a few with static.
My readings look wrong — temperature is 85°C or something impossible.
A stuck value like 85°C can happen if the DHT11 is not communicating properly. Recheck the data pin and power. If it persists, try a different GPIO or a different sensor.
Can I power this setup from a battery?
Yes. The whole thing draws well under 100mA. With deep sleep and occasional wake‑ups, a 2000mAh Li‑Po battery can last weeks. Just remember to disconnect the OLED power before sleeping to save juice.
How do I add more sensors, like a motion detector or light sensor?
The ESP32 has plenty of GPIOs left. You can attach additional I²C sensors on the same bus (just watch addresses) or use digital pins for something like a PIR motion sensor. Your smart weather station can grow into a full environmental hub.
Now go forth and build your own desk‑side oracle. If you manage to brick your OLED, take a photo first — it’ll make a great story later.