Friday, April 16, 2021

Raspberry Pi Pico/CircuitPython + ILI9341 SPI Display with Touch

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

Install display library for ili9341:

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.py
from 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:

According to CircuitPython Frequently Asked Questions, CircuitPython does not currently support interrupts.



No comments:

Post a Comment

Subscribe to: Post Comments (Atom)

AltStyle によって変換されたページ (->オリジナル) /