ESP32-S3 Lab · Day 25 of 30

Put words
on a screen

Today the project gets a face. You wire the LCD1602's four pins to the board, add one library to the Arduino IDE, and upload a sketch that greets you on the top row while the bottom row counts the seconds. The screen rides on just two signal wires — a shared bus the board could hang many more devices off, each one called up by its own address.

About 25 minutesArduino firstMicroPython optionalFirst I2C device
Agent assist code TSK-DAY25-LCD

Hand this to an agent so it can pull the lesson packet and coach you step by step.

01 First, know the pieces

What you need

Five things, and only four wires between them. Tap Define on any part you haven't met — the answer opens as a field note you can read and dismiss without losing your place.

Official manual photo of the ESP32-S3 development board.
Manual photo

ESP32-S3 board

The brain that runs your uploaded sketch.

Official manual image of the ESP32-S3 GPIO extension board.
Manual photo

GPIO extension board

Spreads the pins into rows you can reach and label.

Official manual photo of the I2C LCD1602 module, a sixteen-by-two character display with a four-pin I2C backpack.
Manual photo

LCD1602 module

A sixteen-by-two character screen with an I2C backpack on its back.

Official manual image of a jumper wire.
Manual photo

4 × jumper wires (F/M)

Female ends slide onto the module's pins; male ends reach the board.

Official manual screenshot of the Arduino IDE interface.
Manual screenshot

Arduino IDE

Uploads the sketch to the board.

02 Make the physical circuit

Chart the circuit

The official Freenove diagram is your chart — schematic on top, the same circuit built on a breadboard below. Click it to enlarge. Four wires today, all from the little backpack board soldered to the back of the screen.

Official Freenove circuit — C Tutorial, Chapter 18 (LCD1602), page 165.
LCD GND GND Gives the module the same zero-volt reference as the board.
LCD VCC 5V The screen and its backlight run on the full five volts.
LCD SDA GPIO 14 The data wire — every character rides in on this line.
LCD SCL GPIO 13 The clock wire that keeps sender and screen in step.

Mind the 5V pin. The module's VCC wants the board's 5V pin — on 3.3V the display runs faint or dark. Unplug USB before you move any wire.

03 One action at a time

Build it

This is the main path — you can finish the day without opening a single field note. Tap each step as you go to keep your place.

0 / 8 done
  1. Seat the ESP32-S3 on the GPIO extension board and keep USB unplugged while you wire.

  2. Turn the LCD module over and find the four labelled pins on its backpack — GND, VCC, SDA, SCL.

  3. Slide female jumper ends onto GND and VCC, then land them on the board's GND and 5V pins.

  4. Wire SDA to GPIO 14 and SCL to GPIO 13.

  5. Compare every wire to the chart before you plug in USB.

  6. In Arduino IDE choose Sketch → Include Library → Add .ZIP Library… and pick LiquidCrystal_I2C-1.1.2.zip from the kit download's C/Libraries folder — you install it once and it stays.

  7. Open Sketch_18.1_Display_the_string_on_LCD1602.ino and upload it. (Wire.h, the I2C library, ships with the board package.)

  8. Watch row 0 greet you and row 1 start counting seconds.

The project has a face.

The screen is greeting you and counting on its own. Head to Test & debug to confirm both rows.

04 Read just enough code

Read the code

The sketch does its real work in setup — start the I2C bus, find the display's address, wake it, and print the greeting. The loop then rewrites row 1 once a second with the count. Switch to MicroPython if you'd rather see the same idea in Python — the wiring never changes.

Sketch_18.1_Display_the_string_on_LCD1602.ino
#include <LiquidCrystal_I2C.h>
#include <Wire.h>

#define SDA 14                    //Define SDA pins
#define SCL 13                    //Define SCL pins

/*
 * note:If lcd1602 uses PCF8574T, IIC's address is 0x27,
 *      or lcd1602 uses PCF8574AT, IIC's address is 0x3F.
*/
LiquidCrystal_I2C lcd(0x27,16,2);
void setup() {
  Wire.begin(SDA, SCL);           // attach the IIC pin
  if (!i2CAddrTest(0x27)) {
    lcd = LiquidCrystal_I2C(0x3F, 16, 2);
  }
  lcd.init();                     // LCD driver initialization
  lcd.backlight();                // Open the backlight
  lcd.setCursor(0,0);             // Move the cursor to row 0, column 0
  lcd.print("hello, world!");     // The print content is displayed on the LCD
}

void loop() {
  lcd.setCursor(0,1);             // Move the cursor to row 1, column 0
  lcd.print("Counter:");          // The count is displayed every second
  lcd.print(millis() / 1000);
  delay(1000);
}

