Skip to main content

16×2 I2C LCD Display Using Arduino Uno – Step-by-Step Guide (Beginner Friendly)

📟 Simulate Now
00

Introduction

Overview

In this project you'll learn how to connect a 16×2 LCD display with I²C module to an Arduino Uno. The I²C interface dramatically reduces wiring — from 12 wires down to just 4 — making it the preferred method for Arduino beginners and professionals alike.

Just 4 Wires

VCC, GND, SDA, SCL — that's all you need

📝

Text Display

Print any string, number, or variable value

🎨

Custom Chars

Create heart, smile, degree symbol and more

📜

Scrolling Text

Marquee-style scrolling messages

📡

Sensor Readout

Display live temperature, humidity, light

🕐

Digital Clock

Running HH:MM:SS clock using millis()

This tutorial is perfect for:

  • Beginners learning Arduino display output
  • School and robotics club projects
  • IoT sensor monitoring dashboards
  • Any project needing a human-readable output
💡

By the end of this guide you'll be able to display text, sensor values, custom icons, scrolling messages, and a working clock — all on a single LCD screen!

01

Why Use I²C Instead of Standard LCD?

Key Concept

A regular 16×2 LCD requires up to 12 connection wires. The I²C backpack module reduces this to just 4 — a massive simplification, especially for beginners.

❌ Standard LCD (No I²C)
  • Needs 6–12 data connections
  • Uses up most of Arduino's digital pins
  • Complex wiring — easy to make mistakes
  • No room left for sensors/actuators
  • Contrast requires a potentiometer
✅ I²C LCD Module
  • Only 4 wires: VCC, GND, SDA, SCL
  • Uses only 2 Arduino pins (A4 + A5)
  • Simple wiring — hard to get wrong
  • Plenty of pins left for your project
  • Built-in contrast adjustment via trim pot
💡

The I²C bus supports multiple devices on the same 2 wires. You can have an LCD, RTC module, and OLED all sharing SDA/SCL — each with a unique address like 0x27, 0x3C, 0x68.

02

Components Required

Step 1

You need just 2 components (plus wires). All are available as virtual parts in Wokwi — no shopping required!

#ComponentPurposeNotes
01Arduino UNOMain microcontrollerRuns all the code
0216×2 LCD with I²C moduleDisplay outputBackpack address: 0x27 or 0x3F
03Jumper wires × 4ConnectionsVCC, GND, SDA, SCL only
04USB cablePower + uploadWokwi handles this virtually
03

Wiring Guide — Only 4 Connections!

Step 2

This is the simplest wiring in all of Arduino LCD projects. Connect these 4 wires and you're done:

LCD I²C PinArduino PinWire ColourPurpose
VCC5VRedPower supply
GNDGNDBlackGround reference
SDAA4GreenI²C Data line
SCLA5BlueI²C Clock line
💡

On Arduino UNO, A4 and A5 are the dedicated hardware I²C pins. On Arduino Mega, use pins 20 (SDA) and 21 (SCL) instead.

⚠️

If your LCD shows only blocks (squares) and no text, the contrast needs adjusting. Turn the small blue trimmer potentiometer on the I²C backpack with a screwdriver until text appears.

04

Install the Library

Step 3
A

In Wokwi (Recommended — Instant!)

Create a file called libraries.txt by clicking the "+" icon next to the file tabs. Paste this single line:

libraries.txt
LiquidCrystal I2C
💡

Wokwi downloads this library automatically when the simulation starts!

B

