In this exercise, Raspberry Pi Pico is flashed with Adafruit CircuitPython 6.2.0. Display on ILI9341 SPI Display with Touch using adafruit_ili9341 library. For the touch detection, xpt2046.py of rdagger/micropython-ili9341 was modified to work for CircuitPython. It's CircuitPython version of my former exercise RPi Pico/MicroPython + ILI9341 SPI Display with Touch.
The display used in this exercise is a 2.4-inch 65K color using ili9341 driver with touch, 2.4inch_SPI_Module_ILI9341_SKU:MSP2402.
Connection:
Connection:
ILI9341 TFT SPI RPi Pico
------------------------------
VCC 3V3
GND GND
CS GP13
RESET GP14
DC GP15
SDI(MOSI) GP7
SCK GP6
LED 3V3
SDO(MISO)
T- T_CLK GP10
O T_CS GP12
U T_DIN GP11
C T_DO GP8
H- T_IRQ
Visit https://circuitpython.org/libraries, download the appropriate bundle for your version of CircuitPython. Unzip the file, and copy adafruit_ili9341.mpy and adafruit_display_text directory to /lib directory of your Raspberry Pi Pico CIRCUITPY driver.
Touch driver for xpt2046:
(If you do not use touch part, you can skip this part.)
xpt2046.py of rdagger/micropython-ili9341 is a xpt2046 driver for MicroPython. I modify to make it work for CircuitPython. The main modification are removing interrupt and re-config pin for CircuitPython using digitalio.
Save it to Raspberry Pi Pico CIRCUITPY driver, name cpy_xpt2046.py.
"""
XPT2046 Touch module for CircuitPython
modified from xpt2046.py of rdagger/micropython-ili9341
https://github.com/rdagger/micropython-ili9341/blob/master/xpt2046.py
remove interrupt and re-config pin for CircuitPython
"""
from time import sleep
import digitalio
class Touch(object):
"""Serial interface for XPT2046 Touch Screen Controller."""
# Command constants from ILI9341 datasheet
GET_X = const(0b11010000) # X position
GET_Y = const(0b10010000) # Y position
GET_Z1 = const(0b10110000) # Z1 position
GET_Z2 = const(0b11000000) # Z2 position
GET_TEMP0 = const(0b10000000) # Temperature 0
GET_TEMP1 = const(0b11110000) # Temperature 1
GET_BATTERY = const(0b10100000) # Battery monitor
GET_AUX = const(0b11100000) # Auxiliary input to ADC
""" remove support of interrupt
def __init__(self, spi, cs, int_pin=None, int_handler=None,
width=240, height=320,
x_min=100, x_max=1962, y_min=100, y_max=1900):
"""
def __init__(self, spi, cs, width=240, height=320,
x_min=100, x_max=1962, y_min=100, y_max=1900):
"""Initialize touch screen controller.
Args:
spi (Class Spi): SPI interface for OLED
cs (Class Pin): Chip select pin
int_pin (Class Pin): Touch controller interrupt pin
int_handler (function): Handler for screen interrupt
width (int): Width of LCD screen
height (int): Height of LCD screen
x_min (int): Minimum x coordinate
x_max (int): Maximum x coordinate
y_min (int): Minimum Y coordinate
y_max (int): Maximum Y coordinate
"""
self.spi = spi
self.cs = cs
#self.cs.init(self.cs.OUT, value=1)
self.cs_io = digitalio.DigitalInOut(cs)
self.cs_io.direction = digitalio.Direction.OUTPUT
self.cs_io.value=1
self.rx_buf = bytearray(3) # Receive buffer
self.tx_buf = bytearray(3) # Transmit buffer
self.width = width
self.height = height
# Set calibration
self.x_min = x_min
self.x_max = x_max
self.y_min = y_min
self.y_max = y_max
self.x_multiplier = width / (x_max - x_min)
self.x_add = x_min * -self.x_multiplier
self.y_multiplier = height / (y_max - y_min)
self.y_add = y_min * -self.y_multiplier
""" ignore int_pin
if int_pin is not None:
self.int_pin = int_pin
self.int_pin.init(int_pin.IN)
self.int_handler = int_handler
self.int_locked = False
int_pin.irq(trigger=int_pin.IRQ_FALLING | int_pin.IRQ_RISING,
handler=self.int_press)
"""
def get_touch(self):
"""Take multiple samples to get accurate touch reading."""
timeout = 2 # set timeout to 2 seconds
confidence = 5
buff = [[0, 0] for x in range(confidence)]
buf_length = confidence # Require a confidence of 5 good samples
buffptr = 0 # Track current buffer position
nsamples = 0 # Count samples
while timeout > 0:
if nsamples == buf_length:
meanx = sum([c[0] for c in buff]) // buf_length
meany = sum([c[1] for c in buff]) // buf_length
dev = sum([(c[0] - meanx)**2 +
(c[1] - meany)**2 for c in buff]) / buf_length
if dev <= 50: # Deviation should be under margin of 50
return self.normalize(meanx, meany)
# get a new value
sample = self.raw_touch() # get a touch
if sample is None:
nsamples = 0 # Invalidate buff
else:
buff[buffptr] = sample # put in buff
buffptr = (buffptr + 1) % buf_length # Incr, until rollover
nsamples = min(nsamples + 1, buf_length) # Incr. until max
sleep(.05)
timeout -= .05
return None
"""
def int_press(self, pin):
if not pin.value() and not self.int_locked:
self.int_locked = True # Lock Interrupt
buff = self.raw_touch()
if buff is not None:
x, y = self.normalize(*buff)
self.int_handler(x, y)
sleep(.1) # Debounce falling edge
elif pin.value() and self.int_locked:
sleep(.1) # Debounce rising edge
self.int_locked = False # Unlock interrupt
"""
def normalize(self, x, y):
"""Normalize mean X,Y values to match LCD screen."""
x = int(self.x_multiplier * x + self.x_add)
y = int(self.y_multiplier * y + self.y_add)
return x, y
def raw_touch(self):
"""Read raw X,Y touch values.
Returns:
tuple(int, int): X, Y
"""
x = self.send_command(self.GET_X)
y = self.send_command(self.GET_Y)
if self.x_min <= x <= self.x_max and self.y_min <= y <= self.y_max:
return (x, y)
else:
return None
def send_command(self, command):
"""Write command to XT2046 (MicroPython).
Args:
command (byte): XT2046 command code.
Returns:
int: 12 bit response
"""
self.tx_buf[0] = command
#self.cs(0)
self.cs_io.value=0
self.spi.try_lock()
self.spi.write_readinto(self.tx_buf, self.rx_buf)
self.spi.unlock()
#self.cs(1)
self.cs_io.value=1
return (self.rx_buf[1] << 4) | (self.rx_buf[2] >> 4)
Example code:
cpyPico_spi_ILI9341_20210416.pyfrom sys import implementation
from os import uname
import board
import time
import displayio
import terminalio
import busio
import adafruit_ili9341
from adafruit_display_text import label
print('=======================')
print(implementation[0], uname()[3])
displayio.release_displays()
TFT_WIDTH = 320
TFT_HEIGHT = 240
tft_cs = board.GP13
tft_dc = board.GP15
tft_res = board.GP14
spi_mosi = board.GP7
#spi_miso = board.GP4
spi_clk = board.GP6
spi = busio.SPI(spi_clk, MOSI=spi_mosi)
display_bus = displayio.FourWire(
spi, command=tft_dc, chip_select=tft_cs, reset=tft_res)
display = adafruit_ili9341.ILI9341(display_bus,
width=TFT_WIDTH, height=TFT_HEIGHT,
rowstart=0, colstart=0)
display.rotation = 0
# Make the display context
splash = displayio.Group(max_size=10)
display.show(splash)
color_bitmap = displayio.Bitmap(display.width, display.height, 1)
color_palette = displayio.Palette(1)
color_palette[0] = 0x00FF00
bg_sprite = displayio.TileGrid(color_bitmap,
pixel_shader=color_palette, x=0, y=0)
splash.append(bg_sprite)
# Draw a smaller inner rectangle
inner_bitmap = displayio.Bitmap(display.width-2, display.height-2, 1)
inner_palette = displayio.Palette(1)
inner_palette[0] = 0x0000FF
inner_sprite = displayio.TileGrid(inner_bitmap,
pixel_shader=inner_palette, x=1, y=1)
splash.append(inner_sprite)
# Draw a label
text_group1 = displayio.Group(max_size=10, scale=2, x=20, y=40)
text1 = "RPi Pico"
text_area1 = label.Label(terminalio.FONT, text=text1, color=0xFF0000)
text_group1.append(text_area1) # Subgroup for text scaling
# Draw a label
text_group2 = displayio.Group(max_size=10, scale=1, x=20, y=60)
text2 = implementation[0] + ' ' + uname()[3]
text_area2 = label.Label(terminalio.FONT, text=text2, color=0xFFFFFF)
text_group2.append(text_area2) # Subgroup for text scaling
# Draw a label
text_group3 = displayio.Group(max_size=10, scale=2, x=20, y=100)
text3 = adafruit_ili9341.__name__
text_area3 = label.Label(terminalio.FONT, text=text3, color=0xF0F0F0)
text_group3.append(text_area3) # Subgroup for text scaling
# Draw a label
text_group4 = displayio.Group(max_size=10, scale=2, x=20, y=120)
text4 = adafruit_ili9341.__version__
text_area4 = label.Label(terminalio.FONT, text=text4, color=0xF0F0F0)
text_group4.append(text_area4) # Subgroup for text scaling
splash.append(text_group1)
splash.append(text_group2)
splash.append(text_group3)
splash.append(text_group4)
rot = 0
print('rot: ', rot, '\t-', display.width," x ", display.height)
time.sleep(3.0)
while True:
time.sleep(5.0)
rot = rot + 90
if (rot>=360):
rot =0
display.rotation = rot
print('rot: ', rot, '\t-', display.width," x ", display.height)
print('- bye -')
cpyPico_spi_ILI9341_bitmap_20210416.py
"""
Example of CircuitPython/Raspberry Pi Pico
to display on 320x240 ili9341 SPI display
"""
import os
import board
import time
import terminalio
import displayio
import busio
from adafruit_display_text import label
import adafruit_ili9341
print("==============================")
print(os.uname())
print("Hello Raspberry Pi Pico/CircuitPython ILI8341 SPI Display")
print(adafruit_ili9341.__name__ + " version: " + adafruit_ili9341.__version__)
print()
# Release any resources currently in use for the displays
displayio.release_displays()
TFT_WIDTH = 320
TFT_HEIGHT = 240
tft_spi_clk = board.GP6
tft_spi_mosi = board.GP7
#tft_spi_miso = board.GP4
tft_cs = board.GP13
tft_dc = board.GP15
tft_res = board.GP14
tft_spi = busio.SPI(tft_spi_clk, MOSI=tft_spi_mosi)
display_bus = displayio.FourWire(
tft_spi, command=tft_dc, chip_select=tft_cs, reset=tft_res)
display = adafruit_ili9341.ILI9341(display_bus,
width=TFT_WIDTH, height=TFT_HEIGHT)
display.rotation = 90
print('rot: ', display.rotation, '\t-', display.width," x ", display.height)
group = displayio.Group(max_size=10)
display.show(group)
bitmap = displayio.Bitmap(display.width, display.height, display.width)
palette = displayio.Palette(display.width)
for p in range(display.width):
palette[p] = (0x010000*p) + (0x0100*p) + p
for y in range(display.height):
for x in range(display.width):
bitmap[x,y] = x
tileGrid = displayio.TileGrid(bitmap, pixel_shader=palette, x=0, y=0)
group.append(tileGrid)
time.sleep(3.0)
while True:
for p in range(display.width):
palette[p] = p
time.sleep(3.0)
for p in range(display.width):
palette[p] = 0x0100 * p
time.sleep(3.0)
for p in range(display.width):
palette[p] = 0x010000 * p
time.sleep(3.0)
print('-bye -')
cpyPico_spi_ILI9341_touch_20210416.py
"""
Example of CircuitPython/Raspberry Pi Pico
to display on 320x240 ili9341 SPI display
with touch detection
"""
from sys import implementation
from os import uname
import board
import time
import terminalio
import displayio
import busio
from adafruit_display_text import label
import adafruit_ili9341
from cpy_xpt2046 import Touch
print("==============================")
print(implementation[0], uname()[3])
print("Hello Raspberry Pi Pico/CircuitPython ILI8341 SPI Display")
print("with touch")
print(adafruit_ili9341.__name__ + " version: " + adafruit_ili9341.__version__)
print()
# Release any resources currently in use for the displays
displayio.release_displays()
TFT_WIDTH = 320
TFT_HEIGHT = 240
tft_spi_clk = board.GP6
tft_spi_mosi = board.GP7
#tft_spi_miso = board.GP4
tft_cs = board.GP13
tft_dc = board.GP15
tft_res = board.GP14
touch_spi_clk = board.GP10
touch_spi_mosi = board.GP11
touch_spi_miso = board.GP8
touch_cs = board.GP12
#touch_int = board.GP0
touch_x_min = 64
touch_x_max = 1847
touch_y_min = 148
touch_y_max = 2047
touch_spi = busio.SPI(touch_spi_clk, MOSI=touch_spi_mosi, MISO=touch_spi_miso)
touch = Touch(touch_spi, cs=touch_cs,
x_min=touch_x_min, x_max=touch_x_max,
y_min=touch_y_min, y_max=touch_y_max)
tft_spi = busio.SPI(tft_spi_clk, MOSI=tft_spi_mosi)
display_bus = displayio.FourWire(
tft_spi, command=tft_dc, chip_select=tft_cs, reset=tft_res)
display = adafruit_ili9341.ILI9341(display_bus,
width=TFT_WIDTH, height=TFT_HEIGHT)
display.rotation = 90
scrWidth = display.width
scrHeight = display.height
print('rot: ', display.rotation, '\t-', scrWidth," x ", scrHeight)
group = displayio.Group(max_size=10)
display.show(group)
bitmap = displayio.Bitmap(display.width, display.height, 5)
BLACK = 0
WHITE = 1
RED = 2
GREEN = 3
BLUE = 4
palette = displayio.Palette(5)
palette[0] = 0x000000
palette[1] = 0xFFFFFF
palette[2] = 0xFF0000
palette[3] = 0x00FF00
palette[4] = 0x0000FF
for y in range(display.height):
for x in range(display.width):
bitmap[x,y] = BLACK
tileGrid = displayio.TileGrid(bitmap, pixel_shader=palette, x=0, y=0)
group.append(tileGrid)
taskInterval_50ms = 0.050
NxTick = time.monotonic() + taskInterval_50ms
EVT_NO = const(0)
EVT_PenDown = const(1)
EVT_PenUp = const(2)
EVT_PenRept = const(3)
touchEvent = EVT_NO
touchSt_Idle_0 = const(0)
touchSt_DnDeb_1 = const(1)
touchSt_Touching_2 = const(2)
touchSt_UpDeb_3 = const(3)
touchSt = touchSt_Idle_0
touchDb_NUM = const(3)
touchDb = touchDb_NUM
touching = False
"""
state diagram for touch debounce
touchStIdle_0 validXY!=None-> touchSt_DnDeb_1
<-validXY==None
^ validXY!=None
| |
validXY==None V
touchSt_UpDeb_3 <- validXY==None touchSt_Touching_2
validXY!=None->
"""
"""
None: no touch or invalid touch
normailzedX, normailzedY: valid touch
"""
def validTouch():
xy = touch.raw_touch()
if xy == None:
return None
normailzedX, normailzedY = touch.normalize(*xy)
if (normailzedX < 0 or normailzedX >= scrWidth
or normailzedY < 0 or normailzedY >= scrHeight):
return None
return (normailzedX, normailzedY)
def TouchDetTask():
global touch
global touching
global touchSt
global touchEvent
global touchedX, touchedY
global touchDb
validXY = validTouch()
if touchSt == touchSt_Idle_0:
if validXY != None:
touchDb = touchDb_NUM
touchSt = touchSt_DnDeb_1
elif touchSt == touchSt_DnDeb_1:
if validXY != None:
touchDb = touchDb-1
if touchDb==0:
touchSt = touchSt_Touching_2
touchEvent = EVT_PenDown
touchedX, touchedY = validXY
touching = True
else:
touchSt = touchSt_Idle_0
elif touchSt == touchSt_Touching_2:
if validXY != None:
touchedX, touchedY = validXY
touchEvent = EVT_PenRept
else:
touchDb=touchDb_NUM
touchSt = touchSt_UpDeb_3
elif touchSt == touchSt_UpDeb_3:
if validXY != None:
touchSt = touchSt_Touching_2
else:
touchDb=touchDb-1
if touchDb==0:
touchSt = touchSt_Idle_0
touchEvent = EVT_PenUp
touching = False
def drawCross(x, y, col):
if y>=0 and y<scrHeight:
for i in range(x-5, x+5):
if i>=0 and i<scrWidth:
bitmap[i, y] = col
if x>=0 and y<scrWidth:
for i in range(y-5, y+5):
if i>=0 and i<scrHeight:
bitmap[x, i] = col
while True:
curTick = time.monotonic()
if curTick >= NxTick:
NxTick = curTick + taskInterval_50ms
#print(NxTick)
TouchDetTask()
#handle touch event
if touchEvent != EVT_NO:
if touchEvent == EVT_PenDown:
print('ev PenDown - ', touchedX, " : ", touchedY)
drawCross(touchedX, touchedY, WHITE)
if touchEvent == EVT_PenUp:
print('ev PenUp - ')
drawCross(touchedX, touchedY, RED)
if touchEvent == EVT_PenRept:
if (touchedX>=0 and touchedX<scrWidth
and touchedY>=0 and touchedY<scrHeight):
bitmap[touchedX, touchedY] = GREEN
touchEvent = EVT_NO
print('-bye -')
Remark about CircuitPython support for interrupt: