Pico Explorer screen corruption and communication crashes

I’ve returned to my Pico Explorer intending to port over a new large graphics program with filled & outlined triangles and circles, a different font with multiple text sizes and lower-case letters. We need filled triangles as a basic element for filling any larger polygons and rings/circles are also useful graphic elements.

This is a long program and I quickly got totally fed up with the corrupted pixels along the bottom edge of the screen and repeated loss of communication between the Pico and my PC needing re-sets every time I edited the code.

I’ve been using four different sized WaveShare Pico Displays for a couple of months without either problem appearing.

I decided to dump the Pimoroni UF2, install the basic Pi Pico Micropython UF2 and modify a Waveshare ST7789 driver to work with the Pico Explorer screen. The driver was easy to move about (just cut and paste) and include at the top of your code. I just had to change the SPI port over from 1 to 0 by changing the pins used in the code and remove the backlight brightness code as the Explorer runs at full brightness all the time – not controlled by PWM.

The screen corruption and communication losses immediately disappeared!

I was then able make use of the graphical primitives in FrameBuffer for text and drawing (fill, pixel, hline, vline, line, rect and fill_rect). I installed an extra font with lower case letters that can be resized and could then edit away without the system falling over. A minor problem was that the image was rotated by 90°.

Just been reading the 317 page documentation for the ST7789 and found the MADCTR (Memory Access Data Control) register controls rotation and reflection and changing previous value of 0x70 to 0x10 corrects the rotation.

Here is the corrected code:

# Triangles Circles and Text improvements for Pimoroni Pico Explorer
# Tony Goodhew 8th November 2021 - New driver & Basic Micropython UF2
from machine import Pin,SPI,PWM
import framebuf
import utime
import os
import math
import random
# ============ Start of Driver Code for Pimoroni Explorer ================
#  == Copy and paste into your code ==
DC = 16
RST = 12
MOSI = 19
SCK = 18
CS = 17

class lcd_Pim240(framebuf.FrameBuffer):
# Modified from WaveShare 1.3" driver 240x240
    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)
        self.spi = SPI(0,1000_000)
        self.spi = SPI(0,100000_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 # Pre-defined colours
        self.green =   0x001f # Probably easier to use colour(r,g,b) defined below
        self.blue  =   0xf800
        self.white =   0xffff
        
    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):
        """Initialize display"""  
        self.rst(1)
        self.rst(0)
        self.rst(1)
        
        self.write_cmd(0x36) # MADCTR register
        self.write_data(0x10) # Was 0x70 - Explorer needs 0x10

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

        self.write_cmd(0xB2)
        self.write_data(0x0C)
        self.write_data(0x0C)
        self.write_data(0x00)
        self.write_data(0x33)
        self.write_data(0x33)

        self.write_cmd(0xB7)
        self.write_data(0x35) 

        self.write_cmd(0xBB)
        self.write_data(0x19)

        self.write_cmd(0xC0)
        self.write_data(0x2C)

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

        self.write_cmd(0xC3)
        self.write_data(0x12)   

        self.write_cmd(0xC4)
        self.write_data(0x20)

        self.write_cmd(0xC6)
        self.write_data(0x0F) 

        self.write_cmd(0xD0)
        self.write_data(0xA4)
        self.write_data(0xA1)

        self.write_cmd(0xE0)
        self.write_data(0xD0)
        self.write_data(0x04)
        self.write_data(0x0D)
        self.write_data(0x11)
        self.write_data(0x13)
        self.write_data(0x2B)
        self.write_data(0x3F)
        self.write_data(0x54)
        self.write_data(0x4C)
        self.write_data(0x18)
        self.write_data(0x0D)
        self.write_data(0x0B)
        self.write_data(0x1F)
        self.write_data(0x23)

        self.write_cmd(0xE1)
        self.write_data(0xD0)
        self.write_data(0x04)
        self.write_data(0x0C)
        self.write_data(0x11)
        self.write_data(0x13)
        self.write_data(0x2C)
        self.write_data(0x3F)
        self.write_data(0x44)
        self.write_data(0x51)
        self.write_data(0x2F)
        self.write_data(0x1F)
        self.write_data(0x1F)
        self.write_data(0x20)
        self.write_data(0x23)
        
        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)
# ========= End of Driver ===========

