Description
What MCU/Processor/Board and compiler are you using?
Raspberry Pi Pico
LVGL 9.1
What do you want to achieve?
Having a simple gui with a button, and the ability to press it.
What have you tried so far?
I made a super simple main script and display driver, then i took the existing xpt2046 driver.
I can succesfully display the button, but the script runs only for a couple of seconds, not giving me the ability to press the button or do anything else. If i start the script with my finger already where the button is going to be, i succesfully see it presse, but then everything always freezes.
Code to reproduce
Add the relevant code snippets here.
This is main.py:
import time
import lvgl as lv
import st7735
from machine import Pin, SPI
from xpt2046 import Xpt2046
# Initialize LittlevGL
lv.init()
# Initialize the SPI and GPIO for the ST7735 display
spi = SPI(0, baudrate=16000000, sck=Pin(18), mosi=Pin(19))
cs = Pin(17, Pin.OUT)
dc = Pin(16, Pin.OUT)
rst = Pin(20, Pin.OUT)
# Initialize the ST7735 display driver
display = st7735.ST7735(160, 128, spi, cs, dc, rst)
# Create a buffer for LittlevGL
buf1 = bytearray(160 * 10 * 2) # Buffer for 10 lines
# Create a display object
disp = lv.display_create(160, 128)
print(dir(disp))
# Set the flush callback
disp.set_flush_cb(display.flush)
disp.flush_ready()
# Register the display buffers
disp.set_buffers(buf1, None, len(buf1), lv.DISPLAY_RENDER_MODE.PARTIAL)
# Create a screen
scr = lv.obj()
# Create a button
btn = lv.button(scr) # Create a button
btn.set_size(90, 30) # Set the size of the button
btn.center() # Center the button on the screen
# Add a label to the button
label = lv.label(btn) # Create a label inside the button
label.set_text("Click Me!") # Set the text of the label
# Load the screen
lv.screen_load(scr)
#lv.refr_now(None)
# Initialize the SPI and GPIO for the touch screen
spi_touch = SPI(1, baudrate=1000000, sck=Pin(14), mosi=Pin(11), miso=Pin(12))
cs_touch = Pin(13, Pin.OUT)
irq_touch = Pin(9, Pin.IN)
touch = Xpt2046(
spi=spi_touch,
cs=cs_touch,
bits=12,
ranges=((100, 1900), (200, 1950)),
width=128,
height=160,
rot=1
)
indev = lv.indev_create() # Create an input device object
indev.set_type(lv.INDEV_TYPE.POINTER) # Set the input device type to POINTER
indev.set_read_cb(touch.indev_drv_read_cb) # Set the read callback to the touch driver's read method
#touch.indev_drv_read_cb(None, lv.indev_data_t())
# Main loop to handle LVGL tasks
while True:
lv.tick_inc(5) # Increment LVGL tick by 5 ms
lv.timer_handler() # Handle LVGL tasks
time.sleep(0.005) # Sleep for 5 ms
This is st7735.py:
import machine
import time
import framebuf
import lvgl as lv
class ST7735:
NOP = const(0x00) # No-op
SWRESET = const(0x01) # Software reset
RDDID = const(0x04) # Read display ID info
RDDST = const(0x09) # Read display status
SLPIN = const(0x10) # Enter sleep mode
SLPOUT = const(0x11) # Exit sleep mode
PTLON = const(0x12) # Partial mode on
NORON = const(0x13) # Normal display mode on
INVOFF = const(0x20) # Display inversion off
INVON = const(0x21) # Display inversion on
GAMMASET = const(0x26) # Gamma set
DISPLAY_OFF = const(0x28) # Display off
DISPLAY_ON = const(0x29) # Display on
SET_COLUMN = const(0x2A) # Column address set
SET_PAGE = const(0x2B) # Page address set
WRITE_RAM = const(0x2C) # Memory write
READ_RAM = const(0x2E) # Memory read
PTLAR = const(0x30) # Partial area
VSCRDEF = const(0x33) # Vertical scrolling definition
VSCRSADD = const(0x37) # Vertical scrolling start address
COLMOD = const(0x3A) # COLMOD: Pixel format set
MADCTL = const(0x36) # Memory access control
FRMCTR1 = const(0xB1) # Frame rate control (In normal mode/full colors)
INVCTR = const(0xB4) # Display inversion control
DFUNCTR = const(0xB6) # Display function control
PWCTR1 = const(0xC0) # Power control 1
PWCTR2 = const(0xC1) # Power control 2
VMCTR1 = const(0xC5) # VCOM control 1
GMCTRP1 = const(0xE0) # Positive gamma correction
GMCTRN1 = const(0xE1) # Negative gamma correction
MIRROR_ROTATE = { # MADCTL configurations for rotation and mirroring
(False, 0): 0x00, # 0000 0000
(False, 90): 0x60, # 0110 0000
(False, 180): 0xC0, # 1100 0000
(False, 270): 0xA0, # 1010 0000
(True, 0): 0x40, # 0100 0000
(True, 90): 0x20, # 0010 0000
(True, 180): 0x80, # 1000 0000
(True, 270): 0xE0 # 1110 0000
}
def __init__(self, width, height, spi, cs, dc, rst, bl=None):
self.rotation = self.MIRROR_ROTATE[False, 90]
#BGR
self.rotation |= 0b00001000
self.width = width
self.height = height
self.spi = spi
self.cs = cs
self.dc = dc
self.rst = rst
self.bl = machine.Pin(bl, machine.Pin.OUT) if bl is not None else None
self.cs.init(self.cs.OUT, value=1)
self.dc.init(self.dc.OUT, value=0)
self.rst.init(self.rst.OUT, value=1)
self.buffer = bytearray(self.width * self.height * 2)
self.framebuf = framebuf.FrameBuffer(self.buffer, self.width, self.height, framebuf.RGB565)
self.init_display()
def init_display(self):
self.reset()
self.send_command(0x01) # Software reset
time.sleep(0.150)
self.send_command(0x11) # Sleep out
time.sleep(0.500)
time.sleep(0.500)
self.send_command(self.FRMCTR1, 0x01, 0x2C, 0x2D) # Frame rate ctrl
self.send_command(self.PWCTR1, 0xA2, 0x02, 0x84) # Pwr ctrl 1
self.send_command(self.PWCTR2, 0xC5) # Pwr ctrl 2
self.send_command(self.VMCTR1, 0x0A, 0x00) # VCOM ctrl 1
self.send_command(self.MADCTL, self.rotation) # Memory access ctrl
self.send_command(self.COLMOD, 0x55) # COLMOD: Pixel format set
self.send_command(self.INVCTR, 0x07) # Display inversion ctrl
self.send_command(self.GMCTRP1, # Positive gamma correction
0x0F, 0x1A, 0x0F, 0x18, 0x2F, 0x28, 0x20, 0x22,
0x1F, 0x1B, 0x23, 0x37, 0x00, 0x07, 0x02, 0x10)
self.send_command(self.GMCTRN1, # Negative gamma correction
0x0F, 0x1B, 0x0F, 0x17, 0x33, 0x2C, 0x29, 0x2E,
0x30, 0x30, 0x39, 0x3F, 0x00, 0x07, 0x03, 0x10)
self.send_command(self.NORON) # Normal display mode on
time.sleep(0.100)
self.send_command(0x36, self.rotation)
self.send_command(0x3A) # Pixel format set
self.send_data(bytearray([0x05]))
self.send_command(0x29) # Display on
def reset(self):
self.rst.value(0)
time.sleep(0.050)
self.rst.value(1)
time.sleep(0.150)
def send_command(self, cmd, *args):
self.dc(0)
self.cs(0)
self.spi.write(bytearray([cmd]))
self.cs(1)
# Handle any passed data
if len(args) > 0:
self.write_data(bytearray(args))
def write_data(self, data):
self.dc(1)
self.cs(0)
self.spi.write(data)
self.cs(1)
def send_data(self, data):
print("senddata called")
self.dc.value(1)
self.cs.value(0)
self.spi.write(data)
self.cs.value(1)
def flush(self, disp, area, color_p):
self.send_command(0x2A, 0x00, area.x1, 0x00, area.x2)
self.send_command(0x2B, 0x00, area.y1, 0x00, area.y2)
self.send_command(0x2C)
# Convert color_p to bytearray for SPI transmission
length = (area.x2 - area.x1 + 1) * (area.y2 - area.y1 + 1) * 2 # 2 bytes per pixel
buf = bytes(color_p.__dereference__(length))
# Send pixel data
self.send_data(buf)
disp.flush_ready()
This is xpt2046.py:
(lv_binding_micropython/driver/generic/xpt2046.py at master · lvgl/lv_binding_micropython · GitHub)
# © 2022 Václav Šmilauer <eu@doxos.eu>
# MIT-licensed
import machine
import struct
import lvgl as lv
# for inspiration see e.g.
# https://github.com/MatthewLowden/RPi-XPT2046-Touchscreen-Python/blob/master/XPT2046.py
# and the XPT2046 datasheet
XPT2046_PORTRAIT = const(0)
XPT2046_LANDSCAPE = const(1)
XPT2046_INV_PORTRAIT = const(2)
XPT2046_INV_LANDSCAPE = const(3)
class Xpt2046_hw(object):
CHAN_X = const(0b0101_0000)
CHAN_Y = const(0b0001_0000)
CHAN_Z1 = const(0b0011_0000)
CHAN_Z2 = const(0b0100_0000)
CHAN_T0 = const(0b0000_0000)
CHAN_T1 = const(0b0111_0000)
CHAN_BAT= const(0b0010_0000)
CHAN_AUX= const(0b0110_0000)
CONV_8_BIT =const(0b0000_1000)
CONV_12_BIT=const(0b0000_0000)
START_BIT =const(0b1000_0000)
def _chanRead(self,chan):
self.cs.value(0)
struct.pack_into('BBB',self.buf,0,Xpt2046.START_BIT|self.conv|chan,0,0)
self.spi.write_readinto(self.buf,self.buf)
if self.conv==Xpt2046.CONV_8_BIT: ret=self.buf[1]
else: ret=(self.buf[1]<<4)|(self.buf[2]>>4)
self.cs.value(1)
return ret
def __init__(self,*,
spi: machine.SPI,cs,bits=12,ranges=((100,1900),(200,1950)),width=240,height=320,rot=XPT2046_PORTRAIT):
'''
Construct the Xpt2046 touchscreen controller.
*spi*: spi bus instance; its baud rate must *not* exceed 2_000_000 (2MHz) for correct functionality
*cs*: chip select (GPIO number or machine.Pin instance)
*bits*: ADC precision, can be 12 or 8; note that 8 will require you to provide different *ranges*
*ranges*: `(x_min,x_max),(y_min,y_max)` for raw coordinate readings; calibrated values might be provided.
*width*: width of the underlying screen in pixels, in natural (rot=0) orientation (0..*width* is the range for reported horizontal coordinate)
*height*: height of the underlying screen in pixels
*rot*: screen rotation (0: portrait, 1: landscape, 2: inverted portrait, 3: inverted landscape); the constants XPT2046_PORTRAIT, XPT2046_LANDSCAPE, XPT2046_INV_PORTRAIT, XPT2046_INV_LANDSCAPE may be used.
'''
self.buf = bytearray(3)
self.spi = spi
self.cs = (machine.Pin(cs,machine.Pin.OUT) if isinstance(cs,int) else cs)
self.cs.value(1)
if bits not in (8,12): raise ValueError('Xpt2046.bits: must be 8 or 12 (not %s)'%str(bits))
self.conv=(Xpt2046.CONV_8_BIT if bits==8 else Xpt2046.CONV_12_BIT)
self.xy_range,self.dim,self.rot=ranges,(width,height),(rot%4)
self.xy_scale=[self.dim[ax]*1./(self.xy_range[ax][1]-self.xy_range[ax][0]) for ax in (0,1)]
self.xy_origin=[self.xy_range[ax][0] for ax in (0,1)]
def _raw2px(self,rxy):
'Convert raw coordinates to pixel coordinates'
x,y=[int(self.xy_scale[ax]*(rxy[ax]-self.xy_origin[ax])) for ax in (0,1)]
if self.rot==0: return self.dim[0]-x,y
elif self.rot==1: return self.dim[1]-y,x
elif self.rot==2: return x,self.dim[1]-y
else: return y,self.dim[0]-x
def _raw_pos(self):
'Read raw position; return value if within valid ranges (`__init__(ranges=...)`) or `None` if outside.'
ret=[0,0]
for ax,chan in [(0,Xpt2046.CHAN_X),(1,Xpt2046.CHAN_Y)]:
r=self._chanRead(chan)
#print(f"Raw axis {ax}: {r}") # Debug: Print raw touch data
if not self.xy_range[ax][0]<=r<=self.xy_range[ax][1]: return None
ret[ax]=r
return ret
def pos(self,N=10,attempts=20):
''''
Get N position readings (limited by 20 attempts) and return mean position of valid readings.
If attempts are exhausted, return None.
'''
N,attempts=10,20
xx,yy,done=0,0,0
for _ in range(attempts):
if (r:=self._raw_pos()) is None: continue
xx+=r[0]; yy+=r[1]; done+=1
if done==N: break
else: return None
mx,my=xx*1./N,yy*1./N
return self._raw2px((mx,my))
class Xpt2046(Xpt2046_hw):
def indev_drv_read_cb(self, indev_drv, data):
# wait for DMA transfer (if any) before switchint SPI to 1 MHz
if self.spiPrereadCb: self.spiPrereadCb()
# print('.',end='')
if self.spiRate: self.spi.init(baudrate=1_000_000)
pos=self.pos()
if pos is None:
data.state = lv.INDEV_STATE.RELEASED
print("Touch released")
data.state=0
else:
print(f"Touch state: PRESSED at x={data.point.x}, y={data.point.y}")
data.state = lv.INDEV_STATE.PRESSED
(data.point.x,data.point.y),data.state=pos,1
# print('#',end='')
# switch SPI back to spiRate
if self.spiRate: self.spi.init(baudrate=self.spiRate)
def __init__(self,spi,spiRate=24_000_000,spiPrereadCb=None,**kw):
'''XPT2046 touchscreen driver for LVGL; cf. documentation of :obj:`Xpt2046_hw` for the meaning of parameters being passed.
*spiPrereadCb*: call this before reading from SPI; used to block until DMA transfer is complete (when sharing SPI bus).
*spiRate*: the SPI bus must set to low frequency (1MHz) when reading from the XPT2046; when *spiRate* is given, the bus will be switched back to this frequency when XPT2046 is done reading. The default 24MHz targets St77xx display chips which operate at that frequency and come often with XPT2046-based touchscreen.
'''
super().__init__(spi=spi,**kw)
self.spiRate=spiRate
self.spiPrereadCb=spiPrereadCb
import lvgl as lv
if not lv.is_initialized(): lv.init()
self.indev_drv = lv.indev_create()
self.indev_drv.set_type(lv.INDEV_TYPE.POINTER)
self.indev_drv.set_read_cb(self.indev_drv_read_cb)
I’m probably not very familiar with lvgl version 9.1, and maybe I’m using some simple function the wrong way, can anyone help me so I can get started?
Thanks in advance!