How to Control Arduino Using a ILI9488 3.5" Touchscreen and a Simple GUI Library

by imageguy in Circuits > Arduino

8810 Views, 20 Favorites, 0 Comments

How to Control Arduino Using a ILI9488 3.5" Touchscreen and a Simple GUI Library

2022-07-18_17-20-39.jpg
Demo of a ILI9488 touchscreen powered by Arduino

I was working on a device that controlled a couple of circuits and had a few parameters that needed to be set at run time . My first thought was to have a display and a few switches, buttons or possibly thumbwheels. Then I realized I could find a cheap LCD touchscreen and do all of that in software.

This Instructable shows how to attach an inexpensive ILI9488-controlled 3.5” LCD touchscreen to Arduino and provides a small class library to make it straightforward to write a simple GUI.

3.5” display is quite large, but all the touchscreen real estate comes useful if you want to dispense with the pen and just use your fingers. It’s a cheap device, so it requires quite a firm press and also writes to the screen fairly slowly – our class library makes some effort to draw the smallest amount required for each operation.

The Instructable is targeted at Arduino Uno, but Arduino Nano can be used as well – though the shield we have here is a Uno shield, Nano uses the same pins. The display (and the code in this Instructable) should also work with any AVR based Arduino (so it has PROGMEM) that provides EEPROM. In particular, the LCDWIKI library we're using has a set of examples for Mega2560.

There are plenty of Instructables and other articles on the web that deal with ILI9488 displays, but I couldn’t find any that had exactly what I needed.

In this instructable, we look at the circuit needed to connect the display to Arduino, then describe the LCDWIKI library I used to drive the display with the display and finally a small class library that can be used to quickly build a GUI.

There is also a sample sketch with examples of all the object types, so you don’t have to study the class code – you should hopefully be able to just copy and modify the example code.

All the code, libraries, Fritzing files and parts can be found on GitHub.

This was a pretty fun project to develop and write up. I hope you find it useful.

Supplies

2022-07-18_17-30-47.jpg
2022-07-18_17-31-16.jpg

3.5” ILI9488 touchscreens can be found mounted on several different boards with different pinouts. I used AliExpress - $11.5 + $4.68 shipping, I bought two for $27.41

The same model can be found on Amazon for $20.26

Read the product descriptions carefully when you shop – there are displays that look the same, but do not have the touchscreen. ILI9488 refers to the chip controlling the display, touchscreen is controlled via a XPT2046 chip.

Logic level converters. Voltage has to be converted between 5V (Arduino) and 3.3V (display). Not every logic level converter has performance high enough to support SPI. SPI can run on different frequencies, ILI9488 uses 4Mhz.

I didn't understand the possible performance issue when I bought converters on Amazon ($6.99 for 10), but I got lucky - the spec doesn't mention SPI, but it does happen to work.

This converter, also on Amazon ($7.99 for 5) explicitly mentions SPI support, but I haven't tried it.

You will need to convert voltage on 11 lines, so you'll need three converters.

male and female headers

3.5"x2.75" or similar size prototype board

Arduino Uno

SPI – Serial Peripheral Interface

SPI is a synchronous serial data protocol used to communicate over short distances. SPI connects a controller to one or more peripherals. SPI uses three lines for communication:

  • CIPO (controller in, peripheral out)
  • COPI (controller out, peripheral in)
  • SCK (serial clock)
  • CS (chip select)

CIPO, COPI and SCK lines can be shared by multiple peripherals. Each peripheral has a dedicated CS line. When CS is low, the peripheral communicates with the controller. Otherwise, the peripheral ignores the controller.

“Controller” and “Peripheral” used to be called “Master” and “Slave”. This usage has been deprecated, but products often still label lines CIPO, COPI and CS as MISO (master in, slave out), MOSI (master our, slave in) and SS (slave select).

To configure a SPI link, three things must be selected: clock speed, bit order (MSB vs. LSB) and mode (clock phase and polarity).

Electronics

2022-05-04_14-27-46.jpg
Screenshot from 2022-05-07 14-12-48.png
Screenshot from 2022-05-07 14-12-55.png
Screenshot from 2022-05-07 14-13-08.png
2022-07-04_16-45-28.jpg
2022-07-10_13-35-03.jpg
2022-07-10_13-35-25.jpg
2022-07-10_13-37-32.jpg
2022-07-10_13-38-23.jpg
2022-07-10_13-40-15.jpg

The display we are using is really three devices in one: LCD, touchscreen and SD card reader. Each of these devices has its own set of pins. LCD and SD communicate using SPI, while the touchscreen uses a different protocol. This Instructable ignores the SD card.

LCD SPI is configured to 4Mhz clock speed, most significant bit (MSB) and SPI_MODE0. This is done inside the LCDWIKI library.

The display runs on 3.3V. It can be made to run on 5V power by soldering together two contacts marked “J1” on its board. This, however, reportedly makes it prone to overheating, so I haven’t tried it. Instead, we use a logic level converter to bridge from 5V to 3.3V and back.

