Skip to main content

Digital Weather Station using Raspberry Pi Pico and DHT22

Raspberry Pi Pico DHT22 Weather Station – MicroPython + LCD | MakeMindz
▶ Open Wokwi
🌡️ Summer Class — IoT Project

Raspberry Pi Pico
Digital Weather Station

Monitor real-time temperature and humidity with a DHT22 sensor and 16×2 LCD display — all in MicroPython, fully testable in the free Wokwi browser simulator!

🟢 Beginner 🐍 MicroPython 🌡️ DHT22 📺 I2C LCD 🖥️ Wokwi ⏱ 30 min
16×2 I2C LCD
Temp:24.5°C
Humid:65.2%
Pico GP0/GP1 (I2C)DHT22 → GP15

What You'll Build

A fully functional environmental monitor that reads real sensor data and displays it live on an LCD — in MicroPython!

⏱️
~30
Minutes to build
🌡️
±0.5°C
DHT22 accuracy
🔄
2 sec
LCD update rate
🖥️
Free
Wokwi simulator
💡
Simulate first, build later! Run this entire project in Wokwi for free. You can even drag the virtual DHT22 slider to test different temperature/humidity scenarios without any hardware.

What You Need

All components are available virtually in Wokwi — nothing to purchase to get started!

Raspberry Pi Pico
DHT22 Temperature & Humidity Sensor
16×2 I2C LCD Display
10kΩ Pull-up Resistor (DHT22 data)
Breadboard & Jumper Wires
MicroPython Firmware on Pico
🐍
3 Python files required: This project uses main.py (your logic), lcd_api.py (HD44780 base class), and pico_i2c_lcd.py (I2C driver). All three are provided below — just upload them together.

Circuit Connections

The DHT22 uses a single data wire, while the LCD connects via I2C with just 2 wires.

3.3V
DHT22 VCC
3.3V power
GND
DHT22 GND
Ground
GP15
DHT22 DATA
+ 10kΩ pull-up to 3.3V
GP0
LCD SDA
I2C data
GP1
LCD SCL
I2C clock
3.3V
LCD VCC
Power
🔌

Full Connection Table

ComponentPinPico GPIOWire
DHT22 VCCVCC3.3V (Pin 36)🔴 Red
DHT22 GNDGNDGND (Pin 3)⬛ Black
DHT22 DATASDA/DataGP15 (Pin 20)🟡 Yellow
10kΩ ResistorBetween DATA & VCCPull-up to 3.3V
LCD SDASDAGP0 (Pin 1)🟢 Green
LCD SCLSCLGP1 (Pin 2)🔵 Blue
LCD VCCVCC3.3V🔴 Red
LCD GNDGNDGND⬛ Black
⚠️
Pull-up resistor is required! The DHT22 DATA line needs a 10kΩ resistor connected between the DATA pin and 3.3V. Without it, readings will fail. This is already included in the diagram.json below.

Step-by-Step Guide

8 steps to get your weather station reading live data in Wokwi.

  1. Open the Wokwi Simulation

    Go to wokwi.com/projects/459537616532034561 or click ▶ Run Free Simulation above. Select Raspberry Pi Pico as the board and set language to MicroPython.

  2. Paste the diagram.json

    Click the diagram.json tab and replace all content with the JSON from the Diagram JSON section below. This auto-places and wires the Pico, DHT22, LCD, and pull-up resistor.

  3. Create the lcd_api.py file

    In Wokwi click + New File → name it lcd_api.py → paste the lcd_api.py code from the Code section below. This is the HD44780 base library.

  4. Create the pico_i2c_lcd.py file

    Click + New File again → name it pico_i2c_lcd.py → paste the I2C driver code. This handles the PCF8574 I2C-to-parallel translation chip used on most I2C LCD backpacks.

  5. Paste main.py code

    Open the main.py tab and paste the main application code. This reads the DHT22 sensor every 2 seconds and updates both lines of the LCD.

  6. Click ▶ Run Simulation

    Press the green play button. The LCD will show the startup message "MakeMindz / Temp & Humidity" for 2 seconds, then switch to live sensor readings.

    Expected LCD output: Line 1 shows Temp:24.5°C, Line 2 shows Humid:65.2% — updating every 2 seconds.
  7. Check the Serial Console

    Open Wokwi's Serial Monitor tab to see console output: Temperature: 24.5°C (76.1°F) and Humidity: 65.2% printed every 2 seconds.

  8. Adjust virtual sensor values

    Click the DHT22 component in Wokwi and drag its temperature and humidity sliders to test different readings. Watch the LCD update in real time — perfect for testing error handling too!


