Basics of Design CC Blog Research & Design Hub

Building a Touchscreen Display

Written by Brian Millier

Using a Bridgetek BT81x

In this two-part series, Brian examines the features of Bridgetek’s BT815 graphics controller. He then shares the details of his project to build a touchscreen display system controlled by the BT815.


  • How to build a Touchscreen display?
  • How is the touchscreen display element organized?
  • How does Matrix Orbital’s capacitive Touchscreen work?
  • What is an EVE screen editor?
  • How is the Touchscreen calibrated?
  • How does Touchscreen respond to user touch?
  • How to display the BITMAP image?
  • How to load a BITMAP file from MCU program flash?
  • Bridgetek BT81x
  • FT800 controller
  • Arduino IDE
  • MCU
  • Matrix Orbital EVE3x
  • USB2SPI
  • TFT display
  • Goodix GT911 controller
  • Bridgetek | www.brtchip.com
  • Matrix Orbital | www.matrixorbital.com

About eight years back, FTDI (the company that makes the popular USB bridge chips) introduced a group of graphics controllers for TFT displays with touchscreens. Initially the company sold the FT800 controller, which handled displays up to 5” with a pixel resolution of 480 x 272 and a resistive touchscreen. I’ve done several projects using different display modules containing the FT800. On the plus side, they are fast and require very little in terms of resources on the host MCU.

The downside was that the FT800 controller used an architecture and programming model that was much different from any TFT controller I had ever used. Compounding this was the fact that FTDI seems to have done most of their driver library development using a native “C” language compiler on a PC, along with a USB adapter board to interface the PC to the FT800’s SPI port. When they ported this version of “C” code over to MCUs running under the Arduino IDE, it was bloated and really hard to follow. Nevertheless, the display modules using the FT800 worked so well, and rendered graphics so quickly, that I took the time to become fairly proficient with these displays.

Flash-forward to the present, and several things have occurred. Although FTDI developed additional, enhanced controller modules, they later sold the graphics controller business to Bridgetek in Singapore. Bridgetek is currently developing the latest controllers, though within the current family of devices, there is a mixture of the older FT8xx controllers and the newer BT81x ones. The BT815 controller, which is the subject of this article, must have been introduced around the switchover to Bridgetek. As a result, documentation and programming examples pertaining to the BT815 device may refer to it using either the FT or BT prefixes. I use whatever designation is used in Bridgetek’s documentation/code examples in this article as well.

Matrix Orbital’s Capacitive Touchscreens

My interest in the newer Bridgetek controller modules came about after using a Matrix Orbital EVE3x-50G-IPS 5” capacitive touchscreen display. I really liked the improved response of its capacitive touchscreen, compared to the resistive ones supported by the FT800 controllers in the past. These Matrix Orbital boards can be ordered with either 32 or 256MB of QSPI flash memory that can be used to store image files, and other files. It produces fast transfers to the screen, since this memory is directly controlled by a dedicated QSPI port on the BT815.

Unlike some other touchscreen displays that I have purchased, which were marginal in terms of brightness/contrast, the Matrix Orbital EVE3x line of displays is super bright. I’m not familiar with the terms cd/m2 (candelas per square meter) or ”nits” in regard to display brightness (luminance), but the Matrix Orbital EVE3x displays are rated as sunlight-readable with a brightness of 600 nits to 1000 nits. Visually, they are a lot brighter than the Nextion modules I have tried, which appear to be rated at 200-300 nits.

The Matrix Orbital EVE3 BT815 is shown in Figure 1. What may not be obvious is that the thin plastic bezel has an adhesive backing, making it easy to mount on a panel.

Figure 1
Front view of the EVE3-50G-IPS display I used for the article series. The black plastic bezel has a double-sided adhesive backing, making it easy to mount on a panel. It still requires a rectangular cutout for the module itself, but that cutout won’t be visible, so its finish/accuracy need not be a consideration.
Figure 1
Front view of the EVE3-50G-IPS display I used for the article series. The black plastic bezel has a double-sided adhesive backing, making it easy to mount on a panel. It still requires a rectangular cutout for the module itself, but that cutout won’t be visible, so its finish/accuracy need not be a consideration.

I was initially discouraged by its main interface connector—a tiny, 20-pin FFC socket, meant for 0.5mm-spaced flex cables. That interconnect method doesn’t match well with the 0.1” grid Vector boards that I routinely use. However, a closer look at the module’s Hardware Manual showed a 20-pin header on 0.1” centers, to which I could easily connect a ribbon cable. This header contains all the signals present on the FFC socket.

