Raspberry Pi Pico
Paging Text On LCDs

I have a couple of LCDs that I have been using with the Pico. I have already shown two of these in the MicroPython Projects section drawing the Sierpiński Triangle. One of them is the 240 x 240 pixel 1.3 inch LCD from the breakout garden collection. Since then, I have acquired the Pimoroni Pico Display Pack. Both of these use an ST7789V driver chip.

I decided to use CircuitPython and Adafruit's displayio library. I had been using a display to show text that I had taken from a web api. When the amount of text fetched from the web site, I wanted a method to break that into navigable pages of text so that they could be read comfortably.

Libraries

You need the following libraries for this project. Download the mpy library for these and copy to your lib folder.

Pico Display Pack

I will start with the display pack because you aren't going to be concerned with how to make the connections. I have it here on a Pico Omnibus Dual Expander from Pimoroni.

Pico Circuit

The display is full of text here. The Y and B buttons will allow you to page forward and backward through the complete text at your leisure.

In order to put the page together, I looked at some of the examples for the using LCDs with text. I checked out the pinning information on the Pimoroni product page for the pack. I then wrote a simple subroutine text_to_pages which starts by wrapping the text using a method from the display_text library. It then chunks the text into a list based on the number of lines you can fit on the display with the size of text you are using. Line breaks are put into the text at the ends of these lines and a list of pages is returned. The rest of the code is just getting things onto the display.

import board
import busio
import terminalio
import displayio
from digitalio import DigitalInOut, Pull
from adafruit_display_text import label, wrap_text_to_lines
from adafruit_st7789 import ST7789
from time import sleep

def text_to_pages(txt, columns, rows):
    lines = wrap_text_to_lines(txt, columns)
    lines = [lines[i:i+rows] for i in range(0, len(lines), rows)]
    return ["\n".join(i) for i in lines]

# function to read buttons into a binary pattern, 1 for pressed
def read_btns(btn_list):
    result = 0
    for i, b in enumerate(btn_list):
        result +=  (b.value^1)<<i
    return result

# function to determine if a specific button is pressed
def pressed(pattern, b):
    return pattern>>b & 1

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

tft_cs = board.GP17
tft_dc = board.GP16
spi_mosi = board.GP19
spi_clk = board.GP18
spi = busio.SPI(spi_clk, spi_mosi)

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

display = ST7789(display_bus, width=240, height=135, rowstart=40, colstart=53, rotation=90)

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

color_bitmap = displayio.Bitmap(240, 135, 1)
color_palette = displayio.Palette(1)
color_palette[0] = 0x0000FF

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(236, 131, 1)
inner_palette = displayio.Palette(1)
inner_palette[0] = 0x000000
inner_sprite = displayio.TileGrid(inner_bitmap, pixel_shader=inner_palette, x=2, y=2)
splash.append(inner_sprite)

# Draw a label
text_group = displayio.Group(scale=1, x=10, y=16)

text = ("Lots and lots and lots of writing that won't fit the screen"
" unless there is some text wrapping. Uh oh. This ain't gonna work."
" So let's try to make a mechanism for working out pages of writing"
" for the particular display we are working with. If we use the font"
" without any scaling, we can fit a shedload of text on the screen"
" without too many issues. So I need quite a bit of text here for"
" testing. I think I have a couple of pages now to make a reasonable"
" for the paging mechanic that I want to implement.")

pages = text_to_pages(text, 34, 8)
text_area = label.Label(terminalio.FONT, text=pages[0], color=0xFFFFFF, line_spacing = 1.0)
text_group.append(text_area)
splash.append(text_group)

# set up pins
btn_pins = [board.GP12, board.GP13, board.GP14, board.GP15]
btns = [DigitalInOut(p) for p in btn_pins]

# set up as buttons
for b in btns:
    b.switch_to_input(pull = Pull.UP)

