Code excerpt of the display driver install script

ST7789 display drivers

Getting the SPI display to work

For my build I'm using a 2.0" 320x240 color IPS TFT Display with a ST7789 driver chip and 4-wire SPI communication. “IPS” (In-Plane Switching) promises beautiful, consistent colors with a large viewing angle, high contrast, and fast response time (according to some descriptions).

There are several solutions to display content on the TFT with Raspberry Pi. These are the ones I tried:

Method 1: Drawing images with Python

Using the Python Imaging Library (PIL)

Back when I had received the TFT display, I had temporarily connected it to the SPI0 GPIOs of the Raspberry Pi for an initial test (wiring example here). I followed the setup instructions and sample codes to draw images with Python. The frame refresh rate seemed to be slow with this method.

Although it worked, I didn't like this method of defining shapes in Python or displaying static images. I wanted my TR-590 to play short animations in a loop (prepared as MP4 video files or similar), with the ability to switch between different clips at the touch of a button. So I looked for other options.

Method 2: Program “fbcp-ili9341”

TFT displays with up to 60 fps via SPI

fbcp-ili9341 is a great open source project, supporting different types of TFT driver chips (like ILI9341, ST7735, ST7789 and others). The short description is “A blazing fast display driver for SPI-based LCD displays for Raspberry Pi A, B, 2, 3, 4 and Zero”.

Although the display is connected via the SPI GPIOs, this program does not utilize the default SPI driver, neither the notro/fbtft framebuffer driver (both must be disabled – at least that was the case at the time I tested it). The functionality of the program is documented in great detail on Github:

https://github.com/­juj/­fbcp-ili9341

I followed the installation steps and tried several settings (described on Github) for my screen, like

cmake -DSPI_BUS_CLOCK_DIVISOR=8 -DST7789=ON -DGPIO-TFT-DATA-CONTROL=25 -DGPIO-TFT-RESET-PIN=24 -DUSE_DMA_TRANSFERS=OFF DSINGLE-CORE-BOARD=ON ..

... or ...

cmake -DSPI_BUS_CLOCK_DIVISOR=24 -DST7789=ON -DGPIO-TFT-DATA-CONTROL=25 -DGPIO-TFT-RESET-PIN=24 -DSINGLE-CORE-BOARD=ON -DDMAX-RX-CHANNEL=1 -DDMAX-TX-CHANEL=11 ..

Some tests worked, the TFT displayed the Raspi desktop with a high refresh rate and test videos played fine with VLC player (unfortunately, I did not make any further notes on the tests at the time).

But I had a problem: As already written, the default SPI driver must be disabled. This makes it impossible to use other SPI devices, like the TLC5947 breakout I wanted to use for LEDs.

I asked on Github about the compatibility with other SPI devices. Theoretically, using SPI could be possible. But the method by which fbcp-ili9341 operates can potentially interfere with the standard SPI driver. Testing or fixing this compatibility issue is way beyond my capabilities.

Method 3: “PiTFT helper” installer script

Extending the Open Source script

I had ordered my display from Adafruit and while browsing their website and learning guides I noticed that they provide Open Source Raspberry Pi Installer Scripts for many of their products, including the TFT screens they sell as “PiTFT” (shields placed on a Raspberry Pi).

At the time of my test, the 2.0" 320x240 ST7789 display was not supported by the installation script adafruit-pitft.py. BUT they had just added support for two other displays with the ST7789 driver chip, with 240x240 and 240x135 resolution. This made me curious and I took a closer look at the script.

I duplicated the code snippets used for the 240x240 TFT and changed the variable names. I found the Sitronix ST7789 datasheet online and tried to understand the fb-init sequence, changed the MADCTL definition (avoid cropping), corrected gamma values and other settings...

After lots of debugging and testing, it finally worked and I proposed my solution on Github and asked if the 2.0" 320x240 ST7789 display could be included in the install script. Although they didn't adopt my changes 1:1, they did includ the TFT. The script now supports this display.

Below is the code of my version of the installer script. Note: This script is out of date now, do not use it for current projects. Just use the latest script from Adafruit as described below, their version is up to date.

#!/bin/bash
# (C) Adafruit Industries, Creative Commons 3.0 - Attribution Share Alike
#
# Instructions!
# cd ~
# wget https://raw.githubusercontent.com/adafruit/Raspberry-Pi-Installer-Scripts/master/adafruit-pitft.sh
# chmod +x adafruit-pitft.sh
# sudo ./adafruit-pitft.sh

if [ $(id -u) -ne 0 ]; then
        echo "Installer must be run as root."
        echo "Try 'sudo bash $0'"
        exit 1
fi


UPDATE_DB=false


