Interfacing LCD 16×2 Display with Arduino

Introduction

In many Arduino projects, we use the Serial Monitor to debug code or display data. It’s very handy during the prototyping phase, but in the final phase of your project, and to keep the result clean, without a PC or USB cable attached, you need a small display connected to the Arduino to take over from the serial monitor.

One of the most popular displays for this is the 16×2 LCD (Liquid Crystal Display). It can show 16 characters per line, over 2 lines, which is enough to display text, numbers, sensor readings, menus, and simple animations. It’s cheap, easy to find, and very well supported by Arduino libraries.

16×2 LCD Display

In this tutorial, we’ll learn how the 16×2 LCD works, how to wire it to an Arduino, and through three practical examples, we’ll have a good understanding of how to display any type of information on it.

Working principle of liquid crystal displays

The name “Liquid Crystal Display” comes from the special material used to create the image, liquid crystals. These are strange substances that behave a bit like a liquid, they can flow, but their molecules still line up in an organized way, like a solid.

Each character on the screen is actually made of a small grid of tiny cells filled with these liquid crystals, sandwiched between two thin sheets of polarized glass.

Inside the LCD display

● Light comes from a backlight behind the screen, or from surrounding light if there’s no backlight.

● Light naturally vibrates in every direction. A polarizing filter only lets light through that vibrates in one specific direction, blocking the rest.

● Normally, the liquid crystal molecules twist the light as it passes through, allowing it to pass through the second polarizing filter as well.

● When we sends a tiny electric current to a specific cell, the liquid crystal molecules straighten up. This stops them from twisting the light, so the light gets blocked by the second filter, and the cell appears dark.

● By turning specific cells on and off inside each character block, the display can form any letter, number, or symbol you need. This happens instantly and repeatedly, which is why the screen can update its content in real time.

How the 16x2 LCD works

Inside every 16×2 LCD sits a small chip called the HD44780 controller. This chip already knows how to draw every letter, number, and symbol on the screen, it’s built into its memory as a set of predefined character patterns.
 
When the Arduino wants to display something, it doesn’t send an image or a drawing. Instead, it sends simple instructions and character codes to the controller, and the controller takes care of lighting up the correct pixels to form each character.
 
Communication between the Arduino and the LCD happens in two types of messages:
Commands : Used to set up the display, for example clearing the screen, moving the cursor, or turning the backlight on. These are recognized because the RS pin is set LOW.
Data (characters) : Used to actually print text on the screen. These are recognized because the RS pin is set HIGH.
 
Every time the Enable (E) pin receives a short pulse, the LCD reads whatever is currently present on the data pins, and either executes it as a command or prints it as a character, depending on the state of RS.

16x2 LCD display Pinout

The standard 16×2 LCD uses the HD44780 controller and comes with a 16-pin header on the back.
VSS is the ground pin, connect to Arduino GND
VDD is the power supply pin, connect to Arduino 5V
R0 is the contrast adjustment pin, usually connected to the wiper of a potentiometer
RS is the register Select, tells the LCD whether the incoming data is a command or text to display
RW is the Read/Write, selects reading or writing mode (usually just grounded, since we almost always write)
EN is the enable, triggers the LCD to actually read the data present on the data pins
DATA are the Data pins, used to send the actual character or command data
A is the backlight anode (+), connect to 5V through a small resistor if not already built in
K is the backlight cathode (–), connect to GND
Most projects use only 6 of the 8 data pins (D4 to D7), a mode called 4-bit mode, which saves several Arduino pins while working just as well as the full 8-bit mode.

Wiring the 16x02 LCD display to the Arduino

The LCD needs quite a few connections, but they’re all simple. Here’s how to wire it in 4-bit mode :
⦿ Connect VSS to Arduino GND.
⦿ Connect VDD to Arduino 5V.
⦿ Connect V0 to the middle pin of a 10kΩ potentiometer. Connect the two outer pins of the potentiometer to 5V and GND, this lets you adjust the screen contrast.
⦿ Connect RS to Arduino digital pin 12.
⦿ Connect RW directly to GND (we’re only writing, never reading).
⦿ Connect EN to Arduino digital pin 11.
⦿ Connect D4, D5, D6, D7 to Arduino digital pins 5, 4, 3, 2.
⦿ Connect A (backlight +) to 5V through a 220Ω resistor.
⦿ Connect K (backlight –) to GND.
⦿ Connect your Arduino to the computer via USB.

Example 1 : Reading temperature with LM35

This example shows how to read the LM35’s analog output and convert it into a temperature reading in degrees Celsius (°C) and degrees Fahrenheit (°F), displayed on the Serial Monitor. Keep the same wiring shown above.

const int sensorPin = A0;  // LM35 output connected to analog pin A0

void setup() {
  Serial.begin(9600);      // Start serial communication at 9600 baud
}

void loop() {
  int sensorValue = analogRead(sensorPin);           // Read raw ADC value (0-1023)
  float voltage = sensorValue * (5.0 / 1023.0);      // Convert ADC value to voltage
  float temperatureC = voltage * 100.0;              // Convert voltage to Celsius (10mV/°C)
  float temperatureF = (temperatureC * 9.0 / 5.0) + 32.0;  // Convert Celsius to Fahrenheit

  Serial.print("Temperature: ");
  Serial.print(temperatureC);
  Serial.print(" °C / ");
  Serial.print(temperatureF);
  Serial.println(" °F");

  delay(1000);  // Wait 1 second before next reading
}

Once uploaded, open the Serial Monitor (set to 9600 baud) to see the live readings :

The values update every second, reflecting real-time changes in the sensor’s surrounding temperature. Try holding the LM35 between your fingers or blowing warm air on it to see the readings rise.

Code explanation

