micropython-samples/tft_gui/touch.py

294 wiersze
12 KiB
Python

#
# The MIT License (MIT)
#
# Copyright (c) 2016 Robert Hammelrath
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
#
# Class supporting the resisitve touchpad of TFT LC-displays
# First example: Controller XPT2046
# It uses Y5..Y8 of PyBoard
#
import pyb, stm
# define constants
#
PCB_VERSION = 2
#if PCB_VERSION == 1:
# CONTROL_PORT = stm.GPIOB
# T_CLOCK = const(1 << 15) ## Y8 = B15
# T_DOUT = const(1 << 14) ## Y7 = B14
# T_DIN = const(1 << 13) ## Y6 = B13
# T_IRQ = const(1 << 12) ## Y5 = B12
if PCB_VERSION == 2:
CONTROL_PORT = stm.GPIOC
T_CLOCK = const(1 << 5) ## X12 = C5
T_DOUT = const(1 << 4) ## X11 = C4
T_DIN = const(1 << 7) ## Y2 = C7
T_IRQ = const(1 << 6) ## Y1 = C6
# T_CS is not used and must be hard tied to GND
T_GETX = const(0xd0) ## 12 bit resolution
T_GETY = const(0x90) ## 12 bit resolution
T_GETZ1 = const(0xb8) ## 8 bit resolution
T_GETZ2 = const(0xc8) ## 8 bit resolution
#
X_LOW = const(10) ## lowest reasonable X value from the touchpad
Y_HIGH = const(4090) ## highest reasonable Y value
class TOUCH:
#
# Init just sets the PIN's to In / out as required
# objsched: scheduler if asynchronous operation intended
# confidence: confidence level - number of consecutive touches with a margin smaller than the given level
# which the function will sample until it accepts it as a valid touch
# margin: Difference from mean centre at which touches are considered at the same position
# delay: Delay between samples in ms. (n/a if asynchronous)
#
DEFAULT_CAL = (-3917, -0.127, -3923, -0.1267, -3799, -0.07572, -3738, -0.07814)
def __init__(self, controller = "XPT2046", objsched = None, *, confidence = 5, margin = 50, delay = 10, calibration = None):
if PCB_VERSION == 1:
self.pin_clock = pyb.Pin("Y8", pyb.Pin.OUT_PP)
self.pin_clock.value(0)
self.pin_d_out = pyb.Pin("Y7", pyb.Pin.OUT_PP)
self.pin_d_in = pyb.Pin("Y6", pyb.Pin.IN)
self.pin_irq = pyb.Pin("Y5", pyb.Pin.IN)
else:
self.pin_clock = pyb.Pin("X11", pyb.Pin.OUT_PP)
self.pin_clock.value(0)
self.pin_d_out = pyb.Pin("X12", pyb.Pin.OUT_PP)
self.pin_d_in = pyb.Pin("Y1", pyb.Pin.IN)
self.pin_irq = pyb.Pin("Y2", pyb.Pin.IN)
# set default values
self.ready = False
self.touched = False
self.x = 0
self.y = 0
self.buf_length = 0
cal = TOUCH.DEFAULT_CAL if calibration is None else calibration
self.asynchronous = False
self.touch_parameter(confidence, margin, delay, cal)
if objsched is not None:
self.asynchronous = True
objsched.add_thread(self._main_thread())
# set parameters for get_touch()
# res: Resolution in bits of the returned values, default = 10
# confidence: confidence level - number of consecutive touches with a margin smaller than the given level
# which the function will sample until it accepts it as a valid touch
# margin: Difference from mean centre at which touches are considered at the same position
# delay: Delay between samples in ms.
#
def touch_parameter(self, confidence = 5, margin = 50, delay = 10, calibration = None):
if not self.asynchronous: # Ignore attempts to change on the fly.
confidence = max(min(confidence, 25), 5)
if confidence != self.buf_length:
self.buff = [[0,0] for x in range(confidence)]
self.buf_length = confidence
self.delay = max(min(delay, 100), 5)
margin = max(min(margin, 100), 1)
self.margin = margin * margin # store the square value
if calibration:
self.calibration = calibration
# get_touch(): Synchronous use. get a touch value; Parameters:
#
# initital: Wait for a non-touch state before getting a sample.
# True = Initial wait for a non-touch state
# False = Do not wait for a release
# wait: Wait for a touch or not?
# False: Do not wait for a touch and return immediately
# True: Wait until a touch is pressed.
# raw: Setting whether raw touch coordinates (True) or normalized ones (False) are returned
# setting the calibration vector to (0, 1, 0, 1, 0, 1, 0, 1) result in a identity mapping
# timeout: Longest time (ms, or None = 1 hr) to wait for a touch or release
#
# Return (x,y) or None
#
def get_touch(self, initial = True, wait = True, raw = False, timeout = None):
if self.asynchronous:
return None # Should only be called in synhronous mode
if timeout == None:
timeout = 3600000 # set timeout to 1 hour
#
if initial: ## wait for a non-touch state
sample = True
while sample and timeout > 0:
sample = self.raw_touch()
pyb.delay(self.delay)
timeout -= self.delay
if timeout <= 0: # after timeout, return None
return None
#
buff = self.buff
buf_length = self.buf_length
buffptr = 0
nsamples = 0
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 <= self.margin: # got one; compare against the square value
if raw:
return (meanx, meany)
else:
return self.do_normalize((meanx, meany))
# get a new value
sample = self.raw_touch() # get a touch
if sample == None:
if not wait:
return None
nsamples = 0 # Invalidate buff
else:
buff[buffptr] = sample # put in buff
buffptr = (buffptr + 1) % buf_length
nsamples = min(nsamples +1, buf_length)
pyb.delay(self.delay)
timeout -= self.delay
return None
# Asynchronous use: this thread maintains self.x and self.y
def _main_thread(self):
buff = self.buff
buf_length = self.buf_length
buffptr = 0
nsamples = 0
yield # Initialisation complete, wait for scheduler to start
while True:
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 <= self.margin: # got one; compare against the square value
self.ready = True
self.x, self.y = self.do_normalize((meanx, meany))
sample = self.raw_touch() # get a touch
if sample == None:
self.touched = False
self.ready = False
nsamples = 0 # Invalidate buff
else:
self.touched = True
buff[buffptr] = sample # put in buff
buffptr = (buffptr + 1) % buf_length
nsamples = min(nsamples + 1, buf_length)
yield
# Asynchronous get_touch
def get_touch_async(self):
if self.ready:
self.ready = False
return self.x, self.y
return None
#
# do_normalize(touch)
# calculate the screen coordinates from the touch values, using the calibration values
# touch must be the tuple return by get_touch
#
def do_normalize(self, touch):
xmul = self.calibration[3] + (self.calibration[1] - self.calibration[3]) * (touch[1] / 4096)
xadd = self.calibration[2] + (self.calibration[0] - self.calibration[2]) * (touch[1] / 4096)
ymul = self.calibration[7] + (self.calibration[5] - self.calibration[7]) * (touch[0] / 4096)
yadd = self.calibration[6] + (self.calibration[4] - self.calibration[6]) * (touch[0] / 4096)
x = int((touch[0] + xadd) * xmul)
y = int((touch[1] + yadd) * ymul)
return (x, y)
#
# raw_touch(tuple)
# raw read touch. Returns (x,y) or None
#
def raw_touch(self):
global CONTROL_PORT
x = self.touch_talk(T_GETX, 12, CONTROL_PORT)
y = self.touch_talk(T_GETY, 12, CONTROL_PORT)
if x > X_LOW and y < Y_HIGH: # touch pressed?
return (x, y)
else:
return None
#
# Send a command to the touch controller and wait for the response
# cmd is the command byte
# int is the expected size of return data bits
# port is the gpio base port
#
# Straight down coding of the data sheet's timing diagram
# Clock low & high cycles must last at least 200ns, therefore
# additional delays are required. At the moment it is set to
# about 500ns each, 1µs total at 168 MHz clock rate.
# Total net time for a 12 bit sample: ~ 25 µs, 8 bit sample ~20 µs
#
@staticmethod
@micropython.viper
def touch_talk(cmd: int, bits: int, port: int) -> int:
gpio_bsr = ptr16(port + stm.GPIO_BSRRL)
gpio_idr = ptr16(port + stm.GPIO_IDR)
#
# now shift the command out, which is 8 bits
# data is sampled at the low-> high transient
#
gpio_bsr[1] = T_CLOCK # Empty clock cycle before start, maybe obsolete
for i in range(2): pass #delay
# gpio_bsr[0] = T_CLOCK # clock High
# for i in range(2): pass #delay
# gpio_bsr[1] = T_CLOCK # set clock low in the beginning
mask = 0x80 # high bit first
for i in range(8):
gpio_bsr[1] = T_CLOCK # set clock low in the beginning
if cmd & mask:
gpio_bsr[0] = T_DOUT # set data bit high
else:
gpio_bsr[1] = T_DOUT # set data bit low
for i in range(1): pass #delay
gpio_bsr[0] = T_CLOCK # set clock high
mask >>= 1
for i in range(0): pass #delay
gpio_bsr[1] = T_CLOCK | T_DOUT# Another clock & data, low
for i in range(2): pass #delay
gpio_bsr[0] = T_CLOCK # clock High
for i in range(0): pass #delay
#
# now shift the data in, which is 8 or 12 bits
# data is sampled after the high->low transient
#
result = 0
for i in range(bits):
gpio_bsr[1] = T_CLOCK # Clock low
for i in range(1): pass # short delay
if gpio_idr[0] & T_DIN: # get data
bit = 1
else:
bit = 0
result = (result << 1) | bit # shift data in
gpio_bsr[0] = T_CLOCK # Clock high
for i in range(1): pass # delay
#
# another clock cycle, maybe obsolete
#
gpio_bsr[1] = T_CLOCK # Another clock toggle, low
for i in range(2): pass # delay
gpio_bsr[0] = T_CLOCK # clock High
for i in range(2): pass #delay
gpio_bsr[1] = T_CLOCK # Clock low
# now we're ready to leave
return result