Arduino Project-Based Learning Series
PART 3: Display & User Interface Projects
Introduction
This part introduces LCDs, keypads, and practical display-based projects used in real electronics and IoT systems.
What You Will Learn in PART 3
- LCD interfacing with Arduino
- I2C communication basics
- Displaying sensor and voltage data
- Keypad interfacing
- Creating simple menu systems
Required Components
- Arduino UNO / Nano
- 16×2 LCD (parallel or I2C)
- I2C LCD module
- 4×4 keypad
- Potentiometer (10kΩ)
- Resistors & jumper wires
- Breadboard
Project 9A: Digital Voltmeter Using Arduino
Project Objective
To measure and display DC voltage using Arduino.
Circuit Description
- Voltage divider → A0
- LCD → Arduino (I2C or parallel)
- Ensure max input ≤ 10V
![]() |
| LCD Parallel Communication |
Arduino Code (Parallel Communication)
#include <LiquidCrystal.h>
LiquidCrystal lcd(2, 3, 4, 5, 6, 7);
int voltPin = A0;
void setup()
{
lcd.begin(16, 2);
lcd.print("Digital Voltmeter");
}
void loop()
{
int value = analogRead(voltPin);
float voltage = value * (10 / 1023.0);
lcd.setCursor(0, 1);
lcd.print("Volt: ");
lcd.print(voltage);
lcd.print(" V");
delay(500);
}
![]() |
| LCD I2C Communication |
Arduino Code (I2C Communication)
#include <LiquidCrystal_I2C.h>
#include <Wire.h>
// Initialize the LCD with the I2C address 0x27 and a 16x2 display
LiquidCrystal_I2C lcd(0x27, 16, 2);
int voltPin = A0;
void setup()
{
lcd.init();
lcd.backlight();
// set cursor at (0,0) position
lcd.setCursor(0, 0);
// Print a message to the LCD
lcd.print("Hello, world!");
delay(5000);
lcd.clear();
lcd.setCursor(0, 0);
// Print a message to the LCD
lcd.print("Digital Voltmeter");
lcd.setCursor(0, 1);
}
void loop()
{
int value = analogRead(voltPin);
float voltage = value * (10 / 1023.0);
lcd.setCursor(0, 1);
lcd.print("Volt: ");
lcd.print(voltage);
lcd.print(" V");
delay(500);
}
#include <LiquidCrystal_I2C.h>
#include <Wire.h>
// Initialize the LCD with the I2C address 0x27 and a 16x2 display
LiquidCrystal_I2C lcd(0x27, 16, 2);
int voltPin = A0;
void setup()
{
lcd.init();
lcd.backlight();
// set cursor at (0,0) position
lcd.setCursor(0, 0);
// Print a message to the LCD
lcd.print("Hello, world!");
delay(5000);
lcd.clear();
lcd.setCursor(0, 0);
// Print a message to the LCD
lcd.print("Digital Voltmeter");
lcd.setCursor(0, 1);
}
void loop()
{
int value = analogRead(voltPin);
float voltage = value * (10 / 1023.0);
lcd.setCursor(0, 1);
lcd.print("Volt: ");
lcd.print(voltage);
lcd.print(" V");
delay(500);
}
Applications
- Power supply testing
- Battery monitoring
- Lab instruments
Project 10: Temperature Display on 16×2 LCD (I2C)
Project Objective
To display temperature data on an LCD using I2C communication.
Circuit Description
- TMP36 → A0
- LCD I2C → SDA, SCL
Arduino Code
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
LiquidCrystal_I2C lcd(0x27, 16, 2);
int tempPin = A0;
void setup() {
lcd.init();
lcd.backlight();
}
void loop() {
int value = analogRead(tempPin);
float temp = ((value * (5.0 / 1023.0))-0.5) * 100;
lcd.setCursor(0, 0);
lcd.print("Temperature:");
lcd.setCursor(0, 1);
lcd.print(temp);
lcd.print(" C");
delay(1000);
}
Working Principle
- I2C uses only two wires (SDA & SCL)
- Reduces pin usage
- Ideal for complex projects
Project 11: 4×4 Keypad Interfacing with Arduino
Project Objective
To read user input from a keypad.
Circuit Description
- Rows & columns connected to digital pins
- Uses keypad scanning technique
Arduino Code
#include <Keypad.h>
const byte rows = 4;
const byte cols = 4;
char keys[rows][cols] = {
{'1','2','3','A'},
{'4','5','6','B'},
{'7','8','9','C'},
{'*','0','#','D'}
};
byte rowPins[rows] = {9,8,7,6};
byte colPins[cols] = {5,4,3,2};
Keypad keypad = Keypad(makeKeymap(keys), rowPins, colPins, rows, cols);
void setup() {
Serial.begin(9600);
}
void loop() {
char key = keypad.getKey();
if (key) {
Serial.print("Key Pressed: ");
Serial.println(key);
}
}
Applications
- Password systems
- Menu navigation
- Access control
Project 12: Menu-Based System Using Keypad + LCD
Project Objective
To create a simple user-friendly menu system.
Circuit Description
We can change the temperature setting point on the keypad for high and low settings. Based on the temperature input, an alarm message will be displayed on the LCD. There are three LEDs for showing the current status. These indicators provide visual feedback to the user, allowing for quick assessment of the system's performance. Additionally, users can customize the alarm thresholds to suit their specific needs, enhancing overall functionality and user experience.
Working Principle
- Keypad changes menu options
- LCD displays selected option
- Logic based on
iforswitchcases
Sample Code:
#include <Keypad.h>
#include <Wire.h>
#include <LiquidCrystal_I2C.h> // Use LiquidCrystal_I2C.h for I2C version
// Define Keypad Layout
const byte ROWS = 4; // four rows
const byte COLS = 4; // four columns
char keys[ROWS][COLS] = {
{'1','2','3','A'},
{'4','5','6','B'},
{'7','8','9','C'},
{'*','0','#','D'}
};
byte rowPins[ROWS] = {9, 8, 7, 6}; // connect to the row pinouts of the keypad
byte colPins[COLS] = {5, 4, 3, 1}; // connect to the column pinouts of the keypad
Keypad customKeypad = Keypad(makeKeymap(keys), rowPins, colPins, ROWS, COLS);
// Initialize the LiquidCrystal library (adjust pins if not using standard setup)
LiquidCrystal_I2C lcd(0x27, 16, 2);
const int tempSensorPin = A0;
const int interruptPin = 2;
const int lowOutPin = 11;
const int highOutPin = 12;
const int normalOutPin = 13;
char customKey ;
// Menu state variable
int menuState = 0; // 0: Main Menu, 1: Sub Menu 1, 2: Sub Menu 2, etc.
int low = 30;
int high = 80;
int lastTemp = 1;
bool menuMode = false;
volatile bool menuRequested = false;
volatile unsigned long pressStart = 0;
String inputBuffer = "";
//..................................//
void keyHoldISR() {
if (digitalRead(interruptPin) == LOW) {
pressStart = millis();
} else {
if (millis() - pressStart >= 2000) {
menuRequested = true;
}
}
}
//................................//
void setup() {
pinMode(interruptPin, INPUT_PULLUP);
attachInterrupt(digitalPinToInterrupt(interruptPin), keyHoldISR, CHANGE);
lcd.init();
lcd.backlight();
lcd.print("Welcome...");
lcd.setCursor(0,1);
lcd.print("electrical-info");
delay(2000);
lcd.clear();
//displayMainMenu();
pinMode(lowOutPin, OUTPUT);
pinMode(highOutPin, OUTPUT);
pinMode(normalOutPin, OUTPUT);
}
void loop()
{
if (menuRequested) {
menuMode = true;
menuRequested = false;
lcd.clear();
lcd.print("Menu Loading...");
delay(1000);
lcd.clear();
displayMainMenu();
}
if (menuMode) //keyPAd
{
char customKey = customKeypad.getKey();
if (customKey)
{
if (menuState == 0)
{
// Main menu options
switch (customKey)
{
case '1':
menuState = 1;
displaySubMenu1();
break;
case '2':
menuState = 2;
displaySubMenu2();
break;
case '#': // Exit button
//menuState =3;
menuMode = false;
lastTemp = 1;
lcd.clear();
break;
}
}
else if (menuState == 1) //view setting
{
// Sub Menu 1 options
switch (customKey)
{
case '#': // back button
menuState = 0;
displayMainMenu();
break;
}
}
else if (menuState == 2) //velue setings
{
// Sub Menu 2 options
switch (customKey)
{
case '1': // High setting
menuState = 3;
displaySubMenu21();
break;
case '2': // Low setting
menuState = 4;
displaySubMenu22();
break;
case '#': // Exit/back button
menuState = 0;
displayMainMenu();
break;
}
}
else if (menuState == 3)
{
// Sub Menu 21(High set) options
if(customKey >= '0' && customKey <= '9')
{
inputBuffer += customKey;
lcd.setCursor(13,0);
//lcd.print("Value: ");
lcd.print(inputBuffer);
//lcd.print(" ");
}
switch (customKey)
{
case '*': // set temp
saveSetpoint();
menuState = 2;
displaySubMenu2();
break;
case '#': // Exit/back button
menuState = 2;
displaySubMenu2();
break;
}
}
else if (menuState == 4)
{
// Sub Menu 22(Low set) options
if(customKey >= '0' && customKey <= '9')
{
inputBuffer += customKey;
lcd.setCursor(13,0);
//lcd.print("Value: ");
lcd.print(inputBuffer);
//lcd.print(" ");
}
switch (customKey)
{
case '*': // set temp
saveSetpoint();
menuState = 2;
displaySubMenu2();
break;
case '#': // Exit/back button
menuState = 2;
displaySubMenu2();
break;
}
}
}
}
else
{
handleTemperature();
}
}
void displayMainMenu() {
//lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Main Menu:");
lcd.print("1.View");
lcd.setCursor(0, 1);
lcd.print("2.Setting #.Quit");
}
void displaySubMenu1() { //View set points
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("SubMenu1:");
lcd.print("#.back");
lcd.setCursor(0, 1);
lcd.print("low:");
lcd.print(low);
lcd.print(" High:");
lcd.print(high);
}
void displaySubMenu2() {
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("SubMenu2: ");
lcd.print("1.High");
lcd.setCursor(0, 1);
lcd.print(" 2.Low #.back");
}
void displaySubMenu21() {
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Set Hi Temp: ");
lcd.setCursor(0, 1);
lcd.print("*.Set #.back");
inputBuffer = "";
}
void displaySubMenu22() {
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Set Low Temp: ");
lcd.setCursor(0, 1);
lcd.print("*.Set #.back");
inputBuffer = "";
}
void saveSetpoint() {
if (inputBuffer.length() == 0) return;
float value = inputBuffer.toFloat();
if(value<=150)
{if (menuState == 3)
{
high = value;
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("High value saved");
delay(2000);
}
else if (menuState == 4 )
{
low = value;
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Low value saved");
delay(2000);
}
}
else
{
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Out of range");
lcd.setCursor(0, 1);
lcd.print("Put it again....");
delay(2000);
}
inputBuffer = "";
}
void handleTemperature()
{ int raw = analogRead(tempSensorPin);
float voltage = (raw * (5.0 / 1023.0))-0.5;
float temperature = voltage * 100.0;
if(abs(temperature - lastTemp)>=1)
{
String state;
digitalWrite(lowOutPin, LOW);
digitalWrite(highOutPin, LOW);
digitalWrite(normalOutPin, LOW);
if (temperature < low) {
digitalWrite(lowOutPin, HIGH);
state = "Low";
}
else if (temperature > high) {
digitalWrite(highOutPin, HIGH);
state = "High";
}
else {
digitalWrite(normalOutPin, HIGH);
state = "Normal";
}
lcd.setCursor(0,0);
lcd.print("Temp:");
lcd.print(temperature,1); // One digit aftr deciml
lcd.print((char)176); // degree symbol
lcd.print("C ");
lcd.setCursor(7,1);
lcd.print(" ");
lcd.setCursor(0,1);
lcd.print("State:");
lcd.print(state);
lastTemp = temperature;
}
delay(10);
}
Applications
- Industrial controllers
- Smart appliances
- Configuration panels
Common Errors & Troubleshooting
- Wrong I2C address
- LCD shows nothing (contrast issue)
- Keypad pins mismatch
- Missing libraries
Frequently Asked Questions (FAQ)
Q1: How to find I2C address of LCD?
👉 Use I2C scanner sketch.
Q2: Can I use Arduino Nano instead of UNO?
👉 Yes, fully compatible.
Q3: Which display is best for beginners?
👉 16×2 I2C LCD is the easiest.
Conclusion
In PART 3, you learned how to:
- Display data on LCD
- Build interactive systems
- Design user interfaces for Arduino projects
These skills are crucial for professional embedded systems and IoT dashboards.
Next Part
PART 4: Communication & Data Logging Projects
(Serial communication, SD card, RTC)

-min.png)
-min.png)


0 Comments