Not all the logic converters will work, since SPI requires faster performance than, say, I2C. Some converters will explicitly list SPI support. Note that 4Mhz is a relatively high speed for SPI. There is, for example, a logic converted listed on Adafruit that can handle SPI only at 2Mhz or less.

The display has 18 pins, 14 in the main header and 4 in the SD card header. We use 13 pins in the main header. The remaining pin, SDO (CIPO/MISO), is for the LCD to write data to Arduino and is not used. We need three converters to drive 11 data pins (the remaining two are 3.3V Vin and Ground).

Connecting all of the parts to a breadboard requires an interesting profusion of wires, but can be done if you take your time. The display will run nicely when powered from the Arduino 3.3V pin.

To save on space, I normally try to use Arduino Nano for my devices. However, the display is larger than Uno, so there is not much space penalty there.

Using 3.5”x2.75” prototype board for the shield leaves enough space for fair number of components you might need to drive your actual device. The shield I show here is my development shield – every pin not reserved for the display has an output pin that can be used to connect to whatever prototype you are building.

While the shield is not particularly complex, there are a fair number of connections. I made wiring test sketch (also on GitHub) that lets you turn on and off each pin used for the screen, so you can use a voltmeter to check whether the pin has been connected properly to the display header.

The Fritzing file and the part file for the one custom part (the display) it needs can be also be found on GitHub.

Downloads

LCDWIKI Library

As I struggled to get the wiring right, I settled on using the LCDWIKI library, since it had a self contained test program that didn’t rely on any libraries. LCDWIKI page for the ILI9488 display is a gold mine of information.

The download package has libraries for Arduino, STM32 and C51 (compiler for 8051 microcontroler). Arduino package (1-Demo/Demo_Arduino/’Install libraries’ after unpacking the ZIP) contains three libraries: LCDWIKI_GUI, LCDWIKI_SPI and LCDWIKI_TOUCH. Install them wherever your Arduino keeps the libraries.

I’ve also mirrored the part of the package that is useful for Arduino on GitHub.

Documentation makes for a wonderful read, since the English is quite rudimentary. But all the demo programs and all the API functions I tried work just fine, so who cares about funky spelling!

Demo program package for Arduino has four flavors: Uno and Mega2560, software and hardware. I used Uno hardware programs. The difference between software and hardware programs are that hardware version uses D11 pin for MOSI, D12 for MISO and D13 for SCK, while software version assigns MOSI, MISO and SCK to other pins and uses different lcd constructor. Note that we don’t use MISO pin.

Checking of a few demo samples didn’t show any differences between Uno and Mega2560 versions, but keep in mind that Mega2560 uses pins D50 (MISO), D51 (MOSI) and D52 (SCK) instead D11-13 used by Uno and Nano. No code changes should be needed if you use the hardware constructor, but the wiring obviously has to go to the right pins.

LCDWIKI libraries support a number of different displays, set the model to select ILI9488.

#define MODEL ILI9488_18
LCDWIKI_SPI lcd(MODEL,CS,CD,RST,LED) ; // hardware constructor
LCDWIKI_SPI lcd(MODEL,CS,CD,MISO,MOSI,RST,SCK,LED);  //software constructor


Using hardware lcd constructor, a minimal sketch with the lcd and touchscreen just instantiated, but the sketch otherwise empty, uses 6002 bytes of program memory and 1162 bytes of dynamic memory.

LCD and touchscreen constructors assign the Arduino pins to use for connections. lcd.Init_LCD(), lcd.Set_Rotation(), tscreen.TP_Init() and tscreen.Set_Rotation methods are used to configure the LCD touchscreen.

LCD orientations are clockwise from portrait. Touchscreen orientations are counterclockwise starting from landscape orientation 90 clockwise from the LCD orientation passed to TP_Init. If 0 was passed to TP_Init as the LCD orientation, LCD Set_Rotation and TP_Set_Rotation behave as follows:

  • Portrait 0 : LCD orientation 0, touchscreen orientation 1
  • Landscape 90: LCD orientation 1, touchscreen orientation 0
  • Portrait 180: LCD orientation 2, touchscreen orientation 3
  • Landscape 270: LCD orientation 3, touchscreen orientation 2

I found it simplest to define LCD_ORIENTATION to be whatewer the desired orientation is (180 degrees works well, since the USB cable, if used, points away from the user), then pass it to both lcd.Set_Orientation and tscreen.Init. Then we pass TSC_ORIENT_0 to tscreen.Set_Rotation to keep the touchscreen the same rotation as LCD.

A Small GUI Class Library

Our main problem in writing a GUI package is the lack of memory. Both Uno and Nano have 2K of dynamic memory. Uno has 32K of program memory, while Nano has only 30720 bytes. Once we include the LCDWIKI libraries, we have just 886 bytes of dynamic memory available. We need to use as little memory as possible for the class library, since the object is to control a sketch which will also need memory!