The BT815 Architecture

Aside from being a fast graphics processor, the BT815 also contains a separate coprocessor that can perform some higher-level functions that you might not expect a graphics processor to do. Figure 2 is a block diagram of the BT815 connected to the screen and other peripherals. These functions fall into three basic categories:

Figure 2
Block diagram of the interconnections between BT815 display controller chip, the TFT display, the Host MCU, and other peripherals.
Figure 2
Block diagram of the interconnections between BT815 display controller chip, the TFT display, the Host MCU, and other peripherals.

Graphics Widgets—These are GUI screen objects that can be drawn simply by calling the appropriate widget command. Examples of these are buttons, sliders, gauges, clocks, keyboards, and progress bars. When you place any of these objects on the screen, you can associate a “tag” symbol with that object. Later on, when you are checking to see if/where the user has touched the screen, the touch scanner will return the tag symbol corresponding to the widget that was touched.

Audio Functions—The BT815 graphics controller contains a rudimentary audio system that allows you to produce 50+ sound effects using a simple routine call. So, in many cases, you wouldn’t need any audio capability from the Host MCU. The Matrix Orbital EVE3x modules contain an on-board audio power amplifier and small speaker to support this feature. Along with sound effects, you can play a full range of musical notes on one of several synthesized instruments, such as piano and organ. I won’t be replacing my music synthesizers with a BT815 any time soon, but the sounds are good enough for user feedback in a GUI-based product. The BT815 sound synthesizer is not well covered in the Programmer’s Guide, but enough information can be found in section 4.6.1 of the FT81X Datasheet. TheBT815’s audio output is a Sigma-Delta signal with a carrier frequency of about 30MHz. It needs an RC network to filter out the 30MHz carrier, but this has been taken care of on the EVE3x module ahead of the audio amplifier.

High-Level Graph Functions—You can certainly produce any custom graph style by drawing it piecemeal, using the primitive graphics functions such as Points, Lines, and Rectangles. However, several forms of graphs lend themselves particularly well to the high-level capabilities of the BT815 coprocessor. Two such graph styles are bar graphs and waterfall displays. Specifics on these two functions appear in Part 2 of the article.

While not a feature of the BT815 controller itself, the Matrix Orbital EVE3x line of modules can optionally be ordered with haptic feedback. This is a tiny vibration motor as found in cell phones. This is useful for giving feedback to the user when a widget on the screen has been touched, for example.

Earlier, I mentioned that FTDI and Bridgetek seem to do their code development on a PC, using a USB bridge to communicate with the display module. Matrix Orbital sells a small adapter board, the USB2SPI-B, to handle this.

Initially, I didn’t order this adapter board because I wasn’t sure how useful it would be to develop code on the PC and then have to migrate it to Arduino C++ for my Teensy host MCU. However, to use the onboard flash memory chip, you really need this adapter board to load the flash memory with image files and other data. So, I obtained one later on for this purpose (Figure 3).

Figure 3
Matrix Orbital’s USB2SPI board allows you to drive the display from a PC computer running appropriate software. The board plugs into the display using the flex cable supplied with the display. It connects to the PC using a Mini USB-to-USB A cable, supplied with the USB2SPI board.
Figure 3
Matrix Orbital’s USB2SPI board allows you to drive the display from a PC computer running appropriate software. The board plugs into the display using the flex cable supplied with the display. It connects to the PC using a Mini USB-to-USB A cable, supplied with the USB2SPI board.

Note the barrel connector on this board. The board, itself, gets power from the PC’s USB port, and can also supply that power to the EVE display, but the PC’s USB port cannot supply enough power for both the USB2SPI board and the larger of the EVE3 display modules.

The EVE3x-50G-IPS board worked when plugged into my host MCU board, which, during development, gets its power from its USB port. However, my code throttles the backlight back to 50%, and even at that reduced brightness, the combination of the two boards draws >500mA. This is more than the standard maximum current draw allowed by USB ports (non USB-C ports, that is).

When the Eve3x-50G display was plugged into the USB2SPI-B adapter, C, it ran a test program controlled by the PC, but drew 800mA from my USB hub. This was way beyond the USB spec, and likely occurred because the compiled test program, running on the PC, ran the backlight at 100%.

For these reasons, I suggest you plug a 5V adapter capable of 1A or greater into the USB2SPI barrel jack, for the 5” EVE display and the larger displays. I’ll discuss more details on BT815 flash memory programming later in this article.

Getting Started

The BT815 uses a slightly expanded instruction set from that of the FT800, making it more versatile. I was able to figure out how to get the available example programs working with my Teensy 3.5 MCU, since I had written earlier Arduino C code for the FT800 displays I’d used in past projects.

