Choose your OS

Take a moment to decide which operating system you'd like to use. Consider the following limitations.

  • Raspberry Pi OS has support for CircuitPython libraries and works with the 1.9in TFT screen for displaying GIFs and information like the Pi's IP address, CPU temperature, etc.
  • Ubuntu variants but does not have support for CircuitPython libraries.

USB Boot with SSD

If you'd like to boot your Pi using an external SSD, you can follow along with the guide from Tom's Hardware. This process utilizes the full storage and features faster performance when reading/writing. This can be a good option for running a media server. 

Ensure only the USB SSD is connected on the initial boot. Pi OS WILL NOT start if any other USB devices such as keyboard/mouse are connected.

Install OS with Raspberry Pi Imager

Download the Raspberry Pi Imager and choose your preferred OS and method of booting.

If you're using Pi OS, click the gear icon to open advanced options.  There you can enable SSH and configure wireless LAN.

Install CircuitPython Libraries

If you'd like to use the 1.9in TFT display, follow the installation guide to get setup.

TFT Display Info

Use the Python script below to display the IP address, CPU temperature and disk/memory usage. Make sure you have the fonts directory installed in the same directory as the Python script. 

# SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries
# SPDX-License-Identifier: MIT

import subprocess
import board
import displayio
from adafruit_display_text import label
from adafruit_bitmap_font import bitmap_font
from adafruit_st7789 import ST7789

BORDER_WIDTH = 4
TEXT_SCALE = 1

font = bitmap_font.load_font("/home/pi/fonts/Arial-18.bdf")
font_small = bitmap_font.load_font("/home/pi/fonts/Arial-14.bdf")
font_bold = bitmap_font.load_font("/home/pi/fonts/Arial-Bold-24.bdf")

# Release any resources currently in use for the displays
displayio.release_displays()

spi = board.SPI()
tft_cs = board.CE0
tft_dc = board.D25
tft_rst = board.D24

display_bus = displayio.FourWire(spi, command=tft_dc, chip_select=tft_cs, reset=tft_rst)

display = ST7789(display_bus, width=320, height=170, colstart=35, rotation=270)

# Make the display context
splash = displayio.Group()
display.root_group = splash

color_bitmap = displayio.Bitmap(display.width, display.height, 1)
color_palette = displayio.Palette(1)
color_palette[0] = 0xAA0088  # Purple
bg_sprite = displayio.TileGrid(color_bitmap, pixel_shader=color_palette, x=0, y=0)
splash.append(bg_sprite)

# Draw a smaller inner rectangle
inner_bitmap = displayio.Bitmap(
    display.width - (BORDER_WIDTH * 2), display.height - (BORDER_WIDTH * 2), 1
)
inner_palette = displayio.Palette(1)
inner_palette[0] = 0x222222  # Dark Gray
inner_sprite = displayio.TileGrid(
    inner_bitmap, pixel_shader=inner_palette, x=BORDER_WIDTH, y=BORDER_WIDTH
)
splash.append(inner_sprite)

# display ip, cpu and memory usage
cmd = "hostname -I | cut -d' ' -f1"
IP = subprocess.check_output(cmd, shell=True).decode("utf-8")
cmd = 'cut -f 1 -d " " /proc/loadavg'
CPU = subprocess.check_output(cmd, shell=True).decode("utf-8")
cmd = "free -m | awk 'NR==2{printf \"Mem: %s/%s MB  %.2f%%\", $3,$2,$3*100/$2 }'"
MemUsage = subprocess.check_output(cmd, shell=True).decode("utf-8")
cmd =  "vcgencmd measure_temp | grep -o -E '[[:digit:]].*'"
cpu_temp = subprocess.check_output(cmd, shell=True).decode("utf-8")

# Draw a label
text_ip = label.Label(
    font_bold,
    text="IP: " + IP,
    color=0x1BF702,
    scale=TEXT_SCALE,
)
text_cpu = label.Label(
    font,
    text="CPU: " + cpu_temp,
    color=0xFFFFFF,
    scale=TEXT_SCALE,
)
text_mem = label.Label(
    font_small,
    text=MemUsage,
    color=0xCCCCCC,
    scale=TEXT_SCALE,
)
text_ip.x = 12
text_cpu.x = 12
text_mem.x = 12

text_ip.y = 30
text_cpu.y = 70
text_mem.y = 150

