Sometimes you want to display measurements without using the serial monitor. In this tutorial we learn how to use an LCD for this purpose.
In this tutorial we will use an LCD to display voltage measurements done with the Arduino's analog-to-digital converter. For this purpose I used an LCD with 16x2 characters, which is commonly used together with the Arduino. But what is an LCD and how does it work?
LCD stands for liquid crystal display. So what are these magic liquid crystals? I won't go into detail on how they work as this would go beyond my knowledge too, but let's look into the basics. LCDs have a backlight in our case an LED backlight. In front of this LED are different materials to diffuse the light to get an even background illumination. The more important part is a polarization filter which only allows the electromagnetic waves of the emitted light to pass when they have the correct orientation. The liquid crystals act as a polarization filter as well, but they are special. By applying a voltage, it is possible to change the direction of polarization. If the direction of polarization matches the one of the polarization filter in the background, the light will pass. The pixel lights up. If it doesn't the pixel stays dark. As a result we are able to draw images or display characters by turning on the pixels we need to be on for them to be displayed.
Of course there are a lot of pixels even on a monochrome LCD. On our LCD we have 5x8 pixels per character. In total this makes 1280 pixels to control. It is not possible to do this with our Arduino. It is also too complicated to control each individual pixel, because of the huge amount of wires that would require. To solve this the pixels are grouped in rows and columns. They won't be controlled all at once, but instead updated row by row. It still nothing that we would want to do by ourselves. For this reason almost all LCDs have an integrated controller that takes care of refreshing the display content. In case of the LCD we use in this tutorial it is the HD44780 controller which is commonly used for alphanumeric dot matrix displays like ours. The controller uses a parallel interface, and we still need a lot of wires to send data to it. You will find how to do this in many other LCD tutorials. For this tutorial we use a second controller to interface the display via I2C. You find these adapter boards as I2C backpack from a lot of different traders. My model is shown in the picture below. It uses a PFC8754T chip to convert the 16 pins of the LCD to just 4 pins.
I2C is a simple serial bus for communication between different on circuit components. The abbreviation stands for Inter IC Communication (IIC). It is sometimes called two wire interface (TWI), as only two data lines are used, one for the clock signal (SCL
) and one for the actual data (SDA
). I2C allows multiple devices to be connected to the bus. In our case the Arduino is the master which drives the clock line and is responsible for initiating communications with all slave devices. To communicate with a specific device on the bus an address is used. For our LCD this address is 0x27
. If you are unsure about the address of your I2C backpack, you can use the example program under File
> Examples
> Wire
> i2c_scanner
to scan for devices on the bus and get their addresses in the serial monitor.
As we use an I2C adapter for our display the wiring is not that complex. The LCD needs 5V supply voltage and a connection to ground. As we need these as well for our potentiometer, I wired them to the breadboard. I then connected the LCD's VCC
and GND
wires to there. The data lines of the I2C bus are connected to the SDA
and SCL
pin of the Arduino Uno. The wiper of the potentiometer is connected to A0, which we will use to measure the voltage.
When looking up elsewhere, how to connect the LCD or other components via I2C to the Arduino Uno, you will also find drawings where the data lines are connected to pin A4
and A5
.
This is because the SDA
and SCL
connectors on the Arduino Uno board are not separate pins on the ATMega328P microcontroller. The SDA
connector is directly connected to A4
. Same for the SCL
which is directly connected to A5
.
It does not matter whether you connect the I2C component to A4
and A5
or SDA
and SCL
.
But be careful, you cannot use the SDA
and SCL
pins for I2C devices, and at the same time A4
and A5
as analog pins.
It is not easy to control a display and in our case we have two controllers in between. We need to send commands via I2C to the backpack. The data is then passed through to the display controller which accepts commands to change the display content. You don't want to write the code for this by yourself. In the last tutorial, I said that there are also preprogrammed libraries for receiving commands from IR remotes. The same holds true for a lot of LCDs. This time we will use one of these libraries.
To install a new library you have to open the library manager in the Arduino IDE.
The library manager shows a list of known Arduino libraries. You can search for a specific library. In our case we search for 'LCD I2C'. I picked the 'LiquidCrystal I2C' library for this tutorial.
How do we use this library? To use it we should, first look at what a library actually is.
A library contains functions and classes written by someone else which we can import and use in our program.
The library contains a so-called header file which declares all exported functions we can make use of.
This file is needed by the compiler to check if the function or class we use exists. You might remember that a function needs to be declared on top of the code that uses it. This is reason why we include the functions declared by the library at the beginning of our program. To do so, you can choose the library using the menu Sketch
> Include Library
. The required #include
directive is then created for you.
To find out which functions a library provides, you need to look into its documentation. It is usually found on the web page referred to by the library entry shown in the Library Manager.
Another possibility is look at the examples the library provides, these are added in the menu File
> Examples
. Please note, that not all libraries include examples.
If are more experienced in C++, you can also search for the header file on disk and look at the functions it exports. For this tutorial I give you all the relevant functions and classes we need.
By the way, what is a class? Classes were introduced with C++ and allow for object-oriented programming. A class bundles functions with data. The functions, which are also called methods, can use the data associated with the class instance. A class instance is also known as an object. In our case the address and size of the display are stored in the object and used internally to adjust the commands to the actual display and the I2C backpack. Don't worry if you do not know the concept of classes. There would be a lot more to say about them, but we don't want to write our own classes and libraries at this stage and most Arduino programmers never will. For us it is enough to know how to use them.
Our LCD library defines the class LiquidCrystal_I2C
. To create an instance we write down the class, the name of the object to create and the parameters needed to instantiate the class, after each other.
Here is how it looks like:
LiquidCrystal_I2C lcd(0x27, 16, 2);
This line creates the object lcd
by instantiating the class LiquidCrystal_I2C
with the address of our I2C backpack and the size of our LCD as parameter.
Once we created this object we can call it methods. I created a list with the ones we need for this tutorial. The syntax might look familiar to you. This is because Serial
is just an object too, it is defined by default, however.
Here are the methods we will use:
lcd.init()
lcd.clear()
lcd.backlight()
lcd.print(value)
value
: Text or number to displaylcd.setCursor(x, y)
x
: New cursor columny
: New cursor rowThat was a long introduction, now it's time to write the code. The code is pretty straight forward. In the setup
procedure we need to initialize the LCD and turn on the backlight. In the loop
procedure we measure the voltage at pin A0
, clear all old value on the LCD and print out the new value.
Here is the code:
#include <LiquidCrystal_I2C.h>
LiquidCrystal_I2C lcd(0x27, 16, 2);
void setup() {
lcd.init();
lcd.backlight();
}
void loop() {
int adc = analogRead(A0);
lcd.clear();
lcd.print("Voltage A0: ");
lcd.print(adc);
delay(500);
}
I put the text 'Voltage A0' in front of the measurement, this is not fully correct as I did not convert the measurement value to volts. If you want you can convert the measurement to millivolts in your code. Do you remember how this is possible? You find the answer in the tutorial on analog inputs.
You may have noticed that I put a delay at the end of the loop
procedure. This delay is needed to prevent the LCD content to be erased right after it is shown. The delay gives you a chance to read the measurement value. If you omit it the text will be barely readable.
You can see the result in the video below. The text and the measurement are shown on the display. There is a minor problem, however. You will notice that there is some flickering when the screen content is refreshed. This is because the LCD controller is not particularly fast and especially clearing the LCD takes a lot of time and redrawing the LCD contents is not very fast either. You notice the short amount of time before a full redraw of the LCD contents, as flickering. There is not much we can do about this. The screen will always take some time for a full redraw. The only solution is to only refresh the parts of the LCD content that change, instead of redrawing everything.
How can we implement a partial redrawing of the screen content? At first, we need to think about which parts of the LCD content needs to be updated. It's easy to figure out that only the measurement value changes. For this reason, we can print the text in front of it right in the setup
procedure.
To update the measurement value we can reposition the cursor and overwrite the old value without clearing the whole screen. There is a trap in doing so. If the number of displayed digits changes the old digits remain on screen and confuse the user. To overcome this problem the unused space can be overwritten with spaces which are not visible to the user. The number is filled up to always four characters using spaces. The code below shows how this can be done. If you want the number to be displayed right aligned you can also print out the spaces in front of the number.
#include <LiquidCrystal_I2C.h>
LiquidCrystal_I2C lcd(0x27, 16, 2);
void setup() {
lcd.init();
lcd.backlight();
lcd.print("Voltage A0:");
lcd.setCursor(12, 0);
}
void loop() {
int adc = analogRead(A0);
lcd.print(adc);
if(adc < 1000) lcd.print(" ");
if(adc < 100) lcd.print(" ");
if(adc < 10) lcd.print(" ");
lcd.setCursor(12, 0);
delay(500);
}
Once you uploaded this new code to the Arduino Uno, you will get a flicker free text on the LCD. If you want to you can also choose a smaller delay and measure more often. As the LCD is not cleared anymore, you will no longer get the problem of unreadable text at small delays.