LCD weather station
A weather station is the friendliest way to use a sensor: a reading you can walk past and understand. This build shows the room's temperature and humidity on the top line of the LCD and the barometric pressure on the bottom line, refreshing every two seconds. A DHT22 measures temperature and humidity; a BME280 measures pressure — the one thing the DHT22 cannot do.
Every part is in the shop:
| Part | Price | Job |
|---|---|---|
| DHT22 | $5 | temperature and humidity |
| 10 kΩ resistor | $0.25 | the DHT22's mandatory data pull-up |
| BME280 | $8 | barometric pressure |
| LCD 16×2 | $9 | two lines of text |
The cheap version wires the DHT22 by hand; the fast version replaces it with the DHT22 module kit ($7), which has the pull-up already fitted. The BME280 and the LCD are hand-wired in both — no kit covers them, so the fast version's saving is the sensor half of the circuit.
The cheap version
Wire the 10 kΩ pull-up from 3V3 to the DHT22's data line first: it is the part everyone forgets, and without it every read fails. The BME280 and the LCD backpack are both I²C devices, so they share SDA/SCL and differ only in address (0x76 and 0x27).
from wiring import *
pi = Pi()
outside = DHT22()
weather = BME280()
lcd = LCD1602()
# DHT22: one data wire, plus the pull-up it needs to idle high
pi.V3_3 >> outside.vcc
pi.V3_3 >> Resistor(10000) >> outside.data
pi.GPIO4 >> outside.data
outside.gnd >> pi.GND
# BME280: I²C sensor at 0x76 (SDO tied low)
pi.V3_3 >> weather.vin
weather.gnd >> pi.GND
pi.SDA >> weather.sda
pi.SCL >> weather.scl
weather.sdo >> pi.GND
# LCD1602: I²C backpack at 0x27, 5 V for a readable backlight
pi.V5 >> lcd.vcc
lcd.gnd >> pi.GND
lcd.sda >> pi.SDA
lcd.scl >> pi.SCLimport board
import adafruit_bme280.basic as adafruit_bme280
from adafruit_dht import DHT22
from RPLCD.i2c import CharLCD
from time import sleep
lcd = CharLCD('PCF8574', 0x27, cols=16, rows=2)
outside = DHT22(4)
weather = adafruit_bme280.Adafruit_BME280_I2C(board.I2C(), address=0x76)
while True:
try:
text = f"{outside.temperature:5.1f}\xdfC {outside.humidity:4.1f}%"
except RuntimeError:
text = "DHT read failed"
pressure = f"{weather.pressure:.0f} hPa"
lcd.cursor_pos = (0, 0)
lcd.write_string(text.ljust(16))
lcd.cursor_pos = (1, 0)
lcd.write_string(pressure.ljust(16))
print(text.strip(), "|", pressure)
sleep(2)ljust(16) matters: write_string only overwrites the characters it writes, so a short line would leave the tail of the previous reading on screen.
The fast version
The DHT22 module kit is the same sensor with its pull-up on a three-pin board — three wires instead of four, and no way to forget the resistor. The BME280 and the LCD1602 stay hand-wired, because nothing in the kit range covers them; the code is identical, since the module behaves exactly like the bare sensor.
from wiring import *
pi = Pi()
outside = kits.DHT22Module()
weather = BME280()
lcd = LCD1602()
# the kit: sensor and pull-up in one, three wires to the Pi
pi.V3_3 >> outside.vcc
pi.GPIO4 >> outside.data
outside.gnd >> pi.GND
# still hand-wired: no kit covers the BME280 or the LCD
pi.V3_3 >> weather.vin
weather.gnd >> pi.GND
pi.SDA >> weather.sda
pi.SCL >> weather.scl
weather.sdo >> pi.GND
pi.V5 >> lcd.vcc
lcd.gnd >> pi.GND
lcd.sda >> pi.SDA
lcd.scl >> pi.SCLimport board
import adafruit_bme280.basic as adafruit_bme280
from adafruit_dht import DHT22
from RPLCD.i2c import CharLCD
from time import sleep
lcd = CharLCD('PCF8574', 0x27, cols=16, rows=2)
outside = DHT22(4)
weather = adafruit_bme280.Adafruit_BME280_I2C(board.I2C(), address=0x76)
while True:
try:
text = f"{outside.temperature:5.1f}\xdfC {outside.humidity:4.1f}%"
except RuntimeError:
text = "DHT read failed"
pressure = f"{weather.pressure:.0f} hPa"
lcd.cursor_pos = (0, 0)
lcd.write_string(text.ljust(16))
lcd.cursor_pos = (1, 0)
lcd.write_string(pressure.ljust(16))
print(text.strip(), "|", pressure)
sleep(2)How it works
DHT22(4)is told the GPIO the data wire landed on, not a part handle: the driver finds the sensor by talking its protocol on that pin. It reads temperature and humidity as one frame, and the same reading within two seconds is served from cache, sosleep(2)is the natural loop speed.Adafruit_BME280_I2C(board.I2C(), address=0x76)is the pressure half.board.I2C()is the Pi's bus object (GPIO2/GPIO3), and the address follows theSDOwire — moving it to 3V3 gives0x77. The driver applies Bosch's factory compensation and returnspressurein hPa.CharLCD('PCF8574', 0x27, ...)drives the LCD through its I²C backpack.cursor_pos = (row, column)puts the cursor at a line's start andwrite_stringwrites there; theljust(16)pads the text so every refresh overwrites the full width.\xdfis the degree sign in the panel's A00 character ROM — the usual Celsius trick, and the LCD page explains why. The string is at most 14 characters wide, so the longest reading still fits on one line.- A DHT22 frame can fail (checksum or timeout), and the driver raises
RuntimeErrorfor it; that is normal, so the read sits in atryand the display shows a short error instead of crashing. The BME280 uses I²C and does not have that failure mode.
Try it
Press Run in the Sandbox: the console prints a pair of lines every two seconds, and the LCD on the board shows the same values. Open the Inspector and drag the sensor zone's temperature, humidity or pressure — the next refresh picks the new value up. Leave it running a few minutes and you will eventually see DHT read failed for one frame: that is the sensor's modelled 5 % failure rate, not a wiring fault.
As a contest submission this becomes a display objective: the judge can hold the world at a fixed temperature and check the LCD text (a display check) or the console directly. Keep the loop polling inside the run window instead of blocking on input(), and remember the DHT22 needs its two seconds — a checker watching the top line will wait for the next refresh.
Variations
- Min/max since power-on: keep
lowandhighvariables in the loop and alternate the bottom line between pressure and the day's range. - A custom glyph:
lcd.create_char(0, ...)draws a thermometer in one of the eight CGRAM slots, then write\x00in front of the temperature. The RPLCD page has the pixel map. - Sea-level pressure: set
weather.sea_level_pressureto the reading and the driver corrects the altitude-induced drop — useful when the sensor is not at sea level, and a good reason to use the BME280 instead of a barometer module.
Going deeper
- Displays: LCD 16×2 and the RPLCD library.
- Sensors: DHT22 with adafruit_dht, and the BME280 over I²C.
- Kits: the DHT22 module, and the knob + ADC board if you want a setpoint knob next.
- Primers: I²C and the other buses and pull-up and pull-down resistors.
- How a run works: circuit.py → power-on → main.py.