Whilst looking for easy ways to provide live data output from my project, we discovered that a family of small and inexpensive LCDs are available from all the usual online suppliers. The displays come in a few variations ranging from 2.4” to 2.8”, with and without a touchscreen, and have a resolution of 320×240. They have a serial peripheral interface (SPI) which connects with (one of) the SPI ports on the Raspberry Pi.

One of the things these displays have in common is that it’s easy to obtain one that carries the ILI9341 controller. This is a very common controller which although not supported at a hardware level by the Raspberry Pi, is supported by a number of libraries which we can use.

In this quick guide we’re going to take a look at how to connect up one of these displays to the GPIO pins on your Pi, and then set up a multicontainer application on balenaCloud allowing the fbcp-ili9341 display driver to run in one container by itself without disturbing any other containers in the application.


Pinouts & Connection

Whilst these displays appear to be very similar in their board layouts it is not guaranteed that they all use the same pinout. Check the pinout of your particular display matches the layout below first! If it doesn’t match you can easily adjust the diagram to suit, but better to do this before making any bad connections and risking damage to the display.

As we’re using one of the hardware SPI ports on the Raspberry Pi, some of our connections are defined by the location of this port within the GPIO header. Others can use any controllable pin so can be specified to fit alongside other connections your project has, and then can be defined when we compile the driver later.

LCD Pin Function Pi GPIO pin Pi BCM number Colour
1 Power (Vcc 3.3V) 1 N/A White
2 Ground (GND) 6 N/A Black
3 Chip Select (CS) 24 8 (CE0) Blue
4 Reset (RST) 13 27 Red
5 Command/Data (CD) 15 22 Orange
6 SPI MOSI 19 10 (MOSI) Yellow
7 Clock (CLK) 23 11 (SCLK) Green
8 Backlight (LED) 11 17 Brown

Of these connections, you are free to choose alternate pins on the Raspberry Pi side for RST, CD, and the backlight control (LED). If you decide to choose different pins, make a note of this and you’ll need to update the build configuration for the driver later in the guide.

If your project does not require software control of the backlight, you can also connect it straight to the 3.3V supply via an appropriate resistor dependant on the specification of your particular display.


balenaCloud configuration

Now that our display is electronically connected to the GPIO port on the Raspberry Pi, there are a couple of things we need to configure before we work on the driver. It’s assumed here that you’re not using the HDMI output and that the ILI9341 display is your only visual output.

We then need to set some variables in balenaCloud. If all the devices within your application are going to be using the same settings you can set these in the fleet configuration.

As mentioned, this is an SPI-based display, but does not use the standard SPI driver. Outside of balenaCloud SPI would normally be disabled and require you to manually enable it, but when using balenaCloud it is enabled by default which means we need to disable SPI, so ensure RESIN_HOST_CONFIG_dtparam sets spi=off.

If only one of the devices in your fleet is being used with this display you can set the required variables on a per-device basis within the device configuration page (shown below).

Name Value
RESIN_HOST_CONFIG_dtparam (note this is one of the default options) blank
RESIN_HOST_CONFIG_gpu_mem (note this is one of the default options) 512
RESIN_HOST_CONFIG_hdmi_cvt 640 480 60 1 0 0 0
RESIN_HOST_CONFIG_hdmi_force_hotplug 1
RESIN_HOST_CONFIG_hdmi_group 2
RESIN_HOST_CONFIG_hdmi_mode 87

To explain what each of these parameters does:

  • gpu_mem allocates the specified amount of RAM (in MB) to the GPU. The minimum you’ll be able to get away with will vary depending on the resolution you’re running at.
  • hdmi_cvt CVT means custom video timings. Here we are specifying a resolution of 640×480 (2x that of the display, the Pi will scale for us), at 60Hz and an aspect ratio of 4:3. You’re free to choose a resolution depending on your application – some things will look fine scaled, others wont!
  • hdmi_force_hotplug setting this means the Pi will operate as if a monitor was connected via HDMI
  • hdmi_group this is essentially specifying that we are working with a monitor rather than a TV
  • hdmi_mode mode 87 is an unspecified mode allowing us to specify a custom one (with CVT above)

You can find the full documentation for the HDMI parameters in the Raspberry Pi documentation.


Driver container build

Now we have the display connected and the Pi configured correctly, we can set up the container to run the driver.

We’ve created a basic Dockerfile that uses a multistage build in order to download the latest version of the driver from the Github repository and compile it, before transferring the created driver and required libraries to a new image. This means that we’re not deploying any of the tools required to build the driver to our device, but just the resultant driver and libraries that we need.

You can find these files on Github in the balena-io-playground/ILI9341_example repository.