splash.append(text_ip)
splash.append(text_cpu)
splash.append(text_mem)

while True:
    text_ip.text = "IP: " + IP
    text_cpu.text = "CPU:" + cpu_temp
    text_mem.text = MemUsage
    display.refresh()

Download and copy the Python file to the home folder on your Raspberry Pi. Use the command in terminal to run the Python script: sudo nano python3 display-info.py

TFT Display GIF images

Use the python script below to display GIF images on the TFT display. You can use buttons to cycle through the images. Make sure you have the GIF images in a directory named GIFs. Ensure the python script is in a directory just outside the GIFs directory.

# SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries
# SPDX-License-Identifier: MIT

import os
import time
import digitalio
import board
from PIL import Image, ImageOps
import numpy  # pylint: disable=unused-import
from adafruit_rgb_display import st7789  # pylint: disable=unused-import

# Change to match your display
BUTTON_NEXT = board.D17
BUTTON_PREVIOUS = board.D22

# Configuration for CS and DC pins (these are PiTFT defaults):
cs_pin = digitalio.DigitalInOut(board.CE0)
dc_pin = digitalio.DigitalInOut(board.D25)
reset_pin = digitalio.DigitalInOut(board.D24)

def init_button(pin):
    button = digitalio.DigitalInOut(pin)
    button.switch_to_input()
    button.pull = digitalio.Pull.UP
    return button

# pylint: disable=too-few-public-methods
class Frame:
    def __init__(self, duration=0):
        self.duration = duration
        self.image = None

# pylint: enable=too-few-public-methods

class AnimatedGif:
    def __init__(self, display, width=None, height=None, folder=None):
        self._frame_count = 0
        self._loop = 0
        self._index = 0
        self._duration = 0
        self._gif_files = []
        self._frames = []

        if width is not None:
            self._width = width
        else:
            self._width = display.width
        if height is not None:
            self._height = height
        else:
            self._height = display.height
        self.display = display
        self.advance_button = init_button(BUTTON_NEXT)
        self.back_button = init_button(BUTTON_PREVIOUS)
        if folder is not None:
            self.load_files(folder)
            self.run()

    def advance(self):
        self._index = (self._index + 1) % len(self._gif_files)

    def back(self):
        self._index = (self._index - 1 + len(self._gif_files)) % len(self._gif_files)

    def load_files(self, folder):
        gif_files = [f for f in os.listdir(folder) if f.endswith(".gif")]
        for gif_file in gif_files:
            gif_file = os.path.join(folder, gif_file)
            image = Image.open(gif_file)
            # Only add animated Gifs
            if image.is_animated:
                self._gif_files.append(gif_file)

        print("Found", self._gif_files)
        if not self._gif_files:
            print("No Gif files found in current folder")
            exit()  # pylint: disable=consider-using-sys-exit

    def preload(self):
        image = Image.open(self._gif_files[self._index])
        print("Loading {}...".format(self._gif_files[self._index]))
        if "duration" in image.info:
            self._duration = image.info["duration"]
        else:
            self._duration = 0
        if "loop" in image.info:
            self._loop = image.info["loop"]
        else:
            self._loop = 1
        self._frame_count = image.n_frames
        self._frames.clear()
        for frame in range(self._frame_count):
            image.seek(frame)
            # Create blank image for drawing.
            # Make sure to create image with mode 'RGB' for full color.
            frame_object = Frame(duration=self._duration)
            if "duration" in image.info:
                frame_object.duration = image.info["duration"]
            frame_object.image = ImageOps.pad(  # pylint: disable=no-member
                image.convert("RGB"),
                (self._width, self._height),
                method=Image.Resampling.NEAREST,
                color=(0, 0, 0),
                centering=(0.5, 0.5),
            )
            self._frames.append(frame_object)

    def play(self):
        self.preload()

        _prev_advance_btn_val = self.advance_button.value
        _prev_back_btn_val = self.back_button.value
        # Check if we have loaded any files first
        if not self._gif_files:
            print("There are no Gif Images loaded to Play")
            return False
        while True:
            for frame_object in self._frames:
                start_time = time.monotonic()
                self.display.image(frame_object.image)
                _cur_advance_btn_val = self.advance_button.value
                _cur_back_btn_val = self.back_button.value
                if not _cur_advance_btn_val and _prev_advance_btn_val:
                    self.advance()
                    return False
                if not _cur_back_btn_val and _prev_back_btn_val:
                    self.back()
                    return False

                _prev_back_btn_val = _cur_back_btn_val
                _prev_advance_btn_val = _cur_advance_btn_val
                while time.monotonic() < (start_time + frame_object.duration / 1000):
                    pass

            if self._loop == 1:
                return True
            if self._loop > 0:
                self._loop -= 1

    def run(self):
        while True:
            auto_advance = self.play()
            if auto_advance:
                self.advance()


