Raspberry Pi Pico
11 x 7 LED Matrix - Web Messages

Having successfully connected to a web API in the Weather over Wifi project, I wanted to see if I could get messages from an API scrolling across one of the IS31FL3731 matrices that I had. The photograph here shows the circuit I made. I am using a Pimoroni Pico Breakout Garden Pack and their 11 x 7 LED matrix breakout. The breakout garden pack is on one of their Omnibus Dual Expanders with the other set of pins being used for a Pimoroni Pico Wireless Pack. Other than the Pico itself, the hardware is all from Pimoroni.

Pico Circuit

You can make out a few letters of the fact that was scrolling across the matrix - something about American's, I believe. The idea with the project was to scroll a new fact across the display once every minute or so.

The software side of this project is all about Adafruit's CircuitPython. You need the following libraries for this project. Download the mpy library for these and copy to your lib folder.

Adafruit have an excellent library for the IS31FL3731 and it does include a subclass for using the 11 x 7 LED matrix. I wanted to the ability to scroll text and, having already implemented that in MicroPython, thought it would be easier just to convert my library. It turned out to be very easy to do. The following program was saved as scroll1x7.py and placed in the lib folder on the Pico.

from time import sleep

font = bytearray(b'\x00\x00\x00\x00\x00\x00\x00\x5f\x00\x00\x00\x07\x00\x07\x00\x14\x7f\x14'
                b'\x7f\x14\x24\x2a\x7f\x2a\x12\x23\x13\x08\x64\x62\x36\x49\x55\x22\x50\x00'
                b'\x05\x03\x00\x00\x00\x1c\x22\x41\x00\x00\x41\x22\x1c\x00\x14\x08\x3e\x08'
                b'\x14\x08\x08\x3e\x08\x08\x00\x50\x30\x00\x00\x08\x08\x08\x08\x08\x00\x60'
                b'\x60\x00\x00\x20\x10\x08\x04\x02\x3e\x51\x49\x45\x3e\x00\x42\x7f\x40\x00'
                b'\x42\x61\x51\x49\x46\x21\x41\x45\x4b\x31\x18\x14\x12\x7f\x10\x27\x45\x45'
                b'\x45\x39\x3c\x4a\x49\x49\x30\x01\x71\x09\x05\x03\x36\x49\x49\x49\x36\x06'
                b'\x49\x49\x29\x1e\x00\x36\x36\x00\x00\x00\x56\x36\x00\x00\x08\x14\x22\x41'
                b'\x00\x14\x14\x14\x14\x14\x00\x41\x22\x14\x08\x02\x01\x51\x09\x06\x32\x49'
                b'\x79\x41\x3e\x7e\x11\x11\x11\x7e\x7f\x49\x49\x49\x36\x3e\x41\x41\x41\x22'
                b'\x7f\x41\x41\x22\x1c\x7f\x49\x49\x49\x41\x7f\x09\x09\x09\x01\x3e\x41\x49'
                b'\x49\x7a\x7f\x08\x08\x08\x7f\x00\x41\x7f\x41\x00\x20\x40\x41\x3f\x01\x7f'
                b'\x08\x14\x22\x41\x7f\x40\x40\x40\x40\x7f\x02\x0c\x02\x7f\x7f\x04\x08\x10'
                b'\x7f\x3e\x41\x41\x41\x3e\x7f\x09\x09\x09\x06\x3e\x41\x51\x21\x5e\x7f\x09'
                b'\x19\x29\x46\x46\x49\x49\x49\x31\x01\x01\x7f\x01\x01\x3f\x40\x40\x40\x3f'
                b'\x1f\x20\x40\x20\x1f\x3f\x40\x38\x40\x3f\x63\x14\x08\x14\x63\x07\x08\x70'
                b'\x08\x07\x61\x51\x49\x45\x43\x00\x7f\x41\x41\x00\x02\x04\x08\x10\x20\x00'
                b'\x41\x41\x7f\x00\x04\x02\x01\x02\x04\x40\x40\x40\x40\x40\x00\x01\x02\x04'
                b'\x00\x20\x54\x54\x54\x78\x7f\x48\x44\x44\x38\x38\x44\x44\x44\x20\x38\x44'
                b'\x44\x48\x7f\x38\x54\x54\x54\x18\x08\x7e\x09\x01\x02\x0c\x52\x52\x52\x3e'
                b'\x7f\x08\x04\x04\x78\x00\x44\x7d\x40\x00\x20\x40\x44\x3d\x00\x7f\x10\x28'
                b'\x44\x00\x00\x41\x7f\x40\x00\x7c\x04\x18\x04\x78\x7c\x08\x04\x04\x78\x38'
                b'\x44\x44\x44\x38\x7c\x14\x14\x14\x08\x08\x14\x14\x18\x7c\x7c\x08\x04\x04'
                b'\x08\x48\x54\x54\x54\x20\x04\x3f\x44\x40\x20\x3c\x40\x40\x20\x7c\x1c\x20'
                b'\x40\x20\x1c\x3c\x40\x30\x40\x3c\x44\x28\x10\x28\x44\x0c\x50\x50\x50\x3c'
                b'\x44\x64\x54\x4c\x44\x00\x08\x36\x41\x00\x00\x00\x7f\x00\x00\x00\x41\x36'
                b'\x08\x00\x10\x08\x08\x10\x08\x00\x00\x00\x00\x00')