############################ CALIBRATIONS ############################
# For TSLib
POINTERCAL_28r0="4232 11 -879396 1 5786 -752768 65536"
POINTERCAL_28r90="33 -5782 21364572 4221 35 -1006432 65536"
POINTERCAL_28r180="-4273 61 16441290 4 -5772 21627524 65536"
POINTERCAL_28r270="-9 5786 -784608 -4302 19 16620508 65536"

POINTERCAL_35r0="5724 -6 -1330074 26 8427 -1034528 65536"
POINTERCAL_35r90="5 8425 -978304 -5747 61 22119468 65536"
POINTERCAL_35r180="-5682 -1 22069150 13 -8452 32437698 65536"
POINTERCAL_35r270="3 -8466 32440206 5703 -1 -1308696 65536"

POINTERCAL_28c="320 65536 0 -65536 0 15728640 65536"

# for PIXEL desktop
TRANSFORM_28r0="0.988809 -0.023645 0.060523 -0.028817 1.003935 0.034176 0 0 1"
TRANSFORM_28r90="0.014773 -1.132874 1.033662 1.118701 0.009656 -0.065273 0 0 1"
TRANSFORM_28r180="-1.115235 -0.010589 1.057967 -0.005964 -1.107968 1.025780 0 0 1"
TRANSFORM_28r270="-0.033192 1.126869 -0.014114 -1.115846 0.006580 1.050030 0 0 1"

TRANSFORM_35r0="-1.098388 0.003455 1.052099 0.005512 -1.093095 1.026309 0 0 1"
TRANSFORM_35r90="-0.000087 1.094214 -0.028826 -1.091711 -0.004364 1.057821 0 0 1"
TRANSFORM_35r180="1.102807 0.000030 -0.066352 0.001374 1.085417 -0.027208 0 0 1"
TRANSFORM_35r270="0.003893 -1.087542 1.025913 1.084281 0.008762 -0.060700 0 0 1"

TRANSFORM_28c0="-1 0 1 0 -1 1 0 0 1"
TRANSFORM_28c90="0 1 0 -1 0 1 0 0 1"
TRANSFORM_28c180="1 0 0 0 1 0 0 0 1"
TRANSFORM_28c270="0 -1 1 1 0 0 0 0 1"

ROTATE_28c0="rotate=0,touch-invx=true,touch-invy=true"
ROTATE_28c90="rotate=90,touch-swapxy=true,touch-invx=true"
ROTATE_28c180="rotate=180"
ROTATE_28c270="rotate=270,touch-swapxy=true,touch-invy=true"

MADCTL_st7789_240x32090="0x36,0x60,-1,0x37,0x00,0x00,-1"
MADCTL_st7789_240x320180="0x36,0x00,-1,0x37,0x00,0x00,-1"
MADCTL_st7789_240x320270="0x36,0xA0,-1,0x37,0x00,0x00,-1"
MADCTL_st7789_240x3200="0x36,0xC0,-1,0x37,0x00,0x00,-1"

MADCTL_st7789_240x2400="0x36,0x60,-1,0x37,0x00,0x00,-1"
MADCTL_st7789_240x24090="0x36,0x00,-1,0x37,0x00,0x00,-1"
MADCTL_st7789_240x240180="0x36,0xA0,-1,0x37,0x00,0x50,-1"
MADCTL_st7789_240x240270="0x36,0xC0,-1,0x37,0x00,0x50,-1"

warning() {
        echo WARNING : $1
}

############################ Script assisters ############################