# Colour Mixing Routine
def colour(R,G,B): # Compact method!
    mix1 = ((R&0xF8)*256) + ((G&0xFC)*8) + ((B&0xF8)>>3)
    return  (mix1 & 0xFF) *256  + int((mix1 & 0xFF00) /256) # low nibble first
    
#ASCII Character Set
cmap = ['00000000000000000000000000000000000', #Space
        '00100001000010000100001000000000100', #!
        '01010010100000000000000000000000000', #"
        '01010010101101100000110110101001010', ##
        '00100011111000001110000011111000100', #$
        '11001110010001000100010001001110011', #%
        '01000101001010001000101011001001101', #&
        '10000100001000000000000000000000000', #'
        '00100010001000010000100000100000100', #(
        '00100000100000100001000010001000100', #)
        '00000001001010101110101010010000000', #*
        '00000001000010011111001000010000000', #+
        '000000000000000000000000000000110000100010000', #,
        '00000000000000011111000000000000000', #-
        '00000000000000000000000001100011000', #.
        '00001000010001000100010001000010000', #/
        '01110100011000110101100011000101110', #0
        '00100011000010000100001000010001110', #1
        '01110100010000101110100001000011111', #2
        '01110100010000101110000011000101110', #3
        '00010001100101011111000100001000010', #4
        '11111100001111000001000011000101110', #5
        '01110100001000011110100011000101110', #6
        '11111000010001000100010001000010000', #7
        '01110100011000101110100011000101110', #8
        '01110100011000101111000010000101110', #9
        '00000011000110000000011000110000000', #:
        '01100011000000001100011000010001000', #;
        '00010001000100010000010000010000010', #<
        '00000000001111100000111110000000000', #=
        '01000001000001000001000100010001000', #>
        '01100100100001000100001000000000100', #?
        '01110100010000101101101011010101110', #@
        '00100010101000110001111111000110001', #A
        '11110010010100111110010010100111110', #B
        '01110100011000010000100001000101110', #C
        '11110010010100101001010010100111110', #D
        '11111100001000011100100001000011111', #E
        '11111100001000011100100001000010000', #F
        '01110100011000010111100011000101110', #G
        '10001100011000111111100011000110001', #H
        '01110001000010000100001000010001110', #I
        '00111000100001000010000101001001100', #J
        '10001100101010011000101001001010001', #K
        '10000100001000010000100001000011111', #L
        '10001110111010110101100011000110001', #M
        '10001110011010110011100011000110001', #N
        '01110100011000110001100011000101110', #O
        '11110100011000111110100001000010000', #P
        '01110100011000110001101011001001101', #Q
        '11110100011000111110101001001010001', #R
        '01110100011000001110000011000101110', #S
        '11111001000010000100001000010000100', #T
        '10001100011000110001100011000101110', #U
        '10001100011000101010010100010000100', #V
        '10001100011000110101101011101110001', #W
        '10001100010101000100010101000110001', #X
        '10001100010101000100001000010000100', #Y
        '11111000010001000100010001000011111', #Z
        '01110010000100001000010000100001110', #[
        '10000100000100000100000100000100001', #\
        '00111000010000100001000010000100111', #]
        '00100010101000100000000000000000000', #^
        '00000000000000000000000000000011111', #_
        '11000110001000001000000000000000000', #`
        '00000000000111000001011111000101110', #a
        '10000100001011011001100011100110110', #b
        '00000000000011101000010000100000111', #c
        '00001000010110110011100011001101101', #d
        '00000000000111010001111111000001110', #e
        '00110010010100011110010000100001000', #f
        '000000000001110100011000110001011110000101110', #g
        '10000100001011011001100011000110001', #h
        '00100000000110000100001000010001110', #i
        '0001000000001100001000010000101001001100', #j
        '10000100001001010100110001010010010', #k
        '01100001000010000100001000010001110', #l
        '00000000001101010101101011010110101', #m
        '00000000001011011001100011000110001', #n
        '00000000000111010001100011000101110', #o
        '000000000001110100011000110001111101000010000', #p
        '000000000001110100011000110001011110000100001', #q
        '00000000001011011001100001000010000', #r
        '00000000000111110000011100000111110', #s
        '00100001000111100100001000010000111', #t
        '00000000001000110001100011001101101', #u
        '00000000001000110001100010101000100', #v
        '00000000001000110001101011010101010', #w
        '00000000001000101010001000101010001', #x
        '000000000010001100011000110001011110000101110', #y
        '00000000001111100010001000100011111', #z
        '00010001000010001000001000010000010', #{
        '00100001000010000000001000010000100', #|
        '01000001000010000010001000010001000', #}
        '01000101010001000000000000000000000' #}~
]