For a newcomer, however, probably the best way to get started is to purchase the EVE2-USB2SPI-KIT-A board and use your PC to drive the display in the early stages. Matrix Orbital provides an EXE program for the PC, which sends commands to the display using the USB2SPI adapter board. This is just a simple “sanity test” to show that your board is working properly. Note: the USB2SPI-KIT-A board, still showing on Matrix Orbital’s website, has now been replaced by a “B” version. This “B” version contains a switching regulator in place of the LDO used on the “A” version, and will handle larger display boards.

I had some difficulty getting this “sanity check” demo to work at first, using the EVE2-USB2SPI board. This adapter board contains an FTDI USB interface chip on it. Since I had used many FTDI USB-serial bridge chips and cables containing FTDI chips and so forth, I assumed that I didn’t need to load a Windows driver. That was an incorrect assumption. Bringing up the Device Manager utility, I could see the USB2SPI device, but it had a “?” for an icon- meaning that it needed a driver. Looking more closely at the product page for the EVE2-USB2SPI board, I found a link for downloading the driver files [1].

Once this file has been downloaded and unzipped, you can return to the Device Manager and specify the folder in which you unzipped this driver. Windows will then perform the driver installation.

Even after I had loaded the correct driver, the EVE Asset Builder still wouldn’t detect the on-board flash memory on my display board. As shown in Figure 4, I selected the “MPSSE” option for Programmer Module, but this wasn’t my first choice! Instead, I had left the default “FT4222” option checked—mainly because I had seen “ftdi” in the driver file names. Matrix Orbital advised me to change to MPSSE mode (the Multi-Purpose Serial Engine Emulator mode that FTDI developed for its USB chips). After changing that, I was able to detect the presence of the on-board flash memory. I’ll go into the flash memory programming in a later section. In Figure 4 you can also see that the flash was successfully detected and some of the parameters that were measured.

Figure 4
The Flash Programming page of the EVE Asset Builder utility. You must select the MPSSE mode (not the default FT4222 mode), or you won’t be able to detect the EVE display board’s flash—or communicate with the board in any way.
Figure 4
The Flash Programming page of the EVE Asset Builder utility. You must select the MPSSE mode (not the default FT4222 mode), or you won’t be able to detect the EVE display board’s flash—or communicate with the board in any way.
The Eve Screen Editor

Unless you have had previous FT8xx controller programming experience, your next step should be to read over the BT81x Series Programming Guide (the latest is the labeled application note BRT_AN_033). Also, you should download the EVE Screen Editor program for the PC. This program allows you to drag/drop various graphics primitives (lines, points, and rectangles) and high-level widgets onto the “Virtual display screen,” which is the black area in the middle of Figure 5. Once objects are placed on this virtual screen, several other windows will be populated with information. In Figure 5, for example you can see that the placement of a button has added the following to the Coprocessor window:

CLEAR(1, 1, 1)

CMD_BUTTON(118, 62, 120, 36, 27, 0, “Button”)

Figure 5
The working page of the EVE Screen Editor PC application. The Virtual screen is the black area in the center. The Coprocessor command window is below it to the left, and the object Properties window is to the right.
Figure 5
The working page of the EVE Screen Editor PC application. The Virtual screen is the black area in the center. The Coprocessor command window is below it to the left, and the object Properties window is to the right.

It has also populated all the button’s parameters in the Properties window, along with descriptions of those parameters. You can edit all these parameters in this window, and the resulting edits will also be reflected in the code that is present in the Coprocessor window.

You can basically compose much of your desired screen GUI by dragging/dropping the various elements needed to the virtual screen and editing any parameters necessary in the Properties window.

Having composed your screen, you select the code that has been added to the Coprocessor window, and import it into your host MCU’s program source code. You do this by right-clicking in the Coprocessor window, picking “Select all” and then “copy.”

That is the way to transfer the code to your host MCU program, but there is more to it than that! Four more things must be done manually:

The commands that you see in the Coprocessor window (all uppercase) are merely macro definitions. These macros are actually defined in Bridgetek’s driver library, and I have replicated them in the BT81x.h file that I supplied to the Circuit Cellar website (see Circuit Cellar’s article code and files download webpage).

Since these are just command macros, each must be invoked by preceding it with Send_CMD. For example:

