Zonnestroompanelen in Nederland

duurzaamheid achter de meter

(54) Color pictures on 320×240 and 320×480 ‘big’ TFT displays, with an ESP32 WROOM-32

Color pictures on 320*240 and 320*480 ‘big’ TFT displays, with an ESP32 WROOM-32

by Floris Wouterlood – December 3, 2021


It is satisfying to display color pictures onto screens attached to an ESP32 microcontroller. Program memory in these microprocessor chips is large enough (4 MB) to contain color images. The two-core processor is lightning fast compared with the Arduino Uno.

In the present project we first convert color pictures into c-array format. These arrays are either included in a sketch or saved in a separate file that is called via an instruction in a sketch. Both methods produce the same result when the sketch is uploaded to the micrcoontroller: the c-array is loaded in program memory. Displays are 320*240 pixel and 320*480 pixel TFT breakout screens with controllers such as ILI9341, ILI9481, ILI9488. Interfaces are either SPI or parallel. These TFT displays have for each pixel 65,536 colors available (16 bits per pixel, RGB565), while their controllers are perfectly supported by the library TFT_eSPI.h created by Bodmer.


TFT type screens offer the luxury of nearly unlimited color compared with LCDs and OLEDs. We are all aware that graphical representation of data benefits greatly from color enhancement. Almost by default new users of TFT screens look for a library supporting graphical functions in color: pixels, lines, circles, triangles and so forth. As a color image is for a microcontroller no more than an immense bunch of colored pixels in which each pixel is one ‘graphic’, any TFT display should be able to show color pictures.


A common way of representing color is overlaying grey-coded bitmaps in three channels: red green and blue (an ‘RGB image’). In the Arduino world we are used to addressing individual pixels. There are several ways for color coding pixels; this is called ‘color depth’, or ‘bits per pixel’. In Arduino convention is to use 16-bit color (RGB565; ‘true color’), which enables representation of 65,536 different colors.

With a TFT display images can be presented as colorful as on your smartphone. Of course a smartphone screen has more pixels than the humble 320*480, which necessitates some reduction in color depth, some cropping and some scaling. In this project I started with a big, 2521*1688 pixel, 24 bit color depth, jpg image downloaded from Wikipedia.

Library and interface issues

To possess a TFT screen is one thing, making it work, that is: showing color pictures, is a small challenge that needs some investment in ideas, time and effort. The selection of a competent supporting library is of prime importance. Another issue is the communication between microcontroller and display. Among the available libraries the TFT_eSPI.h library offers superior support for a variety of controllers / microcontroller board / TFT display configurations, including the configuration used in the current project. TFT_eSPi supports displays with SPI interface but also those with parallel interfaces. Special controllers, e.g. ILI9488, need special libraries, e.g. variations of the Adafruit_GFX library

Specs of the current configurations:

  • microcontroller board: ESP32-WROOM-32, 30 pins,

  • display: most experimentation was done with 3.5’ 320*480 pixel SPI or parallel TFT breakout boards, operating at 3.3V (control logic wires) and 5V (backlight). The display ‘on bench’ in Figure 1 is a 3.95 inch MCUfriend 320*480 display with parallel interface originally applied as an Arduino Uno shield.

  • communication: ILI9341, ILI9488 (SPI) or ILI9341, 9481 9486 controller (parallel)

  • library: the TFT_eSPI.h library ensemble created by Bodmer was used with success for most displays and controllers. This library has interesting options for custom configuration and tweaking – a necessary feature!

  • for the 3.5’ SPI display with ILI 9488 controller the LovyanGFX library was used successfully. This is an Adafruit_GFX library derivative.

TFT_eSPI can be installed via the Library Manager in the Arduino IDE (Tools → Manage Libraries). Additional Configuration: see section ‘Software: library issues’. Install the most recent version.


In order to avoid struggling with masses of stiff Dupont jumper wires I constructed two permanent benches with soldered wires and with pin sockets that accomodate the microcontroller board and various displays: one that supports TFT breakout boards with parallel interface (fig. 1) and one that supports TFT breakouts with SPI interface (fig 2). The construction of both benches is described in detail in posts published previously on this website (*)(**).

Figure 1. ESP32-WROOM-32 parallel bench. On the bench is a 3.95’ 320*480 pixel TFT display, ILI 9486 controller. The ESP32 is running the sketch named ‘rocks_02.ino’.

Difference between SPI and parallel TFT displays