const int sensorPin = A0; Defines the analog pin connected to the LM35's output. Using a named constant makes the code easier to read and modify later.
Serial.begin(9600); Initializes serial communication between the Arduino and your computer at a baud rate of 9600, allowing you to view readings in the Serial Monitor.
analogRead(sensorPin); Reads the analog voltage on pin A0 and converts it into a digital value between 0 and 1023 (the Arduino's ADC has 10-bit resolution).
voltage = sensorValue * (5.0 / 1023.0); Converts the raw ADC reading back into an actual voltage. Since the Arduino's ADC maps 0–5V to 0–1023, dividing by 1023 and multiplying by 5.0 gives the voltage read by the pin.
temperatureC = voltage * 100.0; Converts the voltage into temperature. Since the LM35 outputs 10mV (0.01V) per degree Celsius, multiplying the voltage by 100 gives the temperature directly in °C.
temperatureF = (temperatureC * 9.0 / 5.0) + 32.0; Converts the Celsius value into Fahrenheit using the standard conversion formula.
Serial.print() / Serial.println() Sends the formatted temperature readings (both °C and °F) to the Serial Monitor for real-time viewing.
delay(1000); Pauses for one second between readings, keeping the output readable and preventing the Serial Monitor from flooding with data.

Example 2 : Improving the Accuracy of readings with the Arduino's Internal ADC Reference

By default, an Arduino’s Analog-to-Digital Converter (ADC) compares incoming sensor voltage against the board’s main 5V power supply. Because the Arduino uses a 10-bit ADC, it chops that 5V range into 1024 equal digital steps .

At room temperature (25°C), the LM35 sensor only outputs 0.25V (equivalent to 51 digital steps out of 1024), and even at 100°C it barely reaches 1V (equivalent to 205 digital steps out of 1024). That means the ADC only covers a small slice of its full 5V range, and a lot of resolution just goes to waste.

5V reference

5% used
0V 5V

This is where the internal 1.1V reference of the Arduino helps. Instead of spreading those 1024 steps across 5V, the Arduino spreads them across just 1.1V, so each step becomes much smaller -> about 1.07mV. Using the same example, that same 0.25V reading now corresponds to roughly 234 steps instead of 51. Same temperature, much more precision.

1.1V reference

91% used
0V 1.1V
In this example we will keep the same wiring and samply add the following line in the setup section analogReference(INTERNAL); .

Warning : When you activate the internal reference in code, the Arduino internally connects that 1.1V source to the AREF (Analog Reference) pin on the board. Do not connect any external wires or voltages to the AREF pin. Doing so will cause a short circuit inside the microcontroller and can permanently destroy your Arduino.

const int sensorPin = A0;  // LM35 output connected to analog pin A0

void setup() {
  Serial.begin(9600);      // Start serial communication at 9600 baud
  analogReference(INTERNAL);  // Use internal 1.1V reference (Uno/Nano/Mini)
  delay(1000);                // Allow reference voltage to stabilize
}

void loop() {
  int sensorValue = analogRead(sensorPin);           // Read raw ADC value (0-1023)
  float voltage = sensorValue * (1.1 / 1023.0);      // Convert ADC value to voltage
  float temperatureC = voltage * 100.0;              // Convert voltage to Celsius (10mV/°C)
  float temperatureF = (temperatureC * 9.0 / 5.0) + 32.0;  // Convert Celsius to Fahrenheit

  Serial.print("Temperature: ");
  Serial.print(temperatureC);
  Serial.print(" °C / ");
  Serial.print(temperatureF);
  Serial.println(" °F");

  delay(1000);  // Wait 1 second before next reading
}

Code explanation

analogReference(INTERNAL); Switches the ADC's reference voltage from the default 5V to the chip's internal 1.1V reference.
delay(1000); in setup() Gives the internal reference voltage time to stabilize after switching, since the first few readings can be inaccurate right after changing the reference.
voltage = sensorValue * (1.1 / 1023.0); using 1.1V instead of 5V.
● The rest of the code Serial.begin , analogRead , Serial.print works the same way as in Example 1.

Example 3 : Making thermometer with LM35 and 16x2 I2C LCD Display

In this example, we read the temperature with the LM35 sensor, but instead of showing the readings on the Serial Monitor, we display them on a 16×2 I2C LCD. This means you can disconnect the PC after uploading the code and power the Arduino with a battery to create a standalone thermometer.

 

Update the connections as shown below.

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

LiquidCrystal_I2C lcd(0x27, 16, 2);  // I2C address 0x27, 16 columns, 2 rows
const int sensorPin = A0;             // LM35 output connected to analog pin A0

void setup() {
  analogReference(INTERNAL);  // Use internal 1.1V reference (Uno/Nano/Mini)
  delay(1000);                 // Allow reference voltage to stabilize

  lcd.init();          // Initialize the LCD
  lcd.backlight();      // Turn on the backlight
  lcd.setCursor(0, 0);
  lcd.print("LM35 Thermometer");
  delay(1500);
  lcd.clear();
}

void loop() {
  int sensorValue = analogRead(sensorPin);           // Read raw ADC value (0-1023)
  float voltage = sensorValue * (1.1 / 1023.0);      // Convert ADC value to voltage using 1.1V reference
  float temperatureC = voltage * 100.0;              // Convert voltage to Celsius (10mV/°C)

  lcd.setCursor(0, 0);
  lcd.print("Temp: ");
  lcd.print(temperatureC);
  lcd.print((char)223);  // Degree symbol
  lcd.print("C");

  delay(1000);
}

The LCD will briefly show “LM35 Thermometer” on startup, then continuously display the room temperature. The reading updates every second and holds steady thanks to the internal reference

Subscribe
Notify of
guest
0 Comments
Scroll to Top