🌡️ Test in Your Browser — Completely Free

Adjust virtual temperature & humidity sliders and watch the LCD update live. No hardware needed!

Open Live Simulation

Wokwi diagram.json

Paste this into the diagram.json tab in Wokwi. It auto-places and wires all 4 components — Pico, DHT22, LCD (I2C mode), and the 10kΩ pull-up resistor.

How to use in Wokwi
1. Click the diagram.json tab in Wokwi
2. Select all text (Ctrl+A) and delete it
3. Paste this JSON — all wiring appears automatically
4. Note: DHT22 is pre-set to 24°C / 65% humidity — drag its sliders to change!
diagram.json — Wokwi
{
  "version":  1,
  "author":  "MakeMindz",
  "editor":  "wokwi",
  "parts": [
    {
      "type": "wokwi-pi-pico",
      "id":   "pico",
      "top":  0,  "left": 0,
      "attrs": {}
    },
    {
      "type": "wokwi-dht22",
      "id":   "dht22",
      "top":  -38.4, "left": 220.8,
      "attrs": {
        "temperature": "24",  // editable in simulator
        "humidity":    "65"
      }
    },
    {
      "type": "wokwi-lcd1602",
      "id":   "lcd",
      "top":  -144, "left": -192,
      "attrs": { "pins": "i2c" }  // I2C mode — only 2 data wires needed
    },
    {
      "type": "wokwi-resistor",
      "id":   "r1",
      "top":  -19.2, "left": 268.8,
      "attrs": { "value": "10000" }  // 10kΩ pull-up for DHT22 data line
    }
  ],
  "connections": [
    // LCD I2C connections
    [ "pico:GP0",  "lcd:SDA",  "green", ["h0"] ],
    [ "pico:GP1",  "lcd:SCL",  "blue",  ["h0"] ],
    [ "pico:3V3",  "lcd:VCC",  "red",   ["h0"] ],
    [ "pico:GND.8", "lcd:GND", "black", ["h0"] ],

    // DHT22 sensor connections
    [ "dht22:VCC", "pico:3V3",   "red",    ["v0"] ],
    [ "dht22:GND", "pico:GND.3", "black", ["v0"] ],
    [ "dht22:SDA", "pico:GP15",  "yellow", ["v0"] ],

    // 10kΩ pull-up resistor between DHT22 data and VCC
    [ "r1:1", "dht22:SDA", "", ["v0"] ],
    [ "r1:2", "dht22:VCC", "", ["v0"] ]
  ],
  "dependencies": {}
}

📋 Connection Reference

ConnectionPurpose
pico:GP0 → lcd:SDAI2C data line to LCD — carries all display commands and characters
pico:GP1 → lcd:SCLI2C clock line — synchronises data transfer at 400kHz
pico:3V3 → lcd:VCC3.3V power for the LCD and its PCF8574 backpack
pico:GND.8 → lcd:GNDGround for LCD circuit
dht22:VCC → pico:3V33.3V power for DHT22 sensor
dht22:GND → pico:GND.3Ground for DHT22
dht22:SDA → pico:GP15Single-wire data signal — sends temperature & humidity readings
r1: between DATA & VCC10kΩ pull-up keeps data line HIGH when idle — required for DHT22

MicroPython Files

Three files are needed. Create each one in Wokwi using + New File and paste the code.

main.py
main.py — MicroPython
"""
Temperature and Humidity Monitor — Raspberry Pi Pico
Hardware: DHT22 Sensor + 16x2 I2C LCD Display
Platform: Wokwi Simulator | MakeMindz Summer Class
"""

