Skip to content

Instantly share code, notes, and snippets.

@samneggs
Created November 29, 2024 20:55
Show Gist options
  • Save samneggs/4201388ffca5aa2fb0d544478e6e6677 to your computer and use it in GitHub Desktop.
Save samneggs/4201388ffca5aa2fb0d544478e6e6677 to your computer and use it in GitHub Desktop.
Pi Pico 2 st7796 double buffer, double pixel display driver
from machine import Pin,SPI,PWM, freq, I2C
import framebuf
import time
from time import sleep_ms
import os
'''
GP2 CLK
GP3 DIN
GP5 CS x
GP6 DC x
GP7 RST x
'''
LCD_DC = 6 #
LCD_CS = 5 #
LCD_SCK = 2 #
LCD_MOSI = 3 #
LCD_MISO = 4 #
LCD_BL = 13
LCD_RST = 7 #
MAXSCREEN_X = const(240) #480
MAXSCREEN_Y = const(160) #320
SCALE=const(13)
#class LCD_3inch5(framebuf.FrameBuffer):
class LCD_3inch5():
def __init__(self,max_x = MAXSCREEN_X, max_y = MAXSCREEN_Y):
self.RED = 0b_00000_11111_00000
self.GREEN = 0b_00000_00000_11111
self.BLUE = 0b_11111_00000_00000
self.WHITE = 0xffff
self.BLACK = 0x0000
self.ORANGE = 0b_00000_11111_00100
self.YELLOW = 0xff
self.temp = bytearray(1)
self.width = max_x
self.height = max_y
self.freq = freq()
if self.freq == 230_000_000:
self.spi_freq = 60_000_000 #23
else:
self.spi_freq = 60_000_000 #60
self.cs = Pin(LCD_CS,Pin.OUT)
self.rst = Pin(LCD_RST,Pin.OUT)
self.dc = Pin(LCD_DC,Pin.OUT)
self.cs(1)
self.dc(1)
self.rst(1)
self.fps = 0
self.spi = SPI(0,self.spi_freq,polarity=0, phase=0, bits=8, sck=Pin(LCD_SCK),mosi=Pin(LCD_MOSI),miso=Pin(LCD_MISO))
self.buffer1 = bytearray(self.height * self.width * 2)
self.buffer2 = bytearray(self.height * self.width * 2)
#super().__init__(self.buffer, self.width, self.height, framebuf.RGB565)
self.fbdraw = framebuf.FrameBuffer(self.buffer1, self.width, self.height, framebuf.RGB565)
self.fbshow = framebuf.FrameBuffer(self.buffer2, self.width, self.height, framebuf.RGB565)
self.init_lcd()
def writecommand(self, cmd):
self.temp[0] = cmd
self.cs(1)
self.dc(0)
self.cs(0)
#self.spi.write(bytearray([cmd]))
self.spi.write(self.temp)
self.cs(1)
def writedata(self, buf):
self.temp[0] = buf
self.cs(1)
self.dc(1)
self.cs(0)
#self.spi.write(bytearray([buf]))
self.spi.write(self.temp)
self.cs(1)
# def init_display(self):
# # Initial delay
# sleep_ms(120)
#
# # Software reset
# self.writecommand(0x01)
# sleep_ms(120)
#
# # Sleep exit
# self.writecommand(0x11)
# sleep_ms(120)
#
# # Command Set control - Enable extension command 2 part I
# self.writecommand(0xF0)
# self.writedata(0xC3)
#
# # Command Set control - Enable extension command 2 part II
# self.writecommand(0xF0)
# self.writedata(0x96)
#
# # Memory Data Access Control MX, MY, RGB mode
# self.writecommand(0x36)
# self.writedata(0x28) # X-Mirror, Top-Left to right-Bottom, RGB (0x48)
#
#
# # Interface Pixel Format
# self.writecommand(0x3A)
# self.writedata(0x05) # Control interface color format set to 16 (0x55)
#
# # Column inversion
# self.writecommand(0xB4)
# self.writedata(0x01) # 1-dot inversion
#
# # Display Function Control
# self.writecommand(0xB6)
# self.writedata(0x80) # Bypass
# self.writedata(0x02) # Source Output Scan from S1 to S960, Gate Output scan from G1 to G480
# self.writedata(0x3B) # LCD Drive Line=8*(59+1)
#
# # Display Output Ctrl Adjust
# self.writecommand(0xE8)
# self.writedata(0x40)
# self.writedata(0x8A)
# self.writedata(0x00)
# self.writedata(0x00)
# self.writedata(0x29) # Source equalizing period time= 22.5 us
# self.writedata(0x19) # Timing for "Gate start"=25 (Tclk)
# self.writedata(0xA5) # Timing for "Gate End"=37 (Tclk), Gate driver EQ function ON
# self.writedata(0x33)
#
# # Power control 2
# self.writecommand(0xC1)
# self.writedata(0x06) # VAP(GVDD)=3.85+( vcom+vcom offset), VAN(GVCL)=-3.85+( vcom+vcom offset)
#
# # Power control 3
# self.writecommand(0xC2)
# self.writedata(0xA7) # Source driving current level=low, Gamma driving current level=High
#
# # VCOM Control
# self.writecommand(0xC5)
# self.writedata(0x18) # VCOM=0.9
# sleep_ms(120)
#
# # ST7796 Gamma Sequence
# # Gamma "+"
# self.writecommand(0xE0)
# gamma_plus = [0xF0, 0x09, 0x0B, 0x06, 0x04, 0x15, 0x2F,
# 0x54, 0x42, 0x3C, 0x17, 0x14, 0x18, 0x1B]
# for gamma in gamma_plus:
# self.writedata(gamma)
#
# # Gamma "-"
# self.writecommand(0xE1)
# gamma_minus = [0xE0, 0x09, 0x0B, 0x06, 0x04, 0x03, 0x2B,
# 0x43, 0x42, 0x3B, 0x16, 0x14, 0x17, 0x1B]
# for gamma in gamma_minus:
# self.writedata(gamma)
# sleep_ms(120)
#
# # Disable extension command 2 part I
# self.writecommand(0xF0)
# self.writedata(0x3C)
#
# # Disable extension command 2 part II
# self.writecommand(0xF0)
# self.writedata(0x69)
#
# # End TFT write
# #end_tft_write()
# sleep_ms(120)
#
# # Begin TFT write
# #begin_tft_write()
#
# # Display on
# self.writecommand(0x29)
def init_lcd(self):
# Initial delay
sleep_ms(120)
# Software reset
self.writecommand(0x01)
sleep_ms(120)
# Sleep exit
self.writecommand(0x11)
sleep_ms(120)
self.writecommand(0x21)
"""
Initialize LCD with command sequence
Args:
writecommand: Function to write command to LCD
writedata: Function to write data to LCD
"""
# Command sequence
init_sequence = [
(0xCF, [0x00, 0x83, 0x30]),
(0xED, [0x64, 0x03, 0x12, 0x81]),
(0xE8, [0x85, 0x01, 0x79]),
(0xCB, [0x39, 0x2C, 0x00, 0x34, 0x02]),
(0xF7, [0x20]),
(0xEA, [0x00, 0x00]),
(0xC0, [0x26]), # Power control
(0xC1, [0x11]), # Power control
(0xC5, [0x35, 0x3E]), # VCOM control
(0xC7, [0xBE]), # VCOM control
(0x36, [0x28]), # Memory Access Control (0x28)
(0x3A, [0x05]), # Pixel Format Set
(0xB1, [0x00, 0x1B]),
(0xF2, [0x08]),
(0x26, [0x01]),
(0xE0, [0x1F, 0x1A, 0x18, 0x0A, 0x0F, 0x06, 0x45, 0x87,
0x32, 0x0A, 0x07, 0x02, 0x07, 0x05, 0x00]),
(0xE1, [0x00, 0x25, 0x27, 0x05, 0x10, 0x09, 0x3A, 0x78,
0x4D, 0x05, 0x18, 0x0D, 0x38, 0x3A, 0x1F]),
(0x2A, [0x00, 0x00, 0x00, 0xEF]),
(0x2B, [0x00, 0x00, 0x01, 0x3F]),
(0x2C, []),
(0xB7, [0x07]),
(0xB6, [0x0A, 0x82, 0x27, 0x00]),
(0x11, []), # Sleep Out
(0x29, []), # Display ON
]
# Process each command in the sequence
for cmd, data in init_sequence:
self.writecommand(cmd)
for d in data:
self.writedata(d)
def flip(self):
#self.buffer1,self.buffer2 = self.buffer2, self.buffer1
self.fbdraw, self.fbshow = self.fbshow, self.fbdraw
def show_all(self):
self.writecommand(0x2A)
self.writedata(0x00)
self.writedata(0x00)
self.writedata(0x01)
self.writedata(0xdf) #df
self.writecommand(0x2B)
self.writedata(0x00)
self.writedata(0x00)
self.writedata(0x01)
self.writedata(0x3f) #3f
self.writecommand(0x2C)
self.cs(1)
self.dc(1)
self.cs(0)
#self.spi.write(self.buffer)
#self._spi_write(self.buffer)
#self._spi_write_asm(self.buffer2)
self._spi_write_asm(self.fbshow)
self.cs(1)
@micropython.viper
def _spi_write(self,buffer:ptr8):
spi0_base = ptr32(0x40080000)
length:int = 480 * 320 * 2
count:int = 0
while count < length:
spi0_base[2] = buffer[count] # write data to FIFO
spi0_base[1] = 0b10 # bit 1 to enable
done:int = 0
while not done & 0b10:
done = spi0_base[3] # get FIFO status
count += 1
@staticmethod
@micropython.asm_thumb
def _spi_write_asm(r0):
movwt(r1, 0x40080000) # r1 = SPI0_BASE = 0x40080000
movwt(r2, 240 * 320 * 1) #
movwt(r8,-480)
movwt(r9,480)
mov(r7,0)
label(LOOP)
ldrb(r5,[r0,0]) # load first byte
add(r0,1)
ldrb(r6,[r0,0]) # load second byte
add(r0,1)
strb(r5,[r1,0x8]) # write data to FIFO
mov(r3,0b10)
str(r3,[r1,0x4]) # enable
label(SPI_WAIT1)
ldr(r3,[r1,0xc]) # get FIFO status
mov(r4,0b10) # is it full?
and_(r3,r4)
cmp(r3,r4)
bne(SPI_WAIT1)
strb(r6,[r1,0x8]) # write data to FIFO
mov(r3,0b10)
str(r3,[r1,0x4]) # enable
label(SPI_WAIT2)
ldr(r3,[r1,0xc]) # get FIFO status
mov(r4,0b10) # is it full?
and_(r3,r4)
cmp(r3,r4)
bne(SPI_WAIT2)
strb(r5,[r1,0x8]) # write data to FIFO
mov(r3,0b10)
str(r3,[r1,0x4]) # enable
label(SPI_WAIT3)
ldr(r3,[r1,0xc]) # get FIFO status
mov(r4,0b10) # is it full?
and_(r3,r4)
cmp(r3,r4)
strb(r6,[r1,0x8]) # write data to FIFO
mov(r3,0b10)
str(r3,[r1,0x4]) # enable
label(SPI_WAIT4)
ldr(r3,[r1,0xc]) # get FIFO status
mov(r4,0b10) # is it full?
and_(r3,r4)
cmp(r3,r4)
bne(SPI_WAIT4)
add(r7,1) # pixel counter for line
cmp(r7,240)
bne(REPEAT_LINE)
data(2,0b010001_00_0__1__000_000) #add(r0,r0,r8) subtract 480, r8=-480
label(REPEAT_LINE)
data(2,0b010001_01_0__1__001_111) #cmp(r7,r9) # is 480?
blt(RESET_LINE)
mov(r7,0)
label(RESET_LINE)
sub(r2, 1)
bne(LOOP)
@staticmethod
@micropython.asm_thumb
def fill_asm(r0,r1): # r0 buffer address, r1 color
movwt(r2,0x12c00) # 480 * 320 // 2
lsl(r3,r1,16)
orr(r3,r1)
label(LOOP)
str(r3,[r0,0])
add(r0,4)
sub(r2,1)
bne(LOOP)
@staticmethod
@micropython.asm_thumb
def fill2(r0,r1): # Screen buffer address, Color value
lsl(r3,r1,16)
orr(r3,r1) # r3 now has color in both halves
mov(r1,r0)
mov(r2,r3) # Copy to r2
mov(r4,r3) # Copy to r4
mov(r5,r3) # Copy to r5
mov(r6,r3) # Copy to r6
mov(r7,r3) # Copy to r7
movwt(r0,MAXSCREEN_X * MAXSCREEN_Y // 96) # Divide by 96 as we store 96 pixels per loop
label(LOOP)
# Store multiple - r2 through r7 (6 registers = 24 bytes = 12 pixels)
#stmia(r1!, {r2, r3, r4, r5, r6, r7}) #512
data(2,0b11000_001_11111100)
data(2,0b11000_001_11111100)
data(2,0b11000_001_11111100)
data(2,0b11000_001_11111100)
data(2,0b11000_001_11111100)
data(2,0b11000_001_11111100)
data(2,0b11000_001_11111100)
data(2,0b11000_001_11111100)
# Decrement counter and loop if still positive
sub(r0,1)
bpl(LOOP)
def off(self):
LCD.writecommand(0x28)
def init_sprite():
with open("pi.bin", "rb") as file:
file.read(4) # Read and skip the header
sprite = file.read()
file.close()
return sprite
@micropython.viper
def show_sprite(sprite:ptr16,x1:int,y1:int,w:int,h:int):
screen = ptr16(LCD.fbdraw)
for y in range(h):
y2 = y + y1
for x in range(w):
screen_addr = y2 * MAXSCREEN_X + x + x1
sprite_addr = y * w + x
if screen_addr > (240 * 160): return
screen[screen_addr] = sprite[sprite_addr]
def core1():
global SHOWING, DRAWING, EXIT
sleep_ms(200)
while not EXIT:
gticks=time.ticks_ms()
SHOWING = True
LCD.show_all()
sleep_ms(5)
LCD.flip()
LCD.fps = (1_000//time.ticks_diff(time.ticks_ms(),gticks))
print('core1 done')
LCD.writecommand(0x28) #LCD off
#@micropython.viper
def draw():
global SHOWING, DRAWING, EXIT
deg = 0
n, x, y = 0, 0, 0
while not EXIT and n<3:
while not SHOWING: sleep_ms(1)
gticks=int(time.ticks_ms())
n, points = touch.read_points()
x = (points[0][0] - 200) // 2
y = (320 - points[0][1] - 150) //2
if x<0:x = 0
if y<0:y = 0
gfx.filldma(LCD.fbdraw,0)
#LCD.fbdraw.fill(0)
#gfx.blit(LCD.fbdraw,SPRITE,x,y,200,200)
rot_sprite(deg,200,x,y)
#LCD.fbdraw.line(0,0,239,159,0xff)
#LCD.fbdraw.line(239,0,0,159,0xff)
fps = (1_000//1+int(time.ticks_diff(int(time.ticks_ms()),gticks)))
#LCD.fbdraw.text(str(fps),0,20,0xff)
LCD.fbdraw.text(str(LCD.fps),0,0,0xff)
SHOWING = False
deg += 3
if deg > 359: deg -= 360
LCD.off()
print('core0 done')
@micropython.viper # SCALE = 13
def rot_sprite(deg:int, width:int,d_x:int,d_y:int):
sin=ptr32(isin)
cos=ptr32(icos)
source=ptr16(SPRITE)
dest=ptr16(LCD.fbdraw)
offset_x = width//2
offset_y = offset_x
cos_rad = cos[deg]
sin_rad = sin[deg]
dest_width = MAXSCREEN_X
dest_height = MAXSCREEN_Y
max_addr = MAXSCREEN_X * MAXSCREEN_Y # limit write to screen size
# Scan destination coordinates
qy = 0
while qy < dest_height:
qx = 0
while qx < width:
# Transform back to source coordinates
adjusted_qx = (qx - offset_x)
adjusted_qy = (qy - offset_y)
# Inverse rotation transformation
x = offset_x
x += (cos_rad * adjusted_qx + sin_rad * adjusted_qy) >> SCALE
y = offset_y
y += (-sin_rad * adjusted_qx + cos_rad * adjusted_qy) >> SCALE
# Check if source coordinates are within sprite bounds
if x >= 0 and x < width and y >= 0 and y < width:
i = y * width + x
color = source[i]
if color: # Only write non-zero colors
dest_i = (d_y + qy) * dest_width + qx + d_x
if dest_i < max_addr:
dest[dest_i] = color
qx += 1
qy += 1
def init_imath(): # integer sin,cos lookup table
global isin, icos
isin = array.array('I',range(360))
icos = array.array('I',range(360))
for i in range(0,360):
isin[i]=int(sin(radians(i))*(1<<SCALE))
icos[i]=int(cos(radians(i))*(1<<SCALE))
if __name__=='__main__':
from random import randint
from gfx import Gfx
import time, gc, _thread, array
from math import sin,cos,radians
from gt911 import GT911
SHOWING = False
EXIT = False
touch = GT911(I2C(0, scl=9,sda=8,freq=400_000), reset_pin=7, irq_pin=0, touch_points=3)
freq(220_000_000) #
machine.mem32[0x40010048] = 1<<11 # enable peri_ctrl clock
print(freq())
LCD = LCD_3inch5()
print(LCD.spi)
init_imath()
gfx = Gfx(LCD.fbdraw,MAXSCREEN_X,MAXSCREEN_Y)
SPRITE = init_sprite()
gc.collect()
print(f"Free mem:{gc.mem_free()}")
_thread.start_new_thread(core1, ())
sleep_ms(200)
try:
draw()
except KeyboardInterrupt :
print('core0 done')
EXIT = True
sleep_ms(100)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment