You probably saw these displays and thought "I really want to use more than one at the same time!" since they're so compact and give off a vibe reminiscent of a certain vintage cathode display. You can do this with some Python code and the Adafruit CircuitPython RGB Display module.
Since there's dozens of Linux computers/boards you can use we will show wiring for Raspberry Pi. For other platforms, please visit the guide for CircuitPython on Linux to see whether your platform is supported.
Wiring
Connect the four displays as shown to your Raspberry Pi. This wiring can get tricky so take your time and doublecheck your breadboard of spaghetti. The first two TFT displays are connected to the default SPI0 port on the Raspberry Pi. The second two TFT displays are connected to the secondary SPI1 port on the Raspberry Pi. The SCK and MOSI pins can be shared between displays on the same SPI port. Each display requires its own GPIO pin for CS and DC.
Traditionally, CE0 has been used for the CS pin on SPI displays with Raspberry Pi. In testing though, using the CE0 pin from SPI0 and/or SPI1 caused glitching on the connected displays. When using multiple SPI displays, avoid that pin for CS.
TFT0 (SPI0)
- Pi 3.3V to TFT V+ (red wire)
- Pi GND to TFT G (black wire)
- Pi SCK to TFT CL (yellow wire)
- Pi MOSI to TFT DA (blue wire)
- Pi GPIO23 to TFT CS (green wire)
- Pi GPIO25 to TFT DC (white wire)
TFT1 (SPI0)
- Pi 3.3V to TFT V+ (red wire)
- Pi GND to TFT G (black wire)
- Pi SCK to TFT CL (yellow wire)
- Pi MOSI to TFT DA (blue wire)
- Pi CE1 to TFT CS (grey wire)
- Pi GPIO24 to TFT DC (ochre wire)
TFT2 (SPI1)
- Pi 3.3V to TFT V+ (red wire)
- Pi GND to TFT G (black wire)
- Pi GPIO21 (SCK1) to TFT CL (orange wire)
- Pi GPIO20 (MOSI1) to TFT DA (cyan wire)
- Pi GPIO16 to TFT CS (pink wire)
- Pi GPIO26 to TFT DC (purple wire)
TFT3 (SPI1)
- Pi 3.3V to TFT V+ (red wire)
- Pi GND to TFT G (black wire)
- Pi GPIO21 (SCK1) to TFT CL (orange wire)
- Pi GPIO20 (MOSI1) to TFT DA (cyan wire)
- Pi GPIO17 to TFT CS (brown wire)
- Pi GPIO27 to TFT DC (white wire)
Enable Secondary SPI1 Port
You'll need to enable the secondary SPI1 port on the Raspberry Pi. You can do this by editing the /boot/config.txt file. In a terminal, enter:
sudo nano /boot/config.txt
Add this line to /boot/config.txt:
dtoverlay=spi1-3cs
Save your changes to the file and then reboot the Raspberry Pi.
Software Setup
You'll need to install the Adafruit_Blinka library that provides the CircuitPython support in Python. This may also require enabling SPI on your platform and verifying you are running Python 3. Since each platform is a little different, and Linux changes often, please visit the CircuitPython on Linux guide to get your computer ready!
Python Installation of RGB Display Library
Once that's done, from your command line run the following command:
sudo pip3 install adafruit-circuitpython-rgb-display
If your default Python is version 3 you may need to run 'pip' instead. Just make sure you aren't trying to use CircuitPython on Python 2.x, it isn't supported!
If that complains about pip3 not being installed, then run this first to install it:
sudo apt-get install python3-pip
DejaVu TTF Font
Run the following command to install the DejaVu font:
sudo apt-get install fonts-dejavu
Pillow Library
We also need PIL, the Python Imaging Library, to allow graphics and using text with custom fonts. There are several system libraries that PIL relies on, so installing via a package manager is the easiest way to bring in everything:
sudo apt-get install python3-pil
If you installed the PIL through PIP, you may need to install some additional libraries:
sudo apt-get install libopenjp2-7 libtiff5 libatlas-base-dev
Python Usage
Once you have all of the requirements installed on your Raspberry Pi, copy or download the following example, and run the following, replacing code.py with whatever you named the file:
python3 code.py
# SPDX-FileCopyrightText: 2025 Liz Clark for Adafruit Industries # # SPDX-License-Identifier: MIT import time from threading import Thread, Lock from PIL import Image, ImageDraw, ImageFont import digitalio import board import busio from adafruit_rgb_display import st7789 BAUDRATE = 25000000 WIDTH = 135 HEIGHT = 240 cs_pin0 = digitalio.DigitalInOut(board.D23) dc_pin0 = digitalio.DigitalInOut(board.D25) cs_pin1 = digitalio.DigitalInOut(board.CE1) dc_pin1 = digitalio.DigitalInOut(board.D24) cs_pin2 = digitalio.DigitalInOut(board.D16) dc_pin2 = digitalio.DigitalInOut(board.D26) cs_pin3 = digitalio.DigitalInOut(board.D17) dc_pin3 = digitalio.DigitalInOut(board.D27) spi = board.SPI() spi1 = busio.SPI(clock=board.D21, MOSI=board.D20) disp0 = st7789.ST7789(spi, rotation=180, width=WIDTH, height=HEIGHT, x_offset=53, y_offset=40, cs=cs_pin0, dc=dc_pin0, baudrate=BAUDRATE) disp1 = st7789.ST7789(spi, rotation=180, width=WIDTH, height=HEIGHT, x_offset=53, y_offset=40, cs=cs_pin1, dc=dc_pin1, baudrate=BAUDRATE) disp2 = st7789.ST7789(spi1, rotation=180, width=WIDTH, height=HEIGHT, x_offset=53, y_offset=40, cs=cs_pin2, dc=dc_pin2, baudrate=BAUDRATE) disp3 = st7789.ST7789(spi1, rotation=180, width=WIDTH, height=HEIGHT, x_offset=53, y_offset=40, cs=cs_pin3, dc=dc_pin3, baudrate=BAUDRATE) def update_digit_display(disp, get_digit_func): font = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf", 80) while True: digit = get_digit_func() image = Image.new("RGB", (WIDTH, HEIGHT), "black") draw = ImageDraw.Draw(image) text = str(digit) text_width, text_height = draw.textsize(text, font=font) text_x = (WIDTH - text_width) // 2 text_y = (HEIGHT - text_height) // 2 draw.text((text_x, text_y), text, font=font, fill="white") disp.image(image) time.sleep(0.1) counter = 0 counter_lock = Lock() # pylint: disable=global-statement def increment_counter(): global counter while True: with counter_lock: counter = (counter + 1) % 10000 time.sleep(0.01) def digit_0(): with counter_lock: return (counter // 1000) % 10 def digit_1(): with counter_lock: return (counter // 100) % 10 def digit_2(): with counter_lock: return (counter // 10) % 10 def digit_3(): with counter_lock: return counter % 10 Thread(target=increment_counter).start() Thread(target=update_digit_display, args=(disp0, digit_0)).start() Thread(target=update_digit_display, args=(disp1, digit_1)).start() Thread(target=update_digit_display, args=(disp2, digit_2)).start() Thread(target=update_digit_display, args=(disp3, digit_3)).start()
In this demo, the Raspberry Pi counts from 0 to 9999 in a loop. The digits are displayed across the four displays.
The threading module is used in this demo and the clock demo below to multitask updating the four displays.
# SPDX-FileCopyrightText: 2025 Liz Clark for Adafruit Industries # # SPDX-License-Identifier: MIT import time from threading import Thread from PIL import Image, ImageDraw, ImageFont import digitalio import board import busio from adafruit_rgb_display import st7789 BAUDRATE = 10000000 WIDTH = 135 HEIGHT = 240 cs_pin0 = digitalio.DigitalInOut(board.D23) dc_pin0 = digitalio.DigitalInOut(board.D25) cs_pin1 = digitalio.DigitalInOut(board.CE1) dc_pin1 = digitalio.DigitalInOut(board.D24) cs_pin2 = digitalio.DigitalInOut(board.D16) dc_pin2 = digitalio.DigitalInOut(board.D26) cs_pin3 = digitalio.DigitalInOut(board.D17) dc_pin3 = digitalio.DigitalInOut(board.D27) spi = board.SPI() spi1 = busio.SPI(clock=board.D21, MOSI=board.D20) disp0 = st7789.ST7789(spi, rotation=180, width=WIDTH, height=HEIGHT, x_offset=53, y_offset=40, cs=cs_pin0, dc=dc_pin0, baudrate=BAUDRATE) disp1 = st7789.ST7789(spi, rotation=180, width=WIDTH, height=HEIGHT, x_offset=53, y_offset=40, cs=cs_pin1, dc=dc_pin1, baudrate=BAUDRATE) disp2 = st7789.ST7789(spi1, rotation=180, width=WIDTH, height=HEIGHT, x_offset=53, y_offset=40, cs=cs_pin2, dc=dc_pin2, baudrate=BAUDRATE) disp3 = st7789.ST7789(spi1, rotation=180, width=WIDTH, height=HEIGHT, x_offset=53, y_offset=40, cs=cs_pin3, dc=dc_pin3, baudrate=BAUDRATE) def update_digit_display(disp, get_digit_func): font = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf", 80) while True: digit = get_digit_func() image = Image.new("RGB", (WIDTH, HEIGHT), "black") draw = ImageDraw.Draw(image) text = str(digit) text_width, text_height = draw.textsize(text, font=font) text_x = (WIDTH - text_width) // 2 text_y = (HEIGHT - text_height) // 2 draw.text((text_x, text_y), text, font=font, fill="white") disp.image(image) time.sleep(1) def get_hour_tens(): current_time = time.localtime() hour = current_time.tm_hour % 12 or 12 return hour // 10 def get_hour_ones(): current_time = time.localtime() hour = current_time.tm_hour % 12 or 12 return hour % 10 def get_minute_tens(): current_time = time.localtime() minute = current_time.tm_min return minute // 10 def get_minute_ones(): current_time = time.localtime() minute = current_time.tm_min return minute % 10 Thread(target=update_digit_display, args=(disp0, get_hour_tens)).start() Thread(target=update_digit_display, args=(disp1, get_hour_ones)).start() Thread(target=update_digit_display, args=(disp2, get_minute_tens)).start() Thread(target=update_digit_display, args=(disp3, get_minute_ones)).start()
In this demo, the four displays act as a clock. The first two TFTs show the hour and the second two TFTs show the minutes.
Text editor powered by tinymce.