Send_CMD( CMD_BUTTON(118, 68, 120, 36, 27, 0, “Button”)

All these commands make up the “display list” for the image that you have constructed on the virtual screen. However, to make the BT815 controller actually display that image, a few commands must precede this list, and a few others must follow it.

PRECEDING CODE:

Send_CMD(CMD_DLSTART);

Following Code:

Send_CMD(DISPLAYB());

Send_CMD(CMD_SWAP);

UpdateFIFO();

In my code, I have to use “DISPLAYB()” instead of Bridgetek’s DISPLAY() macro, because Arduino already defines the DISPLAY keyword for something else. I had to modify that macro in my BT81x.h file accordingly.

A configuration routine must first be run by your host MCU to initialize the BT815 controller for your specific LCD screen, and for the type of touchscreen. In my case, that is performed by the following line in the Setup routine of the Arduino sketch:

FT81x_Init(DISPLAY_50, BOARD_EVE3, TOUCH_TPC);

This specifies that I have a 5” LCD screen on an EVE3 board, with capacitive touch capability.

— ADVERTISMENT—

Advertise Here

Between the EVE screen editor, and looking over my BT81x_Example program code, you should be able to design simple GUI screens for your program.

There is one important concept to understand regarding BT81x controllers. Everything you see on the display screen at any given point in time must be drawn by commands contained in a single display list!

In other words, you can’t draw a complex screen initially, and then just send over a short display list later on, to update the value of a text box, slider, or other element. This might seem inefficient, but the display list is made up of high-level commands that can be transferred quickly via SPI to the display. Also, for advanced users, you can load (complex) macro routines to the BT815’s RAM memory, and “call” those macros from within a display list. This increases graphics rendering speed and reduces the SPI traffic between the host MCU and the BT815. Also, images can be stored in the display board’s flash memory and rendered from there directly, speeding things up and eliminating a lot of SPI traffic.

Apart from copying the code generated by the EVE Screen Editor into your host MCU’s source code, you can also replicate the screen you are designing on the EVE display board, itself. This is done from the EVE screen Editor by using the EVE2-USB2SPI adapter, and following the instructions in the EVE Screen Editor’s Help Manual, section VII A.

TOUCHSCREEN OPERATION AND CALIBRATION

The BT815 chip does not contain a capacitive touchscreen controller as such. Instead, it contains a dedicated I2C port that can be wired up to a few common third-party capacitive touchscreen controllers. Bridgetek is somewhat vague on this subject—they mention that it will work with capacitive touchscreen controllers by FocalTech or Goodix. Matrix Orbital is a bit more informative on the subject; they mention that the TFT display that they use contains a Goodix GT911 controller. This is a very common touchscreen controller that can report up to five discrete simultaneous touches.

To be able to access the I2C-based GT911 touch controller using the SPI host port on the BT815, it’s obvious that there must be some hardware contained in the BT815 to bridge the two protocols. But there are no details about this in the BT815’s datasheet. However, in the BT81x Series Programming Guide, they describe a series of registers that are used to control and interrogate the GT911 for touch co-ordinates.

These registers look like all the other BT815 registers, in that they are 32 bits and are mapped into the BT815’s memory space. There must be logic involved in the BT815 controller that converts read/write requests to these memory-mapped register addresses into I2C messages that communicate directly with the GT911. The details are not explained in any literature that I’ve come across to-date.

Looking closely at the BT81x library code provided by Bridgetek/Matrix Orbital, when you run the FT81x_Init routine, along with configuring the BT815 with the proper LCD screen parameters, it also runs a routine called “Cap_Touch_Upload”. This routine uploads a 1,216-byte file (“touch_cap_811.h”) into the BT815 command buffer. This process is apparently explained in Bridgetek’s Application note AN336, which I haven’t read yet. However, I figure that this file is too big to contain only calibration/configuration data alone and must also contain BT815 program code, which does the chore of converting BT815 Touch register read/write accesses into the appropriate I2C messages for the GT911.

Matrix Orbital supplies the “touch_cap_811.h”file as part of their BT81x library. For some reason, their file contains two “C” arrays, each with 1,216 bytes of data. One of the arrays is an 8-bit array, the other is identical but formatted as a 32-bit array of values. I have done away with this file, containing two functionally identical arrays. Instead, I included a single byte array “TOUCH_DATA_U8”, made up of the same data, at the end of my BT81x.h file. (see Circuit Cellar’s article code and files download webpage).

Running the Cap_Touch_Upload routine only gets the GT911 controller running and accessible through various library Touch functions. It does not calibrate the touchscreen to the TFT display. To do so, you must run a high-level BT815 CMD_CALIBRATE coprocessor command. This command will place instructions on the screen to touch three circles in sequence, at extremes of the display area. After the user has finished tapping the three circles, there will be a six-word calibration array stored in registers:

REG_TOUCH_TRANSFORM_A

through

REG_TOUCH_TRANSFORM_F

After this calibration, the touchscreen X and Y values returned by the Touch routines will be quite accurate in terms of the TFT screen co-ordinates. They’re not perfect however; I see values like 805 at the far right of my 800 X 480 screen, and 474 at the bottom. But they are accurate enough to be perfectly useable. These calibration values do not survive a power-off cycle. You would have to perform this same calibration every time you powered the display off/on.

The EVE3 driver library and example file supplied by Matrix Orbital saves these six 32-bit calibration values to a file on an SD card. This eliminates the need for this manual calibration at every power-on cycle. In my code, I substituted code to write/read these six registers to Teensy3.5 EEPROM memory, in place of the SD card storage method. This uses the standard Arduino EEPROM library, so it should work with other MCUs that work with Arduino, such as Atmel AVR MCUs.

RESPONDING TO USER TOUCHES

The TFT displays that I have used in the past generally had resistive touchscreens. To monitor when the user has touched such a screen, your program generally had to periodically call a routine that read two ADC values—one for the X dimension and one for the Y dimension. The proper scaling of that ADC data to the screen coordinates had already been done in some calibration routine performed by the user. If all you needed were the X and Y coordinates of the point touched, you were all set.

With the BT815 controller, the touch operations are more sophisticated. There is a function to call to see if the screen has been touched, and a 32-bit register to read, which contains both the X and Y values that must be separated out. The BT815 also has an interrupt output pin that can be programmed to provide an interrupt signal to the host MCU when the screen is touched. While optional, this interrupt capability might be useful if the host MCU is so busy with other tasks that it can’t poll the BT815’s touch registers on a timely basis.

To use this interrupt capability, two code snippets in Listing 1 are needed. You would, of course, need to add code to enable a pin-change interrupt on your host MCU, in addition to the above. You’d also need to wire up the BT815’s interrupt output pin to whatever MCU pin you assigned that pin-change interrupt.

I should mention that in terms of BT81x touchscreen operation, the Bridgetek Programmer’s Guide only describes the resistive touchscreen registers/operation. You have to refer to the FT81X EVE datasheet for information on both the capacitive and resistive touchscreen registers and operation.

Beyond simply knowing the coordinates of a user’s touch, you are generally more interested in what widget or other object was touched. With other graphics controllers, you basically have to take the X, Y coordinates of the touch and run them through a nested if structure, which checks to see if the touch occurred within the physical space of each object of interest, one by one. It’s not rocket science, but it does involve a lot of code, if many objects are on the screen. If you decide, for esthetic reasons, to move a button on the screen, for example, you would have to re-code the if structure to reflect the new physical coordinates.

The BT815 makes this whole process much simpler. Initially, as you define the various objects on your screen, you can assign a TAG value for each object. You have to put this TAG command in your display list just ahead of the command that defines that widget (or other object). Each TAG must be given a unique byte value in the 1-255 range. Alternately, you can assign the TAG a single letter value, for example, “A.” In that case, the TAG will take on the ASCII value of that character. The program code is:

— ADVERTISMENT—

Advertise Here

Send_CMD(TAG(1)); // tags with a value of 1 (byte value of 1, not the ASCII for the letter”1”)

In your program, to check if the user has touched any of the tagged screen objects, you only need something like the code in Listing 2.

Listing 1
This is the code needed to allow the BT815 to generate interrupts on screen touches
	
// Enable BT815 touch interruptuint8_t IntSt; wr8(REG_INT_EN + RAM_REG, 1); // enable interrupts wr8(REG_INT_MASK + RAM_REG, 2);  // enable only the touch interrupt// poll the INT_FLAGS register to see if a touch has occurred    IntSt = rd8(REG_INT_FLAGS + RAM_REG);   // IntSt = 0 until interrupt has occurred.
Listing 2
This code decides which screen widget was touched by examining the TAG value returned by the BT815.

Tag = rd8(REG_TOUCH_TAG + RAM_REG); // Check for touches						   		// RAM_REG = 0x302000  if (Tag)      {         switch (Tag)         {         case 1:  Button1_routine();    // when TAG=1         case 2:  Button2_routine();    // when TAG=2		….etc	       }
GESTURES

A few types of gestures are easily handled by the BT815’s capacitive touchscreen controller, along with a bit of code on the host MCU. The simplest would be left/right/up/down swipes. Two-finger, Zoom-in, Zoom-out gestures can also be recognized on a TFT display containing a capacitive touchscreen/BT815 controller, because the capacitive touch controller can detect up to five simultaneous touches.

It’s important to note that the BT815 and touchscreen controller combination operates in two modes. By default, the initialization routine for the BT815 configures it in the Compatibility mode. In this mode, the BT815 only responds to one touch at a time, but it must be in this mode to perform the touchscreen calibration routine that I’ve mentioned earlier.

In the Extended mode, the BT815/touchscreen controller can report on up to five touches. To make this switch, just add the following to your code, making sure it is placed after any call to the Calibrate routine:

wr8(REG_CTOUCH_EXTEND + RAM_REG, 0);

To detect swipes, you keep track of the X, Y coordinates whenever a touch is detected. A swipe involves moving your finger across the screen, without lifting it until the swipe is complete. The REG_TOUCH_TAG register will contain zero if there are no touches, and the TAG number if a tagged object is touched. If you poll this register, as soon as it goes non-zero, you read the REG_CTOUCH_TOUCH_XY register and store the current X, Y coordinates. As soon as the REG_TOUCH_TAG register returns to zero, the swipe is over. Then, you read the latest X, Y co-ordinates and calculate the deltaX and deltaY values. If either or both of these values are greater than a threshold (such as 75 pixels), you have detected a valid horizontal or vertical swipe (possibly both if you swiped diagonally).

Detecting two-finger, Zoom-in Zoom-out gestures is more complex, and requires more code on the Host MCU. It works similarly to the swipe routine above, but captures the starting/final coordinates for two touches and bases its results on calculations using both sets. The code is too involved to include here, but can be found in the checkGestures() routine in my example program (see Circuit Cellar’s article code and file download webpage).

For this routine, I don’t use the BT815’s Interrupt pin/status register mentioned in the last section. Instead, I just poll the REG_CTOUCH_TOUCH_XY and REG_CTOUCH_TOUCH1_XY registers. The first will contain 0x80008000 when no touch is detected, and the second will indicate the same if more than two touches are present. I decided to do it this way because there are likely many times when swipes/gestures are not relevant, and I didn’t want to write up an interrupt service routine on the Host MCU (using the interrupt enabling code in the last section), which would monitor/report such gestures at times when they were not applicable. Checking for swipes is also performed in the checkGestures() routine mentioned above.

DISPLAYING BITMAP IMAGES

There are several ways to store the bitmap images you wish to display on your screen. The BT815 EVE display processor can handle several different “raw” (uncompressed) bitmap formats, along with the JPG (lossy compression) and PNG (lossless compression) formats. I have found that the BT815 renders JPEG images so rapidly that there is no reason to tolerate the larger files that you would need if you used any of the raw (uncompressed) formats.

I have tried a few JPG files and found that they can be rendered “as-is” by the EVE display processor. However, BridgeTek’s EVE Asset Builder program contains a utility to “validate” PNG/JPG files before using them. If you click on the “Image Utilities” icon at the top of the Asset Builder program, you will see a tab labelled PNG/JPG Validator (Figure 6). Once you choose an output folder and add a file to the list, you can click on Validate and it will produce a file with the same name followed by “_converted.” Although I haven’t been able to find out exactly what Bridgetek is “validating” here, the validated files are about 2/3 of the size of the original JPG file. Noting that the EVE display processor uses a color depth of 16 bits (RGB565), and that color JPG files generally have a 24-bit color depth, the size reduction by 1/3 can likely be attributed to this reduced color resolution. I have loaded straight JPG files as well as the smaller, validated versions, and found there to be no noticeable difference in image quality. However, the validated versions take up less memory space, and I expect they would render a bit more quickly, though this is not visible to my eyes.

Figure 6
PNG/JPEG Validator function found under the Image Utilities tab of the EVE Asset Builder application. This validates/converts PNG or JPEG image files into a form useable by the BT815 display controller.
Figure 6
PNG/JPEG Validator function found under the Image Utilities tab of the EVE Asset Builder application. This validates/converts PNG or JPEG image files into a form useable by the BT815 display controller.
LOADING BITMAP FILES FROM MCU PROGRAM FLASH

The examples shown in the following sections are taken from my “BT81x_Example_BM” program and associated “BT81x.cpp” and “BT81x.h” library files (see Circuit Cellar’s article code and file download webpage).

So, how do you get these JPG files into the EVE display processor for rendering? If you need only a few small ones, it’s possible to convert the JPG (binary) files into text-based “C” arrays and #include them as part of your MCU’s program. In this case, since you are reading the bitmap directly from your MCU’s flash memory, the rendering of the bitmap on the screen will be quite rapid, depending somewhat on your SPI clock rate. I am using a 12MHz SPI clock, and it’s just a blink of an eye to render even a full-screen JPG image.

One way to convert a JPG-formatted file into a text-based “C” array is to use Bridgetek’s EVE Asset Builder program (which you will be using for other purposes, anyway). In the EVE asset Builder, select the BIN2C icon at the top of the screen. You will see a screen as shown in Figure 7.

Figure  7
Bin2C utility function in the EVE Asset Builder application. This converts an image file (or other binary source) into a text file, formatted as a “C” array containing all the binary values. This file can then be included with your Host MCU’s program code, giving that program access to the JPEG image data.
Figure 7
Bin2C utility function in the EVE Asset Builder application. This converts an image file (or other binary source) into a text file, formatted as a “C” array containing all the binary values. This file can then be included with your Host MCU’s program code, giving that program access to the JPEG image data.

Once you’ve chosen a JPG (or other binary ) file, and have defined an output folder, you just click Convert. Note that you have the choice of data type for the array: byte, word or long, and either decimal or hex representations. If you stick with byte, you don’t have to worry whether your MCU’s C compiler uses little-endian or big-endian format. However, for larger arrays, it might be better to use the long type (32 bits) since it produces a more compact text listing, and it’s faster reading in 32-bit words than bytes. However, in this case, you will have to get the endian value correct, or the resulting “C” array list will be all mixed up. As an example, if a JPG file contains FF D8 FF E0 at the start, saving it in little- endian format will look like:

— ADVERTISMENT—

Advertise Here

0xE0FFD8FF

and the big-endian format will be:

0xFFD8FFE0

So, unless you know the correct endian setting, you’re well-advised to print out the first entry of the array you’ve generated, to make sure the word value agrees with the first 4 bytes in the array list.

The Bin2C function in the EVE Asset Builder generates an array labeled “data.” You will have to edit this name in that file, if you are already using a variable labeled “data” in your program, or if you are generating “C” arrays for more than one JPG image.

The Bin2C function has a few issues that need to be fixed before using its output file. The code that this utility produces places a comma after the last element in the array. That comma should be removed. Following the last array value, the Bin2C utility places a } where it should place a };