bool i2CAddrTest(uint8_t addr) {
  Wire.beginTransmission(addr);
  if (Wire.endTransmission() == 0) {
    return true;
  }
  return false;
}
Wire.begin(SDA, SCL)Starts the I2C bus with GPIO 14 as the data line and GPIO 13 as the clock.
LiquidCrystal_I2C lcd(0x27,16,2)Names the display by its bus address — 16 columns, 2 rows. If nothing answers at 0x27, i2CAddrTest lets the sketch switch to 0x3F by itself.
lcd.setCursor(0,1)Parks the cursor at column 0 of row 1 so the next print lands on the bottom row.
lcd.print(millis() / 1000)Prints the seconds since the board booted — the running count you see tick.

05 Understand, don't memorise

One bus, two wires, devices by address

A bare LCD1602 has a row of pins that each carry one signal, and driving it directly means wiring about a dozen of them. The little backpack on the back trades that for two wires by putting the display on an I2C bus — the same two-wire scheme most screens and sensors use. Understanding the bus is worth more than today's circuit, because you'll meet it again and again.

1 · The problem

A dozen pins

A bare character LCD expects a fistful of parallel wires, one per signal. Wiring that by hand for every display would fill the board fast.

2 · The bus

Two shared wires

The backpack chip takes bytes over just two lines: SDA carries the data one bit at a time, and SCL is a clock that ticks so both ends agree when each bit counts.

3 · Who talks

Board asks, device answers

The board is the one that starts every exchange. Devices sit on the bus and listen, and reply only when they're spoken to — so many can share the same two wires without talking over each other.

4 · By name

The address

The board opens each message with a number — the device's address. This display answers to 0x27 (some boards to 0x3F). Send to the wrong number and the display never hears its name, so the screen stays blank.

5 · Placing text

Row and column

Once it's addressed and awake, the LCD holds a grid of sixteen columns by two rows. You set a cursor to a row and column, then print, and the characters land at that spot.

The model a dozen pins → two shared wires → the right address → cursor at row and column → characters

Another kind of serial

The bits travel single file down SDA the way they travel down USB to your Serial Monitor. The extra wire, SCL, is a shared clock that tells both ends exactly when to read each bit — which is what lets several devices share one pair of lines.

Why the address can be one of two

The backpack's PCF8574T chip answers at 0x27, while the PCF8574AT version answers at 0x3F. The sketch pings 0x27 first, and when nothing replies it switches to 0x3F on its own — a small demonstration that a wrong address means silence.

06 Know it worked

Test & debug

Nothing prints to the Serial Monitor today — the proof is written on the glass in front of you.

What you should see
LCD
  • Row 0 shows "hello, world!" as soon as the sketch starts.
  • Row 1 shows "Counter:" followed by the seconds since the board booted.
  • The number ticks up once a second for as long as the board has power.

A glowing backlight with blank rows is normal at first — the contrast dial on the back of the module sets whether characters show, and it often ships turned too far.

If it doesn't
  • Backlight on but no characters? Turn the small contrast dial on the back of the module slowly until the text appears.
  • Nothing at all — no glow? Check the VCC and GND pair first, then SDA on GPIO 14 and SCL on GPIO 13.
  • Compile fails on LiquidCrystal_I2C.h? The library step was skipped — add the kit's ZIP via Sketch → Include Library → Add .ZIP Library…
  • Counter frozen? Re-upload the sketch, then press the board's reset button.

07 Make the idea yours

Try this: drive the grid, then reason about the address

Same working circuit. The first two cards make the row-and-column model real by putting your own text exactly where you want it. The last one asks you to predict what the bus does when the address is wrong — no wiring change needed.

Name the boat

Change lcd.print("hello, world!") to your boat's name and upload. Row 0 is yours now — the greeting was only ever text you were free to replace.

Place it by column

Before that print, add lcd.setCursor(4,0) and upload again. The word now starts four columns in. Try other numbers from 0 to 15 and watch the text slide along the row — that pair is column then row, the display's whole coordinate system.

Predict a wrong address

The sketch tries 0x27, and if nothing answers it falls back to 0x3F. Ask yourself: if you forced the address to a number no device on your bus uses, what would the screen do — and why? Then look back at the backlight-on-but-blank case in Test & debug and see if your reasoning matches.

08 Learn it with a hand on the tiller

Coach me through it

Every lesson ships with a code and a machine-readable packet, so an agent can guide you with full context.

Lesson code

TSK-DAY25-LCD

How the agent should behave: guide one physical connection at a time and wait for confirmation, then teach the I2C bus behind the circuit — two shared wires, a clock, and each device reached by its own address. Always check wiring, board, port, and USB before changing code.

Keep your place

Finished Day 25?

Mark it complete — it shows on your course map, and your place is saved on this device.

Field note

Shortcut

Prompt copied