_f = 0
_b = bytearray(145)

class Matrix:
    def __init__(self, i2c):
        self.i2c = i2c
        while not self.i2c.try_lock():
            pass
        self._w(253, 11)
        sleep(0.1)
        self._w(10, 0)
        sleep(0.1)
        self._w(10, 1)
        sleep(0.1)
        self._w(0, 0)
        self._w(6, 0)
        for bank in [1,0]:
            self._w(253, bank)
            self._w([0] + [255] * 17)
        self.clear()
        self.show()


    def _w(self, *args):
        if len(args) == 1: args = args[0]
        self.i2c.writeto(117, bytes(args))

    def clear(self):
        global _b
        del _b
        _b = bytearray(145)

    def fill(self, v):
        global _b
        del _b
        _b = bytearray([v]*145)

    def show(self):
        global _f
        _f = not _f
        self._w(253, _f)
        _b[0] = 36
        self._w(_b)
        self._w(253, 11)
        self._w(1, _f)

    def set_pixel(self,col, row, brightness):
        global _b
        _b[self._pixel_addr(col, row)] = brightness

    def get_pixel(self, col, row):
        global _b
        return _b[self._pixel_addr(col, row)]

    def _pixel_addr(self, x, y):
        return (x << 4) - y + 1 + (6 if x <= 5 else -82)

    def scroll(self, txt, delay, v):
        global _b
        msg = bytearray([0]*11)
        for c in txt:
            msg += bytearray(b'\x00')
            msg += font[(ord(c)-32)*5:(ord(c)-32)*5+5]
            msg += bytearray(b'\x00')
        msg += bytearray([0]*11)
        for i in range(len(msg)-11):
            m = msg[i:i+11]
            for y in range(7):
                for x in range(11):
                    if m[x]>>y & 1:
                        self.set_pixel(x,y,v)
                    else:
                        self.set_pixel(x,y,0)
            self.show()
            sleep(delay)

The next job is to make a secrets.py file with the login details for your wireless network or mobile phone hotspot.

# This file is where you keep secret settings, passwords, and tokens!
# If you put them in the code you risk committing that info or sharing it

secrets = {
    'ssid' : "YOUR SSID",
    'password' : 'YOUR PASSWORD',
    'timezone' : "Europe/London", # http://worldtimeapi.org/timezones
    }

Here is the finished program. I have left in a variable with the URL for a cat facts API. If you swap that in, use 'fact' instead of text for the JSON. Both of these APIs do not require any authentication or a token of any sort.

import board
import busio
import adafruit_requests as requests
import adafruit_esp32spi.adafruit_esp32spi_socket as socket
from digitalio import DigitalInOut
from adafruit_esp32spi import adafruit_esp32spi
from scroll11x7 import Matrix
from time import sleep

i2c = busio.I2C(board.GP5, board.GP4)

display = Matrix(i2c)

# Get wifi details and more from a secrets.py file
try:
    from secrets import secrets
except ImportError:
    print("WiFi secrets are kept in secrets.py, please add them there!")
    raise

esp32_cs = DigitalInOut(board.GP7)
esp32_ready = DigitalInOut(board.GP10)
esp32_reset = DigitalInOut(board.GP11)

spi = busio.SPI(board.GP18, board.GP19, board.GP16)
esp = adafruit_esp32spi.ESP_SPIcontrol(spi, esp32_cs, esp32_ready, esp32_reset)

requests.set_socket(socket, esp)

if esp.status == adafruit_esp32spi.WL_IDLE_STATUS:
    print("ESP32 found and in idle mode")
print("Firmware vers.", esp.firmware_version)
print("MAC addr:", [hex(i) for i in esp.MAC_address])

for ap in esp.scan_networks():
    print("\t%s\t\tRSSI: %d" % (str(ap["ssid"], "utf-8"), ap["rssi"]))

print("Connecting to AP...")
while not esp.is_connected:
    try:
        esp.connect_AP(secrets["ssid"], secrets["password"])
    except RuntimeError as e:
        print("could not connect to AP, retrying: ", e)
        continue
print("Connected to", str(esp.ssid, "utf-8"), "\tRSSI:", esp.rssi)


cat_url = "https://catfact.ninja/fact" # fact
fact_url = "https://uselessfacts.jsph.pl/random.json?language=en" # text


while True:
    r = requests.get(fact_url)
    fact = r.json()["text"]
    print(fact)
    display.scroll(fact, 0.02, 128)
    sleep(60)

Watching the messages on the matrix is a little uncomfortable, even at low brightness. It is, however, a nice project to prove the concept. It also shows that my IS31FL3731 library can be repurposed quickly in CircuitPython whenever needed.