1.3 color tft LCD on btreakout garden pHat, SPI i2c

I’m trying to get my 1.3" SPI Colour LCD (240x240) Breakout


working with a Breakout Garden pHat

The Breakout garden Installer didn’t find it, I had to do the manual install from here


Running the scrolling-test.py example from idle3 got me a no module named st7789.
Running sudo pip3 install st7789 fixed that but gets me a new error message that I think is because its plugged into the breakout garden?
Traceback (most recent call last):
File “/home/pi/Color LCD/scrolling-text.py”, line 33, in
spi_speed_hz=80 * 1000 * 1000
File “/usr/local/lib/python3.7/dist-packages/ST7789/init.py”, line 124, in init
self._spi = spidev.SpiDev(port, cs)
FileNotFoundError: [Errno 2] No such file or directory

1 Like

It wouldn’t have anything to do with this section in the program:

# Create ST7789 LCD display class.
disp = ST7789.ST7789(
    port=0,
    cs=ST7789.BG_SPI_CS_FRONT,  # BG_SPI_CSB_BACK or BG_SPI_CS_FRONT
    dc=9,
    backlight=19,               # 18 for back BG slot, 19 for front BG slot.
    spi_speed_hz=80 * 1000 * 1000
)

Changing Front to Back, and 18 to 19 - or some combination of those?

 cs=ST7789.BG_SPI_CS_FRONT,  # BG_SPI_CSB_BACK or BG_SPI_CS_FRONT
backlight=19,               # 18 for back BG slot, 19 for front BG slot.

Mine are coming in the post so I can’t test it for you yet.

I saw that and tried changing them, made no difference. If there was a pinout for that pHat it would likely help. The only one I could find was for the original Breakout Garden Hat.

It appears my issue is with the init.py file in /usr/local/lib/python3.7/dist-packages/ST7789/

# Copyright (c) 2014 Adafruit Industries
# Author: Tony DiCola
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
import numbers
import time
import numpy as np

import spidev
import RPi.GPIO as GPIO


__version__ = '0.0.2'

BG_SPI_CS_BACK = 0
BG_SPI_CS_FRONT = 1

SPI_CLOCK_HZ = 16000000

ST7789_NOP = 0x00
ST7789_SWRESET = 0x01
ST7789_RDDID = 0x04
ST7789_RDDST = 0x09

ST7789_SLPIN = 0x10
ST7789_SLPOUT = 0x11
ST7789_PTLON = 0x12
ST7789_NORON = 0x13

# ILI9341_RDMODE = 0x0A
# ILI9341_RDMADCTL = 0x0B
# ILI9341_RDPIXFMT = 0x0C
# ILI9341_RDIMGFMT = 0x0A
# ILI9341_RDSELFDIAG = 0x0F

ST7789_INVOFF = 0x20
ST7789_INVON = 0x21
# ILI9341_GAMMASET = 0x26
ST7789_DISPOFF = 0x28
ST7789_DISPON = 0x29

ST7789_CASET = 0x2A
ST7789_RASET = 0x2B
ST7789_RAMWR = 0x2C
ST7789_RAMRD = 0x2E

ST7789_PTLAR = 0x30
ST7789_MADCTL = 0x36
ST7789_COLMOD = 0x3A

ST7789_FRMCTR1 = 0xB1
ST7789_FRMCTR2 = 0xB2
ST7789_FRMCTR3 = 0xB3
ST7789_INVCTR = 0xB4
# ILI9341_DFUNCTR = 0xB6
ST7789_DISSET5 = 0xB6

ST7789_GCTRL = 0xB7
ST7789_GTADJ = 0xB8
ST7789_VCOMS = 0xBB

ST7789_LCMCTRL = 0xC0
ST7789_IDSET = 0xC1
ST7789_VDVVRHEN = 0xC2
ST7789_VRHS = 0xC3
ST7789_VDVS = 0xC4
ST7789_VMCTR1 = 0xC5
ST7789_FRCTRL2 = 0xC6
ST7789_CABCCTRL = 0xC7

ST7789_RDID1 = 0xDA
ST7789_RDID2 = 0xDB
ST7789_RDID3 = 0xDC
ST7789_RDID4 = 0xDD

ST7789_GMCTRP1 = 0xE0
ST7789_GMCTRN1 = 0xE1

ST7789_PWCTR6 = 0xFC