We have more program memory available – 26766 bytes on Uno and 24718 bytes on Nano. Our strategy will be to fix as many parameters as possible at compile time and keep them in program memory via PROGMEM keyword.

Our class library has two files, gui_obj.h and gui_obj.cpp and three classes:

gui_obj – the base class. Displays a window, possibly with text. Doesn’t respond to clicks.

clickable – subclass of gui_obj. Responds to clicks and can execute an action on click release. clickable objects come in two flavors. Base flavor has no functionality beyond telling the caller if it’s been clicked and released and executing an action if one has been provided. The second flavor is on-off. It tracks whether the object is “on” or “off” and changes the variable part of the label to match. Examples of clickable objects would be an “OK” button (base flavor) and, say, a START/STOP button.

editval – subclass of clickable. Object is linked to an outside variable, which can be either long or float (displayed as fixed precision), signed or unsigned. When clicked, it switches the display to an edit screen that can be used to edit the value of the variable. Variable value is also stored in EEPROM, so it remains the same if the device is restarted.

Each class has a related struct that contains the fixed data. This data is declared as constant and kept in PROGMEM. Each class has a fixed label, also kept in PROGMEM. clickable class has two “variable” labels, vartxt and vartxt2. vartxt2 is fixed and kept in PROGMEM. vartxt can be either fixed and kept in PROGMEM, or variable and kept in memory. on-off flavor displays vartxt when on and vartxt2 when off.

Variable labels are meant to overwrite a part of the base fixed label. Fixed label length is used to center the label. The sample sketch makes each button one character wider than the label and centers the label in the button. The GUI code lets the caller set the sizes and positioning.

Each object uses program and possibly dynamic memory to store labels. In addition, per object storage usage is as follows:

  • gui_obj uses 4 bytes of dynamic memory and 16 bytes of program memory
  • clickable uses 5 bytes of dynamic memory and 27 bytes of program memory
  • editval uses 5 bytes of dynamic memory, 36 bytes of program memory and 4 bytes of EEPROM

To manage EEPROM, you should have an EEPROM signature at the beginning and, if that has changed from the last time the sketch had run, initialize EEPROM from the sketch defaults. The sample sketch uses the first two bytes of EEPROM for signature.

Each object has a background color used for the button and a foreground color used for the label. clickable objects have a second foreground color, used while the object is being clicked. There is a selection of colors #defined in gui_obj.h. Colors are packed, as is common for Arduino LCD displays, into 16 bits, using 5 bits for red, 6 bits for green and 5 bits for blue: RRRR RGGG GGGB BBBB.

Labels are displayed as “transparent”, since the bounding box starts at a character top left corner and then has padding to the right and below. We instead paint the background of each object separately before displaying the text on top.

All the object sizes, positions and so forth have to be fixed at compile time. This makes for a little more cumbersome code, since we have to fix the screen orientation and effective screen sizes at compile time.

In fact, it’s all a bit cumbersome. Given more memory, classes could be organized better and more flexibly, but having few classes uses less code for methods and thus saves program memory, while making most of data constant lets us substitute a little more abundant program memory for dynamic memory.

The source files are also available on GitHub.

You don’t really need to study the gory details of the class library. The following sample sketch should provide objects that can be adapted or copied to do whatever you need.

Sample Sketch

2022-07-18_17-20-39.jpg
2022-07-18_17-20-25.jpg

lcd_gui.ino sketch encodes a simple counting device. We have a target variable and a counter that starts from zero. The target variable is defined as signed and can be, at compile time, be defined as long or float. There is a START/STOP button that starts or stops the counter. Once started, the counter increments (or decrements if the target is negative) every second until either stopped by the user or if the counter absolute value is greater than the target absolute value.

A separate field displays the running count using a vartxt label stored in memory. Pressing the target button switches to an edit screen that lets the user set a value for the target. If the target is signed, “-” is provided in the key pad. When editing, there is no need to remove any intermediate spaces, since the parser removes them. So, “ - 1 2”, “-1 2” and “-12” all parse to -12.

If the target is a floating point number (n_dec in the object is not zero), the decimal point doesn’t respond to clicks in the edit screen and can’t be moved – the target value is displayed in the fixed precision with n_dec.

Edit screen has OK and Cancel buttons to accept or reject the editing changes.

Changing LCD_ORIENTATION #define at the top of the sketch makes the sketch run in the desired orientation. The sample runs in the 180 orientation, since this makes any USB or power cables point away from the user.

This simple sketch and simple GUI with six objects, when compiled for UNO, use 26600 bytes of program memory and 1312 bytes of dynamic memory, leaving 5656 bytes of program memory and 736 bytes of dynamic memory free. Not enough to write a huge control sketch, but enough for a simple device.

The source files are also available on GitHub.

Downloads