How to use an LCD with GC9A01 driver chip with Pimoroni uf2

Is there a way to use the Pimoroni uf2 firmware with a round LCD, which uses the GC9A01 driver chip. A clever person on Github (Russ Hughes) has his own modified uf2 firmware but that then loses the good facilities in the Pimoroni version.

I wondered if there was a method to use the Pimoroni uf2 and load in a GC9A91 driver, which patched so that I could use the ‘standard’ Pimoroni graphic routines.

Thanks for looking.

You can use pure MicroPython drivers too, with any MicroPython UF2. As far as I know, there is one around for the GC9A01. But of course you can’t use it together with the proprietary Pimoroni graphics routines.

As a side note: this is a typical example why I think that it is such a bad idea to implement all the driver stuff directly in the firmware. It basically makes everything a “closed-shop product” (aka “only works with Pimoroni stuff”), unless you are a real programmer and can hack the source code.

I have not found a separate driver for the GC9A01 chip apart from the complete uf2 firmware produced by Russ Hughes. I will keep looking as you think there is one available. If you happen to spot a driver, please share.

Yes, putting drivers within the uf2 firmware makes it a locked and closed shop. I can understand doing it for commercial reasons, but it does make it a difficult to mix and match devices and economise size as keeping all of the drivers within the firmware bloats the size which is a total waste when many of the drivers will not be needed in certain projects.

Russ has a pure MicroPython driver too: GitHub - russhughes/gc9a01py: GC9A01 Display driver in MicroPython

I did a tutorial using this display some time ago. You can find it here:
Digital Watch Display - MicroPython : 10 Steps - Instructables

It uses a separate driver from Waveshare and is all in MicroPython. I’ve developed a series of graphics routines which are included but can now be replaced in part now that MicroPython have added poly and circle routines within framebuf.

Now that summer (?) has finished and the nights are drawing in I will be getting back to coding shortly. On my list are a reworking of my graphics routines and seeing what I can get out of the new Pimoroni Explorer. (Any ideas about an updated delivery date?)

@bablokb thanks for the link. I have downloaded his standalone py driver to use with the Pimoroni uf2 firmware and will experiment

@Tonygo2 just when I was to start experimenting further with Russ’ stand alone driver, I saw your very interesting project. I have quickly downloaded to try it using a Pico W, with the standard Pimoroni 1.23.0 - bugfix 1 UF2.

I need to use SPI0 and so I changed line 50 to:
self.spi = SPI(0,100_000_000,polarity=0, phase=0,sck=Pin(SCK),mosi=Pin(MOSI),miso=None)

Unfortunately, when I run the program I get ‘MemoryError: memory allocation failed, allocating 115200 bytes’ at line 53, which is:
self.buffer = bytearray(self.height * self.width * 2)

