Built-in project · Raspberry Pi Pico

MicroPython I2C LCD1602 on a Pico, GP14 and GP15

MicroPython on a Raspberry Pi Pico: an I2C LCD1602 on GP14 and GP15 shows a greeting on its first line and a counter that ticks every second. The whole build, a Raspberry Pi Pico and 1 more part, runs here in your browser on the firmware below; open it in the editor to change the wiring or the code and run it again.

Teaching? Assign it to your class in one click.

Beginner About 15 minutes to build and run. Runs in your browser. Free, and no account needed.

The parts retail for about $8. On Mokxi they're free.

The circuit runs here once the page has loaded.

What you'll learn

  • Wiring an I2C device to a Pico's SDA and SCL pins
  • Scanning the I2C bus to find an address
  • Splitting display code into a helper module
  • Showing a changing number on a 16x2 LCD
  • Difficulty: Beginner
  • Time: About 15 minutes
  • Board: Raspberry Pi Pico
  • Parts: 3

How it works

The LCD's I2C backpack needs only four wires: power, ground, SDA on GP14 and SCL on GP15. The script opens I2C bus 1 at 400 kHz and scans it to find the backpack's address.

A helper file, lcd1602.py, does the talking. It sends each byte to the display as two 4-bit halves, because the backpack connects only four data lines.

The main loop writes Hello, Pico! on the first line and count: followed by a number on the second. The number goes up by one each second and also prints to the console.

Parts list

3 parts, plus the jumper wires. They retail for about $8; on Mokxi they're free.

Kits that can build it

This starter kit has every part in the circuit. Each kit's page lists what else it can build.

Wiring table

Every connection, pin by pin, read from the circuit itself. Each row joins the pin on the left to the pin on the right, by a jumper wire or a breadboard strip.

Part and pinConnects to
Character LCD with I2C backpack pin SDARaspberry Pi Pico pin GP14
Character LCD with I2C backpack pin SCLRaspberry Pi Pico pin GP15
Character LCD with I2C backpack pin GNDGround
Character LCD with I2C backpack pin VCC5 V

The code

The MicroPython code exactly as the editor opens it. Change a line there and press Run: the board runs it on the official MicroPython firmware, with nothing to compile.

main.py · Raspberry Pi Pico
# An I2C LCD1602 on I2C bus 1: SDA on GP14, SCL on GP15. The scan finds
# the backpack's address; lcd1602.py, in the next tab, does the rest.
from machine import I2C, Pin
import time
from lcd1602 import LCD1602

i2c = I2C(1, sda=Pin(14), scl=Pin(15), freq=400000)
found = i2c.scan()
print("I2C:", [hex(a) for a in found])
lcd = LCD1602(i2c, found[0])

count = 0
while True:
    lcd.show("Hello, Pico!", "count: %d" % count)
    print("count", count)
    count += 1
    time.sleep(1)
lcd1602.py · Raspberry Pi Pico
# lcd1602.py: a 16x2 character LCD on a PCF8574 I2C backpack.
# The backpack's P0 is RS, P2 is E, P3 is the backlight and P4 to P7 are
# the display's D4 to D7, so every byte is sent as two 4-bit halves.
import time

class LCD1602:
    def __init__(self, i2c, addr=0x27):
        self.i2c = i2c
        self.addr = addr
        self.light = 0x08
        time.sleep_ms(50)
        for half in (0x30, 0x30, 0x30, 0x20):   # wake up, then 4-bit mode
            self._half(half, 0)
            time.sleep_ms(5)
        self.command(0x28)                      # two lines, 5x8 dots
        self.command(0x0C)                      # display on, no cursor
        self.command(0x06)                      # move right after a character
        self.clear()

    def _send(self, bits):
        self.i2c.writeto(self.addr, bytes([bits | self.light]))

    def _half(self, bits, rs):
        self._send(bits | rs | 0x04)            # E high...
        self._send(bits | rs)                   # ...and low: the LCD reads it

    def _byte(self, value, rs):
        self._half(value & 0xF0, rs)
        self._half((value << 4) & 0xF0, rs)

    def command(self, value):
        self._byte(value, 0)
        if value < 4:
            time.sleep_ms(2)                    # clear and home are slow

    def clear(self):
        self.command(0x01)

    def move_to(self, col, row):
        self.command(0x80 | (col + 0x40 * row))

    def write(self, text):
        for ch in text:
            self._byte(ord(ch), 1)

    def show(self, line1, line2=""):
        self.move_to(0, 0)
        self.write("%-16s" % line1[:16])
        self.move_to(0, 1)
        self.write("%-16s" % line2[:16])

Troubleshooting

The usual mistakes with this circuit, and the fix for each.

  • The script stops with an error right after the scan. This project
    The scan found nothing. Check SDA on GP14, SCL on GP15, and the power and ground wires.
  • The backlight is on but there is no text. This project
    Turn the contrast dial on the backpack.
  • The text does not change. This project
    Check that the script is still running. The count goes up once per second.
  • The screen stays blank or shows only a row of blocks. Character LCD with I2C backpack
    The I2C address is usually 0x27 or 0x3F. Run an I2C scanner to find yours. Then turn the contrast trimmer on the backpack slowly until text appears.
  • The backlight is on but there is no text, or text is garbled. Character LCD with I2C backpack
    Wire SDA and SCL to the right pins (A4 and A5 on an Uno), power it from 5 V, and call lcd.init() and the backlight call in setup.
  • The Pico does not show up as a drive, or the program will not load. Raspberry Pi Pico
    Hold the BOOTSEL button while you plug in USB. A drive named RPI-RP2 appears. Then copy the UF2 file onto it.
  • A 5 V signal or sensor is wired to a GPIO pin. Raspberry Pi Pico
    Pico GPIO pins are 3.3 V only and are not 5 V tolerant. Use a level shifter or a voltage divider for any 5 V output going into the board.

Change it and keep it

Open it in the editor, change the circuit or the code, and keep your version in a free account.