Learning Goals 5 min
- Initialise an HD44780 LCD using the right library — built-in
LiquidCrystalfor bare wiring, orLiquidCrystal_I2Cfor the backpack — and callbegin()+print()to display your first message. - Run an I²C scanner sketch once, to discover whether your backpack lives at address 0x27 or 0x3F (or something else) — and never have to guess again.
- Print a static two-line layout: project title on row 0, sub-text on row 1, using
setCursor(col, row)to switch lines.
Warm-Up 10 min
Yesterday: hardware. Today: code. With the LCD wired and the contrast adjusted, sending text to it is shockingly simple — three lines in setup() and one in loop() is the canonical Hello-World. The library hides everything about commands, addresses, busy-flags and timing. We just call lcd.print() and the words appear.
Pick your library
One of two libraries depending on yesterday's wiring choice:
- Bare 16-pin wiring →
LiquidCrystal(built-in, no install). - I²C backpack →
LiquidCrystal_I2C(Tools → Manage Libraries → search "LiquidCrystal I2C" → install Frank de Brabander's version).
We'll show both versions of each sketch below. Pick the one matching your wiring; ignore the other.
New Concept · LCD object, begin(), print() 25 min
Bare wiring — minimum-viable sketch
#include <LiquidCrystal.h>
// pins: RS, EN, D4, D5, D6, D7
LiquidCrystal lcd(12, 11, 5, 4, 3, 2);
void setup() {
lcd.begin(16, 2); // 16 columns, 2 rows
lcd.print("Hello, world!");
}
void loop() {
// nothing — display is static
}Three new ideas:
#include <LiquidCrystal.h>— pull in the library.LiquidCrystal lcd(...)— create an LCD object. The 6 pin numbers in the constructor must match how you wired RS, EN, D4–D7 yesterday.lcd.begin(16, 2)— tell the library this LCD has 16 columns and 2 rows. Done once in setup.lcd.print(...)— write text at the current cursor position. After printing, the cursor moves to the next column automatically.
I²C wiring — minimum-viable sketch
#include <LiquidCrystal_I2C.h>
LiquidCrystal_I2C lcd(0x27, 16, 2); // address, cols, rows
void setup() {
lcd.init();
lcd.backlight();
lcd.print("Hello, world!");
}
void loop() {
// nothing
}Differences from the bare version:
- Different library name and class.
- Constructor takes the I²C address (usually
0x27but sometimes0x3F). lcd.init()instead oflcd.begin()— same idea, different name.lcd.backlight()— explicitly turn the backlight on (some backpacks default to off).
The rest of the API (print, setCursor, etc.) is identical between the two libraries — most of the time you can paste code from one into the other and only the setup lines change.
The I²C address scanner
Before your I²C LCD will work you need to know its address. The classic short sketch:
#include <Wire.h>
void setup() {
Wire.begin();
Serial.begin(9600);
Serial.println("Scanning I2C bus...");
for (byte addr = 1; addr < 127; addr++) {
Wire.beginTransmission(addr);
if (Wire.endTransmission() == 0) {
Serial.print("Found device at 0x");
Serial.println(addr, HEX);
}
}
Serial.println("done.");
}
void loop() {}Upload it, open Serial Monitor at 9600. You should see one line: Found device at 0x27 (or 0x3F). That's the address to put in the constructor. Run this scanner once when you set up the LCD, write the address on a sticker, then go back to the LCD sketch.
setCursor(col, row) — putting text on row 1
After begin() the cursor starts at column 0, row 0. To print on the bottom row:
lcd.setCursor(0, 0);
lcd.print("Weather Station");
lcd.setCursor(0, 1);
lcd.print("v1.0 by Adam");The arguments are column first, row second — easy to swap by mistake. Column is 0–15; row is 0 (top) or 1 (bottom). On the 20×4 LCDs you may meet later, rows go 0–3 and columns 0–19; the same API works unchanged.
Worked Example · Project title + version 20 min
Step 1 — finish wiring (if bare)
Yesterday you wired power, contrast and backlight but skipped the 6 data pins. Wire them now:
- LCD pin 4 (RS) → Arduino D12.
- LCD pin 6 (EN) → Arduino D11.
- LCD pin 11 (D4) → Arduino D5.
- LCD pin 12 (D5) → Arduino D4.
- LCD pin 13 (D6) → Arduino D3.
- LCD pin 14 (D7) → Arduino D2.
Step 2 — scan (if I²C)
Upload the I²C scanner above. Note the address. If you don't see any device, check your SDA / SCL wires; if you see multiple, you have other I²C devices on the bus too — note them all but identify the LCD by trying each address in the next sketch.
Step 3 — the title-card sketch (bare version)
Save as title-card.ino:
#include <LiquidCrystal.h>
LiquidCrystal lcd(12, 11, 5, 4, 3, 2);
void setup() {
lcd.begin(16, 2);
lcd.setCursor(0, 0);
lcd.print("Weather Station");
lcd.setCursor(0, 1);
lcd.print("v1.0 by Adam");
}
void loop() {
// intentionally empty — title card is static
}Step 3 — the title-card sketch (I²C version)
#include <LiquidCrystal_I2C.h>
LiquidCrystal_I2C lcd(0x27, 16, 2); // replace 0x27 if scanner showed otherwise
void setup() {
lcd.init();
lcd.backlight();
lcd.setCursor(0, 0);
lcd.print("Weather Station");
lcd.setCursor(0, 1);
lcd.print("v1.0 by Adam");
}
void loop() {
}Step 4 — upload, watch the cold-boot
Upload. The LCD should:
- Show a brief flash of dark rectangles (the pre-init test pattern).
- Clear.
- Show your two-line title card crisply.
If you see scrambled text (e.g. half-characters, garbage), one of the data wires is wrong — recheck the order RS, EN, D4, D5, D6, D7. The library expects them in that order.
Step 5 — change the text, re-upload
Replace "Weather Station" with your own name; replace the version line with today's date. Re-upload. The new text appears immediately. Note: the OLD text doesn't disappear from the parts you didn't write over — if your new text is shorter than the old, ghost characters remain. We'll fix this with lcd.clear() tomorrow.
Try It Yourself 15 min
Goal: Display your name centred on the top row. The top row has 16 columns, so for a name like "Aisha" (5 chars), set the cursor to column 5 (the (16 − 5) / 2 sweet-spot).
Hint
String name = "Aisha";
int col = (16 - name.length()) / 2;
lcd.setCursor(col, 0);
lcd.print(name);Or just count by hand and write the literal column number — for a one-off, no need to compute it dynamically.
Goal: Print all 16 column positions on row 0 with the digit at each — "0123456789012345" — so you can visually count columns when designing layouts. Useful debug overlay.
Hint
lcd.setCursor(0, 0);
for (int i = 0; i < 16; i++) {
lcd.print(i % 10);
}The mod 10 gives a repeating 0–9 ruler. Now any layout you design can be sanity-checked against the ruler-strip in seconds.
Goal: Print a long string with lcd.print and observe what happens when the text is longer than 16 characters. Then look up lcd.autoscroll() and try it. (Spoiler: it makes long text scroll across the screen as you print, but only behaves well when you're streaming new characters slowly.)
Hint
lcd.setCursor(15, 0);
lcd.autoscroll();
for (int i = 0; i < 100; i++) {
lcd.print(i % 10);
delay(200);
}
lcd.noAutoscroll();The cursor stays in column 15; every new digit shifts the entire row left. You get a horizontal ticker. Useful for very long status messages, less useful for fixed layouts. Stop with noAutoscroll().
Mini-Challenge · Personal welcome screen 15 min
Design and code a 2-line welcome screen for your Arduino kit. It should feel like a boot screen — informative, attractive, finite. Requirements:
- The top row identifies the device (e.g.
Aisha's Toolkit, or a project name from L2 so far). - The bottom row shows a version + date (e.g.
v0.3 2026-05). - Centre at least one of the two lines.
- Use only characters that fit on a standard LCD — basic ASCII letters, numbers, space, and the punctuation
: - . / _. (Skip emoji for now; we'll learn custom characters in L02-30.) - The sketch should not flicker in
loop(). All printing happens insetup();loop()is empty.
It's done when:
- The screen is fully readable from across a normal classroom (3–4 m).
- The text doesn't shimmer, flicker, or rewrite itself.
- You can photograph it and the image clearly shows both lines.
- If you change the name or the version, only that one line of code changes — no shotgun edits.
Recap 5 min
The LiquidCrystal library makes the HD44780 trivial to talk to: include, construct, begin(), print(). The I²C version (LiquidCrystal_I2C) is almost identical — different constructor, init() + backlight() instead of begin(), but the same print and setCursor. Today's key insight: print static text in setup(), leave loop() empty. We didn't handle changing values yet — tomorrow we learn about clear() and cursor positioning so we can refresh just the parts that change without ghosting.
LiquidCrystallibrary- The built-in Arduino library for HD44780 character LCDs wired with parallel pins. No install needed.
LiquidCrystal_I2C- Third-party library for the I²C-backpack version of the same LCD. Install via Library Manager.
begin(cols, rows)/init()- One-shot initialisation in
setup(). Tells the library the panel dimensions and configures the chip. The bare version usesbegin(16, 2); the I²C version usesinit(). print(value)- Write text at the current cursor position. Accepts strings, numbers, characters — anything Serial.print accepts.
setCursor(col, row)- Move the cursor to column
col(0–15) and rowrow(0 or 1). Column first, row second — easy to swap. backlight()/noBacklight()- I²C library only. Turns the backlight on/off via the PCF8574. Some boards default to off until you call this.
- I²C scanner
- A short utility sketch that asks every possible I²C address (1–127) if anything responds. Used once to discover an unknown device's address.
- 0x27 / 0x3F
- The two most common addresses for an LCD I²C backpack. Hex notation; written in C++ as
0x27= 39 decimal. - Static display
- Content that doesn't change after boot — print once in setup, empty loop. Avoids flicker and saves I²C/parallel bandwidth.
Homework 5 min
Three title screens. Design and code 3 different welcome screens — one per project you've already built in L2:
- Weather Station (L02-20).
- Smart Bin Lid (L02-26).
- Personal Thermometer (L02-12).
For each, the LCD should display the project's name on row 0 and a one-line tagline on row 1 (e.g. Smart Bin Lid / Hands-free v1). Centre or align as you like.
Combine all three into one sketch using a button: pressing a momentary button on D8 cycles through the three screens. (Sneak peek of state machines on an LCD — we'll come back to button + screen interaction in L02-32.) Or, if button-handling isn't fresh, just submit three separate sketches.
Bring back next class:
- Your three title-screen designs (photos or text mockups).
- Your
hw-l02-28.inosketch (one with the button cycle, or three separate sketches). - Note which library you used (
LiquidCrystalorLiquidCrystal_I2C) and the address if I²C.