def printchar(letter,xpos,ypos,size,c):
    origin = xpos
    charval = ord(letter)
    #print(charval)
    index = charval-32 #start code, 32 or space
    #print(index)
    character = cmap[index] #this is our char...
    rows = [character[i:i+5] for i in range(0,len(character),5)]
    #print(rows)
    for row in rows:
        #print(row)
        for bit in row:
            #print(bit)
            if bit == '1':
                lcd.pixel(xpos,ypos,c)
                if size==2:
                    lcd.pixel(xpos,ypos+1,c)
                    lcd.pixel(xpos+1,ypos,c)
                    lcd.pixel(xpos+1,ypos+1,c)
                if size == 3:                    
                    lcd.pixel(xpos,ypos+1,c)
                    lcd.pixel(xpos,ypos+2,c)
                    lcd.pixel(xpos+1,ypos,c)
                    lcd.pixel(xpos+1,ypos+1,c)
                    lcd.pixel(xpos+1,ypos+2,c)
                    lcd.pixel(xpos+2,ypos,c)
                    lcd.pixel(xpos+2,ypos+1,c)
                    lcd.pixel(xpos+2,ypos+2,c)                  
            xpos+=size
        xpos=origin
        ypos+=size
    
def delchar(xpos,ypos,size,):
    if size == 1:
        charwidth = 5
        charheight = 9
    if size == 2:
        charwidth = 10
        charheight = 18
    if size == 3:
        charwidth = 15
        charheight = 27
    c=colour(40,40,40)
    lcd.fill_rect(xpos,ypos,charwidth,charheight,0) #xywh


def printstring(string,xpos,ypos,size,c):   
    if size == 1:
        spacing = 8
    if size == 2:
        spacing = 14
    if size == 3:
        spacing = 18
    for i in string:
        printchar(i,xpos,ypos,size,c)
        xpos+=spacing

def centrestring(string,ypos,size,c):
    if size == 1:
        spacing = 8
    if size == 2:
        spacing = 14
    if size == 3:
        spacing = 18
    used = len(string)*spacing
    xpos = int((159-used)/2)
    printstring(string,xpos,ypos,size,c)        
def ringold(cx,cy,r,cc):   # Draws a circle - with centre (x,y), radius, colour 
    for angle in range(91):  # 0 to 90 degrees in 2s
        y3=int(r*math.sin(math.radians(angle)))
        x3=int(r*math.cos(math.radians(angle)))
        lcd.pixel(cx-x3,cy+y3,cc)  # 4 quadrants
        lcd.pixel(cx-x3,cy-y3,cc)
        lcd.pixel(cx+x3,cy+y3,cc)
        lcd.pixel(cx+x3,cy-y3,cc)
        




# Colour Mixing Routine
def colour(R,G,B): # Compact method!
    mix1 = ((R&0xF8)*256) + ((G&0xFC)*8) + ((B&0xF8)>>3)
    return  (mix1 & 0xFF) *256  + int((mix1 & 0xFF00) /256) # low nibble first

# ========== Start of Triangles code =============
# Modified from https://github.com/SpiderMaf/PiPicoDsply/blob/main/filled-triangles.py
# To work on WaveShare Pi Pico displays
class Point:
    def __init__(self,x,y):
        self.X=x
        self.Y=y
    def __str__(self):
        return "Point(%s,%s)"%(self.X,self.Y)
        