Using the current version of the Arduino compiler, these syntax mistakes are flagged as an error—one that shows up for me as a meaningless error message referencing the first program line after the “#include filename” statement, which adds this data array to your program.

The other issue is that such flash-based data arrays are, by convention, added to a program using an “.h” file, not a “.c” file. So, personally, I change this file extension to “.h” for consistency. Because these files are processed by a routine in the BT81x.cpp, the “#include filename” statement must be placed in this BT81x.cpp file.

Assuming that the binary file that you have processed in this way was a JPEG file, and your array is defined in bytes, the way that you would transfer it into EVE RAM is shown in Listing 3.

In the third line, BaseAdd refers to the location to store it in EVE RAM. This is normally set to RAM_G (defined as 0) for the first image. If you had several images, subsequent ones would have to be stored at higher RAM locations. The Options value is also zero.

The CMD_LOADIMAGE Coprocessor command will take a byte-stream from a JPEG or PNG file and expand it into an uncompressed (RGB565) bitmap image that the EVE controller can handle.

To determine the image’s width, height and memory needed for the expanded image, the routine shown in Listing 4 is used.

The BT81x’s Programming Guide states that the “ptr” variable (above) will contain the last memory location used in the G_RAM memory by the expanded image file. However, I have found this to not be the case.

Instead, I found that “ptr” is equal to the Starting address in G_RAM (which you already know, since you specified it). So, I calculate this FinalAddress by multiplying the width and height of the image by 2 (the number of bytes per pixel).