from machine import Pin, I2C
from time    import sleep
import dht
from lcd_api      import LcdApi
from pico_i2c_lcd import I2cLcd

# ── LCD Configuration ──
I2C_ADDR     = 0x27   # Default I2C address for most LCD backpacks
I2C_NUM_ROWS = 2
I2C_NUM_COLS = 16

# ── Pin Configuration ──
DHT_PIN     = 15      # DHT22 data pin → GPIO15
I2C_SDA_PIN = 0       # I2C SDA → GPIO0
I2C_SCL_PIN = 1       # I2C SCL → GPIO1

# ── Initialize I2C and LCD ──
i2c = I2C(0, sda=Pin(I2C_SDA_PIN), scl=Pin(I2C_SCL_PIN), freq=400000)
lcd = I2cLcd(i2c, I2C_ADDR, I2C_NUM_ROWS, I2C_NUM_COLS)

# ── Initialize DHT22 sensor ──
sensor = dht.DHT22(Pin(DHT_PIN))

# ── Startup splash screen ──
lcd.clear()
lcd.putstr("MakeMindz")
lcd.move_to(0, 1)
lcd.putstr("Temp & Humidity")
sleep(2)

print("Temperature and Humidity Monitor Started!")
print("=" * 40)

def celsius_to_fahrenheit(celsius):
    """Convert Celsius to Fahrenheit."""
    return (celsius * 9/5) + 32

def read_sensor():
    """Read temperature and humidity from DHT22."""
    try:
        sensor.measure()
        temp_c   = sensor.temperature()
        humidity = sensor.humidity()
        return temp_c, humidity
    except OSError as e:
        print(f"Failed to read sensor: {e}")
        return None, None

def display_readings(temp_c, humidity):
    """Display temperature and humidity on LCD and console."""
    if temp_c is not None and humidity is not None:
        temp_f = celsius_to_fahrenheit(temp_c)

        lcd.clear()
        lcd.putstr(f"Temp:{temp_c:.1f}C")     # Line 1
        lcd.move_to(0, 1)
        lcd.putstr(f"Humid:{humidity:.1f}%")   # Line 2

        print(f"Temperature: {temp_c:.1f}°C ({temp_f:.1f}°F)")
        print(f"Humidity: {humidity:.1f}%")
        print("-" * 40)
    else:
        lcd.clear()
        lcd.putstr("Sensor Error!")
        lcd.move_to(0, 1)
        lcd.putstr("Check Wiring")
        print("Error: Could not read sensor data")

# ── Main loop ──
while True:
    try:
        temperature, humidity = read_sensor()
        display_readings(temperature, humidity)
        sleep(2)

    except KeyboardInterrupt:
        print("\nProgram stopped by user")
        lcd.clear()
        lcd.putstr("Program Stopped")
        break

    except Exception as e:
        print(f"Error in main loop: {e}")
        lcd.clear()
        lcd.putstr("System Error!")
        sleep(2)
lcd_api.py
lcd_api.py — HD44780 Base Library
"""
LCD API Library for MicroPython
Provides base API for HD44780 compatible character LCDs.
"""

import time

