Created
November 29, 2024 20:55
-
-
Save samneggs/4201388ffca5aa2fb0d544478e6e6677 to your computer and use it in GitHub Desktop.
Pi Pico 2 st7796 double buffer, double pixel display driver
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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