To render that image, the code in Listing 5 is needed.

The correct values for Xsize and Ysize would be the values of the w,h variables (determined by the GETPROPS command in the previous section of code). Also, note that the EVE Asset Builder program will show the X and Y dimensions of an image while you are working with it.

The Windows File Manager will also show this information if you right-click on Properties while focused on the file.

Listing 3
This is the code that transfers a JPEG file, saved in a C array, to EVE's RAM

Uint32 Remaining = sizeof(data);  // “data” is the default name of the array generated by 																	 		    // the EVE Asset Builder BIN2C utility.	Send_CMD(CMD_LOADIMAGE);          // Tell the CoProcessor to prepare for compressed dataSend_CMD(BaseAdd);                // This is the address where decompressed data will go  Send_CMD(Options);  while (Remaining)    {        if (Remaining > COPYBUFSIZE)    // COPYBUFSIZE = 64            ReadBlockSize = COPYBUFSIZE;        else            ReadBlockSize = Remaining;        for (uint16_t j = 0; j < ReadBlockSize; j++) {            LogBuf[j] = data[index];            index++;        }        CoProWrCmdBuf(LogBuf, ReadBlockSize);   // write the block to FIFO - 							                                     // Does FIFO triggering        Remaining -= ReadBlockSize;    // Reduce remaining data value by amount just read    }Wait4CoProFIFOEmpty();
Listing 4
This code will extract an image's height, width and memory size from the compressed image file