page = 0
previous = 0
while True:
    reading = read_btns(btns)
    if reading != previous:
        if pressed(reading, 3):
            page -= 1
        elif pressed(reading, 1):
            page += 1
        page = max(0,min(page, len(pages)-1))
        text_area.text = pages[page]
    previous = reading

I used quite small text on this display and, because I am old with bad eyesight, it's at the edge of what I can cope with.

1.3 Inch LCD Breakout

I then switched to the larger display which has more pixels to use for displaying the text. I have also increased the size of the font. This means I need more pages. I have connected this breakout using one of Pimoroni's adapters for SPI devices. Behind the bird's nest of jumper cables, there are two small pushbuttons to use for connecting the display.

The connections I made from the breakout, left to right, are,

  • 3-5V: 3V3
  • CS: GP17
  • SCK: GP18
  • MOSI: GP19
  • DC: GP16
  • BL: 3V3
  • GND: GND

These can all be replaced with equivalents. If you do change them, SCK and MOSI need to go to SPI pins labelled for that purpose.

The pushbuttons are connected to GND on one pin and to GP21 and GP22 on the Pico.

The code is very similar. I have changed the text scaling and the display setup line. I have also chosen a different number of columns to account for the larger font.

Pico Circuit

import board
import busio
import terminalio
import displayio
from digitalio import DigitalInOut, Pull
from adafruit_display_text import label, wrap_text_to_lines
from adafruit_st7789 import ST7789
from time import sleep

def text_to_pages(txt, columns, rows):
    lines = wrap_text_to_lines(txt, columns)
    lines = [lines[i:i+rows] for i in range(0, len(lines), rows)]
    return ["\n".join(i) for i in lines]

# function to read buttons into a binary pattern, 1 for pressed
def read_btns(btn_list):
    result = 0
    for i, b in enumerate(btn_list):
        result +=  (b.value^1)<<i
    return result

# function to determine if a specific button is pressed
def pressed(pattern, b):
    return pattern>>b & 1

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

tft_cs = board.GP17
tft_dc = board.GP16
spi_mosi = board.GP19
spi_clk = board.GP18
spi = busio.SPI(spi_clk, spi_mosi)

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

display = ST7789(display_bus, width=240, height=240, rowstart=80, rotation=180)

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

color_bitmap = displayio.Bitmap(240, 240, 1)
color_palette = displayio.Palette(1)
color_palette[0] = 0x0000FF

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(236, 236, 1)
inner_palette = displayio.Palette(1)
inner_palette[0] = 0x000000
inner_sprite = displayio.TileGrid(inner_bitmap, pixel_shader=inner_palette, x=2, y=2)
splash.append(inner_sprite)

# Draw a label
text_group = displayio.Group(scale=2, x=10, y=10)

text = ("Lots and lots and lots of writing that won't fit the screen"
" unless there is some text wrapping. Uh oh. This ain't gonna work."
" So let's try to make a mechanism for working out pages of writing"
" for the particular display we are working with. If we use the font"
" without too much scaling, we can fit a shedload of text on the screen"
" without too many issues. So I need quite a bit of text here for"
" testing. I think I have a couple of pages now to make a reasonable"
" test for the paging mechanic that I want to implement.")

pages = text_to_pages(text, 17, 8)
text_area = label.Label(terminalio.FONT, text=pages[0], color=0xFFFFFF, line_spacing = 1.0)
text_group.append(text_area)
splash.append(text_group)

# set up pins
btn_pins = [board.GP21, board.GP22]
btns = [DigitalInOut(p) for p in btn_pins]

# set up as buttons
for b in btns:
    b.switch_to_input(pull = Pull.UP)

page = 0
previous = 0
while True:
    reading = read_btns(btns)
    if reading != previous:
        if pressed(reading, 0):
            page -= 1
        elif pressed(reading, 1):
            page += 1
        page = max(0,min(page, len(pages)-1))
        text_area.text = pages[page]
    previous = reading

Whilst this may not be the most exciting of projects, it does address a need I have had with some projects using these displays. The paging works nicely and is something I will be using whenever I want to display an amount of text that is not known until runtime.