Module 7 - Displays, Buses & Signals

20. I2C, Address Scanning & I2C LCD

Wire a 16x2 LCD with just SDA and SCL, find its I2C address with a scan, and print text and live values.

Estimated time 1 session + optional extrasIn class Week 7 · Session 31

Learning outcomes

  • Explain SDA, SCL, addresses and pull-ups on the Uno
  • Compare parallel LCD wiring with an I2C backpack
  • Scan or confirm a backpack address (often 0x27 or 0x3F)
  • Drive a 16x2 I2C LCD with LiquidCrystal I2C by Frank de Brabander

Parts and preparation

Uno, I2C LCD backpack/display, 10 k ohm potentiometer (the one from Lesson 14) and jumper wires. Install LiquidCrystal I2C by Frank de Brabander (see Libraries box).

Before power: inspect wiring, confirm supply voltage and ensure all connected circuits share GND.

Libraries for this lesson

In Arduino IDE 2 open Tools → Manage Libraries…. Search by the Library Manager name and install the package by the exact author below. Similar names from other authors can use different APIs and will break the example.

IncludeLibrary Manager nameAuthorInstall note
Wire.hWireArduinoBuilt into the Arduino core. No Library Manager install required.
LiquidCrystal_I2C.hLiquidCrystal I2CFrank de BrabanderInstall via Library Manager. Other LiquidCrystal_I2C packages use different APIs (init vs begin).

What I2C adds

I2C is a two-wire serial bus: SDA carries data and SCL carries a shared clock. On the Uno, SDA is A4 and SCL is A5.

Several devices can share those same two wires because each device has its own address. The Uno (master) calls a device by address; only that device answers.

Uno master connected to shared SDA and SCL lines with an LCD backpack at 0x27, an RTC at 0x68 and another device on the same bus
One SDA and one SCL for every device. Addresses keep them separate. Share GND.

Parallel LCD vs I2C backpack

Almost every 16x2 character LCD uses the same controller chip, the HD44780 (the standard character-LCD driver). Wired directly, it needs RS, E and four data lines - six Uno pins plus power and contrast. An I2C backpack sits on the LCD and talks over SDA/SCL instead, so the Uno only needs VCC, GND, SDA and SCL. (The optional Lesson 34 shows the six-pin wiring if you are curious.)

The character grid is still 16 columns by 2 rows. setCursor(column, row) works the same way.

Comparison of parallel LCD needing six signal pins versus I2C backpack needing only SDA and SCL plus power
Same display glass; I2C cuts the signal wiring to two pins.

Pull-ups

SDA and SCL are open-drain: devices only pull a line LOW. Pull-up resistors to 5 V hold the idle state HIGH. Most LCD backpacks already include those resistors.

Many modules on one bus can make the combined pull-up too strong. If the bus becomes unreliable with several devices, check module documentation and pull-up strategy.

Schematic showing pull-up resistors from 5 V to the SDA and SCL lines
Idle HIGH via Rpu; devices pull LOW. Backpacks usually include Rpu.
Optional extension - skip on your first pass through this lesson.

See I2C on the oscilloscope

You capture I2C on the scope in class in Lesson 21 - see its Capture it section. Try it here only if you have time.

Put channel 1 on SCL (A5) and channel 2 on SDA (A4), both at 2 V/div with the ground clip on GND. Trigger on a falling edge of SDA and set about 100 µs/div. Each time the sketch updates the display you will see a burst: SCL clocks at about 100 kHz (10 µs per bit), and SDA only changes while SCL is LOW - except at the start and stop conditions.

Both lines must idle at a clean 5 V. Slow, rounded rising edges mean weak pull-ups or long wires; a line stuck LOW means something is holding the bus. If your scope has serial decode, set it to I2C and read the address byte - 0x27 appears as the first byte of every transfer.

What you seeWhat it means
Both lines steady at 5 VBus idle - healthy
Bursts of square clock pulses on SCLThe Uno is talking to a device
Rounded, slow rising edgesPull-ups too weak or wiring too long
SDA or SCL stuck at 0 VShort, wrong wiring, or a device holding the line

Addresses and scanning

The constructor includes the backpack address, columns and rows: LiquidCrystal_I2C lcd(0x27, 16, 2). Common backpack addresses are 0x27 and 0x3F; solder pads on the backpack can change this.

If the screen stays blank, run the I2C scanner sketch further down this page, match the constructor to the reported address, and adjust the backpack contrast potentiometer.

Three cards showing constructor address, common 0x27 and 0x3F values, and blank-display checklist with scanner and contrast
Scan first if the display is blank. Then match the constructor address.
ItemCourse practice
LibraryLiquidCrystal I2C by Frank de Brabander
Init APIlcd.init() and lcd.backlight()
Default try0x27, then 0x3F if needed
Uno pinsSDA A4, SCL A5, shared GND