class LcdApi:
    """Base class for HD44780 compatible LCD displays."""

    LCD_CLR            = 0x01
    LCD_HOME           = 0x02
    LCD_ENTRY_MODE     = 0x04
    LCD_ENTRY_INC      = 0x02
    LCD_ENTRY_SHIFT    = 0x01
    LCD_ON_CTRL        = 0x08
    LCD_ON_DISPLAY     = 0x04
    LCD_ON_CURSOR      = 0x02
    LCD_ON_BLINK       = 0x01
    LCD_MOVE           = 0x10
    LCD_MOVE_DISP      = 0x08
    LCD_MOVE_RIGHT     = 0x04
    LCD_FUNCTION       = 0x20
    LCD_FUNCTION_8BIT  = 0x10
    LCD_FUNCTION_2LINES= 0x08
    LCD_FUNCTION_10DOTS= 0x04
    LCD_CGRAM          = 0x40
    LCD_DDRAM          = 0x80
    LCD_RS_CMD         = 0
    LCD_RS_DATA        = 1
    LCD_RW_WRITE       = 0
    LCD_RW_READ        = 1

    def __init__(self, num_lines, num_columns):
        self.num_lines   = min(num_lines, 4)
        self.num_columns = min(num_columns, 40)
        self.cursor_x    = 0
        self.cursor_y    = 0
        self.backlight   = True
        self.display_off()
        self.backlight_on()
        self.clear()
        self.hal_write_command(self.LCD_ENTRY_MODE | self.LCD_ENTRY_INC)
        self.hide_cursor()
        self.display_on()

    def clear(self):
        self.hal_write_command(self.LCD_CLR)
        self.hal_write_command(self.LCD_HOME)
        self.cursor_x = 0
        self.cursor_y = 0

    def show_cursor(self):
        self.hal_write_command(self.LCD_ON_CTRL | self.LCD_ON_DISPLAY | self.LCD_ON_CURSOR)

    def hide_cursor(self):
        self.hal_write_command(self.LCD_ON_CTRL | self.LCD_ON_DISPLAY)

    def display_on(self):
        self.hal_write_command(self.LCD_ON_CTRL | self.LCD_ON_DISPLAY)

    def display_off(self):
        self.hal_write_command(self.LCD_ON_CTRL)

    def backlight_on(self):
        self.backlight = True
        self.hal_backlight_on()

    def backlight_off(self):
        self.backlight = False
        self.hal_backlight_off()

    def move_to(self, cursor_x, cursor_y):
        self.cursor_x = cursor_x
        self.cursor_y = cursor_y
        addr = cursor_x & 0x3f
        if cursor_y & 1: addr += 0x40
        if cursor_y & 2: addr += self.num_columns
        self.hal_write_command(self.LCD_DDRAM | addr)

    def putchar(self, char):
        if char == '\n':
            if self.cursor_y < self.num_lines - 1:
                self.cursor_y += 1
            self.cursor_x = 0
            self.move_to(self.cursor_x, self.cursor_y)
        else:
            self.hal_write_data(ord(char))
            self.cursor_x += 1
            if self.cursor_x >= self.num_columns:
                self.cursor_x = 0
                if self.cursor_y < self.num_lines - 1:
                    self.cursor_y += 1
                self.move_to(self.cursor_x, self.cursor_y)

    def putstr(self, string):
        for char in string:
            self.putchar(char)

    def hal_backlight_on(self):  pass
    def hal_backlight_off(self): pass
    def hal_write_command(self, cmd):  raise NotImplementedError
    def hal_write_data(self, data):    raise NotImplementedError
pico_i2c_lcd.py
pico_i2c_lcd.py — I2C Driver
"""
I2C LCD Driver for Raspberry Pi Pico (MicroPython)
Controls HD44780 LCD via PCF8574 I2C expander chip.
"""

import time
from lcd_api import LcdApi

# PCF8574 pin bit masks
MASK_RS       = 0x01   # P0 — Register Select
MASK_RW       = 0x02   # P1 — Read/Write (always 0 = write)
MASK_E        = 0x04   # P2 — Enable strobe
MASK_BACKLIGHT= 0x08   # P3 — Backlight control
SHIFT_DATA    = 4      # P4–P7 carry 4-bit data nibble

