Built-in project · ESP32-DevKitC V4

MicroPython I2C LCD1602: hello and a counter, on an ESP32-DevKitC V4

MicroPython on an ESP32: an LCD1602 on an I2C backpack, SDA on GPIO 13 and SCL on 14, says hello and counts the seconds. The whole build, an ESP32-DevKitC V4 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.

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

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

The circuit runs here once the page has loaded.

What you'll learn

  • Scanning an I2C bus to find a device's address
  • How an I2C backpack drives an LCD in 4-bit mode
  • Splitting a script into a main file and a helper module
  • Formatting numbers into a string with %d
  • Difficulty: Intermediate
  • Time: About 15 minutes
  • Board: ESP32-DevKitC V4
  • Parts: 3

How it works

The LCD has a small I2C backpack, so it needs only four wires: power, ground, SDA on GPIO 13 and SCL on GPIO 14. The script opens the I2C bus at 400 kHz and scans it to find the display's address, usually 0x27.

A helper file, lcd1602.py, talks to the backpack. It sends every byte to the display as two 4-bit halves, because the backpack wires only four data lines.

The main loop writes Hello, world! on the top line and Counter: followed by a number on the bottom line. 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 $14; on Mokxi they're free.

Kits that can build it

These 2 starter kits have 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 SCLESP32-DevKitC V4 pin 14
Character LCD with I2C backpack pin SDAESP32-DevKitC V4 pin 13
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 · ESP32-DevKitC V4
# LCD1602 on I2C: SDA on GPIO 13, SCL on GPIO 14. Find the display's
# address, say hello on the top line and count on the bottom one.
from machine import I2C, Pin
from lcd1602 import LCD1602
import time

i2c = I2C(0, sda=Pin(13), scl=Pin(14), freq=400000)
found = i2c.scan()
print("I2C:", [hex(a) for a in found])     # 0x27 here; some modules are 0x3f
lcd = LCD1602(i2c, found[0])
count = 0

while True:
    lcd.show("Hello, world!", "Counter: %d" % count)
    print("counter", count)
    count += 1
    time.sleep(1)
lcd1602.py · ESP32-DevKitC V4
# 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 GPIO 13, SCL on GPIO 14, and the power and ground wires.
  • The LCD lights up but shows boxes or nothing. This project
    Some modules sit at 0x3f instead of 0x27. The script uses the first address the scan finds, so check the contrast dial on the backpack too.
  • SDA and SCL swapped. This project
    SDA goes to GPIO 13 and SCL to GPIO 14.
  • 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 board is not found, or upload fails with a connecting message. ESP32-DevKitC V4
    Use a USB cable that carries data, not charge only. Pick the ESP32 Dev Module board. If it still fails, hold BOOT while uploading starts, then release.
  • Some pins never read right, or the board will not boot with a wire attached. ESP32-DevKitC V4
    GPIO34 to 39 are input only and have no pull-ups. GPIO6 to 11 run the flash, so leave them alone. GPIO0, 2 and 12 are boot pins; avoid pulling them the wrong way at reset.

Change it and keep it

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