Same 16x2 coordinates

setCursor(column, row) is still zero-based: columns 0-15, rows 0-1. The worked sketch prints a heading on row 0, then updates an ADC field (the potentiometer on A0) on row 1. Pad with spaces when numbers get shorter so leftover digits do not remain.

16 by 2 character grid showing I2C connected on row 0 and ADC 512 on row 1 with setCursor 0,1 highlighted
Same grid as any HD44780 16x2 LCD. Highlighted cell is setCursor(0, 1).

Library variation

Several Library Manager packages use similar names. This course uses LiquidCrystal I2C by Frank de Brabander (init/backlight). A different author's package may expect begin() instead and will not compile or run with this sketch.

Wiring and safe build sequence

Breadboard wiring for 20. I2C, Address Scanning & I2C LCD
Breadboard layout for this lesson. Match colours and pins before powering the circuit. Click the image for a larger view.
  1. LCD VCC -> 5 V
  2. LCD GND -> GND
  3. LCD SDA -> A4
  4. LCD SCL -> A5
  5. 10 k ohm pot: outer legs -> 5 V and GND, middle leg (wiper) -> A0 - the same pot circuit as Lesson 14
Power rule: switch off before moving wires. Arduino I/O pins are control signals; high-current loads require a driver and suitable external supply.

I2C scanner: find the address

Download .ino

What this sketch is for: Run this first if you do not know your backpack's address, or whenever an I2C device stays silent. It asks every address once and prints the ones that answer in the Serial Monitor (9600 baud). Same wiring as below.

#include <Wire.h>

void setup() {
  Serial.begin(9600);
  Wire.begin();
  Serial.println("I2C addresses:");
  for (byte address = 1; address < 127; address++) {
    Wire.beginTransmission(address);
    if (Wire.endTransmission() == 0) {
      Serial.print("0x");
      if (address < 16) Serial.print('0');
      Serial.println(address, HEX);
    }
  }
}

void loop() {}

How the code works

  1. Wire.begin() starts the bus with the Uno as master.
  2. The for loop tries every valid 7-bit address (1-126) once in setup.
  3. endTransmission() returning 0 means a device answered (acknowledged) at that address.
  4. No addresses printed usually means wiring, power or SDA/SCL swapped.

Worked sketch

Download .ino
#include <Wire.h>
#include <LiquidCrystal_I2C.h>

LiquidCrystal_I2C lcd(0x27, 16, 2);

void setup() {
  Wire.begin();
  lcd.init();
  lcd.backlight();
  lcd.setCursor(0, 0);
  lcd.print("I2C connected");
}

void loop() {
  lcd.setCursor(0, 1);
  lcd.print("ADC ");
  lcd.print(analogRead(A0));
  lcd.print("    ");
  delay(250);
}

How the code works

  1. Change 0x27 only if the scanner sketch above reports another address.
  2. All devices on the bus share SDA, SCL and GND.
  3. analogRead(A0) reads the potentiometer (0-1023).
  4. Trailing spaces clear leftover ADC digits when the value gets shorter.

Test and record evidence

Expected result: The LCD shows a heading and a live A0 reading that changes as you turn the pot, using only two signal wires.

Practical evidence checklist

Common faults and checks
  • Run the I2C scanner sketch on this page if the display is blank.
  • Adjust the backpack contrast potentiometer.
  • Confirm the installed library's initialization API (init vs begin).
  • Check A4/A5 are not swapped and GND is common.
Challenge (extension - optional): Connect an RTC on the same bus and explain why no extra SDA/SCL pins are required.

Check your understanding

Q1. Why can devices share SDA and SCL?

Show answer

Each device responds to its own I2C address.

Q2. Which Uno pins are SDA and SCL?

Show answer

A4 is SDA and A5 is SCL.

Q3. What should you do if 0x27 shows nothing?

Show answer

Scan the bus, try the reported address, and adjust contrast.

Q4. Does I2C change the 16x2 cursor rules?

Show answer

No - columns are still 0-15 and rows 0-1.

Code Along 14: The I2C LCD Display

Practise in your browser

Write and test this lesson's code on a virtual Uno. Work through the 5 exercises in order. Press Test and each one checks itself.

Exercises passed 0 / 5About 75-100 minOpen as its own page

Four wires and a library

A bare 16x2 LCD needs six data pins. The little I2C backpack soldered to the back of yours replaces them with four wires: 5 V, GND, SDA (A4 on the Uno) and SCL (A5).

In code the display is an object you create once, above setup():

  • LiquidCrystal_I2C lcd(0x27, 16, 2); - address, columns, rows
  • lcd.init(); and lcd.backlight(); in setup() to wake it up
  • lcd.print(...) writes at the cursor, lcd.setCursor(column, row) moves it

The address is the backpack's identity on the I2C bus - usually 0x27, sometimes 0x3F. The board in this chapter answers at 0x27.

