Setting Up a TFT Touchscreen on a Raspberry Pi with a fbtft Linux Driver

Setting Up a TFT Touchscreen on a Raspberry Pi with a fbtft Linux Driver

Having a small touchscreen in a Raspberry Pi project adds a sense of interactivity and convenience by providing a compact and intuitive user interface. You don't have to plugin your big HDMI screen since your Raspberry Pi already ships with a portable touchscreen. But setting up a small touchscreen can be a bit of hassle. When purchasing a 3.5-inch TFT LCD touch screen from Waveshare, I figured out that the provided installation scripts by Waveshare are both outdated and the provided routine will also overwrite your existing Linux configuration. This leads to losing other configurations, e.g. for the camera. Since this is not intended, in this guide I will explain the necessary steps to setup a Waveshare TFT display with an SPI interface. Also I will provide a script for setting up the display automatically. These are the steps I will touch in this guide:

  1. First we will configure a device tree overlay to enable an fbtft driver for the display. Furthermore this will enable the the device driver for the ADS7846 touch screen controller. Other adjustments to the boot/config.txt on the Raspberry Pi are done as well.

  2. In the second step the XOrg FBdev driver will be configured. This is needed so that the X display server renders to the right framebuffer device which is provided by the fbtft driver enabled in step 1.

  3. For the touchscreen input the XOrg display server needs to be configured as well. Therefore the evdev driver is enabled which is an Xorg input driver for Linux´s generic event devices.

  4. In the last step the touchscreen can be calibrated using the xinput-calibrator app.

TLDR

I put all steps into an installation script on GitHub. Just clone the repo on your Raspberry Pi and execute the script installer-script.sh.

git clone https://github.com/chrizog/rpi-waveshare-lcd-35-setup.git
cd rpi-waveshare-lcd-35-setup
./installer-script.sh

Hardware

For this guide a Raspberry Pi Model 3B+ is used. The OS is the Raspberry PI OS (64-bit) with desktop environment.

pi@raspberrypi:~ $ uname -a
Linux raspberrypi 6.1.21-v8+ #1642 SMP PREEMPT Mon Apr  3 17:24:16 BST 2023 aarch64 GNU/Linux

The display itself is this 3.5" TFT touch screen by Waveshare.

Architecture of the Graphics Stack - Display Server, Kernel Modules and Hardware

Before diving into the first step I will give a brief overview of the graphics stack, so we know what we need to configure. The following picture gives a good overview of the building blocks and the interfaces between them.

Linux graphics stack for a TFT touchscreen using SPI

At the heart of this stack lies the X11 windowing system, which serves as the foundation for graphical user interfaces on Unix-like operating systems. X11 clients, the applications, communicate with the X server via the X11 protocol. The X server manages the display, input devices, and various other resources. The XOrg display server is a widely used implementation of the X server. It acts as an intermediary between the application and the underlying hardware, facilitating the rendering and management of windows, handling user input, and coordinating with the display driver.

Speaking of drivers, the FBdev and evdev Xorg drivers are used. The FBdev driver, short for frame buffer device driver, is an X driver for so called framebuffer devices. It interacts via the framebuffer device /dev/fb1 with the fb_ili9486 fbtft kernel module. This will be explained in a few sentences.

The evdev driver, on the other hand, handles input devices such as keyboards, mice, and touchscreens. It captures input events from these devices and forwards them to the X server, which in turn relays them to the application. This driver ensures that the application can receive and respond to user interactions.

The used hardware consists of an LCD Controller ILI9846 and a touch screen controller ADS7846. The hardware is connected via SPI (Serial Peripheral Interface) and GPIO to the system on chip. In order to communicate with the hardware there are two essential kernel modules needed. The fb_ili9486 kernel module which is an fbtft driver, registers the device /dev/fb1 and allocates the framebuffer inside the kernel. It also handles the low-level details of sending display commands, configuring the display parameters, and transferring pixel data to the screen via SPI. GPIO is used as well e.g. for backlight brightness or power supply. The other kernel module needed is the ADS7846 touch screen driver. This kernel module registers the /dev/input0 device and setups up a GPIO interrupt for touchscreen events. The touchscreen events are then communicated to the Xorg display server and made accessible to the applications.

Step 1: Configure Device Tree Overlays

Now let's start with the first step of the setup. First we will obtain the device tree overlay to configure the waveshare35a device which results in loading the two kernel modules "fb_ili9486" and "ads7846". There is already a compiled device tree overlay available in the waveshare repository. The .dtbo file is downloaded via git and put into the /boot/overlays directory. The source of the overlay can be found on GitHub as well. Afterwards the overlay is enabled in the /boot/config.txt as well. We'll also remove the overlay for vc4-kms-v3d.

git clone https://github.com/waveshare/LCD-show.git
cd LCD-show
git checkout e91282e
sudo cp waveshare35a-overlay.dtb /boot/overlays/waveshare35a.dtbo

rm -rf LCD-show

sed -i 's/dtoverlay=vc4-kms-v3d/# dtoverlay=vc4-kms-v3d/' /boot/config.txt
echo -e "\n[all]\ndtoverlay=waveshare35a" >> /boot/config.txt

After a reboot you shall be able to see the device /dev/fb1 as well as /dev/input0. Also when printing /proc/modules you shall see the two kernel modules "fb_ili9486" and "ads7846" enabled.