int props_start_address = rd32(REG_CMD_READ + RAM_REG); // the CMD_GETPROPS command will					// write the results into the fifo buffer, so we have to keep track what address						// we are currently at.    Send_CMD(CMD_GETPROPS);      // Tell the CoProcessor we would like to query the				                          // properties of the last compressed image. // Because the command will write into the fifo buffer we will have to reserve space for the// output of all output variables    Send_CMD(0);       // +4 ptr    Send_CMD(0);       // +8 width    Send_CMD(0);       // +12 height    UpdateFIFO();      // force run the GetProps command, so we can read back the results     ptr = rd32(props_start_address + RAM_CMD + 4);    // Read back the ptr variable     w = rd32(props_start_address + RAM_CMD + 8);      // Read back the width variable     h = rd32(props_start_address + RAM_CMD + 12);     // Read back the height variable   FinalAddress = BaseAdd + 2 * (w * h);
Listing 5
This code renders the compressed image

// Screen start  Send_CMD(CMD_DLSTART);                    // Start a new display list  Send_CMD(CLEAR_COLOR_RGB(255,255,255));   // Set the color for clearing to full white  Send_CMD(CLEAR(1, 1, 1));                 // clear screen     // Define the bitmap  Send_CMD(BITMAP_HANDLE(Reference));                  // handle for this bitmap  Cmd_SetBitmap(BMPBaseAdd, RGB565, Xsize, Ysize);     // Use the CoProc Command to fill in 																															 // the bitmap parameters   // Place the bitmap  Send_CMD(BEGIN(BITMAPS));  Send_CMD(BITMAP_HANDLE(Reference));        // reference =0 normally  Send_CMD(VERTEX2II(Xloc, Yloc, Reference, 0));  // Define the X,Y position of the bitmap     Send_CMD(END());                               // end of placing bitmaps   Send_CMD(DISPLAY());                            // End display list  Send_CMD(CMD_SWAP);                             // Activate this display list  UpdateFIFO();             				                 // Trigger the CoProcessor to process FIFO
What’s next