class Triangle:
    def __init__(self,p1,p2,p3):
        self.P1=p1
        self.P2=p2
        self.P3=p3

    def __str__(self):
        return "Triangle(%s,%s,%s)"%(self.P1,self.P2,self.P3)
    
    def draw(self):
        print("I should draw now")
        self.fillTri()
    # Filled triangle routines ported from http://www.sunshine2k.de/coding/java/TriangleRasterization/TriangleRasterization.html      
    def sortVerticesAscendingByY(self):    
        if self.P1.Y > self.P2.Y:
            vTmp = self.P1
            self.P1 = self.P2
            self.P2 = vTmp
        
        if self.P1.Y > self.P3.Y:
            vTmp = self.P1
            self.P1 = self.P3
            self.P3 = vTmp

        if self.P2.Y > self.P3.Y:
            vTmp = self.P2
            self.P2 = self.P3
            self.P3 = vTmp
        
    def fillTri(self):
        self.sortVerticesAscendingByY()
        if self.P2.Y == self.P3.Y:
            fillBottomFlatTriangle(self.P1, self.P2, self.P3)
        else:
            if self.P1.Y == self.P2.Y:
                fillTopFlatTriangle(self.P1, self.P2, self.P3)
            else:
                newx = int(self.P1.X + (float(self.P2.Y - self.P1.Y) / float(self.P3.Y - self.P1.Y)) * (self.P3.X - self.P1.X))
                newy = self.P2.Y                
                pTmp = Point( newx,newy )
                fillBottomFlatTriangle(self.P1, self.P2, pTmp)
                fillTopFlatTriangle(self.P2, pTmp, self.P3)

def fillBottomFlatTriangle(p1,p2,p3):
    slope1 = float(p2.X - p1.X) / float (p2.Y - p1.Y)
    slope2 = float(p3.X - p1.X) / float (p3.Y - p1.Y)

    x1 = p1.X
    x2 = p1.X + 0.5

    for scanlineY in range(p1.Y,p2.Y):
#        lcd.pixel_span(int(x1), scanlineY, int(x2)-int(x1))   # Switch pixel_span() to hline() / Pimoroni to WS
        lcd.hline(int(x1),scanlineY, int(x2)-int(x1),c)
        x1 += slope1
        x2 += slope2

def fillTopFlatTriangle(p1,p2,p3):
    slope1 = float(p3.X - p1.X) / float(p3.Y - p1.Y)
    slope2 = float(p3.X - p2.X) / float(p3.Y - p2.Y)

    x1 = p3.X
    x2 = p3.X + 0.5

    for scanlineY in range (p3.Y,p1.Y-1,-1):
#        lcd.pixel_span(int(x1), scanlineY, int(x2)-int(x1))  # Switch pixel_span() to hline() / Pimoroni to WS
        lcd.hline(int(x1),scanlineY, int(x2)-int(x1),c)
        x1 -= slope1
        x2 -= slope2
            
# ============== End of Triangles Code ===============

# =========== New GFX Routines ============
def triangle(x1,y1,x2,y2,x3,y3,c): # Draw outline triangle
    lcd.line(x1,y1,x2,y2,c)
    lcd.line(x2,y2,x3,y3,c)
    lcd.line(x3,y3,x1,y1,c)
    
def tri_filled(x1,y1,x2,y2,x3,y3,c): # Draw filled triangle
    t=Triangle(Point(x1,y1),Point(x2,y2),Point(x3,y3)) # Define corners
    t.fillTri() # Call main code block  

def circle(x,y,r,c):
    lcd.hline(x-r,y,r*2,c)
    for i in range(1,r):
        a = int(math.sqrt(r*r-i*i)) # Pythagoras!
        lcd.hline(x-a,y+i,a*2,c) # Lower half
        lcd.hline(x-a,y-i,a*2,c) # Upper half

def ring(x,y,r,c):
    lcd.pixel(x-r,y,c)
    lcd.pixel(x+r,y,c)
    lcd.pixel(x,y-r,c)
    lcd.pixel(x,y+r,c)

    for i in range(1,r):
        a = int(math.sqrt(r*r-i*i))
        lcd.pixel(x-a,y-i,c)
        lcd.pixel(x+a,y-i,c)
        lcd.pixel(x-a,y+i,c)
        lcd.pixel(x+a,y+i,c)
        lcd.pixel(x-i,y-a,c)
        lcd.pixel(x+i,y-a,c)
        lcd.pixel(x-i,y+a,c)
        lcd.pixel(x+i,y+a,c)

# =================== Main ======================
lcd = lcd_Pim240() # Initilise screen with Modified WS driver
# Background colour 
lcd.fill(colour(0,0,0)) # BLACK
lcd.show()
printstring("Basic",35,20,2,colour(255,0,0))
printstring("Graphical",35,40,2,colour(0,255,0))
printstring("Components",35,60,2,colour(0,0,255))
c = colour(200,200,200)
lcd.pixel(10,120,c)
lcd.hline(20,120,200,c)
lcd.vline(10,130,100,c)
lcd.rect(20,140,80,20,c)
lcd.fill_rect(20,170,80,20,c)
lcd.line(120,130,230,210,c)
lcd.line(140,235,235,145,c)
lcd.text("Framebuffer text",20,200,c)
lcd.show()
utime.sleep(5)