class ST7789(object):
    """Representation of an ST7789 TFT LCD."""

    def __init__(self, port, cs, dc, backlight=None, rst=None, width=240,
                 height=240, rotation=90, invert=True, spi_speed_hz=4000000):
        """Create an instance of the display using SPI communication.

        Must provide the GPIO pin number for the D/C pin and the SPI driver.

        Can optionally provide the GPIO pin number for the reset pin as the rst parameter.

        :param port: SPI port number
        :param cs: SPI chip-select number (0 or 1 for BCM
        :param backlight: Pin for controlling backlight
        :param rst: Reset pin for ST7789
        :param width: Width of display connected to ST7789
        :param height: Height of display connected to ST7789
        :param rotation: Rotation of display connected to ST7789
        :param invert: Invert display
        :param spi_speed_hz: SPI speed (in Hz)

        """

        GPIO.setwarnings(False)
        GPIO.setmode(GPIO.BCM)

        self._spi = spidev.SpiDev(port, cs)
        self._spi.mode = 0
        self._spi.lsbfirst = False
        self._spi.max_speed_hz = spi_speed_hz

        self._dc = dc
        self._rst = rst
        self._width = width
        self._height = height
        self._rotation = rotation
        self._invert = invert

        self._offset_left = 0
        self._offset_top = 0

        # Set DC as output.
        GPIO.setup(dc, GPIO.OUT)

        # Setup backlight as output (if provided).
        self._backlight = backlight
        if backlight is not None:
            GPIO.setup(backlight, GPIO.OUT)
            GPIO.output(backlight, GPIO.LOW)
            time.sleep(0.1)
            GPIO.output(backlight, GPIO.HIGH)

        # Setup reset as output (if provided).
        if rst is not None:
            GPIO.setup(rst, GPIO.OUT)

        self.reset()
        self._init()

    def send(self, data, is_data=True, chunk_size=4096):
        """Write a byte or array of bytes to the display. Is_data parameter
        controls if byte should be interpreted as display data (True) or command
        data (False).  Chunk_size is an optional size of bytes to write in a
        single SPI transaction, with a default of 4096.
        """
        # Set DC low for command, high for data.
        GPIO.output(self._dc, is_data)
        # Convert scalar argument to list so either can be passed as parameter.
        if isinstance(data, numbers.Number):
            data = [data & 0xFF]
        # Write data a chunk at a time.
        for start in range(0, len(data), chunk_size):
            end = min(start + chunk_size, len(data))
            self._spi.xfer(data[start:end])

    def set_backlight(self, value):
        """Set the backlight on/off."""
        if self._backlight is not None:
            GPIO.output(self._backlight, value)

    @property
    def width(self):
        return self._width if self._rotation == 0 or self._rotation == 180 else self._height

    @property
    def height(self):
        return self._height if self._rotation == 0 or self._rotation == 180 else self._width

    def command(self, data):
        """Write a byte or array of bytes to the display as command data."""
        self.send(data, False)

    def data(self, data):
        """Write a byte or array of bytes to the display as display data."""
        self.send(data, True)

    def reset(self):
        """Reset the display, if reset pin is connected."""
        if self._rst is not None:
            GPIO.output(self._rst, 1)
            time.sleep(0.500)
            GPIO.output(self._rst, 0)
            time.sleep(0.500)
            GPIO.output(self._rst, 1)
            time.sleep(0.500)

    def _init(self):
        # Initialize the display.

        self.command(ST7789_SWRESET)    # Software reset
        time.sleep(0.150)               # delay 150 ms

        self.command(ST7789_MADCTL)
        self.data(0x70)

        self.command(ST7789_FRMCTR2)    # Frame rate ctrl - idle mode
        self.data(0x0C)
        self.data(0x0C)
        self.data(0x00)
        self.data(0x33)
        self.data(0x33)

        self.command(ST7789_COLMOD)
        self.data(0x05)

        self.command(ST7789_GCTRL)
        self.data(0x14)

        self.command(ST7789_VCOMS)
        self.data(0x37)

        self.command(ST7789_LCMCTRL)    # Power control
        self.data(0x2C)

        self.command(ST7789_VDVVRHEN)   # Power control
        self.data(0x01)

        self.command(ST7789_VRHS)       # Power control
        self.data(0x12)

        self.command(ST7789_VDVS)       # Power control
        self.data(0x20)

        self.command(0xD0)
        self.data(0xA4)
        self.data(0xA1)

        self.command(ST7789_FRCTRL2)
        self.data(0x0F)

        self.command(ST7789_GMCTRP1)    # Set Gamma
        self.data(0xD0)
        self.data(0x04)
        self.data(0x0D)
        self.data(0x11)
        self.data(0x13)
        self.data(0x2B)
        self.data(0x3F)
        self.data(0x54)
        self.data(0x4C)
        self.data(0x18)
        self.data(0x0D)
        self.data(0x0B)
        self.data(0x1F)
        self.data(0x23)

        self.command(ST7789_GMCTRN1)    # Set Gamma
        self.data(0xD0)
        self.data(0x04)
        self.data(0x0C)
        self.data(0x11)
        self.data(0x13)
        self.data(0x2C)
        self.data(0x3F)
        self.data(0x44)
        self.data(0x51)
        self.data(0x2F)
        self.data(0x1F)
        self.data(0x1F)
        self.data(0x20)
        self.data(0x23)

        if self._invert:
            self.command(ST7789_INVON)   # Invert display
        else:
            self.command(ST7789_INVOFF)  # Don't invert display

        self.command(ST7789_SLPOUT)

        self.command(ST7789_DISPON)     # Display on
        time.sleep(0.100)               # 100 ms

    def begin(self):
        """Set up the display

        Deprecated. Included in __init__.

        """
        pass

    def set_window(self, x0=0, y0=0, x1=None, y1=None):
        """Set the pixel address window for proceeding drawing commands. x0 and
        x1 should define the minimum and maximum x pixel bounds.  y0 and y1
        should define the minimum and maximum y pixel bound.  If no parameters
        are specified the default will be to update the entire display from 0,0
        to width-1,height-1.
        """
        if x1 is None:
            x1 = self._width - 1

        if y1 is None:
            y1 = self._height - 1

        y0 += self._offset_top
        y1 += self._offset_top

        x0 += self._offset_left
        x1 += self._offset_left

        self.command(ST7789_CASET)       # Column addr set
        self.data(x0 >> 8)
        self.data(x0 & 0xFF)             # XSTART
        self.data(x1 >> 8)
        self.data(x1 & 0xFF)             # XEND
        self.command(ST7789_RASET)       # Row addr set
        self.data(y0 >> 8)
        self.data(y0 & 0xFF)             # YSTART
        self.data(y1 >> 8)
        self.data(y1 & 0xFF)             # YEND
        self.command(ST7789_RAMWR)       # write to RAM

    def display(self, image):
        """Write the provided image to the hardware.

        :param image: Should be RGB format and the same dimensions as the display hardware.

        """
        # Set address bounds to entire display.
        self.set_window()
        # Convert image to array of 18bit 666 RGB data bytes.
        # Unfortunate that this copy has to occur, but the SPI byte writing
        # function needs to take an array of bytes and PIL doesn't natively
        # store images in 18-bit 666 RGB format.
        pixelbytes = list(self.image_to_data(image, self._rotation))
        # Write data to hardware.
        for i in range(0, len(pixelbytes), 4096):
            self.data(pixelbytes[i:i + 4096])

    def image_to_data(self, image, rotation=0):
        """Generator function to convert a PIL image to 16-bit 565 RGB bytes."""
        # NumPy is much faster at doing this. NumPy code provided by:
        # Keith (https://www.blogger.com/profile/02555547344016007163)
        pb = np.rot90(np.array(image.convert('RGB')), rotation // 90).astype('uint8')

        result = np.zeros((self._width, self._height, 2), dtype=np.uint8)
        result[..., [0]] = np.add(np.bitwise_and(pb[..., [0]], 0xF8), np.right_shift(pb[..., [1]], 5))
        result[..., [1]] = np.add(np.bitwise_and(np.left_shift(pb[..., [1]], 3), 0xE0), np.right_shift(pb[..., [2]], 3))
        return result.flatten().tolist()

In this section

self._spi = spidev.SpiDev(port, cs)
        self._spi.mode = 0
        self._spi.lsbfirst = False
        self._spi.max_speed_hz = spi_speed_hz

The error message is very confusing though?
FileNotFoundError: [Errno 2] No such file or directory

@sandyjmacdonald

I got out my ohm meter and di some checking the pinout for the SPI is as follows

3.3v… > 3.3 V,…Pin 1
CS…> GPIO 7,… Pin 26
SCLK > GPIO 11, Pin 23
MOSI > GOIO 10, Pin 19
MISO > GPIO 9,… Pin 21
GPIO > GPIO 19, Pin 35
GND > to one of the ground pins, I didn’t check which one.

That jives with the hookup instructions for the Color LCD on its product page

3-5V to any 5 or 3 V Pin
CS to BCM 7
SCK to BCM 11
MOSI to BCM 10
DC to BCM 9
BL to BCM 19
GNG to any Ground Pin.

So the examples for the Color LCD should work?

I can confirm that the examples should work as-is with the Breakout Garden pHAT with SPI. It might be worth trying a sudo apt install python3-spidev and that nothing else is inadvertently using one of the pins needed.

As it says on the page for the Breakout Garden HAT with SPI:

The top/back slot (closest to the Breakout Garden logo) uses chip select 0 (BCM 8) and BCM 18 for the GPIO (used for things like LCD backlights) pin. The bottom/front slot uses chip select 1 (BCM 7) and BCM 19 for the GPIO pin.

The single slot on the Breakout Garden pHAT with SPI is equivalent to the front slot on the larger HAT, using chip select 1 (BCM 7) and BCM 19 for the backlight (labelled GPIO) pin.

I don’t know if this will be helpful but I used the 1.2" mono SPI display with the BG Mini i found this note on the page:

  • With the SPI version, you can run an example like so: python3 bounce.py --display sh1106 --height 128 --rotate 2 --interface spi --gpio-data-command 9 (add --spi-device 0 for the back slot, or --spi-device 1 for the front slot)

By trial and error I found that on the BG Mini the SPI slot is slot 0 not slot 1.

I’m just now reflashing my SD card with the latest Buster. I’m going to start over fresh. I would like to have it all work from python 3.
Should any of this be updated for python 3?

sudo apt-get update
sudo apt-get install python-rpi.gpio python-spidev python-pip python-pil python-numpy

Install this library by running:

sudo pip install st7789

Was in the same boat as you with the same error message 30 mins ago, but now have it working.

Used sudo raspi-config to enable SPI (it was enabled anyway, but I was trying everything)

I ran sudo ./install-legacy.sh from:

Rebooted, and now the rainbows are deployed :)

