Arduino Sketches: Tools and Techniques for Programming Wizardry (2015)
Part II. Standard Libraries
Chapter 13. TFT
This chapter discusses the following functions of the TFT library:
The hardware needed to use the example in this chapter includes:
· Arduino Uno
· LM35 Temperature sensor
· Adafruit ST7735 TFT breakout board (available at http://www.adafruit.com/product/358)
· Micro-SD card
· Connection cables
· 10-kilohm resistor
· Light Dependent Resistor
You can find the code download for this chapter at http://www.wiley.com/go/arduinosketches on the Download Code tab. The code is in the Chapter 13 download folder and the filename is chapter13.ino.
Computer enthusiasts love their hardware, and one of the most loved (and most feared) devices is the humble monitor. When you talk about a monitor, some people immediately think about a previous technology, known as CRT.
Cathode Ray Tubes (CRT for short) was the technology used by televisions and monitors for decades. Put simply, it is an electron canon; a device at one end blasts out electrons onto a fluorescent screen. Large magnets divert the electron beam to hit specific places on the screen, causing the screen to light up at distinct points. Of course, electrons are highly susceptible to atmospheric impurities, and even air, so the gun and the screen were encased inside a large glass shell in a vacuum. To avoid becoming too fragile, the glass was often thick, and to block most X-ray radiation, the glass used often was lead glass. Devices could be made fairly small but were often deep. (In extreme cases, CRTs were as deep as they were wide, but most were about one-half as deep as they were wide.) They have been used as televisions, of course, but also on oscilloscopes, data output, signaling, aircraft cockpits, and even as memory devices.
CRT screens could produce beautiful images but at a cost. The bigger they were, the heavier they got. A 27-inch CRT TV could weigh more than 100 lbs (40 kg). One of the largest and heaviest was a 40-inch screen that weighed in at 750 lbs (340 kg). If you wanted a big screen, you made sure you had friends available to help you install it.
The arrival of LCD screens changed home theater technology at a speed that has rarely been seen. LCD seems to have many advantages over CRT; it is relatively cheap, lightweight, robust, and easier to recycle. Screens could suddenly become bigger, but ironically, they could also become smaller. Large CRT screens were impractical for their size, but similarly, who could honestly imagine a mobile telephone with a CRT screen? Old mobile computers did have CRT screens though. They weren't the clamshell shape that you can see today; rather, they were like large bricks. The keyboard came off the top, and on one side was a CRT screen with floppy drives on the other. LCD screens not only made mobile telephones possible, but also changed the way mobile computers are used.
Many screen technologies have been introduced since the introduction of LCD displays, each generation addressing problems and inconveniences of the previous technology.
One of the first changes was the introduction of passive matrix addressing. This technology allowed a single pixel to be changed by addressing its x-and y-coordinates, and pixels retained their state until ordered to change. This technology was reliable but offered slow refresh rates and became impractical as the screen resolution increased.
Dual Scan, known as DSTN (short for dual-scan supertwist nematic), gave faster screen refresh rates but at the cost of sharpness and brightness. DSTN screens were uncomfortable for watching films; there was visible noise and smears on these screens. I can remember taking a long-haul flight where a new multimedia system was installed on every seat but using DSTN screens. (Previously, flying was like going to a cinema, one large screen for a single cabin.) The lack of screen comfort actually made me stop watching a film and prefer reading in-flight magazines.
TFT, short for Thin Film Transistor, is another technology for displays. Originally, it was much more expensive compared to DSTN panels, but production costs were reduced as demand increased. TFT allows for crystal clear text and graphics, with superb colors. TFT panels are used in almost all mobile devices and nonportable equipment such as televisions and computer monitors.
The ST7735 is an integrated circuit that can drive small-sized TFT displays (128 x 160 pixels in size). An Arduino or other device can communicate with the ST7735 which will talk to the screen. Because the driver has on-board memory for storing a video buffer, once it sends commands to the chip, the Arduino's memory is free for sketches and variables.
ST7735-based LCD screens are available from a large number of manufacturers. SainSmart, Adafruit, and Arduino sell LCD screens based on this device.
The controller can handle a large number of colors, up to 252,000 discrete values (though the library isn't capable of accessing all of them).
Arduino has its own TFT library capable of controlling small-factor TFT screens. The TFT library is based on the hard work from Adafruit Industries. Adafruit originally sold a board containing a TFT screen—the ST7735—and created two libraries to accompany that device: one for the ST7735 and a graphical library common to all its LCD TFT devices. The Arduino TFT library is based on the ST7735 library and the Adafruit GFX library. The primary difference between the Arduino and Adafruit libraries has to do with the way drawing commands are called. The Arduino TFT library tries to emulate the processing programming language for its commands. It “talks” via the SPI bus and is simple to use.
SPI is presented in Chapter 7.
To use the TFT library, you must first import it and the SPI library. As it relies on SPI for communication, it is imperative. This can be done automatically by importing the library from the Arduino IDE (go to the menu Sketch Import Library TFT), or import the library manually:
Next, the TFT object needs to be initialized. For this, it requires some information: the different pins used to communicate with the controller. It requires at least three pins: CS, DC, and RESET. The DC pin is for Data/Command and tells the controller if the information being sent is data or a command. CS is for Chip Select and is used by the SPI bus. The last pin is the RESET pin and it resets the TFT screen if necessary. This can also be placed onto the Arduino's reset pin. The TFT object is initialized as follows:
#define TFT_CS 10
#define TFT_DC 9
#define TFT_RESET 8
TFT screen = TFT(TFT_CS, TFT_DC, TFT_RESET);
The ST7735 is an SPI device, and as such, it uses the SPI MOSI, MISO, and CLK pins. These are already present on fixed pins on the Arduino, so it is not necessary to define them. If necessary, you can use software SPI, in which case, you need to define the MOSI and CLK pins. While hardware SPI is significantly faster for drawing objects on the screen, sometimes you may have to use those pins for other reasons. (MISO is not required for this controller.) Using software SPI, you would be declare pins as follows:
#define TFT_SCLK 4
#define TFT_MOSI 5
#define TFT_CS 10
#define TFT_DC 9
#define TFT_RESET 8
TFT screen = TFT(CS, DC, MOSI, SCLK, RESET);
The Arduino Esplora has a socket that is designed specifically for TFT screens. As such, it uses fixed pins and is not initialized in the same way. For more information on the Esplora, and how to use a TFT screen with the Esplora, see Chapter 21.
The last thing you need to do is to begin the TFT subsystem; to do this, use the begin() function:
This function does not take any parameters and does not return any data.
For most graphics to work, it is essential to know the screen's size, that is, its resolution. The resolution is the number of pixels wide and the number of pixels high. Not all screens are the same size, both in terms of physical screen size and pixels. It is not always possible to know the physical screen size, but you can ask the library the screen's resolution. There are two functions for this: one that returns the screen height and one that reports the screen width. For this, use width() and height().
int scrwidth = screen.width();
int scrheight = screen.height();
Neither of these functions take any parameters, and both return int values—the size in pixels.
Before using the screen, it is often necessary to clear the screen of any text and graphics. Performing a screen wipe is good practice when initializing an LCD screen. It might be a cold boot (where the system was powered off before use) in which case the screen is probably blank, or a warm boot (where the system was reset but was already powered) in which case there may be text and graphics on the screen. To clear the screen of any graphics, use the background() function:
screen.background(red, green, blue);
This function requires three parameters: the red, green, and blue components of the color to be used. The red, green, and blue parameters are int variables and contain 8-bit color levels (from 0 to 255). The screen does not display colors with full 8-bit colors per channel. The red and blue values are scaled to 5 bits (32 steps each), while the green is scaled to 6 bits (64 steps). The advantage of scaling these values in the library means that the Arduino can read in graphics data with 8-bit components without the need to modify them.
The Arduino TFT library has support for text operations enabling you to write text directly onto the screen without having to do any complicated calculations. Writing text is as simple as specifying the text and the coordinates. The TFT library does the rest.
To write text to the screen, use text().
screen.text(text, xPos, yPos);
The text parameter is the text to be written on the screen as a char array. The xPos and yPos coordinates are integers and correspond to the top-left corner of the text.
Computer screens use an x,y coordinate system, but unlike coordinates that you see in mathematics, computer screens use a slightly different way. The origin or coordinate 0,0 is the top-left corner of a screen. The x-value increases the further to the right it goes, and the y-value increases the further down it goes. This is illustrated in Figure 13.1.
Figure 13.1 Computer screen coordinate system
Unlike in serial consoles, text written to the TFT screen does not wrap automatically. That is to say, if the length of the text written to the screen is wider than the screen's width, it is not automatically put onto the next line. You must be sure not to write too much data. Text written outside the screen is ignored.
Text can be printed in several sizes; for this, use setTextSize():
The size parameter is an int between 1 and 5. It corresponds to the height of the text in pixels divided by 10: text size 1 is 10-pixels high, text size 2 is 20-pixels high, and so on. The size can go up to 5 for text that is 50-pixels high. By default, text size is set to 1. This function does not change any text already present on the screen but sets the size for all future calls to the text() function.
The Arduino TFT library also has functions for graphical operations: drawing lines, circles, and dots. It is with these simple tools that you can create advanced graphics, graphs, and interfaces.
The most basic of all drawing functions is the point. This simply places one pixel at the specified coordinates:
The xPos and yPos parameters are int values and represent the location of the pixel to be drawn on screen.
The next drawing function is the line, which connects a pair of coordinates to each other. It is called like this:
screen.line(xStart, yStart, xEnd, yEnd);
The xStart and yStart parameters are int values and specify the start coordinates. The xEnd and yEnd parameters are also int values and specify the end coordinates. A solid line is drawn between these two points.
You can create a rectangle with four lines, but Arduino offers a way to do this automatically using rect().
screen.rect(xStart, yStart, width, height);
Just like line(), this function takes a pair of coordinates as int values that corresponds to the top-left corner of a rectangle. The width and height parameters correspond to the width and height of the rectangle, in pixels. The lines will be drawn parallel to the screen edges. All four angles will be right angles.
To draw circles, use circle():
screen.circle(xPos, yPos, radius);
The xPos and yPos parameters are int values and specify the center of the circle. The radius parameter, also an int, is the radius of the circle to print, in pixels.
All the graphical functions take coordinates and parameters to define their size and shape but do not take parameters for color. This is done through different functions. The philosophy is this: you tell the controller what color you want to use, and all subsequent drawing will use that color.
Color functions aren't used only for lines but also for any filled spaces. A rectangle can have one color for the lines defining its boundary, while the interior of the rectangle could be a different color. By specifying a fill color, anything present inside the rectangle would be erased by a solid color. The color can be any RGB value. It's also possible to declare no color, in which case the color is “transparent”; where any existing pixels are left untouched.
This is accomplished using two functions: stroke() and fill(). To define the color of points and lines, use stroke():
screen.stroke(red, green, blue);
This function takes three int values; 8-bit values for the red, green, and blue components. Again, these values are scaled down to what the TFT screen is capable of displaying. When this function is called, no previous drawings are modified; only future calls to drawing elements will be affected. This function works only on points, lines, and outline graphics for circles and rectangles. To specify how to fill a circle or rectangle, use fill():
screen.fill(red, green, blue);
Again, it takes three int values: the red, green, and blue components expressed as 8-bit values.
To set the outline color as transparent, use the noStroke() function:
To set the fill color as transparent, use the noFill() function:
If you were creating a weather station with graphic icons on an LCD screen, it would be possible to create a basic geometric image representing the Sun. Lightning would be a little more difficult to render and clouds are quite complicated. It is much easier to use a ready-made image file to load and display on the screen. The TFT library can do this off of an SD card.
Most modules and shields that use the ST7735 controller also have an SD-card slot that can read micro-SD cards. They are an excellent way to store large amounts of data like images. Because SD-card controllers use SPI and the ST7735 device is also an SPI device, it is easy to combine the two; they both share the MOSI/MISO/CLK lines. All that is needed is another slave select pin.
SPI is explained in more detail in Chapter 7. SD card usage is explained in Chapter 12.
To load an image directly from an SD card, use loadImage():
PImage image = screen.loadImage(name);
The name parameter is the filename to be loaded from an SD card. This function returns a PImage object. A PImage object is the base class used to draw bitmap images onto a TFT screen. It contains the image data and can be used to write an image to a specific place on the screen. When this object has been loaded, you can retrieve information about it. You can use two functions to get the image width and height, and another function verifies the validity of the data.
width = image.width();
height = image.height();
These two functions are called on the PImage object, and both functions return an int, corresponding to the width and height of the image in pixels.
To verify that the PImage object is valid, use isValid():
result = image.isValid();
This function, called on the PImage object, returns a boolean; true if the image is valid and false if there is a problem.
To display an image at specific coordinates, use image():
screen.image(image, xPos, yPos);
The image parameter is the PImage object created when using the loadImage() function. The xPos and yPos parameters are the coordinates where the top-left corner of the image will be displayed.
In the previous chapter, you created a system capable of data logging the level of sunlight. It is time to take that example a little further and to create a visual data logger application. Just how much light is there outside? And what is the temperature? Now you can put that together visually on a TFT screen.
The temperature will be a real-time readout, but the light levels will be over a period of time shown as a graph. To make things look nice, a background image will display. The graph displays from left to right, and when the graph reaches the far right side, the screen refreshes, and the graphs starts over again.
The screen used in this example is the Adafruit ST7735 breakout board. Adafruit sells an LCD screen by itself, but this is not what you want. A screen without any additional hardware may be great for creating your own device after a prototype has been made, but to create this sketch, you need the ST7735 breakout board, a more complete version that is hosted on its own PCB, with pins that can be placed onto a breadboard. As an added bonus: the breakout board also has a micro-SD slot, which will come in handy for this project.
The breakout board must be hooked up to the SPI bus. It has two chip select pins: one for the embedded SD-card controller, and one for the TFT screen itself. The SD-card reader is also an SPI device, and therefore it will share the SPI bus with the ST7735, but it needs its own chip select pin. The device also has a Lite pin, allowing the Arduino to turn on the TFT backlight.
To get a temperature reading, use an LM35 temperature sensor connected to A0, and to get a light level reading, use a photo-resistor on A1.
The assembly is shown in Figure 13.2. The SPI MISO and MOSI pins are connected to the TFT breakout board's SPI pins, as well as the clock line. The backlight pin is connected to the 5-volt rail, turning the TFT's backlight on as soon as it is powered. The SD–controller chip select is connected to the Arduino's D4 pin, and the TFT chip select is connected to D10. There are two remaining pins—D/C, combined with the SPI pins, will be used to tell the TFT screen if this is a command or data, and the Reset pin is also used to reset the TFT screen if required.
Figure 13.2 Project assembly (Image created with Fritzing)
Now comes the fun part; it is time to put everything together. The sketch that you will be using to start off with is shown in Listing 13.1.
Listing 13.1: TFT Sketch (filename: Chapter13.ino)
1 // Required headers
2 #include <SD.h>
3 #include <TFT.h>
4 #include <SPI.h>
6 // Pin definitions
7 #define TFT_CS 10
8 #define SD_CS 4
9 #define DC 9
10 #define RST 8
12 int lightPos = 0;
13 int currentTemp = 1;
15 PImage backgroundIMG;
17 // Create an instance of the TFT library
18 TFT screen = TFT(TFT_CS, DC, RST);
20 // Char array for printing text on the screen
21 char tempPrintout;
23 void setup()
25 // Initialize the screen
28 // TFT screen will first be used to output error messages
29 screen.stroke(255, 255, 255);
30 screen.background(0, 0, 0); // Erase the screen
32 // Initialize the SD card
33 if (!SD.begin(SD_CS))
35 screen.text("Error: Can't init SD card", 0, 0);
39 // Load and print a background image
40 backgroundIMG = screen.loadImage("bg.bmp");
41 if (!backgroundIMG.isValid())
43 screen.text("Error: Can't open background image", 0, 0);
47 // Now that the image is validated, display it
48 screen.image(backgroundIMG, 0, 0);
50 // Set the font size to 50 pixels high
54 void loop()
57 // Get a light reading
58 int lightLevel = map(analogRead(A1), 0, 1023, 0, 64);
60 // Have we reached the edge of the screen?
61 if (lightPos == 160)
63 screen.image(backgroundIMG, 0, 0);
64 screen.stroke(0, 0, 255);
65 screen.fill(0, 0, 255);
66 screen.rect(100, 0, 60, 50);
67 lightPos = 0;
70 // Set up line color, and draw a line
71 screen.stroke(127, 255, 255);
72 screen.line(lightPos, screen.height() - lightLevel,
73 lightPos, screen.height());
75 // Get the temperature
76 int tempReading = analogRead(A2);
77 int tempC = tempReading / 9.31;
79 // Has the temperature reading changed?
80 if (tempC != currentTemp)
82 // Need to erase previous text
83 screen.stroke(0, 0, 255);
84 screen.fill(0, 0, 255);
85 screen.rect(100, 0, 60, 50);
87 // Set the font color
88 screen.stroke(255, 255, 255);
90 // Convert the reading to a char array, and print it
91 String tempVal = String(tempC);
92 tempVal.toCharArray(tempPrintout, 4);
93 screen.text(tempPrintout, 120, 5);
95 // Update the temperature
96 currentTemp = tempC;
99 // Wait for a moment
On the first few lines of the sketch, you import the libraries that will be required for this project: the TFT library for the LCD screen, the SD library for the SD card reader, and the SPI library, which is required for communication by the other libraries.
On the following lines, some pin declarations are made; these are the pins that will be used for the TFT screen. RST is the reset pin that will be used to reset the TFT screen when the TFT subsystem is ready, or as required by the sketch. DC is used as an extension to SPI to tell the TFT screen if the incoming message is either data, or an instruction. Also, the chip select pins for both the TFT screen and the SD card reader.
On lines 12 and 13, two int variables are declared: lightPos and currentTemp. These two variables contain the graph position and the current temperature, respectively.
On line 15, a PImage object is created, called background. This is where the sketch loads an image into memory and allows you to display a background image on the screen.
On line 18, a TFT object, named screen, is created. It is instantiated with three arguments, the three pins used to control the screen. The SPI wires are not specified because they are on fixed pins. Because they cannot be changed, there is no need to specify them.
On line 21, another variable is created, a char array called tempPrintout. This will be used to store the temperature that will be printed out on the screen.
On line 23, setup() is declared. There are a lot of things to configure in this sketch, so setup() will have a lot of work to do. First, communication with the screen is started on line 26. In this example, the TFT screen is used for debug messages, so it must be set up to display any status messages before proceeding. On line 29, stroke() is called, informing the TFT screen of the color that should be used for future drawing events, including text messages. To make sure that any text is readable, background() is called, setting the screen to black.
On line 33, the sketch attempts to initialize the SD library. In case of failure, text() is called with a message at coordinates 0,0. This results in some text being displayed on the top-left corner of the screen. If the SD library did start, the next step is to load an image. The sketch looks for a file called bg.bmp in the root directory of the SD card. If it finds the image, it places it into the PImage object backgroundIMG. The sketch then tests the contents of backgroundIMG for a valid graphics file. If the contents are not valid, a text error message displays on the TFT screen. If the contents are valid, then the background image displays on the screen starting at coordinates 0,0, the top-left corner. Finally, text size is set to 5; 50 pixels high.
loop() is declared on line 54. This function begins by reading in the light level the voltage on pin A3. The analog-to-digital converter returns values varying from 0 to 1023, but the sketch would like a different value. Ideally, these values should not exceed 64. The screen is 128 pixels high, and the graph takes up the lower portion of the screen, so 64 is an excellent maximum. The ideal function to do this is map(). Next, the sketch needs to print a new line on the graphs, but before doing that, there is one question that needs answering; has the graph reached the edge of the screen? This is checked in the if() statement on line 61. If the graph has reached the edge of the screen, several things need to be done. First, the background image is refreshed, erasing anything present on the screen. Next, both the stroke and fill graphics are set to blue. Then, a rectangle is printed, where the temperature is supposed to go. Finally, the lightPos variable is set to 0, the left side of the screen.
On line 72, a line is drawn on the screen. The first set of arguments are the x and y starting coordinates of the line, and the second set of coordinates is screen and y-end coordinates of the line. height() and the value from the light sensor are used to determine the length of the line on the y-axis.
Now that the light level has been calculated and drawn on screen, it is time to look at the temperature. The analog value of the LM35 is read in, and a small conversion is made to transform the value into a temperature in Celsius. Now the sketch checks if the temperature has changed. Erasing a portion of the screen and printing a new number can cause a visible flicker. Because the temperature shouldn't vary that much, a simple system has been put in place to print the temperature when a change is detected. The comparison is made on line 80, using an if() statement. If the temperature has changed since the last reading, in lines 85 through 88 a background color is declared, the stroke color is changed, and a portion of the screen is erased. Before the text is displayed, the color is changed back to white.
Text must be supplied as a char array, but it is often much easier to print text into a String. On line 91 a String object called tempVal is created, storing the temperature as a String. The next line converts the String into a char array, storing it into the tempPrintout. This array is printed on the TFT screen at coordinates that match up with the rectangle you drew earlier.
Finally, the sketch is told to wait for 2 seconds before repeating.
The temperature display is visible on the screen, but it could do with being a little prettier—or maybe even more colorful. Modify the sketch to change either the foreground or the background of the text according to the temperature; 15 degrees could be a cool blue and 35 a bright red.
In this chapter, you have seen what a TFT screen is, how it can be used for your projects, and how an Arduino communicates with it. You have seen how to initialize the screen, how to print text and pictures to the screen, as well as basic graphics in black and white and in color. In the next chapter, I will talk about servo motors and how to control them using an Arduino with just a few lines of code.