Liquid crystal displays (LCDs) and LED displays offer a convenient and inexpensive way to provide a user interface for a project. This chapter explains how to connect and use common text and graphical LCD/LED panels with Arduino. By far the most popular LCD is the text panel based on the Hitachi HD44780 chip. This displays two or four lines of text, with 16 or 20 characters per line (32- and 40-character versions are available, but usually at higher prices). A library for driving text LCD displays is provided with Arduino, and you can print text on your LCD as easily as on the Serial Monitor (see Chapter 4), because LCD and serial share the same underlying print
functions.
LCDs can do more than display simple text: words can be scrolled or highlighted and you can display a selection of special symbols and non-English characters.
You can create your own symbols and block graphics with a text LCD, but if you want fine graphical detail, you need a graphical display. Graphical LCD (GLCD) and graphical LED displays are available at a small price premium over text displays.
Graphical displays can have more wires connecting to Arduino than most other recipes in this book. Incorrect connections are the major cause of problems with graphical displays, so take your time wiring things up and triple-check that things are connected correctly. An inexpensive multimeter capable of measuring voltage and resistance is a big help for verifying that your wiring is correct. It can save you a lot of head scratching if nothing is being displayed. You don’t need anything fancy, as even the cheapest multimeter will help you verify that the correct pins are connected and that the voltages are correct.
The Arduino software includes the LiquidCrystal library for driving LCD displays based on the HD44780 chip such as the SparkFun LCD-00255 or Adafruit part number 181.
Most text LCDs supplied for use with Arduino will be compatible with the Hitachi HD44780 controller. If you are not sure about your controller, check the datasheet to see if it is a 44780 or compatible. If your LCD has a backpack-style controller board on it, you may be able to interface with it over a serial protocol with far fewer wires. See Recipe 4.11 for more details.
To get the display working, you need to wire the power, data, and control pins. Connect the data and status lines to digital output pins, and wire up a contrast potentiometer and connect the power lines. If your display has a backlight, this needs connecting, usually through a resistor.
Figure 11-1 shows the most common LCD connections. It’s important to check the datasheet for your LCD to verify the pin connections. Table 11-1 shows the most common pin connections, but if your LCD uses different pins, make sure it is compatible with the Hitachi HD44780—this recipe will only work on LCD displays that are compatible with that chip. The LCD will have 16 pins (or 14 pins if there is no backlight)—make sure you identify pin 1 on your panel; it may be in a different position than shown in the figure.
Most problems with LCDs are due to bad connections. Double-check that the Arduino wires go to the correct LCD pins, as these could be positioned or numbered differently from what is shown in Figure 11-1. Also check that the wires or headers are properly soldered.
You may wonder why LCD pins 7 through 10 are not connected. The LCD display can be connected using either four pins or eight pins for data transfer. This recipe uses the four-pin mode because this frees up the other four Arduino pins for other uses. There is a theoretical performance improvement using eight pins, but it’s insignificant and not worth the loss of four Arduino pins.
LCD pin | Function | Arduino pin |
---|---|---|
1 |
GND or 0V or Vss |
GND |
2 |
+5V or Vdd |
5V |
3 |
Vo or contrast |
|
4 |
RS |
12 |
5 |
R/W |
GND |
6 |
E |
11 |
7 |
D0 |
|
8 |
D1 |
|
9 |
D2 |
|
10 |
D3 |
|
11 |
D4 |
5 |
12 |
D5 |
4 |
13 |
D6 |
3 |
14 |
D7 |
2 |
15 |
A or anode |
|
16 |
K or cathode |
You will need to connect a 10K potentiometer to provide the contrast voltage to LCD pin 3. Without the correct voltage on this pin, you may not see anything displayed. In Figure 11-1, one side of the pot connects to GND (ground), the other side connects to Arduino +5V, and the center of the pot goes to LCD pin 3. The LCD is powered by connecting GND and +5V from Arduino to LCD pins 1 and 2.
Many LCD panels have an internal lamp called a backlight to illuminate the display. Your datasheet should indicate whether there is a backlight and if it requires an external resistor—many do need this to prevent burning out the backlight LED assembly (if you are not sure, you can be safe by using a 220 ohm resistor). The backlight is polarized, so make sure pin 15 is connected to +5V and pin 16 to GND. (The resistor is shown connected between pin 16 and GND, but it can also be connected between pin 15 and +5V.)
Double-check the wiring before you apply power, as you can damage the LCD if you connect the power pins incorrectly. To run the HelloWorld sketch provided with Arduino, click the IDE Files menu item and navigate to Examples→Library→LiquidCrystal→HelloWorld.
The following code is modified slightly from the example. Change numRows
and numCols
to match the rows and columns in your LCD:
/*
* LiquidCrystal Library - Hello World
*
* Demonstrates the use of a 16 × 2 LCD display.
* https://www.arduino.cc/en/Tutorial/HelloWorld
*/
#include <LiquidCrystal.h>
// include the library code
//constants for the number of rows and columns in the LCD
const
int
numRows
=
2
;
const
int
numCols
=
16
;
// initialize the library with the numbers of the interface pins
LiquidCrystal
lcd
(
12
,
11
,
5
,
4
,
3
,
2
);
void
setup
()
{
lcd
.
begin
(
numCols
,
numRows
);
lcd
.
(
"Hello, World!"
);
// Print a message to the LCD.
}
void
loop
()
{
// set the cursor to column 0, line 1
// (row numbering starts with 0, so line 1 is the second row):
lcd
.
setCursor
(
0
,
1
);
// print the number of seconds since the sketch started:
lcd
.
(
millis
()
/
1000
);
}
Run the sketch; you should see “hello world” displayed on the first line of your LCD. The second line will display a number that increases by one every second.
If you don’t see any text and you have double-checked that all wires are connected correctly, you may need to adjust the contrast pot. With the pot shaft rotated to one side (usually the side connected to GND), you will have maximum contrast and should see blocks appear in all the character positions. With the pot rotated to the other extreme, you probably won’t see anything at all. The correct setting will depend on many factors, including viewing angle and temperature—turn the pot until you get the best-looking display.
If you can’t see blocks of pixels appear at any setting of the pot, check that the LCD is being driven on the correct pins.
Once you can see text on the screen, using the LCD in a sketch is easy. You use similar print commands to those for serial printing, covered in Chapter 4. The next recipe reviews the print commands and explains how to control text position.
See the LiquidCrystal reference.
See Chapter 4 for details on print
commands.
The datasheet for the Hitachi HD44780 LCD controller is the definitive reference for detailed, low-level functionality. The Arduino library insulates you from most of the complexity, but if you want to read about the raw capabilities of the chip, you can download the datasheet.
The LCD page in the Arduino Playground contains software and hardware tips and links.
This sketch displays a countdown from 9 to 0. It then displays a sequence of digits in three columns of four characters. Change numRows
and numCols
to match the rows and columns in your LCD:
/*
* LiquidCrystal Library - FormatText
*/
#include <LiquidCrystal.h>
// include the library code:
//constants for the number of rows and columns in the LCD
const
int
numRows
=
2
;
const
int
numCols
=
16
;
int
count
;
// initialize the library with the numbers of the interface pins
LiquidCrystal
lcd
(
12
,
11
,
5
,
4
,
3
,
2
);
void
setup
()
{
lcd
.
begin
(
numCols
,
numRows
);
lcd
.
(
"Starting in "
);
// this string is 12 characters long
for
(
int
i
=
9
;
i
>
0
;
i
--
)
// count down from 9
{
// the top line is row 0
lcd
.
setCursor
(
12
,
0
);
// move the cursor to the end of the string
lcd
.
(
i
);
delay
(
1000
);
}
}
void
loop
()
{
int
columnWidth
=
4
;
//spacing for the columns
int
displayColumns
=
3
;
//how many columns of numbers
lcd
.
clear
();
for
(
int
col
=
0
;
col
<
displayColumns
;
col
++
)
{
lcd
.
setCursor
(
col
*
columnWidth
,
0
);
count
=
count
+
1
;
lcd
.
(
count
);
}
delay
(
1000
);
}
The lcd.print
functions are similar to Serial.print
. In addition, the LCD library has commands that control the cursor location (the row and column where text will be printed).
The lcd.print
statement displays each new character after the previous one. Text printed beyond the end of a line may not be displayed or may be displayed on another line. The lcd.setCursor()
command enables you to specify where the next lcd.print
will start. You specify the column and row position (the top-left corner is 0,0). Once the cursor is positioned, the next lcd.print
will start from that point, and it will overwrite existing text. The sketch in this recipe’s Solution uses this to print numbers in fixed locations.
For example, in setup
:
lcd
.
setCursor
(
12
,
0
);
// move the cursor to the 13th position
lcd
.
(
i
);
lcd.setCursor(12,0)
ensures that each number is printed in the same position, the thirteenth column, first row, producing the digit shown at a fixed position, rather than each number being displayed after the previous number.
Rows and columns start from zero, so setCursor(4,0)
would set the cursor to the fifth column on the first row.
The following lines use setCursor
to space out the start of each column to provide columnwidth
spaces from the start of the previous column:
lcd
.
setCursor
(
col
*
columnWidth
,
0
);
count
=
count
+
1
;
lcd
.
(
count
);
lcd.clear
clears the screen and moves the cursor back to the top-left corner:
lcd
.
clear
();
Here is a variation on loop
that displays numbers using all the rows of your LCD. Replace your loop
code with the following (make sure you set numRows
and numCols
at the top of the sketch to match the rows and columns in your LCD):
void
loop
()
{
int
columnWidth
=
4
;
int
displayColumns
=
3
;
lcd
.
clear
();
for
(
int
row
=
0
;
row
<
numRows
;
row
++
)
{
for
(
int
col
=
0
;
col
<
displayColumns
;
col
++
)
{
lcd
.
setCursor
(
col
*
columnWidth
,
row
);
count
=
count
+
1
;
lcd
.
(
count
);
}
}
delay
(
1000
);
}
The first for
loop steps through the available rows, and the second for
loop steps through the columns.
To adjust how many numbers are displayed in a row to fit the LCD, calculate the displayColumns
value rather than setting it. Change:
int
displayColumns
=
3
;
to:
int
displayColumns
=
numCols
/
columnWidth
;
This sketch shows how you can cause the cursor (a flashing block at the position where the next character will be displayed) to blink. It also illustrates how to turn the display on and off; for example, to draw attention by blinking the entire display:
/*
* blink
*/
// include the library code:
#include <LiquidCrystal.h>
// initialize the library with the numbers of the interface pins
LiquidCrystal
lcd
(
12
,
11
,
5
,
4
,
3
,
2
);
void
setup
()
{
// set up the LCD's number of columns and rows and:
lcd
.
begin
(
16
,
2
);
// Print a message to the LCD.
lcd
.
(
"hello, world!"
);
}
void
loop
()
{
lcd
.
setCursor
(
0
,
1
);
lcd
.
(
"cursor blink"
);
lcd
.
blink
();
delay
(
2000
);
lcd
.
noBlink
();
lcd
.
(
" noBlink"
);
delay
(
2000
);
lcd
.
clear
();
lcd
.
(
"Display off ..."
);
delay
(
1000
);
lcd
.
noDisplay
();
delay
(
2000
);
lcd
.
display
();
// turn the display back on
lcd
.
setCursor
(
0
,
0
);
lcd
.
(
" display flash !"
);
displayBlink
(
2
,
250
);
// blink twice
displayBlink
(
2
,
500
);
// and again for twice as long
lcd
.
clear
();
}
void
displayBlink
(
int
blinks
,
int
duration
)
{
while
(
blinks
--
)
{
lcd
.
noDisplay
();
delay
(
duration
);
lcd
.
display
();
delay
(
duration
);
}
}
The sketch calls the blink
and noBlink
functions to toggle cursor blinking on and off.
The code to blink the entire display is in a function named displayBlink
that makes the display flash a specified number of times. The function uses lcd.display()
and lcd.
no
Display()
to turn the display text on and off (without clearing it from the screen’s internal memory).
This sketch demonstrates both lcd.ScrollDisplayLeft
and lcd.ScrollDisplayRight
.
It scrolls a line of text to the left when tilted and to the right when not tilted. Connect one side of a tilt sensor to pin 7 and the other pin to GND (see Recipe 6.2 if you are not familiar with tilt sensors):
/*
* Scroll
* this sketch scrolls text left when tilted
* text scrolls right when not tilted.
*/
#include <LiquidCrystal.h>
// initialize the library with the numbers of the interface pins
LiquidCrystal
lcd
(
12
,
11
,
5
,
4
,
3
,
2
);
const
int
numRows
=
2
;
const
int
numCols
=
16
;
const
int
tiltPin
=
7
;
// pin connected to tilt sensor
const
char
textString
[]
=
"tilt to scroll"
;
const
int
textLen
=
sizeof
(
textString
)
-
1
;
// the number of characters
bool
isTilted
=
false
;
void
setup
()
{
// set up the LCD's number of columns and rows:
lcd
.
begin
(
numCols
,
numRows
);
pinMode
(
tiltPin
,
INPUT_PULLUP
);
lcd
.
(
textString
);
}
void
loop
()
{
if
(
digitalRead
(
tiltPin
)
==
LOW
&&
isTilted
==
false
)
{
// here if tilted left so scroll text left
isTilted
=
true
;
for
(
int
position
=
0
;
position
<
textLen
;
position
++
)
{
lcd
.
scrollDisplayLeft
();
delay
(
150
);
}
}
if
(
digitalRead
(
tiltPin
)
==
HIGH
&&
isTilted
==
true
)
{
// here if previously tilted but now flat, so scroll text right
isTilted
=
false
;
for
(
int
position
=
0
;
position
<
textLen
;
position
++
)
{
lcd
.
scrollDisplayRight
();
delay
(
150
);
}
}
}
The first half of the loop
code handles the change from not tilted to tilted. The code checks to see if the tilt switch is closed (LOW
) or open (HIGH
). If it’s LOW
and the current state (stored in the isTilted
variable) is not tilted, the text is scrolled left. The delay in the for
loop controls the speed of the scroll; adjust the delay if the text moves too fast or too slow.
The second half of the code uses similar logic to handle the change from tilted to not tilted.
A scrolling capability is particularly useful when you need to display more text than can fit on an LCD line.
This sketch has a marquee
function that will scroll text up to 32 characters in length:
/*
* Marquee
* this sketch can scroll a very long line of text
*/
#include <LiquidCrystal.h>
LiquidCrystal
lcd
(
12
,
11
,
5
,
4
,
3
,
2
);
const
int
numRows
=
2
;
const
int
numCols
=
16
;
void
setup
()
{
// set up the LCD's number of columns and rows:
lcd
.
begin
(
numCols
,
numRows
);
marquee
(
"This is a very long string of text that will scroll"
);
}
void
loop
()
{
}
// this function uses scrolling to display a message up to 32 bytes long
void
marquee
(
char
*
text
)
{
lcd
.
(
text
);
delay
(
1000
);
for
(
int
position
=
0
;
position
<
strlen
(
text
)
-
numCols
;
position
++
)
{
lcd
.
scrollDisplayLeft
();
delay
(
300
);
}
}
The sketch uses the lcd.scrollDisplayLeft
function to scroll the display when the text is longer than the width of the screen.
The LCD chip has internal memory that stores the text. This memory is limited (32 bytes on most four-line displays). If you try to use longer messages, they may start to wrap over themselves. If you want to scroll longer messages (e.g., a tweet), or control scrolling more precisely, you need a different technique. The following function stores the text in RAM on Arduino and sends sections to the screen to create the scrolling effect. These messages can be any length that can fit into Arduino memory:
// this version of marquee uses manual scrolling for very long messages
void
marquee
(
char
*
text
)
{
int
length
=
strlen
(
text
);
// the number of characters in the text
if
(
length
<
numCols
)
lcd
.
(
text
);
else
{
int
pos
;
for
(
pos
=
0
;
pos
<
numCols
;
pos
++
)
lcd
.
(
text
[
pos
]);
delay
(
1000
);
// allow time to read the first line before scrolling
pos
=
1
;
while
(
pos
<=
length
-
numCols
)
{
lcd
.
setCursor
(
0
,
0
);
for
(
int
i
=
0
;
i
<
numCols
;
i
++
)
lcd
.
(
text
[
pos
+
i
]);
delay
(
300
);
pos
=
pos
+
1
;
}
}
}
Identify the character code you want to display by locating the symbol in the character pattern table in the LCD datasheet. This sketch prints some common symbols in setup
. It then shows all displayable symbols in loop
:
/*
* LiquidCrystal Library - Special Chars
*/
#include <LiquidCrystal.h>
//set constants for number of rows and columns to match your LCD
const
int
numRows
=
2
;
const
int
numCols
=
16
;
// defines for some useful symbols
const
byte
degreeSymbol
=
B11011111
;
const
byte
piSymbol
=
B11110111
;
const
byte
centsSymbol
=
B11101100
;
const
byte
sqrtSymbol
=
B11101000
;
const
byte
omegaSymbol
=
B11110100
;
// the symbol used for ohms
byte
charCode
=
32
;
// the first printable ascii character
int
col
;
int
row
;
// initialize the library with the numbers of the interface pins
LiquidCrystal
lcd
(
12
,
11
,
5
,
4
,
3
,
2
);
void
setup
()
{
lcd
.
begin
(
numRows
,
numCols
);
showSymbol
(
degreeSymbol
,
"degrees"
);
showSymbol
(
piSymbol
,
"pi"
);
showSymbol
(
centsSymbol
,
"cents"
);
showSymbol
(
sqrtSymbol
,
"sqrt"
);
showSymbol
(
omegaSymbol
,
"ohms"
);
lcd
.
clear
();
}
void
loop
()
{
lcd
.
write
(
charCode
);
calculatePosition
();
if
(
charCode
==
255
)
{
// finished all characters so wait another few seconds and start over
delay
(
2000
);
lcd
.
clear
();
row
=
col
=
0
;
charCode
=
32
;
}
charCode
=
charCode
+
1
;
}
void
calculatePosition
()
{
col
=
col
+
1
;
if
(
col
==
numCols
)
{
col
=
0
;
row
=
row
+
1
;
if
(
row
==
numRows
)
{
row
=
0
;
delay
(
2000
);
// pause
lcd
.
clear
();
}
lcd
.
setCursor
(
col
,
row
);
}
}
// function to display a symbol and its description
void
showSymbol
(
byte
symbol
,
char
*
description
)
{
lcd
.
clear
();
lcd
.
write
(
symbol
);
lcd
.
(
' '
);
// add a space before the description
lcd
.
(
description
);
delay
(
3000
);
}
The datasheet for the LCD controller chip contains a table showing the available character patterns.
To use the table, locate the symbol you want to display. The code for that character is determined by combining the binary values for the column and row for the desired symbol (see Figure 11-2).
For example, the degree symbol (°) is the third-from-last entry at the bottom row of the table shown in Figure 11-2. Its column indicates the upper four bits are 1101 and its row indicates the lower four bits are 1111. Combining these gives the code for this symbol: B11011111. You can use this binary value or convert this to its hex value (0xDF) or decimal value (223). Note that Figure 11-2 shows only four of the 16 actual rows in the datasheet.
The LCD screen can also show any of the ASCII characters listed in the datasheet by passing the ASCII value to lcd.write
.
The sketch uses a function named showSymbol
to print the symbol and its description:
void
showSymbol
(
byte
symbol
,
char
*
description
)
(See Recipe 2.6 if you need a refresher on using character strings and passing them to functions.)
The datasheet for the Hitachi HD44780 display
Uploading the following code will create an animation of a face, switching between smiling and frowning:
/*
* custom_char sketch
* creates an animated face using custom characters
*/
#include <LiquidCrystal.h>
LiquidCrystal
lcd
(
12
,
11
,
5
,
4
,
3
,
2
);
byte
happy
[
8
]
=
{
B00000
,
B10001
,
B00000
,
B00000
,
B10001
,
B01110
,
B00000
,
B00000
};
byte
saddy
[
8
]
=
{
B00000
,
B10001
,
B00000
,
B00000
,
B01110
,
B10001
,
B00000
,
B00000
};
void
setup
()
{
lcd
.
createChar
(
0
,
happy
);
lcd
.
createChar
(
1
,
saddy
);
lcd
.
begin
(
16
,
2
);
}
void
loop
()
{
for
(
int
i
=
0
;
i
<
2
;
i
++
)
{
lcd
.
setCursor
(
0
,
0
);
lcd
.
write
(
i
);
delay
(
500
);
}
}
The LiquidCrystal library enables you to create up to eight custom characters, which can be printed as character codes 0 through 8. Each character on the screen is drawn on a grid of 5 × 8 pixels. To define a character, you need to create an array of eight bytes. Each byte defines one of the rows in the character. When written as a binary number, the 1 indicates a pixel is on, 0 is off (any values after the fifth bit are ignored). The sketch example creates two characters, named happy
and saddy
(see Figure 11-3).
The following line in setup
creates the character using data defined in the happy
array that is assigned to character 0:
lcd
.
createChar
(
0
,
happy
);
To print the custom character to the screen you would use this line:
lcd
.
write
(
0
);
Code in the for
loop switches between character 0 and character 1 to produce an animation.
Note the difference between writing a character with or without an apostrophe. The following will print a zero, not the happy symbol:
lcd
.
write
(
'0'
);
// this prints a zero
The following sketch writes double-height numbers using custom characters:
/*
* customChars
* This sketch displays double-height digits
* the bigDigit arrays were inspired by Arduino forum member dcb
*/
#include <LiquidCrystal.h>
LiquidCrystal
lcd
(
12
,
11
,
5
,
4
,
3
,
2
);
byte
glyphs
[
5
][
8
]
=
{
{
B11111
,
B11111
,
B00000
,
B00000
,
B00000
,
B00000
,
B00000
,
B00000
},
{
B00000
,
B00000
,
B00000
,
B00000
,
B00000
,
B00000
,
B11111
,
B11111
},
{
B11111
,
B11111
,
B00000
,
B00000
,
B00000
,
B00000
,
B11111
,
B11111
},
{
B11111
,
B11111
,
B11111
,
B11111
,
B11111
,
B11111
,
B11111
,
B11111
}
,
{
B00000
,
B00000
,
B00000
,
B00000
,
B00000
,
B01110
,
B01110
,
B01110
}
};
const
int
digitWidth
=
3
;
// the width in characters of a big digit
// (excludes space between characters)
//arrays to index into custom characters that will comprise the big numbers
// digits 0 - 4 0 1 2 3 4
const
char
bigDigitsTop
[
10
][
digitWidth
]
=
{
3
,
0
,
3
,
0
,
3
,
32
,
2
,
2
,
3
,
0
,
2
,
3
,
3
,
1
,
3
,
// digits 5-9 5 6 7 8 9
3
,
2
,
2
,
3
,
2
,
2
,
0
,
0
,
3
,
3
,
2
,
3
,
3
,
2
,
3
};
const
char
bigDigitsBot
[
10
][
digitWidth
]
=
{
3
,
1
,
3
,
1
,
3
,
1
,
3
,
1
,
1
,
1
,
1
,
3
,
32
,
32
,
3
,
1
,
1
,
3
,
3
,
1
,
3
,
32
,
32
,
3
,
3
,
1
,
3
,
1
,
1
,
3
};
char
buffer
[
12
];
// used to convert a number into a string
void
setup
()
{
lcd
.
begin
(
20
,
4
);
// create the custom glyphs
for
(
int
i
=
0
;
i
<
5
;
i
++
)
lcd
.
createChar
(
i
,
glyphs
[
i
]);
// create the 5 custom glyphs
// show a countdown timer
for
(
int
digit
=
9
;
digit
>=
0
;
digit
--
)
{
showDigit
(
digit
,
0
);
// show the digit
delay
(
1000
);
}
lcd
.
clear
();
}
void
loop
()
{
// now show the number of seconds since the sketch started
int
number
=
millis
()
/
1000
;
showNumber
(
number
,
0
);
delay
(
1000
);
Serial
.
begin
(
9600
);
}
void
showDigit
(
int
digit
,
int
position
)
{
lcd
.
setCursor
(
position
*
(
digitWidth
+
1
),
0
);
for
(
int
i
=
0
;
i
<
digitWidth
;
i
++
)
lcd
.
write
(
bigDigitsTop
[
digit
][
i
]);
lcd
.
setCursor
(
position
*
(
digitWidth
+
1
),
1
);
for
(
int
i
=
0
;
i
<
digitWidth
;
i
++
)
lcd
.
write
(
bigDigitsBot
[
digit
][
i
]);
}
void
showNumber
(
int
value
,
int
position
)
{
int
index
;
// index to the digit being printed, 0 is the leftmost digit
String
valStr
=
String
(
value
);
// display each digit in sequence
for
(
index
=
0
;
index
<
5
;
index
++
)
// display up to five digits
{
char
c
=
valStr
.
charAt
(
index
);
if
(
c
==
0
)
// check for null (not the same as '0')
return
;
// the end of string character is a null
c
=
c
-
48
;
// convert ascii value to a numeric value (see Chapter 2)
showDigit
(
c
,
position
+
index
);
}
}
The LCD display has fixed-size characters, but you can create larger symbols by combining characters. This recipe creates five custom characters using the technique described in Recipe 11.6. These symbols (see Figure 11-4) can be combined to create double-sized digits (see Figure 11-5). The sketch displays a countdown from 9 to 0 on the LCD using the big digits. It then displays the number of seconds since the sketch started.
The glyphs
array defines pixels for the five custom characters. The array has two dimensions given in the square brackets:
byte
glyphs
[
5
][
8
]
=
{
[5]
is the number of glyphs and [8]
is the number of rows in each glyph. Each element contains 1s and 0s to indicate whether a pixel is on or off in that row. If you compare the values in glyph[0]
(the first glyph) with Figure 11-2, you can see that the 1s correspond to dark pixels:
{
B11111
,
B11111
,
B00000
,
B00000
,
B00000
,
B00000
,
B00000
,
B00000
}
,
Each big number is built from six of these glyphs, three forming the upper half of the big digit and three forming the lower half. bigDigitsTop
and bigDigitsBot
are arrays defining which custom glyph is used for the top and bottom rows on the LCD screen.
See Chapter 7 for information on 7-segment LED displays if you need really big numerals. Note that 7-segment displays can give you digit sizes from one-half inch to two inches or more. They can use much more power than LCD displays and don’t present letters and symbols very well, but they are a good choice if you need something big.
Recipe 11.7 describes how to build big symbols composed of more than one character. This recipe uses custom characters to do the opposite; it creates eight small symbols, each a single pixel higher than the previous one (see Figure 11-6).
These symbols are used to draw bar charts, as shown in the sketch that follows:
/*
* customCharPixels
*/
#include <LiquidCrystal.h>
LiquidCrystal
lcd
(
12
,
11
,
5
,
4
,
3
,
2
);
//set constants for number of rows and columns to match your LCD
const
int
numRows
=
2
;
const
int
numCols
=
16
;
// array of bits defining pixels for 8 custom characters
// ones and zeros indicate if a pixel is on or off
byte
glyphs
[
8
][
8
]
=
{
{
B00000
,
B00000
,
B00000
,
B00000
,
B00000
,
B00000
,
B00000
,
B11111
},
// 0
{
B00000
,
B00000
,
B00000
,
B00000
,
B00000
,
B00000
,
B11111
,
B11111
},
// 1
{
B00000
,
B00000
,
B00000
,
B00000
,
B00000
,
B11111
,
B11111
,
B11111
},
// 2
{
B00000
,
B00000
,
B00000
,
B00000
,
B11111
,
B11111
,
B11111
,
B11111
},
// 3
{
B00000
,
B00000
,
B00000
,
B11111
,
B11111
,
B11111
,
B11111
,
B11111
},
// 4
{
B00000
,
B00000
,
B11111
,
B11111
,
B11111
,
B11111
,
B11111
,
B11111
},
// 5
{
B00000
,
B11111
,
B11111
,
B11111
,
B11111
,
B11111
,
B11111
,
B11111
},
// 6
{
B11111
,
B11111
,
B11111
,
B11111
,
B11111
,
B11111
,
B11111
,
B11111
}};
// 7
void
setup
()
{
lcd
.
begin
(
numCols
,
numRows
);
for
(
int
i
=
0
;
i
<
8
;
i
++
)
lcd
.
createChar
(
i
,
glyphs
[
i
]);
// create the custom glyphs
lcd
.
clear
();
}
void
loop
()
{
for
(
byte
i
=
0
;
i
<
8
;
i
++
)
lcd
.
write
(
i
);
// show all eight single height bars
delay
(
2000
);
lcd
.
clear
();
}
The sketch creates eight characters, each a single pixel higher than the one before; see Figure 11-6. These are displayed in sequence on the top row of the LCD. These “bar chart” characters can be used to display values in your sketch that can be mapped to a range from 0 to 7. For example, the following will display a value read from analog input 0:
int
value
=
analogRead
(
A0
);
byte
glyph
=
map
(
value
,
0
,
1023
,
0
,
8
);
// proportional value from 0 through 7
lcd
.
write
(
glyph
);
delay
(
100
);
You can stack the bars for greater resolution. The doubleHeightBars
function shown in the following code displays a value from 0 to 15 with a resolution of 16 pixels, using two lines of the display:
void
doubleHeightBars
(
int
value
,
int
column
)
{
char
upperGlyph
;
char
lowerGlyph
;
if
(
value
<
8
)
{
upperGlyph
=
' '
;
// no pixels lit
lowerGlyph
=
value
;
}
else
{
upperGlyph
=
value
-
8
;
lowerGlyph
=
7
;
// all pixels lit
}
lcd
.
setCursor
(
column
,
0
);
// do the upper half
lcd
.
write
(
upperGlyph
);
lcd
.
setCursor
(
column
,
1
);
// now to the lower half
lcd
.
write
(
lowerGlyph
);
}
The doubleHeightBars
function can be used as follows to display the value of an analog input:
for
(
int
i
=
0
;
i
<
16
;
i
++
)
{
int
value
=
analogRead
(
A0
);
value
=
map
(
value
,
0
,
1023
,
0
,
16
);
doubleHeightBars
(
value
,
i
);
// show a value from 0 to 15
delay
(
1000
);
// one second interval between readings
}
If you want horizontal bars, you can define five characters, each a single pixel wider than the previous one, and use similar logic to the vertical bars to calculate the character to show.
There are many graphical displays suitable for use with Arduino. Some of the things to consider when choosing a display are: resolution (number of pixels and text lines), display size, color, touch screen capability, onboard SD memory, and price. You will also need a library to interface with your display and you may be spoiled for choice for well-written and documented libraries that you can use. This recipe provides an overview of what is available along with links for more reading to help you select and use the display that is right for your project.
The increasingly rapid introduction of new smartphones and related devices has resulted in reducing costs to a level where monochrome or color displays can be purchased for a fraction of the cost of an Arduino board. When selecting a graphical display, the first thing to consider is color or monochrome. This is partially an aesthetic decision, but it can also be influenced by things like display size (in some cases, larger color panels may be cheaper than large monochrome displays), power consumption (OLED displays described later need less power), touch capability (color panels tend to have better support for touch), and connection type.
Graphical displays that are popular for Arduino projects typically use either liquid crystal display (LCD) or organic light-emitting diode (OLED) technology. OLED is newer technology that consumes less power than a conventional LCD because it does not need a backlight to be visible in low-light conditions. However, OLED panels cost more to manufacture so they tend to be much smaller in size than similarly priced LCD panels. Color OLEDs are available but are very expensive and those sold for use with Arduino tend to be very small.
Monochrome displays were historically much cheaper than color but the price differential is decreasing. Monochrome displays are a good choice if you want a small display or if low current consumption is important. Also, sketch memory needed for monochrome is generally much less than color so that may be a factor if you have limited available memory.
There are Adafruit libraries for the SSD1306, SSD1325, SSD1331, and SSD1351 OLED displays. A nice feature of these is that the API is similar to the Adafruit color libraries if you want to use both mono and color panels in future projects, or may upgrade the screen in the project you are working on at a later date. Refer to the documentation for the Adafruit displays and libraries.
A highly capable monochrome library supporting over 60 controller variants is the u8g2 library. These include: SSD1305, SSD1306, SSD1309, SSD1322, SSD1325, SSD1327, SSD1606, SH1106, T6963, RA8835, LC7981, PCD8544, PCF8812, UC1604, UC1608, UC1610, UC1611, UC1701, ST7565, ST7567, NT7534, IST3020, ST7920, LD7032, and KS0108.
U8g2 is a good choice if you want the widest range of monochrome options. The full list of controller variants can be found on the u8g2 website.
This library supports common connection types including I2C, SPI, and parallel. If you have a monochrome graphical display, the u8g2 will more than likely work with it. You can find the library source here. The u8g2 wiki has extensive documentation.
Color displays offer the ability to display information in full color. The Adafruit GFX library provides a common set of graphic primitives that support many display-specific libraries such as the Adafruit_ST7735, Adafruit_HX8340B, Adafruit_HX8357D, Adafruit_ILI9340/1, and Adafruit_PCD8544 libraries. This library is popular because it is well documented with many tutorials. This Adafruit tutorial covers the graphical functions common to all Adafruit libraries. The Adafruit Arcada library combines GFX support with a large collection of functionality useful for creating games or rich graphical user experiences, including various types of user input.
The library for four-wire resistive touch screens
The library for touch screens
If you have a low-cost TFT screen with or without touch capability that uses ITDB02 or ILI9341 display controllers, check out the UTFT library.
URTouch is a companion to UTFT supporting touch screens.
Because of the vast variety of graphical displays, selecting a display can be daunting. If you don’t have the experience to understand highly technical display controller datasheets, then do not be tempted by some ultra-cheap item from suppliers that do not provide documentation and support. A product listing may say it comes with software for Arduino, but it is not unusual for that code to not actually work with the supplied display or with the latest Arduino environment. The safer choice is through suppliers like Adafruit and SparkFun that have libraries and tutorials and support forums for their products.
This recipe uses the Adafruit ST7735 and ST7789 library, which provides support for ST7735- and ST7789-based TFT LCD displays, such as the 1.8aʺ breakout (Adafruit product ID 358) or the 2.0ʺ IPS breakout (product ID 4311). Or if you want a simple and strange all-in-one option, the Adafruit HalloWing M0 combines a SAMD21 dev board, 8 MB of flash, sensors, speaker driver, and a 1.44ʺ full-color LCD, all on a skull-shaped PCB board. You can install this library using the Arduino Library Manager (see Chapter 16).
The sketch initializes the display on a HalloWing M0, shows three lines of text in varying sizes, and then switches to an animation of a yellow ball moving back and forth:
/*
* Adafruit GFX ST7735 sketch
* Display text and a moving ball on the display
*/
#include <Adafruit_GFX.h>
// Core graphics library
#include <Adafruit_ST7735.h>
// Hardware-specific library for ST7735
#include <SPI.h>
// Define the connections for your panel. This will vary depending on
// your display and the board you are using
#define TFT_CS 39
#define TFT_RST 37
#define TFT_DC 38
#define TFT_BACKLIGHT 7
Adafruit_ST7735
tft
=
Adafruit_ST7735
(
TFT
_CS
,
TFT
_DC
,
TFT
_RST
);
void
setup
(
void
)
{
tft
.
initR
(
INITR_144GREENTAB
);
// Initialize ST7735, green tab packaging
pinMode
(
TFT
_BACKLIGHT
,
OUTPUT
);
// Backlight pin
digitalWrite
(
TFT
_BACKLIGHT
,
HIGH
);
// Turn on the backlight
tft
.
setRotation
(
2
);
// This will depend on how you mounted the panel
tft
.
fillScreen
(
ST77XX_BLACK
);
// Fill the screen with black
// Display some text in a variety of fonts
tft
.
setCursor
(
0
,
0
);
tft
.
setTextWrap
(
false
);
tft
.
setTextColor
(
ST77XX_RED
);
tft
.
setTextSize
(
1
);
tft
.
println
(
"Small"
);
tft
.
setTextColor
(
ST77XX_GREEN
);
tft
.
setTextSize
(
2
);
tft
.
println
(
"Medium"
);
tft
.
setTextColor
(
ST77XX_BLUE
);
tft
.
setTextSize
(
3
);
tft
.
println
(
"Large"
);
}
int
ballDir
=
1
;
// Current direction of motion
int
ballDiameter
=
8
;
// Diameter
int
ballX
=
ballDiameter
;
// Starting X position
void
loop
()
{
// If the ball is approaching the edge of the screen, reverse direction
if
(
ballX
>=
tft
.
width
()
-
ballDiameter
||
ballX
<
ballDiameter
)
{
ballDir
*=
-
1
;
}
ballX
+=
ballDir
;
// Move the ball's X position
// Calculate the Y position based on where the cursor was
int
ballY
=
tft
.
getCursorY
()
+
ballDiameter
*
2
;
tft
.
fillCircle
(
ballX
,
ballY
,
ballDiameter
/
2
,
0xffff00
);
// Yellow ball
delay
(
25
);
tft
.
fillCircle
(
ballX
,
ballY
,
ballDiameter
/
2
,
0x000000
);
// Erase the ball
}
If you are using the HalloWing all-in-one board (or any Adafruit board), you’ll need to add the Adafruit board manager URL to the Arduino IDE and use Tools→Board→Boards Manager to install support for Adafruit SAMD boards, then select the Adafruit HalloWing M0 from the Tools→Board menu.
If you use this sketch with a standalone Arduino board (or compatible), you will need a graphical LCD supported by the Adafruit ST7735/ST7789 library such as those sold by Adafruit. You’ll need to connect the board as shown in Figure 11-7. The pins used for MOSI and SCLK are dependent on your board (the Uno wiring is shown in the figure, but see “SPI” for other boards). For the Uno, you’d need to modify the #define
s and the initialization as follows:
#define TFT_CS 10
#define TFT_RST 9
#define TFT_DC 8
#define TFT_BACKLIGHT 7
Adafruit_ST7735
tft
=
Adafruit_ST7735
(
TFT
_CS
,
TFT
_DC
,
TFT
_RST
);
The chip select and data/command pins are defined by TFT_CS
and TFT_DC
, and you are free to change these to another pin. The TFT_RST
pin is used to reset the display, and you can change this as well. If you use a different connection for any of these, be sure to change the code, too.
The code sets up the #defines
for the pins that are not defined by your board, and then initializes an instance of Adafruit_ST7735 as the object tft
. Depending on what board you are using and how you’ve wired it, this initialization could vary. For example, you can, in fact, use software SPI and choose different pins for MOSI and SCLK, but this will result in slower performance (and if your LCD board has an SD card included, it won’t work with software SPI):
#define TFT_SCLK 5
#define TFT_MOSI 6
Adafruit_ST7735
tft
=
Adafruit_ST7735
(
TFT
_CS
,
TFT
_DC
,
TFT
_MOSI
,
TFT
_SCLK
,
TFT
_RST
);
Within setup()
, the sketch initializes the display with a call to initR()
. There are many variants of these displays, so be sure to consult the documentation for them (the graphics test example program included with the library has explanatory comments for many of the supported boards). For example, there is a group of variants that can be identified by the color of the tab on the tape that’s affixed to the screen when you unbox it. The Adafruit 1.8ʺ TFT screen uses INITR_BLACKTAB
. The 1.44ʺ display on the HalloWing M0 is the same as the 1.44ʺ green tab display, except that it’s mounted upside down. You can either use INITR_144GREENTAB
and setRotation(2)
(upside-down portrait mode) as shown in the sketch or just pass INITR_HALLOWING
to initR()
.
After initialization is complete, the sketch draws black over the entire screen, and uses setCursor()
to position the cursor on the screen before displaying three lines of text that get progressively larger. In the loop()
function, the sketch moves a ball from one side of the screen to the other. It uses a ballDir
variable to determine whether the ball moves to the right (1) or to the left (–1). When the ball comes within a ball’s width of one side or the other, it reverses direction.
The datasheet for the ST7735
This recipe uses the Adafruit 128x32 SPI SSD1306 OLED display and the Adafruit SSD1306 library to display text and graphics on the display. The SSD1306 library works with a variety of OLED displays that use this chipset. You can install this library using the Arduino Library Manager (see Chapter 16).
Wire the display to your Arduino as shown in Figure 11-8, and run this sketch, which displays some scrolling text followed by a moving ball:
/*
* OLED SSD13xx sketch
* Display text and a moving ball on an OLED display.
*/
#include <SPI.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#define WIDTH 128
#define HEIGHT 32
#define OLED_DC 8
#define OLED_CS 10
#define OLED_RESET 9
Adafruit_SSD1306
display
(
WIDTH
,
HEIGHT
,
&
SPI
,
OLED_DC
,
OLED_RESET
,
OLED_CS
);
#define MODE SSD1306_SWITCHCAPVCC
// get display voltage from 3.3V internally
void
setup
()
{
Serial
.
begin
(
9600
);
if
(
!
display
.
begin
(
MODE
))
{
Serial
.
println
(
"Could not initialize display"
);
while
(
1
);
// halt
}
showAndScroll
(
"Small"
,
1
);
showAndScroll
(
"Medium"
,
2
);
showAndScroll
(
"Large"
,
3
);
}
// Show text and scroll it briefly
void
showAndScroll
(
String
text
,
int
textSize
)
{
display
.
setTextColor
(
SSD1306_WHITE
);
// Draw white text
display
.
setCursor
(
0
,
0
);
// Move the cursor to 0,0
display
.
clearDisplay
();
// clear the screen
display
.
setTextSize
(
textSize
);
display
.
println
(
text
);
display
.
display
();
// Scroll the display right for 3 seconds
display
.
startscrollright
(
0x00
,
0x0F
);
delay
(
3000
);
display
.
stopscroll
();
}
int
ballDir
=
1
;
// Current direction of motion
int
ballDiameter
=
8
;
// Diameter
int
ballX
=
ballDiameter
;
// Starting X position
int
ballY
=
ballDiameter
*
2
;
// Y position
void
loop
()
{
display
.
clearDisplay
();
// If the ball is approaching the edge of the screen, reverse direction
if
(
ballX
>=
WIDTH
-
ballDiameter
||
ballX
<
ballDiameter
)
{
ballDir
*=
-
1
;
}
// Move the ball's X position
ballX
+=
ballDir
;
// Draw a ball
display
.
fillCircle
(
ballX
,
ballY
,
ballDiameter
/
2
,
SSD1306_INVERSE
);
display
.
display
();
delay
(
25
);
// Erase the ball
display
.
fillCircle
(
ballX
,
ballY
,
ballDiameter
/
2
,
SSD1306_INVERSE
);
display
.
display
();
}
The chip select and data/command pins are defined in the sketch by OLED_CS
and OLED_DC
. The OLED_RESET
pin is used to reset the display. You can use different pins for any of those. The pins used for MOSI and SCLK are dependent on your board (the Uno wiring is shown in the figure, but see “SPI” for other boards).
The wiring in the Solution uses hardware SPI. If you want to use software SPI, you will need to add a #define
for each of those pins, wire them up, and use a different form of the constructor:
#define OLED_CLK 5
#define OLED_MOSI 6
Adafruit_SSD1306
display
(
WIDTH
,
HEIGHT
,
OLED_MOSI
,
OLED_CLK
,
OLED_DC
,
OLED_RESET
,
OLED_CS
);
The Adafruit library supports a variety of boards that use this chipset, and you’ll need to change your code to match the display you are using. If your display is a different size but also uses SPI, you can probably just change the #defines
for WIDTH
and HEIGHT
. If your display uses I2C instead of SPI, you can connect with fewer wires. You’ll need to connect the reset pin as before, but you’ll then only need to connect the SCL and SDA pins between the Arduino and the display. You’ll need to modify the sketch to include Wire.h and use a different variant of the initialization:
#include <Wire.h>
#define WIDTH 128
#define HEIGHT 32
#define OLED_RESET 13
Adafruit_SSD1306
display
(
WIDTH
,
HEIGHT
,
&
Wire
,
OLED_RESET
);
The initialization of display
gives you an Adafruit_SSD1306
object that you can use to control the display. In setup()
, the sketch starts the display in the SSD1306_SWITCHCAPVCC
mode (to tell the display to get its voltage internally). After that, the sketch uses the showAndScroll
function to display and scroll text in three sizes. This function configures the display to draw in white text, clears the screen, and sets the cursor position to the top left. Then it sets the font, draws it on the screen, and calls display.display()
to show what you’ve drawn. This is different from how you worked with the color LCD (Recipe 11.10) where anything you drew was immediately displayed. After the text is displayed, the sketch scrolls the screen for three seconds.
In the loop()
, the sketch draws a ball on the screen and moves it back and forth across the screen. It uses the SSD1306_INVERSE
color to alternate the color from white to black each time it’s drawn. Each time through loop()
, the sketch increments the ballX
variable until it hits the right side of the screen, when it starts decrementing it (until it hits the left wall). It then displays the ball and shows it for a fraction of a second, before erasing it.
You can also use the u8g2 library to generate a similar display. The code is slightly different, but mostly the same. The biggest difference is that instead of drawing on the screen and calling display.display()
to update it, the sketch uses the u8g2 library’s page buffer to update the display in stages. To use this, you must first call u8g2.firstPage()
, then set up a do-while loop that calls u8g2.nextPage()
at the end. Your drawing commands go inside the loop, and u8g2 goes as many times through the loop as is needed to draw the complete screen. U8g2 supports a wide variety of monochrome displays, and there are variants of each display (for example, hardware SPI, software SPI, I2C, and also permutations of the preceding for a variety of frame buffer/page buffer sizes). See the list of supported devices and their corresponding setup functions.
/*
* u8g2 oled sketch
* Draw some text, move a ball.
*/
#include <Arduino.h>
#include <U8g2lib.h>
#include <SPI.h>
#define OLED_DC 8
#define OLED_CS 10
#define OLED_RESET 9
U8G2_SSD1306_128X32_UNIVISION_2_4W_HW_SPI
u8g2
(
U8G2_R0
,
OLED_CS
,
OLED_DC
,
OLED_RESET
);
u8g2_uint_t
displayWidth
;
void
setup
(
void
)
{
u8g2
.
begin
();
u8g2
.
setFontPosTop
();
displayWidth
=
u8g2
.
getDisplayWidth
();
showAndScroll
(
"Small"
,
u8g2_font_6x10_tf
);
showAndScroll
(
"Medium"
,
u8g2_font_9x15_tf
);
showAndScroll
(
"Large"
,
u8g2_font_10x20_tf
);
}
int
ballDir
=
1
;
// Current direction of motion
int
ballRadius
=
4
;
// Radius
int
ballX
=
ballRadius
*
2
;
// Starting X position
int
ballY
=
ballRadius
*
4
;
// Y position
void
loop
(
void
)
{
u8g2
.
firstPage
();
// picture loop
do
{
// If the ball is approaching the edge of the screen, reverse direction
if
(
ballX
>=
displayWidth
-
ballRadius
*
2
||
ballX
<
ballRadius
*
2
)
{
ballDir
*=
-
1
;
}
ballX
+=
ballDir
;
// Move the ball's X position
u8g2
.
drawDisc
(
ballX
,
ballY
,
ballRadius
);
// Draw the ball
}
while
(
u8g2
.
nextPage
()
);
delay
(
25
);
}
void
showAndScroll
(
String
text
,
uint8_t
*
font
)
{
for
(
int
i
=
0
;
i
<
20
;
i
++
)
{
u8g2
.
firstPage
();
// picture loop
do
{
u8g2
.
setFont
(
font
);
u8g2
.
drawStr
(
10
+
i
,
10
,
text
.
c_str
());
}
while
(
u8g2
.
nextPage
()
);
delay
(
125
);
}
}