lcd.fill(colour(0,0,0)) # BLACK
lcd.show()
printstring("Triangles",35,20,2,colour(255,0,0))
printstring("Circles",35,40,2,colour(0,255,0))
printstring("and Rings",35,60,2,colour(0,0,255))
c = colour(255,0,0) 
lcd.show()
 
# Triangle
x1 = 30
y1=115
x2=10
y2=200
x3=120
y3=225
c = colour(255,0,0)
triangle(40,115,10,180,95,235,c)
lcd.show()
utime.sleep(1.2)
c = colour(0,255,0)
tri_filled(x1,y1,x2,y2,x3,y3,c)
lcd.show()
utime.sleep(1.2)
c = colour(0,0,255)
tri_filled(50,120,5,190,70,230,c)
triangle(5,125,100,140,100,230,colour(255,0,255))
lcd.show()

# Circle
c = colour(0,0,255)
ring(150,180,25,c)
lcd.show()
utime.sleep(1.2)
circle(150,180,25,c)
lcd.show()
utime.sleep(1.2)
c = colour(255,255,0)
ring(150,180,25,c)
ring(150,180,30,c)
lcd.show()
utime.sleep(1.2)
c = colour(255,0,0)
circle(150,180,10,c)
lcd.show()
utime.sleep(3)

# Random circles & Rings
lcd.fill(0)
lcd.show()
centrestring("Circles",15,3,colour(200,200,0))
centrestring("& Rings",40,3,colour(200,200,0))
lcd.show()
utime.sleep(2)

lcd.fill(0)
lcd.show()
for cc in range(200):
    c = colour(random.randint(40,255),random.randint(40,255),random.randint(40,255))
    ring(random.randint(5,230),random.randint(5,230),random.randint(5,120),c)
    lcd.show()
utime.sleep(2)
lcd.fill(0)
lcd.show()
y = 130
x = 20
for i in range(40):
    c = colour(random.randint(40,255),random.randint(40,255),random.randint(40,255))
    xx = 10+i*5
    yy = y-random.randint(0,20)
    rr = 40-i
    ring(xx,yy,rr,c)
    lcd.show()
utime.sleep(2)

lcd.fill(0)
lcd.show() 
utime.sleep(1)
for cc in range(200):
    c = colour(random.randint(40,255),random.randint(40,255),random.randint(40,255))
    circle(random.randint(5,230),random.randint(5,230),random.randint(5,120),c)
    lcd.show()
utime.sleep(2)

lcd.fill(0)
lcd.show()
y = 130
x = 20
for i in range(80):
    c = colour(random.randint(40,255),random.randint(40,255),random.randint(40,255))
    xx = 10+i*5
    yy = y-random.randint(0,20)
    rr = 80-i*2
    circle(xx,yy,rr,c)
    lcd.show()
utime.sleep(2)
lcd.fill(0)
lcd.show() 

# Random Triangles
lcd.fill(0)
centrestring("Triangles",30,2,colour(200,200,0))
lcd.show()
utime.sleep(3)

lcd.fill(0)
lcd.show()
for i in range(200):
    c = colour(random.randint(0,255),random.randint(0,255),random.randint(0,255))
    x1 = random.randint(0,239)
    x2 = random.randint(0,239)
    x3 = random.randint(0,239)
    y1 = random.randint(0,239)
    y2 = random.randint(0,239)
    y3 = random.randint(0,239)
    # Draw Triangle outline - one side at a time
    triangle(x1,y1,x2,y2,x3,y3,c)
    lcd.show()
utime.sleep(2)

lcd.fill(0)
lcd.show()
# 200 filled triangles
for i in range(200):
    c = colour(random.randint(0,255),random.randint(0,255),random.randint(0,255))
    x1 = random.randint(0,239)
    x2 = random.randint(0,239)
    x3 = random.randint(0,239)
    y1 = random.randint(0,239)
    y2 = random.randint(0,239)
    y3 = random.randint(0,239)
    tri_filled(x1,y1,x2,y2,x3,y3,c) # Draw filled triangle
    lcd.show()
utime.sleep(2)

# Finish
lcd.fill(0)
for r in range(10):
    ring(120,120,60+r,colour(255,255,0))