In Part 2 of this article, I’ll describe how to handle bitmap images from other sources, such as SD cards and flash memory connected to the BT815 directly.

I’ll also discuss some other bitmap-related functions such as bar graphs and waterfall displays. These require a somewhat deeper understanding of the controller than you would need to place widgets and other objects using the EVE Screen Editor application.

Code Files

REFERENCES
[1] Download driver files for the EVE2-USB2SPI board:
https://www.matrixorbital.com/index.php?route=download/download/get&did=1378

RESOURCES
Matrix Orbital EVE3-50G-IPS 5” Display module with capacitive touchscreen:
https://www.matrixorbital.com/eve3-50g-ips
Matrix Orbital EVE2-USB2SPI-KIT-A:
https://www.matrixorbital.com/eve2-usb2spi-kit-a
Matrix Orbital EVE-SPI2BBC breakout module:
https://www.matrixorbital.com/eve-spi2bbc

PUBLISHED IN CIRCUIT CELLAR MAGAZINE • MAY 2024 #382 – Get a PDF of the issue

Keep up-to-date with our FREE Weekly Newsletter!

Don't miss out on upcoming issues of Circuit Cellar.


Note: We’ve made the Dec 2022 issue of Circuit Cellar available as a free sample issue. In it, you’ll find a rich variety of the kinds of articles and information that exemplify a typical issue of the current magazine.

Would you like to write for Circuit Cellar? We are always accepting articles/posts from the technical community. Get in touch with us and let's discuss your ideas.

Sponsor this Article
+ posts

Brian Millier runs Computer Interface Consultants. He was an instrumentation engineer in the Department of Chemistry at Dalhousie University (Halifax, NS, Canada) for 29 years.

Supporting Companies

Upcoming Events


Copyright © KCK Media Corp.
All Rights Reserved

Copyright © 2026 KCK Media Corp.

Building a Touchscreen Display

by Brian Millier time to read: 27 min