I2C LCD backpack wired to 5 V, GND, SDA on A4 and SCL on A5
Four wires: power, ground, SDA and SCL.
Worked example

Text on the screen

Press Run and watch the display on the virtual board. setCursor(0, 1) means column 0 of the second row - both count from 0.

#include <LiquidCrystal_I2C.h>

LiquidCrystal_I2C lcd(0x27, 16, 2);

void setup() {
  lcd.init();
  lcd.backlight();

  lcd.print("Wilteq Arduino");
  lcd.setCursor(0, 1);
  lcd.print("16x2 over I2C");
}

void loop() {
}

Turn on JavaScript to edit, run and test this code in the browser.

Exercise 14.1

Hello on the screen

Not started

Wake the display up and print Hello, Uno! on the top line.

Three lines in setup(): lcd.init();, lcd.backlight();, then the print.

#include <LiquidCrystal_I2C.h>

LiquidCrystal_I2C lcd(0x27, 16, 2);

void setup() {
  // Wake the display and print Hello, Uno!

}

void loop() {
}

Turn on JavaScript to edit, run and test this code in the browser.

Exercise 14.2

Both lines

Not started

Print Wilteq on the top line and Code Along on the bottom one. The cursor does not move down on its own - send it there with lcd.setCursor(0, 1).

#include <LiquidCrystal_I2C.h>

LiquidCrystal_I2C lcd(0x27, 16, 2);

void setup() {
  lcd.init();
  lcd.backlight();
  // Top line, then move the cursor and print the bottom line

}

void loop() {
}

Turn on JavaScript to edit, run and test this code in the browser.

Sixteen columns, and what falls off the edge

Each row shows 16 characters, numbered 0 to 15. Text longer than that does not continue on the next line - it runs off into memory you cannot see. That is the display behaving exactly as the real one does, and it surprises everybody once.

Characters also stay where they are until something overwrites them. Print 12, then print 9 in the same place, and the screen reads 92 - the old 2 is still there. Clear it with lcd.clear(), or print a space or two after the number.

16 by 2 character layout with columns numbered 0 to 15
Columns 0-15, rows 0-1. Anything past column 15 is off the screen.
Exercise 14.3

Put it in the right place

Not started

Print Temp: at the start of the top line, and 21 C starting at column 8 of the same line, so the display reads exactly as in the test.

#include <LiquidCrystal_I2C.h>

LiquidCrystal_I2C lcd(0x27, 16, 2);

void setup() {
  lcd.init();
  lcd.backlight();
  // "Temp:" at the start, "21 C" from column 8

}

void loop() {
}

Turn on JavaScript to edit, run and test this code in the browser.

Exercise 14.4

The counter that leaves crumbs

Not started

This countdown prints 11, 10, 9... in the same spot. Watch it run: when it reaches 9 the screen reads 90, because the old 0 was never cleared.

Fix it so each number appears on its own. Do not use lcd.clear() on every pass - it makes the display flicker on real hardware.

#include <LiquidCrystal_I2C.h>

LiquidCrystal_I2C lcd(0x27, 16, 2);
int count = 11;

void setup() {
  lcd.init();
  lcd.backlight();
}

void loop() {
  lcd.setCursor(0, 0);
  lcd.print(count);
  count = count - 1;
  delay(1000);
}

Turn on JavaScript to edit, run and test this code in the browser.

Exercise 14.5

A live reading

Not started

Show the potentiometer as a percentage on the bottom line, updating five times a second. The starter already reads the knob into percent (0 to 100), so only the display part is new.

The line must read exactly Level: then the number then % - for example Level: 50%. The test turns the knob from full, to half, to zero - and the display must not leave digits behind when the number gets shorter.

#include <LiquidCrystal_I2C.h>

LiquidCrystal_I2C lcd(0x27, 16, 2);
const int POT = A0;

void setup() {
  lcd.init();
  lcd.backlight();
  lcd.print("Wilteq meter");
}

void loop() {
  int percent = map(analogRead(POT), 0, 1023, 0, 100);
  // Show Level: and the percent on the bottom line

  delay(200);
}

Turn on JavaScript to edit, run and test this code in the browser.

Try it on a real Uno

Wire the backpack: VCC to 5 V, GND to GND, SDA to A4, SCL to A5. Install LiquidCrystal I2C in the Library Manager, then upload A live reading.

If the screen stays blank or shows only boxes, work through it in order: is the backlight on, is the contrast pot on the back turned up, and is the address right? Lesson 20's I2C scanner sketch prints the address of whatever is on the bus - run it once and write the answer on the module with a marker.

Practical Task B puts this display in front of an alarm, where a stale character is a marked fault. Exercises 16.5 to 16.8 in Assessment Practice rehearse it.

Want more? Code Along chapter 14 has a Deeper exercise that goes further than this lesson. It is optional.