class I2cLcd(LcdApi):
    """HD44780 LCD via PCF8574 I2C backpack."""

    def __init__(self, i2c, i2c_addr, num_lines, num_columns):
        self.i2c      = i2c
        self.i2c_addr = i2c_addr
        self.i2c.writeto(self.i2c_addr, bytes([0]))
        time.sleep_ms(20)

        # 8-bit reset sequence (3×)
        self.hal_write_init_nibble(self.LCD_FUNCTION | self.LCD_FUNCTION_8BIT)
        time.sleep_ms(5)
        self.hal_write_init_nibble(self.LCD_FUNCTION | self.LCD_FUNCTION_8BIT)
        time.sleep_ms(1)
        self.hal_write_init_nibble(self.LCD_FUNCTION | self.LCD_FUNCTION_8BIT)
        time.sleep_ms(1)

        # Switch to 4-bit mode
        self.hal_write_init_nibble(self.LCD_FUNCTION)
        time.sleep_ms(1)

        LcdApi.__init__(self, num_lines, num_columns)
        cmd = self.LCD_FUNCTION
        if num_lines > 1:
            cmd |= self.LCD_FUNCTION_2LINES
        self.hal_write_command(cmd)

    def hal_write_init_nibble(self, nibble):
        byte = ((nibble >> 4) & 0x0f) << SHIFT_DATA
        self.i2c.writeto(self.i2c_addr, bytes([byte | MASK_E]))
        time.sleep_ms(1)
        self.i2c.writeto(self.i2c_addr, bytes([byte]))
        time.sleep_ms(1)

    def hal_backlight_on(self):
        self.i2c.writeto(self.i2c_addr, bytes([1 << 3]))

    def hal_backlight_off(self):
        self.i2c.writeto(self.i2c_addr, bytes([0]))

    def hal_write_command(self, cmd):
        byte = ((self.backlight << 3) |
                (((cmd >> 4) & 0x0f) << SHIFT_DATA))
        self.i2c.writeto(self.i2c_addr, bytes([byte | MASK_E]))
        time.sleep_ms(1)
        self.i2c.writeto(self.i2c_addr, bytes([byte]))
        byte = ((self.backlight << 3) |
                ((cmd & 0x0f) << SHIFT_DATA))
        self.i2c.writeto(self.i2c_addr, bytes([byte | MASK_E]))
        time.sleep_ms(1)
        self.i2c.writeto(self.i2c_addr, bytes([byte]))
        if cmd <= 3:
            time.sleep_ms(5)

    def hal_write_data(self, data):
        byte = (MASK_RS |
                (self.backlight << 3) |
                (((data >> 4) & 0x0f) << SHIFT_DATA))
        self.i2c.writeto(self.i2c_addr, bytes([byte | MASK_E]))
        time.sleep_ms(1)
        self.i2c.writeto(self.i2c_addr, bytes([byte]))
        byte = (MASK_RS |
                (self.backlight << 3) |
                ((data & 0x0f) << SHIFT_DATA))
        self.i2c.writeto(self.i2c_addr, bytes([byte | MASK_E]))
        time.sleep_ms(1)
        self.i2c.writeto(self.i2c_addr, bytes([byte]))

How It Works

Three systems work together: sensor reading, I2C LCD display, and error handling.

🌡️

DHT22 Single-Wire Protocol

The DHT22 uses a proprietary single-wire protocol. When the Pico pulls the data line LOW for ~18ms, the sensor responds with 40 bits of data — 16 bits temperature, 16 bits humidity, 8 bits checksum. MicroPython's built-in dht module handles all the timing automatically. You just call sensor.measure() then read sensor.temperature() and sensor.humidity().

📡

I2C LCD Communication

The LCD uses an I2C backpack module (PCF8574 chip) that translates 2-wire I2C into the 8-bit parallel interface of the HD44780 controller. The Pico sends data to address 0x27 at 400kHz. The pico_i2c_lcd.py driver handles the nibble-splitting (4-bit mode) and the Enable pulse toggling needed by the HD44780 protocol.

🛡️

Error Handling

The read_sensor() function wraps the DHT22 read in a try/except OSError block. If the sensor fails to respond (loose wire, timing issue), instead of crashing, it returns None, None. The display_readings() function checks for this and shows Sensor Error! / Check Wiring on the LCD — great practice for production embedded code.


What You'll Learn

Reading DHT22 sensor with MicroPython
I2C protocol — SDA/SCL communication
I2C LCD interfacing with PCF8574 backpack
Temperature conversion (°C to °F)
Exception handling in embedded Python
f-string data formatting for display
Multi-file MicroPython project structure
Wokwi simulation for hardware prototyping

Real-World Applications

🏠Home climate monitoring system
🌿Greenhouse temperature & humidity control
❄️Smart HVAC control system
💧Mold prevention humidity alert
🌤️IoT weather station with data logging
🏭Industrial environmental monitoring

Comments