# Given a list of strings representing options, display each option
# preceded by a number (1 to N), display a prompt, check input until
# a valid number within the selection range is entered.
selectN() {
        for ((i=1; i<=$#; i++)); do
                echo $i. ${!i}
        done
        echo
        REPLY=""
        while :
        do
                echo -n "SELECT 1-$#: "
                read
                if [[ $REPLY -ge 1 ]] && [[ $REPLY -le $# ]]; then
                        return $REPLY
                fi
        done
}


function print_version() {
    echo "Adafruit PiTFT Helper v2.1.0"
    exit 1
}

function print_help() {
    echo "Usage: $0 "
    echo "    -h            Print this help"
    echo "    -v            Print version information"
    echo "    -u [homedir]  Specify path of primary user's home directory (defaults to /home/pi)"
    exit 1
}

group=ADAFRUIT
function info() {
    system="$1"
    group="${system}"
    shift
    FG="1;32m"
    BG="40m"
    echo -e "[&#92;033[${FG}&#92;033[${BG}${system}&#92;033[0m] $*"
}

function bail() {
    FG="1;31m"
    BG="40m"
    echo -en "[&#92;033[${FG}&#92;033[${BG}${group}&#92;033[0m] "
    if [ -z "$1" ]; then
        echo "Exiting due to error"
    else
        echo "Exiting due to error: $*"
    fi
    exit 1
}

function ask() {
    # http://djm.me/ask
    while true; do

        if [ "${2:-}" = "Y" ]; then
            prompt="Y/n"
            default=Y
        elif [ "${2:-}" = "N" ]; then
            prompt="y/N"
            default=N
        else
            prompt="y/n"
            default=
        fi

        # Ask the question
        read -p "$1 [$prompt] " REPLY

        # Default?
        if [ -z "$REPLY" ]; then
            REPLY=$default
        fi

        # Check if the reply is valid
        case "$REPLY" in
            Y*|y*) return 0 ;;
            N*|n*) return 1 ;;
        esac
    done
}

function has_repo() {
    # Checks for the right raspbian repository
    # http://mirrordirector.raspbian.org/raspbian/ stretch main contrib non-free rpi firmware
    if [[ $(grep -h ^deb /etc/apt/sources.list /etc/apt/sources.list.d/* | grep "mirrordirector.raspbian.org") ]]; then
        return 0
    else
        return 1
    fi
}

progress() {
    count=0
    until [ $count -eq $1 ]; do
        echo -n "..." && sleep 1
        ((count++))
    done
    echo
}

sysupdate() {
    if ! $UPDATE_DB; then
        # echo "Checking for correct software repositories..."
        # has_repo || { warning "Missing Apt repo, please add deb http://mirrordirector.raspbian.org/raspbian/ stretch main contrib non-free rpi firmware to /etc/apt/sources.list.d/raspi.list" && exit 1; }
        echo "Updating apt indexes..." && progress 3 &
        sudo apt update 1> /dev/null || { warning "Apt failed to update indexes!" && exit 1; }
        sudo apt-get update 1> /dev/null || { warning "Apt failed to update indexes!" && exit 1; }
        echo "Reading package lists..."
        progress 3 && UPDATE_DB=true
    fi
}


# Given a filename, a regex pattern to match and a replacement string,
# perform replacement if found, else append replacement to end of file.
# (# $1 = filename, $2 = pattern to match, $3 = replacement)
reconfig() {
        grep $2 $1 >/dev/null
        if [ $? -eq 0 ]; then
                # Pattern found; replace in file
                sed -i "s/$2/$3/g" $1 >/dev/null
        else
                # Not found; append (silently)
                echo $3 | sudo tee -a $1 >/dev/null
        fi
}


############################ Sub-Scripts ############################

function softwareinstall() {
    echo "Installing Pre-requisite Software...This may take a few minutes!"
                apt-get install -y libts0 1> /dev/null 2>&1 || apt-get install -y tslib 1> /dev/null 2>&1 || { warning "Apt failed to install TSLIB!" && exit 1; }
    apt-get install -y bc fbi git python-dev python-pip python-smbus python-spidev evtest libts-bin device-tree-compiler 1> /dev/null  || { warning "Apt failed to install software!" && exit 1; }
    pip install evdev 1> /dev/null  || { warning "Pip failed to install software!" && exit 1; }
}

# update /boot/config.txt with appropriate values
function update_configtxt() {
    if grep -q "adafruit-pitft-helper" "/boot/config.txt"; then
        echo "Already have an adafruit-pitft-helper section in /boot/config.txt."
        echo "Removing old section..."
        cp /boot/config.txt /boot/configtxt.bak
        sed -i -e "/^# --- added by adafruit-pitft-helper/,/^# --- end adafruit-pitft-helper/d" /boot/config.txt
    fi

    # remove any old flexfb/fbtft stuff
    rm -f /etc/modprobe.d/fbtft.conf
    sed -i 's/spi-bcm2835//g' "/etc/modules"
    sed -i 's/flexfb//g' "/etc/modules"
    sed -i 's/fbtft_device//g' "/etc/modules"

    if [ "${pitfttype}" == "22" ]; then
        overlay="dtoverlay=pitft22,rotate=${pitftrot},speed=64000000,fps=30"
    fi

    if [ "${pitfttype}" == "28r" ]; then
        overlay="dtoverlay=pitft28-resistive,rotate=${pitftrot},speed=64000000,fps=30"
    fi

    if [ "${pitfttype}" == "28c" ]; then                               
        rotateparams=$(eval echo "\$ROTATE_$pitfttype$pitftrot")
        overlay=$(printf "dtoverlay=pitft28-capacitive,speed=64000000,fps=30\ndtoverlay=pitft28-capacitive,${rotateparams}")
    fi

    if [ "${pitfttype}" == "35r" ]; then
        overlay="dtoverlay=pitft35-resistive,rotate=${pitftrot},speed=20000000,fps=20"
    fi

    if [ "${pitfttype}" == "st7789_240x320" ]; then
        madctl=$(eval echo "\$MADCTL_$pitfttype$pitftrot")
        if [ "${pitftrot}" == "90" ] || [ "${pitftrot}" == "270" ]; then
            fbtftdevicerotate="rotate=90"
        else
            fbtftdevicerotate=""
        fi
        # -1,0x26,0x01 = gamma curve 2 (G2.2)
        # -1,0x26,0x02 = gamma curve 2 (G1.8) = darker
        # -1,0x26,0x04 = gamma curve 3 (G2.5) = lighter
        cat >> /etc/modprobe.d/fbtft.conf <<EOF
# --- added by adafruit-pitft-helper $date ---
options fbtft_device name=flexfb gpios=dc:25,cs:8,led:12 speed=40000000 bgr=1 fps=60 $fbtftdevicerotate
options flexfb setaddrwin=0 width=240 height=320 init=-1,0x11,-2,120,-1,$madctl,0x3A,0x05,-1,0x26,0x04,-1,0xBA,0x01,-1,0xB2,0x0C,0x0C,0x00,0x33,0x33,-1,0xB7,0x35,-1,0xBB,0x1A,-1,0xC0,0x2C,-1,0xC2,0x01,-1,0xC3,0x0B,-1,0xC4,0x20,-1,0xC6,0x0F,-1,0xD0,0xA4,0xA1,-1,0x21,-1,0xE0,0x00,0x19,0x1E,0x0A,0x09,0x15,0x3D,0x44,0x51,0x12,0x03,0x00,0x3F,0x3F,-1,0xE1,0x00,0x18,0x1E,0x0A,0x09,0x25,0x3F,0x43,0x52,0x33,0x03,0x00,0x3F,0x3F,-1,0x29,-3
# --- end adafruit-pitft-helper $date ---
EOF
        echo "spi-bcm2835" >> /etc/modules
        echo "flexfb" >> /etc/modules
        echo "fbtft_device" >> /etc/modules
        overlay=""
    fi
    
    if [ "${pitfttype}" == "st7789_240x240" ]; then
        madctl=$(eval echo "\$MADCTL_$pitfttype$pitftrot")
        cat >> /etc/modprobe.d/fbtft.conf <<EOF
# --- added by adafruit-pitft-helper $date ---
options fbtft_device name=flexfb gpios=dc:25,cs:8,led:26 speed=40000000 bgr=1 fps=60
options flexfb setaddrwin=0 width=240 height=240 init=-1,0x11,-2,120,-1,$madctl,0x3A,0x05,-1,0xB2,0x0C,0x0C,0x00,0x33,0x33,-1,0xB7,0x35,-1,0xBB,0x1A,-1,0xC0,0x2C,-1,0xC2,0x01,-1,0xC3,0x0B,-1,0xC4,0x20,-1,0xC6,0x0F,-1,0xD0,0xA4,0xA1,-1,0x21,-1,0xE0,0x00,0x19,0x1E,0x0A,0x09,0x15,0x3D,0x44,0x51,0x12,0x03,0x00,0x3F,0x3F,-1,0xE1,0x00,0x18,0x1E,0x0A,0x09,0x25,0x3F,0x43,0x52,0x33,0x03,0x00,0x3F,0x3F,-1,0x29,-3
# --- end adafruit-pitft-helper $date ---
EOF
        echo "spi-bcm2835" >> /etc/modules
        echo "flexfb" >> /etc/modules
        echo "fbtft_device" >> /etc/modules
        overlay=""
    fi

    if [ "${pitfttype}" == "st7789_240x135" ]; then
        dtc -@ -I dts -O dtb -o /boot/overlays/drm-minipitft114.dtbo overlays/minipitft114-overlay.dts
        echo "############# UPGRADING KERNEL ###############"
        sudo apt update  || { warning "Apt failed to update itself!" && exit 1; }
        sudo apt-get upgrade || { warning "Apt failed to install software!" && exit 1; }
        apt-get install -y raspberrypi-kernel-headers 1> /dev/null  || { warning "Apt failed to install software!" && exit 1; }
        [ -d /lib/modules/$(uname -r)/build ] ||  { warning "Kernel was updated, please reboot now and re-run script!" && exit 1; }
        cd st7789_module
        make -C /lib/modules/$(uname -r)/build M=$(pwd) modules  || { warning "Apt failed to compile ST7789V driver!" && exit 1; }
        mv /lib/modules/$(uname -r)/kernel/drivers/gpu/drm/tinydrm/mi0283qt.ko /lib/modules/$(uname -r)/kernel/drivers/gpu/drm/tinydrm/mi0283qt.BACK
        mv st7789v_ada.ko /lib/modules/$(uname -r)/kernel/drivers/gpu/drm/tinydrm/mi0283qt.ko
        overlay="dtoverlay=drm-minipitft114,rotation=${pitftrot}"
    fi

    date=`date`

    cat >> /boot/config.txt <<EOF
# --- added by adafruit-pitft-helper $date ---
dtparam=spi=on
dtparam=i2c1=on
dtparam=i2c_arm=on
$overlay
# --- end adafruit-pitft-helper $date ---
EOF
}

function update_udev() {
    cat > /etc/udev/rules.d/95-touchmouse.rules <<EOF
SUBSYSTEM=="input", ATTRS{name}=="touchmouse", ENV{DEVNAME}=="*event*", SYMLINK+="input/touchscreen"
EOF
    cat > /etc/udev/rules.d/95-ftcaptouch.rules <<EOF
SUBSYSTEM=="input", ATTRS{name}=="EP0110M09", ENV{DEVNAME}=="*event*", SYMLINK+="input/touchscreen"
EOF
    cat > /etc/udev/rules.d/95-stmpe.rules <<EOF
SUBSYSTEM=="input", ATTRS{name}=="*stmpe*", ENV{DEVNAME}=="*event*", SYMLINK+="input/touchscreen"
EOF
}


function update_pointercal() {
    if [ "${pitfttype}" == "28r" ] || [ "${pitfttype}" == "35r" ]; then
       echo $(eval echo "\$POINTERCAL_$pitfttype$pitftrot") > /etc/pointercal
    fi

    if [ "${pitfttype}" == "28c" ]; then
       echo $(eval echo "\$POINTERCAL_$pitfttype") > /etc/pointercal
    fi
}


function install_console() {
    echo "Set up main console turn on"
    if ! grep -q 'fbcon=map:10 fbcon=font:VGA8x8' /boot/cmdline.txt; then
        echo "Updating /boot/cmdline.txt"
        sed -i 's/rootwait/rootwait fbcon=map:10 fbcon=font:VGA8x8/g' "/boot/cmdline.txt"
    else
        echo "/boot/cmdline.txt already updated"
    fi

    echo "Turning off console blanking"
    # pre-stretch this is what you'd do:
    if [ -e /etc/kbd/config ]; then
      sed -i 's/BLANK_TIME=.*/BLANK_TIME=0/g' "/etc/kbd/config"
    fi
    # as of stretch....
    # removing any old version
    sed -i -e '/^# disable console blanking.*/d' /etc/rc.local
    sed -i -e '/^sudo sh -c "TERM=linux setterm -blank.*/d' /etc/rc.local
    sed -i -e "s|^exit 0|# disable console blanking on PiTFT\\nsudo sh -c \"TERM=linux setterm -blank 0 >/dev/tty0\"\\nexit 0|" /etc/rc.local

    reconfig /etc/default/console-setup "^.*FONTFACE.*$" "FONTFACE=\"Terminus\""
    reconfig /etc/default/console-setup "^.*FONTSIZE.*$" "FONTSIZE=\"6x12\""

    echo "Setting raspi-config to boot to console w/o login..."
    (cd "$target_homedir" && raspi-config nonint do_boot_behaviour B2)

    # remove fbcp
    sed -i -e "/^.*fbcp.*$/d" /etc/rc.local
}


function uninstall_console() {
    echo "Removing console fbcon map from /boot/cmdline.txt"
    sed -i 's/rootwait fbcon=map:10 fbcon=font:VGA8x8/rootwait/g' "/boot/cmdline.txt"
    echo "Screen blanking time reset to 10 minutes"
    if [ -e "/etc/kbd/config" ]; then
      sed -i 's/BLANK_TIME=0/BLANK_TIME=10/g' "/etc/kbd/config"
    fi
    sed -i -e '/^# disable console blanking.*/d' /etc/rc.local
    sed -i -e '/^sudo sh -c "TERM=linux.*/d' /etc/rc.local
}

function install_fbcp() {
    echo "Installing cmake..."
    apt-get --yes --allow-downgrades --allow-remove-essential --allow-change-held-packages install cmake 1> /dev/null  || { warning "Apt failed to install software!" && exit 1; }
    echo "Downloading rpi-fbcp..."
    cd /tmp
    #curl -sLO https://github.com/tasanakorn/rpi-fbcp/archive/master.zip
    curl -sLO https://github.com/adafruit/rpi-fbcp/archive/master.zip
    echo "Uncompressing rpi-fbcp..."
    rm -rf /tmp/rpi-fbcp-master
    unzip master.zip 1> /dev/null  || { warning "Failed to uncompress fbcp!" && exit 1; }
    cd rpi-fbcp-master
    mkdir build
    cd build
    echo "Building rpi-fbcp..."
    echo -e "\nset (CMAKE_C_FLAGS \"-std=gnu99 ${CMAKE_C_FLAGS}\")" >> ../CMakeLists.txt
    cmake ..  1> /dev/null  || { warning "Failed to cmake fbcp!" && exit 1; }
    make  1> /dev/null  || { warning "Failed to make fbcp!" && exit 1; }
    echo "Installing rpi-fbcp..."
    install fbcp /usr/local/bin/fbcp
    cd ~
    rm -rf /tmp/rpi-fbcp-master

    # Start fbcp in the appropriate place, depending on init system:
    if [ "$SYSTEMD" == "0" ]; then
        # Add fbcp to /etc/rc.local:
        echo "We have sysvinit, so add fbcp to /etc/rc.local..."
        grep fbcp /etc/rc.local >/dev/null
        if [ $? -eq 0 ]; then
            # fbcp already in rc.local, but make sure correct:
            sed -i "s|^.*fbcp.*$|/usr/local/bin/fbcp \&|g" /etc/rc.local >/dev/null
        else
            # Insert fbcp into rc.local before final 'exit 0':
            sed -i "s|^exit 0|/usr/local/bin/fbcp \&\\nexit 0|g" /etc/rc.local >/dev/null
        fi
    else
        # Install fbcp systemd unit, first making sure it's not in rc.local:
        uninstall_fbcp_rclocal
        echo "We have systemd, so install fbcp systemd unit..."
        install_fbcp_unit || bail "Unable to install fbcp unit file"
        sudo systemctl enable fbcp.service
    fi

    # if there's X11 installed...
    if [ -e /etc/lightdm ]; then
        echo "Setting raspi-config to boot to desktop w/o login..."
        raspi-config nonint do_boot_behaviour B4
    fi

    # Disable overscan compensation (use full screen):
    raspi-config nonint do_overscan 1
    # Set up HDMI parameters:
    echo "Configuring boot/config.txt for forced HDMI"
    reconfig /boot/config.txt "^.*hdmi_force_hotplug.*$" "hdmi_force_hotplug=1"
    reconfig /boot/config.txt "^.*hdmi_group.*$" "hdmi_group=2"
    reconfig /boot/config.txt "^.*hdmi_mode.*$" "hdmi_mode=87"

    # if there's X11 installed...
    if [ -e /etc/lightdm ]; then
        if [ "${pitfttype}" == "35r" ]; then
            echo "Using x1.5 resolution"
            SCALE=1.5
        else
            echo "Using x2 resolution"
            SCALE=2.0
        fi
    else
        echo "Using native resolution"
        SCALE=1
    fi
    
    if [[ "${pitfttype}" == "st7789_240x320" && "${pitftrot}" == "180" ]] || [[ "${pitfttype}" == "st7789_240x320" && "${pitftrot}" == "0" ]]; then
        # swap width/height for portrait display rotation when using st7789_240x320 
        WIDTH=`python -c "print(int(${HEIGHT_VALUES[PITFT_SELECT-1]} * ${SCALE}))"`
        HEIGHT=`python -c "print(int(${WIDTH_VALUES[PITFT_SELECT-1]} * ${SCALE}))"`
    else
        WIDTH=`python -c "print(int(${WIDTH_VALUES[PITFT_SELECT-1]} * ${SCALE}))"`
        HEIGHT=`python -c "print(int(${HEIGHT_VALUES[PITFT_SELECT-1]} * ${SCALE}))"`
    fi
    
    
    reconfig /boot/config.txt "^.*hdmi_cvt.*$" "hdmi_cvt=${WIDTH} ${HEIGHT} 60 1 0 0 0"

    if [ "${pitfttype}" == "st7789_240x320" ]; then
        # dont rotate HDMI when using st7789_240x320 (only display will be rotated)
        reconfig /boot/config.txt "^.*display_hdmi_rotate.*$" ""
    else
    
        if [ "${pitftrot}" == "90" ] || [ "${pitftrot}" == "270" ]; then
            # dont rotate HDMI on 90 or 270
            reconfig /boot/config.txt "^.*display_hdmi_rotate.*$" ""
        fi
    
        if [ "${pitftrot}" == "0" ]; then
            reconfig /boot/config.txt "^.*display_hdmi_rotate.*$" "display_hdmi_rotate=1"
            # this is a hack but because we rotate HDMI we have to 'unrotate' the TFT!
            pitftrot=90
            update_configtxt || bail "Unable to update /boot/config.txt"
            pitftrot=0
        fi
        
        if [ "${pitftrot}" == "180" ]; then
            reconfig /boot/config.txt "^.*display_hdmi_rotate.*$" "display_hdmi_rotate=3"
             this is a hack but because we rotate HDMI we have to 'unrotate' the TFT!
            pitftrot=90
            update_configtxt || bail "Unable to update /boot/config.txt"
            pitftrot=180
        fi
    
    fi

}

function install_fbcp_unit() {
    cat > /etc/systemd/system/fbcp.service <<EOF
[Unit]
Description=Framebuffer copy utility for PiTFT
After=network.target

[Service]
Type=simple
ExecStartPre=/bin/sleep 10
ExecStart=/usr/local/bin/fbcp

[Install]
WantedBy=multi-user.target
EOF
}

function uninstall_fbcp() {
    uninstall_fbcp_rclocal
    # Enable overscan compensation
    raspi-config nonint do_overscan 0
    # Set up HDMI parameters:
    echo "Configuring boot/config.txt for default HDMI"
    reconfig /boot/config.txt "^.*hdmi_force_hotplug.*$" "hdmi_force_hotplug=0"
    sed -i -e '/^hdmi_group=2.*$/d' /boot/config.txt
    sed -i -e '/^hdmi_mode=87.*$/d' /boot/config.txt
    sed -i -e '/^hdmi_cvt=.*$/d' /boot/config.txt
}

function uninstall_fbcp_rclocal() {
    # Remove fbcp from /etc/rc.local:
    echo "Remove fbcp from /etc/rc.local, if it's there..."
    sed -i -e '/^.*fbcp.*$/d' /etc/rc.local
}

function update_xorg() {
    if [ "${pitfttype}" == "28r" ] || [ "${pitfttype}" == "35r" ]; then
        matrix=$(eval echo "\$TRANSFORM_$pitfttype$pitftrot")
        transform="Option \"TransformationMatrix\" \"${matrix}\""
        cat > /usr/share/X11/xorg.conf.d/20-calibration.conf <<EOF
Section "InputClass"
        Identifier "STMPE Touchscreen Calibration"
        MatchProduct "stmpe"
        MatchDevicePath "/dev/input/event*"
        Driver "libinput"
        ${transform}
EndSection
EOF
    fi

    if [ "${pitfttype}" == "28c" ]; then
        matrix=$(eval echo "\$TRANSFORM_$pitfttype$pitftrot")
        transform="Option \"TransformationMatrix\" \"${matrix}\""
        cat > /usr/share/X11/xorg.conf.d/20-calibration.conf <<EOF
Section "InputClass"
        Identifier "FocalTech Touchscreen Calibration"
        MatchProduct "EP0110M09"
        MatchDevicePath "/dev/input/event*"
        Driver "libinput"
        ${transform}
EndSection
EOF
    fi
}

####################################################### MAIN
target_homedir="/home/pi"


clear
echo "This script downloads and installs"
echo "PiTFT Support using userspace touch"
echo "controls and a DTO for display drawing."
echo "one of several configuration files."
echo "Run time of up to 5 minutes. Reboot required!"
echo

echo "Select configuration:"
selectN "PiTFT 2.4\", 2.8\" or 3.2\" resistive (240x320)" \
        "PiTFT 2.2\" no touch (240x320)" \
        "PiTFT 2.8\" capacitive touch (240x320)" \
        "PiTFT 3.5\" resistive touch (320x480)" \
        "PiTFT 2.0\" no touch (240x320)" \
        "PiTFT Mini 1.3\" or 1.54\" display (240x240)" \
        "MiniPiTFT 1.14\" display (240x135) - WARNING! CUTTING EDGE! WILL UPGRADE YOUR KERNEL TO LATEST" \
        "Quit without installing"
PITFT_SELECT=$?
if [ $PITFT_SELECT -gt 7 ]; then
    exit 1
fi

echo "Select rotation:"
selectN "90 degrees (landscape)" \
        "180 degrees (portait)" \
        "270 degrees (landscape)" \
        "0 degrees (portait)"
PITFT_ROTATE=$?
if [ $PITFT_ROTATE -gt 4 ]; then
    exit 1
fi

PITFT_ROTATIONS=("90" "180" "270" "0")
PITFT_TYPES=("28r" "22" "28c" "35r" "st7789_240x320" "st7789_240x240" "st7789_240x135")
WIDTH_VALUES=(320 320 320 480 320 240)
HEIGHT_VALUES=(240 240 240 320 240 240)
HZ_VALUES=(64000000 64000000 64000000 32000000 32000000 64000000)



args=$(getopt -uo 'hvri:o:b:u:' -- $*)
[ $? != 0 ] && print_help
set -- $args

for i
do
    case "$i"
    in
        -h)
            print_help
            ;;
        -v)
            print_version
            ;;
        -u)
            target_homedir="$2"
            echo "Homedir = ${2}"
            shift
            shift
            ;;
    esac
done

# check init system (technique borrowed from raspi-config):
info PITFT 'Checking init system...'
if command -v systemctl > /dev/null && systemctl | grep -q '\-\.mount'; then
  echo "Found systemd"
  SYSTEMD=1
elif [ -f /etc/init.d/cron ] && [ ! -h /etc/init.d/cron ]; then
  echo "Found sysvinit"
  SYSTEMD=0
else
  bail "Unrecognised init system"
fi

if grep -q boot /proc/mounts; then
    echo "/boot is mounted"
else
    echo "/boot must be mounted. if you think it's not, quit here and try: sudo mount /dev/mmcblk0p1 /boot"
    if ask "Continue?"; then
        echo "Proceeding."
    else
        bail "Aborting."
    fi
fi

if [[ ! -e "$target_homedir" || ! -d "$target_homedir" ]]; then
    bail "$target_homedir must be an existing directory (use -u /home/foo to specify)"
fi

pitfttype=${PITFT_TYPES[$PITFT_SELECT-1]}
pitftrot=${PITFT_ROTATIONS[$PITFT_ROTATE-1]}


if [ "${pitfttype}" != "28r" ] && [ "${pitfttype}" != "28c" ] && [ "${pitfttype}" != "35r" ] && [ "${pitfttype}" != "22" ] && [ "${pitfttype}" != "st7789_240x320" ] && [ "${pitfttype}" != "st7789_240x240" ] && [ "${pitfttype}" != "st7789_240x135" ]; then
    echo "Type must be one of:"
    echo "  '28r' (2.8\" resistive, PID 1601)"
    echo "  '28c' (2.8\" capacitive, PID 1983)"
    echo "  '35r' (3.5\" Resistive)"
    echo "  '22'  (2.2\" no touch)"
    echo "  'st7789_240x320' (2.0\" no touch)"
    echo "  'st7789_240x240' (1.54\" or 1.3\" no touch)"
    echo "  'st7789_240x135' 1.14\" no touch)"
    echo
    print_help
fi

info PITFT "System update"
sysupdate || bail "Unable to apt-get update"

info PITFT "Installing Python libraries & Software..."
softwareinstall || bail "Unable to install software"

info PITFT "Updating /boot/config.txt..."
update_configtxt || bail "Unable to update /boot/config.txt"

if [ "${pitfttype}" == "28r" ] || [ "${pitfttype}" == "35r" ]  || [ "${pitfttype}" == "28c" ] ; then
   info PITFT "Updating SysFS rules for Touchscreen..."
   update_udev || bail "Unable to update /etc/udev/rules.d"

   info PITFT "Updating TSLib default calibration..."
   update_pointercal || bail "Unable to update /etc/pointercal"
fi

# ask for console access
if ask "Would you like the console to appear on the PiTFT display?"; then
    info PITFT "Updating console to PiTFT..."
    uninstall_fbcp  || bail "Unable to uninstall fbcp"
    install_console || bail "Unable to configure console"
else
    info PITFT "Making sure console doesn't use PiTFT"
    uninstall_console || bail "Unable to configure console"

    if ask "Would you like the HDMI display to mirror to the PiTFT display?"; then
        info PITFT "Adding FBCP support..."
        install_fbcp || bail "Unable to configure fbcp"

        if [ -e /etc/lightdm ]; then
            info PITFT "Updating X11 default calibration..."
            update_xorg || bail "Unable to update calibration"
        fi
    fi
fi


#info PITFT "Updating X11 setup tweaks..."
#update_x11profile || bail "Unable to update X11 setup"

#if [ "${pitfttype}" != "35r" ]; then
#    # ask for 'on/off' button
#    if ask "Would you like GPIO #23 to act as a on/off button?"; then
#        info PITFT "Adding GPIO #23 on/off to PiTFT..."
#        install_onoffbutton || bail "Unable to add on/off button"
#    fi
#fi

# update_bootprefs || bail "Unable to set boot preferences"


info PITFT "Success!"
echo
echo "Settings take effect on next boot."
echo
echo -n "REBOOT NOW? [y/N] "
read
if [[ ! "$REPLY" =~ ^(yes|y|Y)$ ]]; then
        echo "Exiting without reboot."
        exit 0
fi
echo "Reboot started..."
reboot
exit 0

Mirroring HDMI with fbcp to the TFT screen

The installation with the adafruit-pitft.py PiTFT helper installation script is quite simple:

cd ~
sudo pip3 install --upgrade adafruit-python-shell click
# Copy the latest version from Github:
git clone https://github.com/adafruit/Raspberry-Pi-Installer-Scripts.git
# Change to the installer directory:
cd Raspberry-Pi-Installer-Scripts
# Run the PiTFT installer script:
sudo python3 adafruit-pitft.py

Then you can

  • Enter the number for your display type (in my case “ST7789V 2.0 no touch”)
  • Select the display rotation (0°, 90°, 180°, 270°)
  • Answer the question “Would you like the console to appear on the PiTFT display?” with NO
  • Answer the question Would you like the HDMI display to mirror to the PiTFT display?” with YES
  • Then fbcp gets installed. Finally answer the question “Reboot now?” with YES

That's all. Et voila, the TFT display shows the Raspi Dektop and all applications as on the HDMI port. Videos are played flawlessly and smoothly with VLC or mplayer. In the init sequence, the frame rate is set to 60 fps, so this PiTFT driver should give a similar result to the fbcp-ili9341 driver above.

For more options and explanations, see the detailed installation guide for PiTFT.

Notes

During the installation, the desktop resolution is changed and reduced to the TFT resolution. It may be difficult to use some applications in this low resolution mode. Therefore, I would recommend changing the settings of the programs you want to use before installing the TFT driver.

I haven't tested it, but I think that the installer script also supports displays with ILI9340, ILI9341 and HX8357-D00/D01 driver chips (those are used in some of the PiTFT displays).