Preview build — page text is a draft awaiting human review. Code badges come from real compiles. The flasher is real but not yet hardware-tested; the Playground is still a preview.

Day 12 of 30 · about 36 minutes

I2C and the 16×2 LCD

Today you build: An LCD that greets you and counts the time

  • Read 5 min
  • Install the library 3 min
  • Wire the LCD 4 min
  • Upload & set contrast 6 min
  • Try it 18 min

Community draft — report a mistake

The Serial Monitor needs a laptop. A 16×2 LCD lets a project show information on its own. With an I2C backpack soldered on the back, it needs only four wires.

What is I2C?

I2C is a two-wire bus: SDA carries data, SCL carries the clock. Many devices can share the same two wires, because each has an address — a number from 0x08 to 0x77. The board calls an address and only that device answers.

On the Uno, SDA is A4 and SCL is A5 (they’re also repeated on the two pins next to AREF).

Wiring

LCD backpack Uno
GND GND
VCC 5V
SDA A4
SCL A5

Install the library

Tools → Manage Libraries… → search LiquidCrystal I2C by Frank de Brabander → Install.

How the sketch finds the LCD

Most backpacks answer at 0x27, some (with a PCF8574A chip) at 0x3F. The sketch knocks on both doors with Wire.beginTransmission() and uses whichever answers — then prints the address so you know it for later projects.

If neither answers, the I2C scanner checks every address.

Custom characters

Each character cell is 5 dots wide and 8 tall. createChar(0, smiley) stores your own drawing in slot 0 (there are 8 slots); write(byte(0)) prints it. Each B01010 row is one line of dots, 1 = on.

Blank screen or a row of boxes?

That’s almost always contrast. Turn the small blue trimmer on the backpack slowly with a screwdriver while the sketch runs. The LCD blank or blocks fix covers the rest.

Try it

  • Show day 10’s temperature on the LCD — day 15 builds exactly this, so try it yourself first.
  • Use (char)223 to print a ° sign — it’s built into most of these LCDs.
  • Turn the backlight off after 30 s with noBacklight() to save power.

You’re done when…

  • The Serial Monitor (9600 baud) prints LCD found at 0x27 (or 0x3F) — note it down for later projects.
  • The LCD shows Namaste! and a smiley on line 1, and Up 00:00:07 counting up on line 2.
  • After turning the contrast trimmer, the characters are crisp, with no row of solid boxes.

Today's code

Verified — core avr 1.8.8, LiquidCrystal I2C 1.1.2, 3 Nov 2026Compile-only
lesson-lcd.inoArduino C++Open in PlaygroundOpen in Playground
// Day 12 — I2C and the 16x2 LCD (Arduino Uno / Nano)
// LCD with a PCF8574 I2C backpack: GND → GND, VCC → 5V, SDA → A4, SCL → A5.
// The sketch first asks "who is on the bus?" at the two usual addresses, then uses the one that answers.
#include <Wire.h>
#include <LiquidCrystal_I2C.h>

LiquidCrystal_I2C* lcd = nullptr;

// A custom character: 8 rows of 5 dots each. 1 = dot on.
byte smiley[8] = {
  B00000,
  B01010,
  B01010,
  B00000,
  B10001,
  B01110,
  B00000,
  B00000,
};

bool answers(uint8_t address) {
  Wire.beginTransmission(address);
  return Wire.endTransmission() == 0;   // 0 = a device acknowledged
}

void setup() {
  Serial.begin(9600);
  Wire.begin();
  uint8_t address = 0;
  if (answers(0x27)) address = 0x27;        // most PCF8574T backpacks
  else if (answers(0x3F)) address = 0x3F;   // PCF8574AT backpacks
  if (address == 0) {
    Serial.println(F("No LCD at 0x27 or 0x3F - check SDA/SCL and power"));
    while (true) delay(1000);
  }
  Serial.print(F("LCD found at 0x"));
  Serial.println(address, HEX);

  lcd = new LiquidCrystal_I2C(address, 16, 2);
  lcd->init();
  lcd->backlight();
  lcd->createChar(0, smiley);   // store the drawing in slot 0
  lcd->setCursor(0, 0);
  lcd->print(F("Namaste! "));
  lcd->write(byte(0));          // print slot 0
  lcd->setCursor(0, 1);
  lcd->print(F("Up "));
}

void loop() {
  unsigned long s = millis() / 1000;
  char line[14];
  snprintf(line, sizeof line, "%02lu:%02lu:%02lu", s / 3600, (s / 60) % 60, s % 60);
  lcd->setCursor(3, 1);
  lcd->print(line);   // same length every time, so no leftover digits
  delay(250);
}

↑↓ to move · Enter to open