I’ve checked my history, and they’re the only commands that look needed.

The two I2C boards I have plugged in were working fine prior to this, and are still happy now.

Hope it works for you!

I just now ran the breakout garden installer. My two i2c modules were working fine before, and detected this time around too. I have enabled i2c and SPI is Raspberry Pi Configuration. Once the Breakout Garden installer is finished I’m going to reboot and do the Color LCD install.
Do you remeber what commands you used, python 2 versus for python 3 etc?
And how are you running the examples, IDE or from command line?

It was all python2.

I did need to install the dependencies and library per:

but I guess you have that done already.

And I ran them all via the command line, then just the above commands from my last post.

When I ran sudo ./install-sh from the Breakout Garden code, I got an smbus ‘Module Not Found’ error, but that didn’t happen with the legacy installer.

Can’t think of anything else I did, and there’s nothing else in my history.

Best of Luck!

I did the following from terminal.

sudo apt-get update
sudo apt-get install python-rpi.gpio python-spidev python-pip python-pil python-numpy

They all said the latest version was already installed.

Install this library by running:

sudo pip install st7789

If I run scrolling-text.py from either Idle3 or Thorny I get an import error no module named ST7789.

Ok if I run the python file from terminal in python 2 it works
sudo python /st7789-python-master/examples/scrolling-text.py
So my issue is with trying to use it in python 3.

EDIT: I ran the following
sudo apt-get install python3-rpi.gpio python3-spidev python3-pip python3-pil python3-numpy
And got the all up to date, 0 updated, 0 newly installed.

EDIT2: Running
sudo python3 /st7789-python-master/examples/scrolling-text.py
gets me that same no module named ST7789 error?

EDIT3: I ran sudo pip3 install st7789 and now it works in python 3 from command line and idle3. I guess it was an SPI issue? I know I enabled it and checked it again after I got that error? Only thing different now is I didn’t mount my breakout garden mini on standoffs. It was on firmly before but maybe not making a good contact?

Anyway, thank you all for the help getting this working. It has been frustrating but its nice to finally succeed. =)

Yay!!!

Glad to hear it works!

I’m going to mount it back on the standoffs and do one more test. Just to see if its an issue. Now I know there is nothing wrong hardware wise so I can take my time programing to do what I want, when I actually figure out what I want to do with it lol.

EDIT: I mounted back on the standoffs and its still working. I guess it was the same deal you ran into? SPI wasn’t really enabled? I did at some point do what you advised and enabled SPI via raspi-config instead of Raspberry Pi configuration. Well both ways really. Just have to remeber this next time around. I may make a note on my install crib sheet / instructions text file.

1 Like