This project is setup in such a way that makes it ready to use as a multicontainer app; in that it already has an example Docker compose file and the fbcp-ili9341 driver in a subdirectory. This is explained in more detail later when we get to the multicontainer setup.

We’re assuming that you’ve already got your device setup with balenaCloud, it’s online in the dashboard, and you’re able to push and deploy code. If not, first take a look at our comprehensive getting started guide, for this example you don’t need to go as far as deploying the example Node app as we’re doing something different.

  1. Clone the example repository:
    git clone [email protected]:balena-io-playground/ILI9341_example.git
  2. Add your balena remote to the example you just cloned (found at the top-right of your application dashboard).
  3. If you’re using alternative connection pins to those I have used above, we need to make some changes (see below).
  4. Push the code to balenaCloud: git push balena master
If you’ve chosen different connection pins…

If you’ve used different connection points for the non-critical connections, we need to make a couple of changes to ILI9341_example/fbcp-ili9341/Dockerfile. Simply, you’re going to alter the BCM pin numbers from the ones I have used to the ones you have used in the following section of the file:

RUN cmake \ -DILI9341=ON \ -DGPIO_TFT_DATA_CONTROL=22 \ -DGPIO_TFT_RESET_PIN=27 \ -DGPIO_TFT_BACKLIGHT=17 \ -DSPI_BUS_CLOCK_DIVISOR=16 \ -DBACKLIGHT_CONTROL=ON \ -DSTATISTICS=0 \ ..

You’ll note here also that backlight control is turned on. This means that when the driver sees that nothing is changing in the output it will turn the LCD backlight off – just something to be aware of as it can look like the display has stopped working when this happens!

Having done all the above and pushed the resultant code to balenaCloud you should see our famous unicorn in your terminal and something similar to the following on your display:

If you don’t see something similar to the above, or perhaps some other corrupt output, it may be necessary to adjust the DSPI_BUS_CLOCK_DIVISOR parameter in the build. The reason for this is that the driver pushes these displays far above their rated specifications to achieve a high level of performance. Variances between manufacturers may mean that some displays can be pushed harder than others. If you encounter problems try a higher, even number. With the display used here, the output would freeze on very low values. There are more details in the readme for the fbcp-ili9341 driver.


Multicontainer setup

As your app is now using a container to run the display driver, you’re going to need to use additional containers to run your application, making it a multicontainer application. We added multicontainer support to balenaCloud earlier this year which allows just that.

For an example, we’re going to use the awesome balena-wpe project in a second container, which provides an easy way to get a fully featured webkit-based browser on the Raspberry Pi. Now that we have our new display configured and setup, we can use the WPE project to display any site we choose on the newly connected display.

As mentioned, the ILI9341 example project has been set up ready for this. Within the project directory we need to pull down the latest balena-wpe code using Git subtree:

git subtree add --prefix balena-wpe g[email protected]:balena-io-projects/balena-wpe.git master --squash 

This creates a copy of the latest code from the master branch within a subfolder called balena-wpe in our project, and the --squash parameter means we don’t get any of the history.

Next, edit the example ILI9341_example\docker_compose.yml and uncomment the commented lines from the file, leaving you with the below:

version: '2' services: fbcp-ili9341: privileged: true restart: always build: ./fbcp-ili9341 balena-wpe: privileged: true build: ./balena-wpe restart: always depends_on: - fbcp-ili9341

Note that specifying the depends_on parameter ensures the display driver is built and runs as a precursor, as we need this no matter what. However bear in mind this currently (at time of writing) only applies to updates, not on power-on or reboot – this will be implemented in the future.

Commit your changes and push the latest changes to balenaCloud again. If all goes well you should see another unicorn and the following on your display:

By default, balena-wpe shows YouTubeTV, but you can override this on an application or per-device basis within the balenaCloud dashboard by setting an environment variable named WPE_URL:

After allowing a few seconds for the data to update and the container to restart, you should have the following (or your own chosen URL) showing on the display:

You can of course now deploy your own application alongside the LCD driver in place of balena-wpe. This is purely an example of what can be achieved using a multicontainer setup, there is a lot of potential left to explore.


Conclusion

The ILI9341 based displays are an inexpensive way to get some visual output from your application. They’re available for around £6/$8US/€7 if you search online.

When used with the fbcp-ili9341 driver, these displays are more than capable of displaying full motion video and things like Jellyfish, which is very impressive.

The viewing angles on the displays we tested were not great to the left and right, above and below were not so bad, but this is likely to vary depending on supplier/batch.

Hopefully this short guide gives a starting point for you to use this display within your own apps on balenaCloud!

If you have any questions, feedback or just want to show off your shiny new app with this display, head over to our forums and let us know!