Whereas the display controller can be the same the interface between the display and the microcontroller may differ. Parallel displays have eight pins (labeled D0 through D7) that transfer signals to the display in addition to clock, chip select, read, write and reset pins. This occupies a total of 13 pins of the microcontroller board. SPI communication uses only 6 pins for control signals and data between the microcontroller and the display (figure 3).

Figure 2. ESP32-WROOM-32 SPI bench with a 3.95’, 320*480 pixel TFT display, ILI 9488 controller. The ESP32 is running the ‘angel.ino’ sketch.

Color pictures: from source to c-array format

The pictures used are ‘rock_02’ which is a picture of Australian coastline southwest of Melbourne, and ‘angel’ which is a picture of a restored ‘azulezos’ plaster wall decoration in Lisbon, Portugal. Originals are multimegabyte jpg digital images.

In the photo editor program:

step 1: scale and crop until a 320 x 480 (portrait or landscape), 72 dpi pixel image results.

step 2: export as .BMP or .PNG image file with 16-bit color depth (R5G6B5, 65,536 colors; this color depth named “True Color” in Windows).

Once the proper size, color depth and format of a picture has been obtained it can be imported in a special program that converts the picture into c-array format. This is the format recognized and processed by the Arduino compiler. The sequence has already been described in a previous post on this website (***), but we repeat it here.

The program we use here is the Windows program named ‘lcd-image-converter’.

Figure 3. The back of a display reveals whether it has a parallel or SPI interface.

Conversion to c-array with lcd-image-converter

lcd-image-converter.exe is freely available on the internet. What one needs to do in the program is shown in figures 4 and 5.

Figure 4. User interface of lcd-image-converter. Check the options!

Conversion procedure

First thing to do is to start lcd-image-converter, provide a name for the project (e.g., ‘rocks_02’) and check the conversion parameters. This is done via Options –> Conversion. Be sure that Block Size is 16 bit, byte order ‘Little Endian’ (figure 4).

After this the BMP or PNG image file can be loaded and the conversion to c-array format performed (figure 5).

Loading is done via the menu option ‘Image’ in the main menu, followed by ‘import’.

Figure 5. Once the conversion parameters have been correctly set (screens in figure 4) the conversion of the picture into c-array format can be completed.

Via ‘File’ and ‘Convert’ the end result is produced: a file named ‘rocks_02.c’ (file size here: 1.2 MB). This file is in ascii format. Its contents can be inspected with an ascii editor (Notepad, Notepad++, Gedit).

From here on: internal c-array or c-array file external to the sketch.

There are two ways to work with a c-array in a sketch: the ‘internal’ option, that is that the c-array is copy-pasted into the sketch, and the ‘external’ option, that includes a call from the sketch to a file located in the same folder as the sketch. Both options have pros and cons. Here we will provide both options.

internal’ c-array:

The essential content from the c-array file (‘rocks_02.c’) is copy-pasted into a unsigned 16-bit variable with the name ‘rocks_02’, at the beginning of the sketch (fig. 6). Notice that ESP32 instructions do not need the PROGMEM statement as for the Arduino Uno. The ESP has plenty of memory!