# Config for display baudrate (default max is 64mhz):
BAUDRATE = 64000000

# Setup SPI bus using hardware SPI:
spi = board.SPI()

disp = st7789.ST7789(
    spi,
    rotation=270,
    width=170,
    height=320,
    x_offset=35,
    cs=cs_pin,
    dc=dc_pin,
    rst=reset_pin,
    baudrate=BAUDRATE,
)
# pylint: enable=line-too-long

if disp.rotation % 180 == 90:
    disp_height = disp.width  # we swap height/width to rotate it to landscape!
    disp_width = disp.height
else:
    disp_width = disp.width
    disp_height = disp.height

gif_player = AnimatedGif(disp, width=disp_width, height=disp_height, folder="gifs")

Download and copy the Python file to the home folder on your Raspberry Pi. Use the command in terminal to run the Python script: sudo nano python3 gif-player.py

Autostart TFT python scripts

Create an autostart script to enable the TFT display automatically on boot. Start by creating a new file with this command. 

sudo nano /lib/systemd/system/tftdisplay.service

Copy the text below and paste it into the new file. Check the .py file is in the correct directory. You may need to modify the path if you've changed the Pi's hostname or title of Python script.  

[Unit]
Description=TFT Display Service
After=multi-user.target

[Service]
Type=idle
ExecStart=/usr/bin/python /home/pi/tft-display.py

[Install]
WantedBy=multi-user.target

Save this file and exit the editor. A few commands then enable this new service and will launch it on startup:

sudo systemctl daemon-reload
sudo systemctl enable tftdisplay.service
sudo reboot

To change which program is loaded on startup, just edit /lib/systemd/system/tftdisplay.service and change the ExecStart line, and reboot. No need to repeat the systemctl commands.

Enable Safe Shutdown

You can use the metal push button to safely shutdown your Pi. Add a line to the config.txt file using the sudo nano /boot/config.txt command in terminal.

In the circuit diagram, the metal push button is wired to both GPIO3 and GPIO27. By default, ground and GPIO3 will power up the Pi. GPIO27 is used to safely shutdown the Pi. 

dtoverlay=gpio-shutdown,gpio_pin=27,active_low=0

Save the config.txt file and exit the editor. Reboot the Raspberry Pi and the pressing the power button should run the safe shutdown service.

Install Plex Media Server

If you'd like to use the Pi as a media center, you can install Plex on the Raspberry Pi. This will use the Chromium web browser to display the Plex interface where you can play and stream media.

Head over to the plex website and choose Linux as the platform and then click Choose Distribution. Select the Ubuntu ARM (v7 for 32-bit Pi OS) and download.

Once downloaded, double-click the file to install the software. When complete, Head over to localhost:32400/web/ where you should see the Plex log in screen. There you can configure your settings and add media.

Follow along with the Basic Setup Wizard guide from Plex for more documentation on configuring your settings. 

Autostart Chromium in Full Screen Kiosk Mode

You can launch Chromium in full screen mode by adding a line to the autostart file on the LXDE desktop. Run the following command in terminal to edit the configuration file.

sudo nano /etc/xdg/lxsession/LXDE-pi/autostart

Modify the autostart file by adding the line below and save with key combination Ctrl+ X then Y.

@lxpanel --profile LXDE-pi
@pcmanfm --desktop --profile LXDE-pi
@xscreensaver -no-splash
/usr/bin/chromium-browser --kiosk --disable-restore-session-state http://localhost:32400/web/

Reboot the Raspberry Pi to test the updated autostart file. Chromium will automatically open and display the Plex interface by loading the http://localhost:32400/web/ website.

This guide was first published on Aug 31, 2022. It was last updated on Mar 28, 2024.

This page (Software Setup) was last updated on Mar 28, 2024.

Text editor powered by tinymce.