A 16x2 LCD with an I2C backpack needs only four wires, but on a Pico W two of them need care: the backpack runs on 5 V and pulls the I2C lines up to 5 V. This guide powers it from VBUS and puts a BSS138 level shifter between the backpack and GP4/GP5.
MicroPython has no LCD driver built in, so the sketch includes a small one. The script prints “Hello, Pico W!”, then shows an uptime clock.
Wiring
| Raspberry Pi Pico W | 16×2 LCD + I2C backpack | Wire | Notes |
|---|---|---|---|
| GND | GND | black | |
| VBUS | VCC | red | VBUS (pin 40) is USB 5 V. At 3.3 V most of these LCDs show almost no contrast. |
| GP4 | SDA | blue | Via level shifter — GP4 (pin 6) → shifter LV1 ↔ HV1 → SDA. Shifter LV = 3V3, HV = VBUS, GND = GND. |
| GP5 | SCL | yellow | Via level shifter — GP5 (pin 7) → shifter LV2 ↔ HV2 → SCL. |
5 V (3.3 V versions exist) part, 3.3 V board. Most of these LCDs need 5 V for good contrast, and the backpack pulls SDA/SCL up to its own VCC. On ESP32 or Pico, put a BSS138 level shifter between the board and the backpack instead of wiring SDA/SCL directly.
Board quirks
- The backpack pulls SDA and SCL up to 5 V. Wired straight to a Pico, that 5 V sits on GP4/GP5 all the time — the BSS138 shifter keeps the Pico side at 3.3 V.
- Copy
lcd_i2c.py(a 60-line driver written for FlashReady, MIT) to the board next tomain.py. The same driver handles a 20x4 LCD:LcdI2c(i2c, addr, 20, 4). - The script scans the bus and picks 0x27 (PCF8574T) or 0x3F (PCF8574AT) automatically.
- Blank screen with the backlight on? Turn the blue contrast pot on the backpack before suspecting the code.
Code
# 16x2 I2C LCD (PCF8574 backpack) on a Raspberry Pi Pico W (MicroPython).
# The backpack runs on 5 V and pulls SDA/SCL up to 5 V, so they go through a BSS138 level shifter:
# LCD VCC -> VBUS (pin 40, USB 5 V), LCD GND -> GND
# LCD SDA -> shifter HV1 / LV1 -> GP4 (pin 6), LCD SCL -> shifter HV2 / LV2 -> GP5 (pin 7)
# Shifter LV -> 3V3 (pin 36), HV -> VBUS, GND -> GND.
# Copy lcd_i2c.py to the board too.
from machine import Pin, I2C
import time
from lcd_i2c import LcdI2c
i2c = I2C(0, sda=Pin(4), scl=Pin(5), freq=100_000)
found = i2c.scan()
print("I2C devices:", [hex(a) for a in found])
addr = 0x27 if 0x27 in found else (0x3F if 0x3F in found else None)
if addr is None:
raise SystemExit("No LCD backpack at 0x27/0x3F - check the level shifter and 5 V supply")
lcd = LcdI2c(i2c, addr, 16, 2)
lcd.line(0, "Hello, Pico W!")
lcd.line(1, "FlashReady.in")
print("LCD at", hex(addr))
time.sleep(2)
start = time.ticks_ms()
while True:
up = time.ticks_diff(time.ticks_ms(), start) // 1000
lcd.line(0, "Uptime")
lcd.line(1, "{:02d}:{:02d}:{:02d}".format(up // 3600, up // 60 % 60, up % 60))
print("Uptime", up, "s")
time.sleep(1)# 16x2 I2C LCD (PCF8574 backpack) on a Raspberry Pi Pico W (MicroPython).
# The backpack runs on 5 V and pulls SDA/SCL up to 5 V, so they go through a BSS138 level shifter:
# LCD VCC -> VBUS (pin 40, USB 5 V), LCD GND -> GND
# LCD SDA -> shifter HV1 / LV1 -> GP4 (pin 6), LCD SCL -> shifter HV2 / LV2 -> GP5 (pin 7)
# Shifter LV -> 3V3 (pin 36), HV -> VBUS, GND -> GND.
# Copy lcd_i2c.py to the board too.
from machine import Pin, I2C
import time
from lcd_i2c import LcdI2c
i2c = I2C(0, sda=Pin(4), scl=Pin(5), freq=100_000)
found = i2c.scan()
print("I2C devices:", [hex(a) for a in found])
addr = 0x27 if 0x27 in found else (0x3F if 0x3F in found else None)
if addr is None:
raise SystemExit("No LCD backpack at 0x27/0x3F - check the level shifter and 5 V supply")
lcd = LcdI2c(i2c, addr, 16, 2)
lcd.line(0, "Hello, Pico W!")
lcd.line(1, "FlashReady.in")
print("LCD at", hex(addr))
time.sleep(2)
start = time.ticks_ms()
while True:
up = time.ticks_diff(time.ticks_ms(), start) // 1000
lcd.line(0, "Uptime")
lcd.line(1, "{:02d}:{:02d}:{:02d}".format(up // 3600, up // 60 % 60, up % 60))
print("Uptime", up, "s")
time.sleep(1)Expected serial output
I2C devices: ['0x27']
LCD at 0x27
Uptime 0 s
Uptime 1 s
Uptime 2 sI2C devices: ['0x27'] LCD at 0x27 Uptime 0 s Uptime 1 s Uptime 2 s
Common errors
No LCD backpack at 0x27/0x3F
Check the shifter: LV to 3V3, HV to VBUS, both grounds connected, and SDA/SCL on matching channels. Run on USB — VBUS is dead on battery.
Row of solid blocks, no text
The LCD has power but missed the initialise sequence. Press Ctrl-D in Thonny to soft-reset so LcdI2c() runs again.
ImportError: no module named 'lcd_i2c'
Save lcd_i2c.py to the Pico (Thonny: File → Save copy → Raspberry Pi Pico).