Step 2: Configure XOrg FBdev Driver

So far we configured the kernel device drivers. In order to use in the Xorg display server, we need to configure the display server accordingly as well to interact with the devices exposes by the loaded kernel modules.

Since we are using the framebuffer device /dev/fb1 we will configure the X driver FBdev to use that particular framebuffer device. This is done by adding a new file /etc/X11/xorg.conf.d/99-fbdev.conf with the following content:

Section "Device"
  Identifier "FB Driver"
  Driver "fbdev"
  Option "fbdev" "/dev/fb1"
EndSection

After a reboot you shall now be able to see the desktop environment already on the screen. In case you want to show the console during bootup as well you need to map the so called Framebuffer Console to right framebuffer device. This is done by adding the mapping to the /boot/cmdline.txt file:

echo " fbcon=map:10 fbcon=font:VGA8x8" >> "/boot/cmdline.txt"

Step 3: Configure XOrg evdev Input Driver for Touch Events

So far we cared about the output of image data - now let's configure the input from the touchscreen in the X display server. We will use the evdev input driver. It stands for "event device" and provides a generic interface for Xorg to communicate with input devices. In Unix-like systems, including Linux, input devices are typically represented as files in the /dev/input/ directory. These files follow the naming convention /dev/input/event*. The evdev driver interacts with these /dev/input/event* devices by reading the raw input events they generate. It collects information about the input events, such as key presses, mouse movements, or touch coordinates, and passes them to the Xorg server.

First install the Xorg evdev driver package via the apt package manager:

sudo apt-get install xserver-xorg-input-evdev

Afterwards create a new file called 10-evdev.conf under /etc/X11/xorg.conf.d/ with following content:

Section "InputClass"
        Identifier "evdev pointer catchall"
        MatchIsPointer "on"
        MatchDevicePath "/dev/input/event*"
        Driver "evdev"
EndSection

Section "InputClass"
        Identifier "evdev keyboard catchall"
        MatchIsKeyboard "on"
        MatchDevicePath "/dev/input/event*"
        Driver "evdev"
EndSection

Section "InputClass"
        Identifier "evdev touchpad catchall"
        MatchIsTouchpad "on"
        MatchDevicePath "/dev/input/event*"
        Driver "evdev"
EndSection

Section "InputClass"
        Identifier "evdev tablet catchall"
        MatchIsTablet "on"
        MatchDevicePath "/dev/input/event*"
        Driver "evdev"
EndSection

Section "InputClass"
        Identifier "evdev touchscreen catchall"
        MatchIsTouchscreen "on"
        MatchDevicePath "/dev/input/event*"
        Driver "evdev"
EndSection

The last config file we will create is related to the calibration of the touchscreen to ensure a precise registration of the touch point on the screen. Inside /etc/X11/xorg.conf.d/ create one more file called 99-calibration.conf with following content:

Section "InputClass"
	Identifier	"calibration"
	MatchProduct	"ADS7846 Touchscreen"
	Option	"Calibration"	"3932 300 294 3801"
EndSection

The calibration values are the ones I use for my particular display and you can use them as a starting point. Once you've rebooted, the touchscreen should be ready for action. However, should you encounter an unexpected behavior where touching the left side registers an input on the right side (or vice versa), you can change the values to e.g. Option "Calibration" "300 3932 294 3801". This will reverse the x-axis.

Step 4: Calibrate the Touch Screen using xinput-calibrator

For precise touch events it's recommend to perform a calibration using the xinput-calibrator program. This app will show you four points on the screen you need to touch and will calculate the right calibration values to use in the Xorg calibration file. Login to your Raspberry Pi, install and start the calibration:

sudo apt-get install xinput-calibrator
DISPLAY=:0.0 xinput_calibrator

After following the instructions you should get an output similar to that one:

Calibrating EVDEV driver for "ADS7846 Touchscreen" id=6
	current calibration values (from XInput): min_x=3932, max_x=300 and min_y=294, max_y=3801

Doing dynamic recalibration:
	Setting calibration data: 3960, 222, 195, 3922
	--> Making the calibration permanent <--
  copy the snippet below into '/etc/X11/xorg.conf.d/99-calibration.conf' (/usr/share/X11/xorg.conf.d/ in some distro's)
Section "InputClass"
	Identifier	"calibration"
	MatchProduct	"ADS7846 Touchscreen"
	Option	"Calibration"	"3960 222 195 3922"
	Option	"SwapAxes"	"0"
EndSection

Note down the four values under the option "Calibration", edit your file /etc/X11/xorg.conf.d/99-calibration.conf and update these values and reboot afterwards. Your touchscreen is now ready to use!

Summary and Installer Script

In conclusion, while the provided installation scripts by Waveshare may be outdated and overwrite existing Linux configurations, this step-by-step guide offers a reliable alternative. By configuring device tree overlays, XOrg drivers, and calibrating the touchscreen, we setup the touchscreen into our Raspberry Pi project. In case you don't want to follow all the steps manually or automatize your setup, feel free to use the repo and the script I put together on GitHub! Just clone it on your Raspberry Pi and execute the contained bash script:

git clone https://github.com/chrizog/rpi-waveshare-lcd-35-setup.git
cd rpi-waveshare-lcd-35-setup
./installer-script.sh