(where height and width are as per your settings, 240/240.

I need to look into why this is happening, but if you have an obvious fix before I find it, could you please let me know.

What I am trying to get is:
Use the standard Pimoroni UF2 using their standard built-in graphics/fonts/text/colours, which draws everything into their framebuffer, then do you have a driver to only shift that framebuffer into the 1.28" GC9A01 display?

This would save duplication of graphic routines and commands.

Thanks, Steve

Unfortunately, the Pico-W uses a lot of available RAM for the TCP/IP stack. This is a common pitfall: with the Pico-W you can either do networking, or update a fairly large display. There are some workarounds, like garbage-collecting stuff you don’t need anymore, but in the end, you run into problems.

So the best is to either switch to a different platform (e.g. ESP32-S3), or wait for the Pico-W2. That will have twice the RAM and therefore allow us to do more.

Thanks for that info @bablokb. It looks like the Pico-W2 is going to be the best way forward with it having more RAM. I will get one on order!

Now that framebuf has routines for triangles and ellipses we no longer need these very large routines in our code. Waveshare recently showed how to enlarge the size of the build-in font. I’ve combined both these space saving improvements in the following code.

# GC9A01 driver with compact GFX & Text routines
# Tony Goodhew 24 Sept 2024
# Driver and text code from Waveshare
# Uses framebuf routines
# This method is easy to adapt to other LCD screens with Waveshare drivers

from machine import Pin,I2C,SPI,PWM
import framebuf
import time
import math
import array
import random

DC = 8
CS = 9
SCK = 10
MOSI = 11
RST = 12

BL = 25

Vbat_Pin = 29
width = 240
height = 240

class LCD_1inch28(framebuf.FrameBuffer): # Waveshare RP2040 1.28" IPS LCD Board Driver - Round Display
    def __init__(self):
        self.width = 240
        self.height = 240
        
        self.cs = Pin(CS,Pin.OUT)
        self.rst = Pin(RST,Pin.OUT)
        
        self.cs(1)
        self.spi = SPI(1,100_000_000,polarity=0, phase=0,sck=Pin(SCK),mosi=Pin(MOSI),miso=None)
        self.dc = Pin(DC,Pin.OUT)
        self.dc(1)
        self.buffer = bytearray(self.height * self.width * 2)
        super().__init__(self.buffer, self.width, self.height, framebuf.RGB565)
        self.init_display()
        
        self.red   =   0x07E0
        self.green =   0x001f
        self.blue  =   0xf800
        self.white =   0xffff
        
        self.fill(self.white)
        self.show()

        self.pwm = PWM(Pin(BL))
        self.pwm.freq(5000)
        
    def write_cmd(self, cmd):
        self.cs(1)
        self.dc(0)
        self.cs(0)
        self.spi.write(bytearray([cmd]))
        self.cs(1)

    def write_data(self, buf):
        self.cs(1)
        self.dc(1)
        self.cs(0)
        self.spi.write(bytearray([buf]))
        self.cs(1)
    def set_bl_pwm(self,duty):
        self.pwm.duty_u16(duty)#max 65535
    def init_display(self):
        """Initialize display"""  
        self.rst(1)
        time.sleep(0.01)
        self.rst(0)
        time.sleep(0.01)
        self.rst(1)
        time.sleep(0.05)
        
        self.write_cmd(0xEF)
        self.write_cmd(0xEB)
        self.write_data(0x14) 
        
        self.write_cmd(0xFE) 
        self.write_cmd(0xEF) 

        self.write_cmd(0xEB)
        self.write_data(0x14) 

        self.write_cmd(0x84)
        self.write_data(0x40) 

        self.write_cmd(0x85)
        self.write_data(0xFF) 

        self.write_cmd(0x86)
        self.write_data(0xFF) 

        self.write_cmd(0x87)
        self.write_data(0xFF)

        self.write_cmd(0x88)
        self.write_data(0x0A)

        self.write_cmd(0x89)
        self.write_data(0x21) 

        self.write_cmd(0x8A)
        self.write_data(0x00) 

        self.write_cmd(0x8B)
        self.write_data(0x80) 

        self.write_cmd(0x8C)
        self.write_data(0x01) 

        self.write_cmd(0x8D)
        self.write_data(0x01) 

        self.write_cmd(0x8E)
        self.write_data(0xFF) 

        self.write_cmd(0x8F)
        self.write_data(0xFF) 


        self.write_cmd(0xB6)
        self.write_data(0x00)
        self.write_data(0x20)

        self.write_cmd(0x36)
        self.write_data(0x98)

        self.write_cmd(0x3A)
        self.write_data(0x05) 


        self.write_cmd(0x90)
        self.write_data(0x08)
        self.write_data(0x08)
        self.write_data(0x08)
        self.write_data(0x08) 

        self.write_cmd(0xBD)
        self.write_data(0x06)
        
        self.write_cmd(0xBC)
        self.write_data(0x00)

        self.write_cmd(0xFF)
        self.write_data(0x60)
        self.write_data(0x01)
        self.write_data(0x04)

        self.write_cmd(0xC3)
        self.write_data(0x13)
        self.write_cmd(0xC4)
        self.write_data(0x13)

        self.write_cmd(0xC9)
        self.write_data(0x22)

        self.write_cmd(0xBE)
        self.write_data(0x11) 

        self.write_cmd(0xE1)
        self.write_data(0x10)
        self.write_data(0x0E)

        self.write_cmd(0xDF)
        self.write_data(0x21)
        self.write_data(0x0c)
        self.write_data(0x02)

        self.write_cmd(0xF0)   
        self.write_data(0x45)
        self.write_data(0x09)
        self.write_data(0x08)
        self.write_data(0x08)
        self.write_data(0x26)
        self.write_data(0x2A)

        self.write_cmd(0xF1)    
        self.write_data(0x43)
        self.write_data(0x70)
        self.write_data(0x72)
        self.write_data(0x36)
        self.write_data(0x37)  
        self.write_data(0x6F)


        self.write_cmd(0xF2)   
        self.write_data(0x45)
        self.write_data(0x09)
        self.write_data(0x08)
        self.write_data(0x08)
        self.write_data(0x26)
        self.write_data(0x2A)

        self.write_cmd(0xF3)   
        self.write_data(0x43)
        self.write_data(0x70)
        self.write_data(0x72)
        self.write_data(0x36)
        self.write_data(0x37) 
        self.write_data(0x6F)

        self.write_cmd(0xED)
        self.write_data(0x1B) 
        self.write_data(0x0B) 

        self.write_cmd(0xAE)
        self.write_data(0x77)
        
        self.write_cmd(0xCD)
        self.write_data(0x63)


        self.write_cmd(0x70)
        self.write_data(0x07)
        self.write_data(0x07)
        self.write_data(0x04)
        self.write_data(0x0E) 
        self.write_data(0x0F) 
        self.write_data(0x09)
        self.write_data(0x07)
        self.write_data(0x08)
        self.write_data(0x03)

        self.write_cmd(0xE8)
        self.write_data(0x34)

        self.write_cmd(0x62)
        self.write_data(0x18)
        self.write_data(0x0D)
        self.write_data(0x71)
        self.write_data(0xED)
        self.write_data(0x70) 
        self.write_data(0x70)
        self.write_data(0x18)
        self.write_data(0x0F)
        self.write_data(0x71)
        self.write_data(0xEF)
        self.write_data(0x70) 
        self.write_data(0x70)

        self.write_cmd(0x63)
        self.write_data(0x18)
        self.write_data(0x11)
        self.write_data(0x71)
        self.write_data(0xF1)
        self.write_data(0x70) 
        self.write_data(0x70)
        self.write_data(0x18)
        self.write_data(0x13)
        self.write_data(0x71)
        self.write_data(0xF3)
        self.write_data(0x70) 
        self.write_data(0x70)

        self.write_cmd(0x64)
        self.write_data(0x28)
        self.write_data(0x29)
        self.write_data(0xF1)
        self.write_data(0x01)
        self.write_data(0xF1)
        self.write_data(0x00)
        self.write_data(0x07)

        self.write_cmd(0x66)
        self.write_data(0x3C)
        self.write_data(0x00)
        self.write_data(0xCD)
        self.write_data(0x67)
        self.write_data(0x45)
        self.write_data(0x45)
        self.write_data(0x10)
        self.write_data(0x00)
        self.write_data(0x00)
        self.write_data(0x00)

        self.write_cmd(0x67)
        self.write_data(0x00)
        self.write_data(0x3C)
        self.write_data(0x00)
        self.write_data(0x00)
        self.write_data(0x00)
        self.write_data(0x01)
        self.write_data(0x54)
        self.write_data(0x10)
        self.write_data(0x32)
        self.write_data(0x98)

        self.write_cmd(0x74)
        self.write_data(0x10)
        self.write_data(0x85)
        self.write_data(0x80)
        self.write_data(0x00) 
        self.write_data(0x00) 
        self.write_data(0x4E)
        self.write_data(0x00)
        
        self.write_cmd(0x98)
        self.write_data(0x3e)
        self.write_data(0x07)

        self.write_cmd(0x35)
        self.write_cmd(0x21)

        self.write_cmd(0x11)
        time.sleep(0.12)
        self.write_cmd(0x29)
        time.sleep(0.02)
        
        self.write_cmd(0x21)

        self.write_cmd(0x11)

        self.write_cmd(0x29)

    def show(self):
        self.write_cmd(0x2A)
        self.write_data(0x00)
        self.write_data(0x00)
        self.write_data(0x00)
        self.write_data(0xef)
        
        self.write_cmd(0x2B)
        self.write_data(0x00)
        self.write_data(0x00)
        self.write_data(0x00)
        self.write_data(0xEF)
        
        self.write_cmd(0x2C)
        
        self.cs(1)
        self.dc(1)
        self.cs(0)
        self.spi.write(self.buffer)
        self.cs(1)


def colour(R,G,B): # Convert RGB888 to RGB565
    return (((G&0b00011100)<<3) +((B&0b11111000)>>3)<<8) + (R&0b11111000)+((G&0b11100000)>>5)

LCD = LCD_1inch28()            #=============== Initialise the display ===================
LCD.set_bl_pwm(65535)          # Brightness


# =========== GFX Routines ============
def clear(c):
    LCD.fill(c) 

def circle(x,y,r,c):
    LCD.ellipse(x,y,r,r,c,1)

def ring(x,y,r,c):
    LCD.ellipse(x,y,r,r,c,0)
  
def write_text(text,x,y,size,color): # Thanks to Waveshare 1.47 incj LCD Module Docs.
    ''' Method to write Text on OLED/lcd Displays
        with a variable font size
        Args:
            text: the string of chars to be displayed
            x: x co-ordinate of starting position
            y: y co-ordinate of starting position
            size: font size of text
            color: color of text to be displayed
    '''
    background = LCD.pixel(x,y)
    info = []
    # Creating reference characters to read their values
    LCD.text(text,x,y,color) # Uses code in Framebuf
    for i in range(x,x+(8*len(text))):
        for j in range(y,y+8):
            # Fetching amd saving details of pixels, such as
            # x co-ordinate, y co-ordinate, and color of the pixel
            px_color = LCD.pixel(i,j)
            info.append((i,j,px_color)) if px_color == color else None
    # Clearing the reference characters from the screen
    LCD.text(text,x,y,0)
    # Writing the custom-sized font characters on screen
    for px_info in info:
        LCD.fill_rect(size*px_info[0] - (size-1)*x , size*px_info[1] - (size-1)*y, size, size, px_info[2])   

def centre_text(text,y,size,color): # Additional routine from Tony Goodhew 
    x = int((width - len(text)*size*8)/2)
    write_text(text,x,y,size,color)

def triangle(x1,y1,x2,y2,x3,y3,c): # Draw outline triangle
    q = array.array('h',(x1,y1,x2,y2,x3,y3))
    LCD.poly(x1,y1,q,c,0)
    
def tri_filled(x1,y1,x2,y2,x3,y3,c): # Draw filled triangle
    q = array.array('h',(x1,y1,x2,y2,x3,y3))
    LCD.poly(x1,y1,q,c,1)

def circle(x,y,r,c):
    LCD.ellipse(x,y,r,r,c,1)
# =========== End of support routines ===========
    
# ==== Board now setup ========== MAIN BELOW====================

clear(0)  # Clear the screen
xc = 120  # Coordinates of centre
yc = 120

centre_text("GC9A01",90,3,colour(0,255,255))
centre_text("GFX",120,3,colour(255,255,0))
centre_text("Tony Goodhew",150,2,colour(200,200,200))
LCD.show()
time.sleep(1.7)
clear(0)

x1 =10
x2 = 100
x3 = 200
y1 = 50
y2 = 150
y3 = 20
c = colour(255,0,0)

tri_filled(x1,y1,x2,y2,x3,y3,c)
LCD.show()
time.sleep(1)
triangle(x1,y1,x2,y2,x3,y3,colour(0,255,0))
LCD.show()
time.sleep(1)

for r in range(120,5,-5):
    c = colour(random.randint(0,255),random.randint(0,255),random.randint(0,255))
    circle(xc,yc,r,c)
    LCD.show()
    time.sleep(0.15)
  
# Character size demo
LCD.fill(0)
for size in range(1,13,1):    
    centre_text(str(size),80-size*2,size,colour(255,255,0))
#    centre_text("Text Size" ,140,4,colour(0,0,255))
    LCD.show()
    time.sleep(0.8)
    if size == 1: time.sleep(0.6)
    LCD.fill(0)
LCD.show()
LCD.fill(0)
size = 5
centre_text("BYE",100-size*2,size,colour(255,0,255))
centre_text("Tony Goodhew",150,2,colour(200,200,200))
LCD.show()
time.sleep(3)
clear(0)
LCD.show()

You can see it working on the small round screen here:

You now have much more room for your own graphics code. It is all done in Micropython so can be added to or edited down by anyone.

I hope this helps.

Very nice. Could you please add some print(f"free memory: {gc.mem_free()}") calls to trace memory usage?

Then, comparing a pristine Pico-UF2 with a Pico-W-UF2 (i.e. gc.mem_free() directly at the start of the program) one could guess if this will also work with the Pico-W (at least if not doing any networking).

Thanks @Tonygo2 for the updated code - yes it certainly helps! I have just quickly tried it using a Pico W with Pimoroni Pico W 1.23.0 - bugfix1.uf2

I have changed to use SPI0.

For @bablokb
free memory: 178160 << start
free memory: 30528 << end

  1. Is there a method to load in a jpg pic with this driver.

The Pimoroni method is
j = jpegdec.JPEG(display) # Create a new JPEG decoder
j.open_file(“mymeter.jpg”) # Open the JPEG file
j.decode(0, 0, jpegdec.JPEG_SCALE_FULL) # place in screen centre and decode JPEG

I am not sure what to change for (display) to be able to create the JPEG instance.

Would you then be able to draw Pimoroni text and fonts into this GC9A01 framebuffer?

It is looking excellent - thanks to you both.
Steve

Thanks to the example code from @Tonygo2 and Waveshare. I have altered it to use the Pimoroni Pico W 1.23.0 uf2 and PicoGraphics which allow me to use their graphics/text/fonts and load in a JPEG file.

The dedicated uf2 by Russ Hughes is much faster and very comprehensive (it is very cleverly done) but I wanted to use the Pimoroni uf2 with their updated fonts and other devices.

This is what I have done and it is probably crude but works.

# 1.28" GC9A01 driver chip LCD, using Pimoroni Pico (W) uf2 and PicoGraphics funcs
# Stephen Alsop. 27-9-24, based with thanks on Waveshare and Tony Goodhew's example code.
# All graphics, text, fonts, jpg, etc, funcs are standard Pimoroni PicoGraphics, except
# use 'lcd128.update()' to update LCD with the framebuf data and not Pimoroni's 'display.update()'.

'''
STILL TO DO:
1. How to rotate framebuf to allow rotating the LCD display, eg PicoGraphics uses 'rotate=180'.
2. How to update the LCD with framebuf data using DMA/PIO in the background to save Pico processing time.
3. How to handle single byte colour (type RGB332 as per PicoGraphics), which will then halve framebuf size.
4. Q: is there a ring/elipse() func within PicoGraphics, like the Micropythpn framebuf funcs?
'''

# import framebuf # not needed as we use Pimoroni uf2 graphic funcs, https://docs.micropython.org/en/latest/library/framebuf.html
from machine import Pin, SPI # PWM not needed as there is no backlight on my LCD
import time
import math
import jpegdec  # for jpg graphics
import gc       # for gc.collect(), etc
gc.enable()     # enable memory funcs
print(f"Initial free memory: {gc.mem_free()}")

# LCD_1inch28() class - if this is stored in the Pico, use 'import LCD_1inch28'
class LCD_1inch28(): # 1.28" 240x240 round LCD
    def __init__(self):
        self.width = 240
        self.height = 240
        
        self.cs = Pin(CS,Pin.OUT)
        self.rst = Pin(RST,Pin.OUT)
         
        self.cs(1)
        self.spi = SPI(0,100_000_000,polarity=0, phase=0,sck=Pin(SCK),mosi=Pin(MOSI),miso=None)
        self.dc = Pin(DC,Pin.OUT)
        self.dc(1)
        self.buffer = bytearray(self.height * self.width * 2) # create correct sized framebuf
        self.init_display()
        ## self.pwm = PWM(Pin(BL)) # not used as there is no backlight on myLCD
        ## self.pwm.freq(5000)
        
    def write_cmd(self, cmd):
        self.cs(1)
        self.dc(0)
        self.cs(0)
        self.spi.write(bytearray([cmd]))
        self.cs(1)

    def write_data(self, buf):
        self.cs(1)
        self.dc(1)
        self.cs(0)
        self.spi.write(bytearray([buf]))
        self.cs(1)
    
    # def set_bl_pwm(self,duty):  # not used as there is no backlight connection on myLCD
    #     self.pwm.duty_u16(duty) # max 65535
        
    def init_display(self): # init GC9A01 display chip
        self.rst(1)
        time.sleep(0.01)
        self.rst(0)
        time.sleep(0.01)
        self.rst(1)
        time.sleep(0.05)
        
        self.write_cmd(0xEF)
        self.write_cmd(0xEB)
        self.write_data(0x14) 
        
        self.write_cmd(0xFE) 
        self.write_cmd(0xEF) 

        self.write_cmd(0xEB)
        self.write_data(0x14) 

        self.write_cmd(0x84)
        self.write_data(0x40) 

        self.write_cmd(0x85)
        self.write_data(0xFF) 

        self.write_cmd(0x86)
        self.write_data(0xFF) 

        self.write_cmd(0x87)
        self.write_data(0xFF)

        self.write_cmd(0x88)
        self.write_data(0x0A)

        self.write_cmd(0x89)
        self.write_data(0x21) 

        self.write_cmd(0x8A)
        self.write_data(0x00) 

        self.write_cmd(0x8B)
        self.write_data(0x80) 

        self.write_cmd(0x8C)
        self.write_data(0x01) 

        self.write_cmd(0x8D)
        self.write_data(0x01) 

        self.write_cmd(0x8E)
        self.write_data(0xFF) 

        self.write_cmd(0x8F)
        self.write_data(0xFF) 

        self.write_cmd(0xB6)
        self.write_data(0x00)
        self.write_data(0x20)

        self.write_cmd(0x36)
        self.write_data(0x98)

        self.write_cmd(0x3A)
        self.write_data(0x05) 

        self.write_cmd(0x90)
        self.write_data(0x08)
        self.write_data(0x08)
        self.write_data(0x08)
        self.write_data(0x08) 

        self.write_cmd(0xBD)
        self.write_data(0x06)
        
        self.write_cmd(0xBC)
        self.write_data(0x00)

        self.write_cmd(0xFF)
        self.write_data(0x60)
        self.write_data(0x01)
        self.write_data(0x04)

        self.write_cmd(0xC3)
        self.write_data(0x13)
        self.write_cmd(0xC4)
        self.write_data(0x13)

        self.write_cmd(0xC9)
        self.write_data(0x22)

        self.write_cmd(0xBE)
        self.write_data(0x11) 

        self.write_cmd(0xE1)
        self.write_data(0x10)
        self.write_data(0x0E)

        self.write_cmd(0xDF)
        self.write_data(0x21)
        self.write_data(0x0c)
        self.write_data(0x02)

        self.write_cmd(0xF0)   
        self.write_data(0x45)
        self.write_data(0x09)
        self.write_data(0x08)
        self.write_data(0x08)
        self.write_data(0x26)
        self.write_data(0x2A)

        self.write_cmd(0xF1)    
        self.write_data(0x43)
        self.write_data(0x70)
        self.write_data(0x72)
        self.write_data(0x36)
        self.write_data(0x37)  
        self.write_data(0x6F)

        self.write_cmd(0xF2)   
        self.write_data(0x45)
        self.write_data(0x09)
        self.write_data(0x08)
        self.write_data(0x08)
        self.write_data(0x26)
        self.write_data(0x2A)

        self.write_cmd(0xF3)   
        self.write_data(0x43)
        self.write_data(0x70)
        self.write_data(0x72)
        self.write_data(0x36)
        self.write_data(0x37) 
        self.write_data(0x6F)

        self.write_cmd(0xED)
        self.write_data(0x1B) 
        self.write_data(0x0B) 

        self.write_cmd(0xAE)
        self.write_data(0x77)
        
        self.write_cmd(0xCD)
        self.write_data(0x63)

        self.write_cmd(0x70)
        self.write_data(0x07)
        self.write_data(0x07)
        self.write_data(0x04)
        self.write_data(0x0E) 
        self.write_data(0x0F) 
        self.write_data(0x09)
        self.write_data(0x07)
        self.write_data(0x08)
        self.write_data(0x03)

        self.write_cmd(0xE8)
        self.write_data(0x34)

        self.write_cmd(0x62)
        self.write_data(0x18)
        self.write_data(0x0D)
        self.write_data(0x71)
        self.write_data(0xED)
        self.write_data(0x70) 
        self.write_data(0x70)
        self.write_data(0x18)
        self.write_data(0x0F)
        self.write_data(0x71)
        self.write_data(0xEF)
        self.write_data(0x70) 
        self.write_data(0x70)

        self.write_cmd(0x63)
        self.write_data(0x18)
        self.write_data(0x11)
        self.write_data(0x71)
        self.write_data(0xF1)
        self.write_data(0x70) 
        self.write_data(0x70)
        self.write_data(0x18)
        self.write_data(0x13)
        self.write_data(0x71)
        self.write_data(0xF3)
        self.write_data(0x70) 
        self.write_data(0x70)

        self.write_cmd(0x64)
        self.write_data(0x28)
        self.write_data(0x29)
        self.write_data(0xF1)
        self.write_data(0x01)
        self.write_data(0xF1)
        self.write_data(0x00)
        self.write_data(0x07)

        self.write_cmd(0x66)
        self.write_data(0x3C)
        self.write_data(0x00)
        self.write_data(0xCD)
        self.write_data(0x67)
        self.write_data(0x45)
        self.write_data(0x45)
        self.write_data(0x10)
        self.write_data(0x00)
        self.write_data(0x00)
        self.write_data(0x00)

        self.write_cmd(0x67)
        self.write_data(0x00)
        self.write_data(0x3C)
        self.write_data(0x00)
        self.write_data(0x00)
        self.write_data(0x00)
        self.write_data(0x01)
        self.write_data(0x54)
        self.write_data(0x10)
        self.write_data(0x32)
        self.write_data(0x98)

        self.write_cmd(0x74)
        self.write_data(0x10)
        self.write_data(0x85)
        self.write_data(0x80)
        self.write_data(0x00) 
        self.write_data(0x00) 
        self.write_data(0x4E)
        self.write_data(0x00)
        
        self.write_cmd(0x98)
        self.write_data(0x3e)
        self.write_data(0x07)

        self.write_cmd(0x35)
        self.write_cmd(0x21)

        self.write_cmd(0x11)
        time.sleep(0.12)
        self.write_cmd(0x29)
        time.sleep(0.02)
        
        self.write_cmd(0x21)
        self.write_cmd(0x11)
        self.write_cmd(0x29)

    # update LCD with framebuf data, used in place of Pimoroni's 'display.update()'
    def update(self):
        self.write_cmd(0x2A)
        self.write_data(0x00)
        self.write_data(0x00)
        self.write_data(0x00)
        self.write_data(0xef)
        
        self.write_cmd(0x2B)
        self.write_data(0x00)
        self.write_data(0x00)
        self.write_data(0x00)
        self.write_data(0xEF)
        
        self.write_cmd(0x2C)
        
        self.cs(1)
        self.dc(1)
        self.cs(0)
        self.spi.write(self.buffer)
        self.cs(1)

def colour888to565(R,G,B): # Convert RGB888 to RGB565, not used in this example code
    return (((G&0b00011100)<<3) +((B&0b11111000)>>3)<<8) + (R&0b11111000)+((G&0b11100000)>>5)
### end of LCD_1inch28() class


# MAIN SECTION

# Hardware connections using a Pico W and SPI0
# LCD CONNS TOP-BOTTOM   MY WIRE COL  PICO PCB CONN
# Vcc         # +3v3       RD          36
# Gnd         # 0ve        BK          38
SCK = 18      # SCK        OR          24
MOSI = 19     # MOSI/SDA   GN          25
DC = 20       # DATA/CMD   BN          26
CS = 17       # CHIP SEL   YL          22
RST = 10      # RESET      WH          14

# BL = 25     # not used as there is no backlight control on this LCD


# Init 1.28" LCD and PicoGraphics
# import same sized 240x240 Pimoroni LCD and set 16-bit colour to match this 1.28" round 240x240 LCD
from picographics import PicoGraphics, DISPLAY_ROUND_LCD_240X240, PEN_RGB565   # PEN_RGB332 does not apply at the moment
display = PicoGraphics(display=DISPLAY_ROUND_LCD_240X240, pen_type=PEN_RGB565) # rotate=180 does not work
display.set_framebuffer(None) # remove Pimoroni's framebuffer to free memory ready for one to be created in LCD_1inch28()

lcd128 = LCD_1inch28()                 # init 1.28" LCD, which creates its own framebuf
display.set_framebuffer(lcd128.buffer) # tell Pimoroni Picographics to use the framebuf created in LCD_1inch28()

# lcd.set_bl_pwm(65535)                # set backlight to full on - not used on my LCD

# create example RGB565 pen colours
red = display.create_pen(255,0,0); green = display.create_pen(0,255,0); blue = display.create_pen(0,0,255);
white = display.create_pen(255,255,255); black = display.create_pen(0,0,0); grey = display.create_pen(160,160,160);
yellow = display.create_pen(255,255,0)

width = lcd128.width; height = lcd128.height # get size of 1.28" LCD, although we already know
xc = width//2   # centre coords
yc = height//2

print(f"Free memory after LCD init: {gc.mem_free()}")

# EXAMPLE GRAPHICS
display.set_pen(blue)
display.circle(120, 120, 119) # ring(xc,yc,119,green) need a ring func within Picographics
lcd128.update()  # update the LCD with framebuf data
time.sleep(0.5)

display.set_font("bitmap8") # load in this font which has lower case included
display.set_pen(white)
display.text("Stephen Alsop", 0, 120, scale=3) # text/x/y/size, can also add 'fixed_width=True'
lcd128.update()
time.sleep(0.5)

display.set_pen(red)
display.triangle(0, yc, xc, 0, width, yc)
lcd128.update()
time.sleep(0.5)

picture = "bluemarble.jpg"     # example 240x240 jpeg picture stored in Pico
j = jpegdec.JPEG(display)      # create a new JPEG decoder for picographics
j.open_file(picture)           # open/load in the JPEG/JPG file
j.decode(0, 0, jpegdec.JPEG_SCALE_FULL) # place at x/y and decode jpeg and draw full size in framebuf
lcd128.update()

print(f"Free memory at program end after drawing graphics: {gc.mem_free()}")
gc.collect()
print(f"Free memory after garbage collection prior to exiting: {gc.mem_free()}")

No doubt it can be improved and if you have suggestions for:

  1. How to rotate framebuf to allow rotating the LCD display, eg PicoGraphics uses ‘rotate=180’.
  2. How to update the LCD with framebuf data using DMA/PIO in the background to save Pico processing time.
  3. How to handle single byte colour (type RGB332 as per PicoGraphics), which will then halve framebuf size.

please let me know. Thanks to you both for getting me started.

Steve

I have added the rotation and single byte RBG332 code, which halves the framebuf size - to be used when only simple colours are need and memory is low. The problem is that I have to convert each RGB332 line in the framebuf to a RGB565 line and output each line. This is very slow and makes it clumsy.

I have optimised this section as best as I know. Does anyone know how to speed that section up, eg compile or use assembler just for that segment of code (around line 372). I have not found how to do this.

If anyone knows how to optimise. please let me know. I have tried @micropython.native. This is the code:

# 1.28" LCD with GC9A01 driver chip using PicoGraphics routines, Stephen Alsop 30-9-24.
# Uses Pimoroni Pico (W) uf2 firmware and its built in PicoGraphics funcs rather than loading
# graphics from 'import framebuf', https://docs.micropython.org/en/latest/library/framebuf.html,
# to try and save memory and enable updated and various Pimoroni's uf2s to be used.
#
# Based on code & examples from Waveshare, Russ Hughes, Tony Goodhew, Peter Hinch.
# All graphics, text, fonts, jpg, etc, use the built in Pimoroni PicoGraphics, except use
# lcd128.update() to update the LCD and not Pimoroni's display.update().
# lcd128.rotate() to rotate display and not Pimoroni's rotate=180.


'''
STILL TO DO:
1. How to speed up the single byte colour update as the current version is very slow and unusable.
   I need to find a way to do that section in compiled code.
2. How to update using DMA/PIO in the background to save Pico processing time.
3. Is there a ring/elipse() func within PicoGraphics, like the Micropythpn framebuf funcs?
'''

# 1.28" LCD with GC9A01 driver chip using PicoGraphics routines, Stephen Alsop 28-9-24.
# Uses Pimoroni Pico (W) uf2 firmware and its built in PicoGraphics funcs rather than loading
# graphics from 'import framebuf', https://docs.micropython.org/en/latest/library/framebuf.html,
# to try and save memory and enable updated and various Pimoroni's uf2s to be used.
#
# Based on snippets & examples from Waveshare, Russ Hughes, Tony Goodhew, Peter Hinch.
# All graphics, text, fonts, jpg, etc, use the built in Pimoroni PicoGraphics, except use
# lcd128.update() to update the LCD and not Pimoroni's display.update().
# lcd128.rotate() to rotate display and not Pimoroni's rotate=180.

'''
STILL TO DO:
1. How to speed up the single byte colour update as the current version is very slow and unusable.
   I need to find a way to do that section in compiled code.
2. How to update using DMA/PIO in the background to save Pico processing time.
3. Is there a ring/elipse() func within PicoGraphics, like the Micropythpn framebuf funcs?
'''

from machine import Pin, SPI # PWM not needed as there is no backlight on my LCD
import time
import math
import jpegdec  # for jpg graphics
import gc       # for gc.collect(), etc

gc.enable()     # enable gc memory funcs
print(f"Initial free memory: {gc.mem_free():,}")

# LCD_1inch28() class - if this is stored in the Pico, use 'import LCD_1inch28'
# commands
GC9A01_SWRESET = const(0x01)
GC9A01_SLPIN = const(0x10)
GC9A01_SLPOUT = const(0x11)
GC9A01_INVOFF = const(0x20)
GC9A01_INVON = const(0x21)
GC9A01_DISPOFF = const(0x28)
GC9A01_DISPON = const(0x29)
GC9A01_CASET = const(0x2A)
GC9A01_RASET = const(0x2B)
GC9A01_RAMWR = const(0x2C)
GC9A01_VSCRDEF = const(0x33)
GC9A01_COLMOD = const(0x3A)
GC9A01_MADCTL = const(0x36) # cmd used in rotation, see p99 in GC9A01 data
GC9A01_VSCSAD = const(0x37)

ROTATION = [ # applicable to my LCD when the the connector row is at the bottom 
    0x08,   # 0 - PORTRAIT, rotate 0 deg, default, /\
    0x68,   # 1 - LANDSCAPE, rotate 90 deg, >
    0xc8,   # 2 - INVERTED_PORTRAIT, rotate 180 deg, \/
    0xa8,   # 3 - INVERTED_LANDSCAPE, rotate 270 deg, <
    0x48,   # 4 - PORTRAIT_MIRRORED, rotate 0 deg, mirrored, /\
    0xe8,   # 5 - LANDSCAPE_MIRRORED, rotate 90 deg, mirrored, >
    0x88,   # 6 - INVERTED_PORTRAIT_MIRRORED, rotate 180 deg, mirrored, \/
    0x28]   # 7 - INVERTED_LANDSCAPE_MIRRORED, rotate 270 deg, mirrored, <

''' original constants for other LCDs
    0x48,   # 0 - PORTRAIT
    0x28,   # 1 - LANDSCAPE
    0x88,   # 2 - INVERTED_PORTRAIT
    0xe8,   # 3 - INVERTED_LANDSCAPE
    0x08,   # 4 - PORTRAIT_MIRRORED *** default rotation for my LCD
    0x68,   # 5 - LANDSCAPE_MIRRORED
    0xc8,   # 6 - INVERTED_PORTRAIT_MIRRORED
    0xa8]   # 7 - INVERTED_LANDSCAPE_MIRRORED
'''
    
class LCD_128_GC9A01(): # 1.28" 240x240 round LCD
    def __init__(self, pentype=9999):                  # default dummy val when nothing is passed
        self.pixcolbytes = 2                           # default to RGB565 16_bit colour
        if pentype == PEN_RGB332: self.pixcolbytes = 1 # PEN_RGB332 changes to 1 byte 8-bit RGB332 colour
        self.width = 240
        self.height = 240
        self.framebufsize = self.height * self.width * self.pixcolbytes # calc size needed for RGB332 or RGB565
        self.framebuf = bytearray(self.framebufsize)   # create framebuf to match 8 or 16-bit colours
        self.cs = Pin(CS,Pin.OUT)
        self.rst = Pin(RST,Pin.OUT)
         
        self.cs(1)
        self.spi = SPI(0,100_000_000,polarity=0, phase=0,sck=Pin(SCK),mosi=Pin(MOSI),miso=None)
        self.dc = Pin(DC,Pin.OUT)
        self.dc(1)
        
        self.init_display()
        
        ## self.pwm = PWM(Pin(BL)) # not used as there is no backlight connection on my LCD
        ## self.pwm.freq(5000)

    # Not used as there is no backlight connection on my LCD
    # def set_bl_pwm(self,duty):
    #     self.pwm.duty_u16(duty) # backlight off = 0, max brightness = 65535
    
    def write_cmd(self, cmd):
        self.cs(1);
        self.dc(0);
        self.cs(0)
        self.spi.write(bytearray([cmd]))
        self.cs(1)

    def write_data(self, buf):
        self.cs(1);
        self.dc(1);
        self.cs(0)
        self.spi.write(bytearray([buf]))
        self.cs(1)
        
    def init_display(self): # init GC9A01 display chip
        self.rst(1)
        time.sleep(0.01)
        self.rst(0)
        time.sleep(0.01)
        self.rst(1)
        time.sleep(0.05)
        
        self.write_cmd(0xEF)
        self.write_cmd(0xEB)
        self.write_data(0x14) 
        
        self.write_cmd(0xFE) 
        self.write_cmd(0xEF) 

        self.write_cmd(0xEB)
        self.write_data(0x14) 

        self.write_cmd(0x84)
        self.write_data(0x40) 

        self.write_cmd(0x85)
        self.write_data(0xFF) 

        self.write_cmd(0x86)
        self.write_data(0xFF) 

        self.write_cmd(0x87)
        self.write_data(0xFF)

        self.write_cmd(0x88)
        self.write_data(0x0A)

        self.write_cmd(0x89)
        self.write_data(0x21) 

        self.write_cmd(0x8A)
        self.write_data(0x00) 

        self.write_cmd(0x8B)
        self.write_data(0x80) 

        self.write_cmd(0x8C)
        self.write_data(0x01) 

        self.write_cmd(0x8D)
        self.write_data(0x01) 

        self.write_cmd(0x8E)
        self.write_data(0xFF) 

        self.write_cmd(0x8F)
        self.write_data(0xFF) 

        self.write_cmd(0xB6)
        self.write_data(0x00)
        self.write_data(0x20)

        self.write_cmd(0x36)
        self.write_data(0x98)
        
        self.write_cmd(0x3A)
        self.write_data(0x05) 

        self.write_cmd(0x90)
        self.write_data(0x08)
        self.write_data(0x08)
        self.write_data(0x08)
        self.write_data(0x08) 

        self.write_cmd(0xBD)
        self.write_data(0x06)
        
        self.write_cmd(0xBC)
        self.write_data(0x00)

        self.write_cmd(0xFF)
        self.write_data(0x60)
        self.write_data(0x01)
        self.write_data(0x04)

        self.write_cmd(0xC3)
        self.write_data(0x13)
        self.write_cmd(0xC4)
        self.write_data(0x13)

        self.write_cmd(0xC9)
        self.write_data(0x22)

        self.write_cmd(0xBE)
        self.write_data(0x11) 

        self.write_cmd(0xE1)
        self.write_data(0x10)
        self.write_data(0x0E)

        self.write_cmd(0xDF)
        self.write_data(0x21)
        self.write_data(0x0c)
        self.write_data(0x02)

        self.write_cmd(0xF0)   
        self.write_data(0x45)
        self.write_data(0x09)
        self.write_data(0x08)
        self.write_data(0x08)
        self.write_data(0x26)
        self.write_data(0x2A)

        self.write_cmd(0xF1)    
        self.write_data(0x43)
        self.write_data(0x70)
        self.write_data(0x72)
        self.write_data(0x36)
        self.write_data(0x37)  
        self.write_data(0x6F)

        self.write_cmd(0xF2)   
        self.write_data(0x45)
        self.write_data(0x09)
        self.write_data(0x08)
        self.write_data(0x08)
        self.write_data(0x26)
        self.write_data(0x2A)

        self.write_cmd(0xF3)   
        self.write_data(0x43)
        self.write_data(0x70)
        self.write_data(0x72)
        self.write_data(0x36)
        self.write_data(0x37) 
        self.write_data(0x6F)

        self.write_cmd(0xED)
        self.write_data(0x1B) 
        self.write_data(0x0B) 

        self.write_cmd(0xAE)
        self.write_data(0x77)
        
        self.write_cmd(0xCD)
        self.write_data(0x63)

        self.write_cmd(0x70)
        self.write_data(0x07)
        self.write_data(0x07)
        self.write_data(0x04)
        self.write_data(0x0E) 
        self.write_data(0x0F) 
        self.write_data(0x09)
        self.write_data(0x07)
        self.write_data(0x08)
        self.write_data(0x03)

        self.write_cmd(0xE8)
        self.write_data(0x34)

        self.write_cmd(0x62)
        self.write_data(0x18)
        self.write_data(0x0D)
        self.write_data(0x71)
        self.write_data(0xED)
        self.write_data(0x70) 
        self.write_data(0x70)
        self.write_data(0x18)
        self.write_data(0x0F)
        self.write_data(0x71)
        self.write_data(0xEF)
        self.write_data(0x70) 
        self.write_data(0x70)

        self.write_cmd(0x63)
        self.write_data(0x18)
        self.write_data(0x11)
        self.write_data(0x71)
        self.write_data(0xF1)
        self.write_data(0x70) 
        self.write_data(0x70)
        self.write_data(0x18)
        self.write_data(0x13)
        self.write_data(0x71)
        self.write_data(0xF3)
        self.write_data(0x70) 
        self.write_data(0x70)

        self.write_cmd(0x64)
        self.write_data(0x28)
        self.write_data(0x29)
        self.write_data(0xF1)
        self.write_data(0x01)
        self.write_data(0xF1)
        self.write_data(0x00)
        self.write_data(0x07)

        self.write_cmd(0x66)
        self.write_data(0x3C)
        self.write_data(0x00)
        self.write_data(0xCD)
        self.write_data(0x67)
        self.write_data(0x45)
        self.write_data(0x45)
        self.write_data(0x10)
        self.write_data(0x00)
        self.write_data(0x00)
        self.write_data(0x00)

        self.write_cmd(0x67)
        self.write_data(0x00)
        self.write_data(0x3C)
        self.write_data(0x00)
        self.write_data(0x00)
        self.write_data(0x00)
        self.write_data(0x01)
        self.write_data(0x54)
        self.write_data(0x10)
        self.write_data(0x32)
        self.write_data(0x98)

        self.write_cmd(0x74)
        self.write_data(0x10)
        self.write_data(0x85)
        self.write_data(0x80)
        self.write_data(0x00) 
        self.write_data(0x00) 
        self.write_data(0x4E)
        self.write_data(0x00)
        
        self.write_cmd(0x98)
        self.write_data(0x3e)
        self.write_data(0x07)

        self.write_cmd(0x35)
        self.write_cmd(0x21)

        self.write_cmd(0x11)
        time.sleep(0.12)
        self.write_cmd(0x29)
        time.sleep(0.02)
        
        self.write_cmd(0x21)
        self.write_cmd(0x11)
        self.write_cmd(0x29)


    @micropython.native
    #@micropython.viper
    def update(self): # update LCD, used in place of Pimoroni's display.update()
        self.write_cmd(0x2A)
        self.write_data(0x00)
        self.write_data(0x00)
        self.write_data(0x00)
        self.write_data(0xef)
        
        self.write_cmd(0x2B)
        self.write_data(0x00)
        self.write_data(0x00)
        self.write_data(0x00)
        self.write_data(0xEF)
        
        self.write_cmd(0x2C)
        self.cs(1)
        self.dc(1)
        self.cs(0)
        
        if self.pixcolbytes == 2: # RGB565 16-bit colour so output the whole framebuf in one go
            self.spi.write(self.framebuf)

        # debug test times for graphic loops: 16-bit = 473mS, 8-bit = 11,574mS
        else:                  # RGB332 8-bit colour so convert each 8-bit line to a 16-bit colour line and output
            framebufsize = self.framebufsize
            linebufsize = self.width * 2     # change to locals for speed
            buf = bytearray(linebufsize)
            x = 0
            while x < framebufsize:
                i=0
                while i<linebufsize:
                    p = self.framebuf[x]
                    c1 = (p & 0xE0) + ((p & 0x1c)>>1); c2 = p<<4 # convert to 565 16-bit 2 byte colour, rrrr rggg | gggb bbbb 
                    buf[i] = c1      # and store back into linebuf
                    buf[i + 1] = c2  
                    x+=1
                    i+=2
                self.spi.write(buf)  # send each 16-bit colour line to LCD
     
        self.cs(1) # finish spi write

    def rotate(self, rotate):   # enter with 0..7 to use the ROTATION const
        if rotate>7: rotate = 0 # limit
        self.write_cmd(GC9A01_MADCTL)
        self.write_data(ROTATION[rotate])

    # def colour888to565(R,G,B): # Convert RGB888 to RGB565, not used in this example code
    #     return (((G&0b00011100)<<3) +((B&0b11111000)>>3)<<8) + (R&0b11111000)+((G&0b11100000)>>5)
    
### end of LCD_128_GC9A01() class

######################################################################
# MAIN SECTION
# Hardware connections using a Pico W and SPI0
# LCD CONNS TOP-BOTTOM   MY WIRE COL  PICO PCB CONN
# Vcc         # +3v3       RD          36
# Gnd         # 0ve        BK          38
SCK = 18      # SCK        OR          24
MOSI = 19     # MOSI/SDA   GN          25
DC = 20       # DATA/CMD   BN          26
CS = 17       # CHIP SEL   YL          22
RST = 10      # RESET      WH          14

# BL = 25     # not used as there is no backlight connector on this LCD

# Init 1.28" GC9A01 240x240 LCD and PicoGraphics
# impirt same sized 240x240 Pimoroni LCD to match the graphic routines
from picographics import PicoGraphics, DISPLAY_ROUND_LCD_240X240, PEN_RGB565, PEN_RGB332

# set the pen type her to either 8 or 16-bit colours, which alters the framebuf size
# PENTYPE = PEN_RGB565   # for 16-bit double byte RGB565 colour pixels: rrrr rggg | gggb bbbb
PENTYPE = PEN_RGB332 # for 8-bit single byte RGB332 colour pixels: rrrg ggbb

# init Picographics with pen_type, note that PicoGraphics rotate, eg rotate=180 is not applicable, use lcd128.rotate()
display = PicoGraphics(display=DISPLAY_ROUND_LCD_240X240, pen_type=PENTYPE)
display.set_framebuffer(None) # remove Pimoroni's framebuffer to free memory ready to create one in LCD_128_GC9A01()

lcd128 = LCD_128_GC9A01(PENTYPE)         # init 1.28" LCD with pen type, PEN_RGB565 or PEN_RGB332 to creates the framebuf
display.set_framebuffer(lcd128.framebuf) # tell Pimoroni PicoGraphics to use the lcd128 framebuf
# lcd.set_bl_pwm(65535)                  # set backlight to full on - not available on my LCD

# Create pen colours to match pen type
if PENTYPE == PEN_RGB332:  # create single byte RGB332 8-bit pen colours: rrrg ggbb
    red = 0b11100000; green = 0b00011100; blue = 0b00000011; darkblue = 0b00000001
    white = 255; black = 0; grey = 0b10010010; orange = 0b11001100
    yellow = red + green; cyan = green + blue; purple = red + blue
else: # create dual byte RGB565 16-bit pen colours: rrrr rggg | gggb bbbb
    red = display.create_pen(255,0,0); green = display.create_pen(0,255,0); blue = display.create_pen(0,0,255);
    white = display.create_pen(255,255,255); black = display.create_pen(0,0,0); grey = display.create_pen(160,160,160);
    yellow = display.create_pen(255,255,0)
    
width = lcd128.width; height = lcd128.height # get size of 1.28" LCD, although we already know
xc = width//2   # centre coords
yc = height//2

print(f"Free memory after LCD init: {gc.mem_free():,}")

# EXAMPLE GRAPHICS
starttick = time.ticks_ms()

display.set_pen(blue)
display.circle(120, 120, 119) # ring(xc,yc,119,green) I need a ring func using Picographics
lcd128.update()               # update the LCD with framebuf data

display.set_font("bitmap8")   # load in a font which has lower case included
display.set_pen(white)
display.text("Steve - 123 >", 10, 126, scale=3) # draw text/x/y/size, can also add 'fixed_width=True'
lcd128.update()
#time.sleep(1)

# raise SystemExit(0) # safe way to exit when debugging

# rotation test, for my LCD the correct orientation is when connections are at the bottom
display.set_pen(red)
display.triangle(0, yc, xc, 0, width, yc) # draw triangle pointing up in default power up rotation position
lcd128.update()
#time.sleep(1)

for r in range (1, 8): # rotate the display 1 to 7
    lcd128.rotate(r)
    lcd128.update()
    #time.sleep(1)

lcd128.rotate(0)       # reset to default rotation
lcd128.update()
#time.sleep(2)

# raise SystemExit(0) # safe way to exit when debugging

# JPEG/JPG picture
picfile = "bluemarble.jpg"     # example 240x240 jpeg picture stored in the Pico
j = jpegdec.JPEG(display)      # create a JPEG decoder for PicoGraphics
j.open_file(picfile)           # open the jpg file
j.decode(0, 0, jpegdec.JPEG_SCALE_FULL) # decode and draw full size at x/y in framebuf
lcd128.update()

endtick = time.ticks_ms()
duration = time.ticks_diff(endtick, starttick)
print(f'Time taken = {duration:,} mS')


print(f"Free memory at program end after drawing graphics: {gc.mem_free():,}")
gc.collect()
print(f"Free memory after garbage collection prior to exiting: {gc.mem_free():,}")


I hope you find it of use.

Steve