static const uint16_t rocks_02 [153600] = {

0x9dd7, 0x9dd7, 0x9dd7, 0x9dd7, 0x9dd7, 0x9dd7, 0x9db7, 0x9dd7, 0x9db7, 0x9db7, 0x9db7, 0x9db7, 0x9db7, ……….. and so forth until the final pixels……. 0x8d96, 0x9df8, 0x9e19, 0x9df8, 0x95f8, 0x8d97, 0x8556


Figure 6. Sketch with c-array embedded in the sketch.

Note that each hexadecimal number in the array codes one pixel. As there are 240×480 = 153,600 pixels an equal number of hexadecimal numbers is represented in the sketch. This enormous volume of data makes sketches like this rather unwieldy. A sketch with ‘internal c-array is supplied at the end of this article.

In void setup () the array is sent to screen with the ‘pushImage’ instruction with as parameters x-position left upper corner, y-position left upper corner, width and height, and the name of the array (figure 7). Placing the pushImage instruction in the setup section instead of the loop avoids reloading and thus flickering.

Figure 7. The image sent to display once, with void loop () running a message service. This avoids flicker

external’ c-array:

The essential image c-array data can be placed into a separate file while all data are ‘imported’ at compilation time via a simple ‘#include’ instruction at the beginning of the sketch. This has the advantage of a very clean sketch that is easy to edit. The data file (here: rocks_02.h) needs to be present in the same folder that contains the sketch.

Figure 8. Sketch with c-array in a separate file that is loaded upon compilation.

A sketch with external c-array file contains the call instruction:

#include “rocks_02.h”

that loads the data from the external c-array file named ‘rocks_02.h’ (figure 8). The file rocks_02.h is no more than rocks_02.c stripped from non essential information. The content of rocks_02.c is:

static const uint16_t rocks_02 [153600] = {

0x9dd7, 0x9dd7, 0x9dd7, 0x9dd7, 0x9dd7, 0x9dd7, 0x9db7, 0x9dd7, 0x9db7, 0x9db7, 0x9db7, 0x9db7, 0x9db7, ……….. and so forth until the final pixels……. 0x8d96, 0x9df8, 0x9e19, 0x9df8, 0x95f8, 0x8d97, 0x8556


which is exactly the same as the c-array loaded in memory in the ‘internal’ sketch. The #include tells the compiler where to find the array data that code for the color bitmap. Condition is that the filename of the xxxxxxxx.h file is no longer than 8 characters.(figure 8).

The ‘external’ sketch is further identical to the ‘internal’ sketch.

The advantage of an ‘exernal’ sketch is that it can be very short and clear because it lacks the massive volume of per-pixel color information.

Software: library issues

While TFT_eSPI is a versatile library it needs some explanation. Conventional display support libraries require a ‘constructor’ line inside a sketch. This configuration formula contains a definition of the type of display and pin assignments. In the TFT_eSPI environment a file named User_Setup.h replaces the ‘constructor’ of the conventional sketch, and the only thing a sketch has to do is to call the TFT_eSPI library. This approach makes it possible to switch display very easy, e.g., between parallel and SPI display. The essence of working with TFT_eSPI.h is therefore first to instruct the library once and for all to use User_Setup.h tweaked for your particular display, and second to prepare a User_Setup.h that fits your display. The second step is most challenging. Fortunately the library suite provides numerous helpful examples.

In short:

1. Uncomment in TFT_eSPI.h the line //#include <User_Setup_Select.h>

The following should remain:

#include <User_Setup_Select.h>

2. in User_Setup_Select.h uncomment the line referring to a specific example file in the folder User_Setup that matches your microcontroller/display combination.

3. Tweak that specific file until your display works. Save it under a name that refers to your particular display.

Results and discussion

Both methods: internal c-array or external c-array, worked fine. Various TFT displays available in my collection were tested. These had different controllers, but with the exception of a 3.95’ 320*480 SPI display with a semi-compatible ILI9488 controller all were easy to work with. The TFT_eSPI library includes User_setups that with a little tweaking can be adjusted to work with a specific microcontroller-display size- interface-controller type. The creator of TFT_eSPI has done a extremely good job in supplying support for several microcontrollers (Arduino, ESP8266, ESP32, STM32), parallel or SPI interface, and common display controller chip (e.g. ST7735, ST7789, ST7796, ILI9341, ILI9163, ILI9481, ILI9486, ILI9488).

All 320*240 displays gave good results when 320*480 c-arrays were included in their sketches, that is, the left upper parts of 320*480 size images were visible on screen like peeping through a keyhole. Thus it is possible with ESP32 microcontrollers to show large images on small displays. If one wants to see an entire 320*480 image on a 320*240 display, then the original BMP image must be scaled down to 320*240 in a bitmap editor and again processed with lcd-image-converter into a 320*240 c-array. A 320*240 c-array displayed on a 320*480 screen will occupy half the screen.

It is possible with the ESP32 to load multiple arrays in memory and provide multiple pushImage instructions in the sketch to display a slide show.

The ‘obnoxious’ 3.95’ 320*480 display appeared to be a touch-sensitive display without touch with a strange, possible counterfeit controller. As extensive tweaking of the TFT_eSPI library User_setups did not much to produce a clean image I experimented in this case with a series of instructions combined with the LovyianGFX library. This completed the job successfully.


(*) Floris Wouterlood – ESP32-WROOM-32 and Uno-shield parallel TFT displays. TheSolarUniverse, June 1, 2021

(**)Floris Wouterlood – ESP32-WROOM-32 and ILI9341 TFT display – an interesting match. TheSolarUniverse, February 25, 2021



  • ESP32WROOM32_rocks_02_320x240_intern.ino

  • ESP32WROOM32_rocks_02_320x240_extern.ino

  • rocks_02.h

  • ESP32WROOM32_angel_320x240_intern.ino

  • angel.h

  • ESP32WROOM32_angel_320x240_extern.ino


The above files, packed into ESP32WROOM32_rocks_02_and_angel.zip