In Arduino IDE (Physical Hardware)

  • Open Arduino IDE → Sketch → Include Library → Manage Libraries
  • Search for "LiquidCrystal I2C"
  • Install the library by Frank de Brabander
  • Also make sure Wire.h is available (it's built-in)
05

The Full Arduino Code — 6 Demo Examples

Step 4

The sketch automatically cycles through 6 different display examples every 5 seconds. Paste the full code into the sketch.ino tab in Wokwi.

Ex 1CounterCounts up every second
Ex 2Temperature°C and °F with degree symbol
Ex 3Custom CharsHeart, smile, arrow, bell icons
Ex 4ScrollingMarquee-style text
Ex 5Sensor DataLight level + humidity
Ex 6ClockRunning HH:MM:SS

① Libraries, Custom Characters & Variables

sketch.ino — Part 1/3
/*
 * 16x2 I2C LCD Display with Arduino
 * MakeMindz Summer Course | Wokwi Simulation
 * 6 Demo Examples: Counter, Temp, Custom Chars,
 *                  Scrolling, Sensor Data, Clock
 * I2C Address: 0x27 (or 0x3F on some modules)
 */

#include <Wire.h>               // I2C communication bus
#include <LiquidCrystal_I2C.h>  // LCD control library

// Initialize LCD: address 0x27, 16 columns, 2 rows
LiquidCrystal_I2C lcd(0x27, 16, 2);

// ── Custom character pixel bitmaps ────────────
// Each byte is one row of 5 pixels (right-aligned)
byte heart[8]   = { 0b00000,0b01010,0b11111,0b11111,0b11111,0b01110,0b00100,0b00000 };
byte smile[8]   = { 0b00000,0b01010,0b01010,0b00000,0b10001,0b01110,0b00000,0b00000 };
byte degree[8]  = { 0b00110,0b01001,0b01001,0b00110,0b00000,0b00000,0b00000,0b00000 };
byte arrow[8]   = { 0b00100,0b01110,0b11111,0b00100,0b00100,0b00100,0b00100,0b00000 };
byte bell[8]    = { 0b00100,0b01110,0b01110,0b01110,0b11111,0b00000,0b00100,0b00000 };

// ── State variables ──────────────────────────
int counter = 0;

② setup() — Initialise & Splash Screen

sketch.ino — Part 2/3
void setup() {
  Serial.begin(9600);

  lcd.init();          // Start the LCD
  lcd.backlight();     // Turn on the backlight

  // Register custom characters (slots 0–4)
  lcd.createChar(0, heart);
  lcd.createChar(1, smile);
  lcd.createChar(2, degree);
  lcd.createChar(3, arrow);
  lcd.createChar(4, bell);

  // Splash screen 1
  lcd.clear();
  lcd.setCursor(0, 0); lcd.print("I2C LCD Display");
  lcd.setCursor(0, 1); lcd.print("Initializing...");
  delay(2000);

  // Welcome message with custom chars
  lcd.clear();
  lcd.setCursor(0, 0); lcd.print("Arduino + LCD");
  lcd.setCursor(0, 1);
  lcd.write(0);                   // ♥ heart icon
  lcd.print(" Hello World! ");
  lcd.write(1);                   // ☺ smile icon
  delay(2000);

  Serial.println("LCD Initialized — I2C address 0x27");
}

void loop() {
  // Auto-cycle through 6 examples every 5 seconds
  int example = (millis() / 5000) % 6;

  switch(example) {
    case 0: displayCounter();     break;
    case 1: displayTemperature(); break;
    case 2: displayCustomChars(); break;
    case 3: displayScrolling();   break;
    case 4: displaySensorData();  break;
    case 5: displayClock();       break;
  }
  delay(100);
}

③ All 6 Display Functions

sketch.ino — Part 3/3
// ── Example 1: Counter (increments every second) ──
void displayCounter() {
  static unsigned long t = 0;
  if (millis() - t >= 1000) {
    t = millis(); counter++;
    lcd.clear();
    lcd.setCursor(0, 0); lcd.print("Counter Display");
    lcd.setCursor(0, 1); lcd.print("Count: "); lcd.print(counter);
  }
}

// ── Example 2: Temperature in °C and °F ──
void displayTemperature() {
  static unsigned long t = 0;
  static float temp = 25.5;
  if (millis() - t >= 1000) {
    t = millis();
    temp += random(-10, 11) / 10.0;          // Simulate reading
    temp = constrain(temp, 0, 50);
    lcd.clear();
    lcd.setCursor(0, 0); lcd.print("Temperature:");
    lcd.setCursor(0, 1);
    lcd.print(temp, 1); lcd.write(2); lcd.print("C  "); // °C
    lcd.print(temp * 9/5 + 32, 1); lcd.write(2); lcd.print("F");
  }
}

// ── Example 3: Custom Characters showcase ──
void displayCustomChars() {
  static unsigned long t = 0;
  if (millis() - t >= 1000) {
    t = millis();
    lcd.clear();
    lcd.setCursor(0, 0); lcd.print("Custom Chars:");
    lcd.setCursor(0, 1);
    lcd.write(0); lcd.print(" ");  // ♥ heart
    lcd.write(1); lcd.print(" ");  // ☺ smile
    lcd.write(2); lcd.print(" ");  // ° degree
    lcd.write(3); lcd.print(" ");  // ↑ arrow
    lcd.write(4); lcd.print(" Icons!"); // 🔔 bell
  }
}

// ── Example 4: Scrolling text marquee ──
void displayScrolling() {
  static int pos = 0;
  static unsigned long t = 0;
  String msg = "   Arduino 16x2 LCD Display - I2C Communication   ";
  if (millis() - t >= 300) {
    t = millis();
    lcd.clear();
    lcd.setCursor(0, 0); lcd.print("Scrolling Text:");
    lcd.setCursor(0, 1); lcd.print(msg.substring(pos, pos + 16));
    pos++;
    if (pos > msg.length() - 16) pos = 0;
  }
}

// ── Example 5: Simulated sensor readout ──
void displaySensorData() {
  static unsigned long t = 0;
  static int light = 512, hum = 65;
  if (millis() - t >= 1000) {
    t = millis();
    light = constrain(light + random(-50, 51), 0, 1023);
    hum   = constrain(hum   + random(-5,  6),  0, 100);
    lcd.clear();
    lcd.setCursor(0, 0); lcd.print("Light: "); lcd.print(light);
    lcd.setCursor(0, 1); lcd.print("Humidity: "); lcd.print(hum); lcd.print("%");
  }
}

// ── Example 6: Software clock (HH:MM:SS) ──
void displayClock() {
  static unsigned long t = 0;
  static int s = 0, m = 30, h = 12;
  if (millis() - t >= 1000) {
    t = millis(); s++;
    if (s >= 60) { s = 0; m++; }
    if (m >= 60) { m = 0; h++; }
    if (h >= 24) { h = 0; }
    lcd.clear();
    lcd.setCursor(2, 0); lcd.print("Digital Clock");
    lcd.setCursor(3, 1);
    if (h < 10) lcd.print("0"); lcd.print(h); lcd.print(":");
    if (m < 10) lcd.print("0"); lcd.print(m); lcd.print(":");
    if (s < 10) lcd.print("0"); lcd.print(s);
  }
}

④ diagram.json — Auto-Wiring for Wokwi

In Wokwi, click the diagram.json tab, delete all existing text, paste this entire block, then press Ctrl+S. The circuit assembles itself automatically.

diagram.json — Paste this into Wokwi
{
  "version": 1,
  "author": "16x2 I2C LCD Display",
  "editor": "wokwi",
  "parts": [
    {
      "type": "wokwi-arduino-uno",
      "id": "uno",
      "top": 0, "left": 0, "attrs": {}
    },
    {
      "type": "wokwi-lcd1602",
      "id": "lcd1",
      "top": -150.4, "left": -179.2,
      "attrs": { "pins": "i2c" }
    }
  ],
  "connections": [
    [ "lcd1:GND", "uno:GND.1", "black",  [ "v0" ] ],
    [ "lcd1:VCC", "uno:5V",    "red",    [ "v0" ] ],
    [ "lcd1:SDA", "uno:A4",    "green",  [ "v0" ] ],
    [ "lcd1:SCL", "uno:A5",    "blue",   [ "v0" ] ]
  ],
  "dependencies": {}
}
💡

The "pins": "i2c" attribute in the diagram.json tells Wokwi to use the I²C version of the LCD component — exposing only SDA, SCL, VCC, GND instead of all 16 parallel pins.

06

Run the Simulation

Step 5

Press Play — Watch the Splash Screen

Click the green ▶ Play button in Wokwi. The LCD should show:

  • "I2C LCD Display / Initializing..." for 2 seconds
  • "Arduino + LCD / ♥ Hello World! ☺" for 2 seconds
  • Then Example 1 (Counter) starts automatically
⚠️

If the LCD shows nothing but the backlight is on — check that your libraries.txt file exists with exactly LiquidCrystal I2C. Spelling matters!

Watch the Auto-Cycling Examples

Every 5 seconds the LCD switches to the next demo. Observe:

  • Counter incrementing every second
  • Temperature in °C and °F with degree symbol icon
  • Heart ♥ Smile ☺ Degree ° Arrow ↑ Bell 🔔 characters on one line
  • Text scrolling smoothly across the bottom row
  • Light level and humidity updating live
  • Clock ticking from 12:30:00 onwards
🔬

Try Modifying the Code

  • Change the I²C address from 0x27 to 0x3F if your physical LCD doesn't respond
  • Add Serial.println() statements and check the Serial Monitor tab
  • Change the example interval from 5000 to 2000 for faster switching
  • Design your own custom character using the 5×8 pixel grid
💡

Each custom character is an 8-byte array. Each byte controls one row of 5 pixels. A 1 bit = pixel ON, 0 = pixel OFF. You can store up to 8 custom characters (slots 0–7).

07

Frequently Asked Questions

FAQ
What is the I²C address of my LCD display?
Most common 16×2 LCD I²C modules use address 0x27. Some use 0x3F. If your LCD doesn't work, try changing the address in the code. On real hardware, you can use an I²C scanner sketch to detect the exact address of any connected device.
My LCD shows only black squares — what's wrong?
This is a contrast issue. Find the small blue potentiometer (trimmer) on the back of the I²C module and turn it slowly with a screwdriver until text appears. In Wokwi this never happens as contrast is set automatically.
Can I display sensor values on the LCD?
Yes! Use lcd.print(sensorValue); — it works for integers, floats, and strings. For example: lcd.print(dht.readTemperature(), 1); displays temperature to 1 decimal place. See Example 5 in the code for a full working demo.
Can I use multiple I²C devices at the same time?
Yes! That's the main advantage of I²C. You can connect an LCD (0x27), an RTC module (0x68), an OLED (0x3C), and more — all sharing the same SDA and SCL wires. Each device must have a unique address.
Is this project beginner-friendly?
Absolutely — it's one of the easiest Arduino display projects. With just 4 wires and the LiquidCrystal I2C library, you can be displaying text within 5 minutes. The I²C approach eliminates all the complex wiring of standard parallel LCD connections.
How do I create my own custom characters?
Each custom character is an 8-element byte array. Each byte represents a row of 5 pixels using binary: 0b10101 means pixels at positions 1, 3, 5 are ON. You can store up to 8 custom characters (0–7). Use online LCD character generators to design your pixels visually.
08

More MakeMindz Projects

Full Curriculum

This project is part of the MakeMindz structured curriculum. Follow the path below for a complete learning journey:

🧠 MakeMindz

Summer Course · 16×2 I²C LCD Display with Arduino Uno

Simulate free at wokwi.com · All projects at makemindz.com

Comments

try for free