c = colour(255,0,0)
printstring("Halted",80,110,2,c)
lcd.text("Tony Goodhew",75,220,colour(150,150,150))
lcd.show()

# Tidy up
utime.sleep(3)
lcd.fill(0)
lcd.show()

Watch video here:

Video of how to fill triangles and circles

I’d be interested in comments from other members still experiencing the problems of corrupted pixels and crashes with large programs on their Explorer Base.

Have fun!
Tony Goodhew

Greetings Tony,
Your lcd example driver looks useful and is working here for me on my Pico, thanks.
I stumbled on it whilst reading up ideas about something I’m investigating.
The explorer lcd seems to use the spi pins in a non-standard way. DC uses the MISO pin. I’m wondering if this will preclude my using another SPI device on the same SPI interface or perhaps I need to use a seperate SPI instance for this. I cant change the LCD pins but I can (and perhaps must) use separate pins for my other SPI device (an rfm95 Lora module).

There seems to be a few contenders for pico firmware, I find I need to swap and change uf2 files depending on the libraries I use, I think I need to use the Adafruit circuitpython to be able to use their lora library, I maybe want to try your lcd example with circuitpython.

I’m not very familiar with python, Ansi C is more my thing really.
I’m just trying to get some sensors and the lora module working with a display.
All good clean fun.
Kind regards, David.

Glad you got it to work. I’m trying to keep away from CircuitPython on the Pico and just stick to Micropython. (I use an Adafruit board for CP but do not like their rather complicated graphics modules.) The MP libraries are slowly appearing, from a wide variety of sources. I would use the other SPI port for the other module and save yourself some heart ache.
I rather like the Waveshare driver method as you keep it all within the same program and do not have to keep adding and removing libraries.

1 Like

I’m still getting a corrupted screen after using
MicroPython v1.18 on 2022-02-10; Raspberry Pi Pico with RP2040on pico Explorer

I ran the very long graphics program from:

ExplorerWorkoutV3.py

Stopped it with the red STOP button before the end.

Then ran this smaller program:

# "Oscilloscope Sine trace"
# For 240x240 pixel display
# Tony Goodhew 20 Feb 2022
import picoexplorer as display
import utime, random, math
width = display.get_width()
height = display.get_height()
display_buffer = bytearray(width * height * 2)
display.init(display_buffer)


def horiz(l,t,r):  # left, right , top
    n = r-l+1        # Horizontal line
    for i in range(n):
        display.pixel(l + i, t)

def vert(l,t,b):   # left, top, bottom
    n = b-t+1      # Vertical line
    for i in range(n):
        display.pixel(l, t+i)

def box(l,t,r,b):  # left, top, right, bottom
    horiz(l,t,r)   # Hollow rectangle
    horiz(l,b,r)
    vert(l,t,b)
    vert(r,t,b)

def line(x,y,xx,yy): # (x,y) to (xx,yy)
    if x > xx:
        t = x  # Swap co-ordinates if necessary
        x = xx
        xx = t
        t = y
        y = yy
        yy = t
    if xx-x == 0:  # Avoid div by zero if vertical
        vert(x,min(y,yy),max(y,yy))
    else:          # Draw line one dot at a time L to R
        n=xx-x+1
        grad = float((yy-y)/(xx-x))  # Calculate gradient
        for i in range(n):
            y3 = y + int(grad * i)
            display.pixel(x+i,y3)  # One dot at a time
            
display.set_pen(0,0,0)
display.clear()
display.update()

display.set_pen(0,200,0)
display.text("Drawing Graphs", 20, 70, 200, 4)
display.update()
utime.sleep(2)

display.set_pen(0,0,0)
display.clear()
display.update()
factor = 361 /60 # change the 60 to alter number of cycles
#sine = []
display.set_pen(0,200,0)
horiz(0,120,239)    
display.update()
display.set_pen(0,200,0)
for x in range(0,240):
    y = int ((math.sin(math.radians(x * factor)))* -70) + 120
#    sine.append(element)
    display.pixel(x,y)
#display.text("Sine", 40, 70, 200, 2)
display.update()

and got this!

I also got red junk in the REPL window.

You can find the fix here:
https://github.com/pimoroni/pimoroni-pico/suites/5400141396/artifacts/170277436
(It downloads the updated UF2)
Many thanks to Zodiousinfuser