kopia lustrzana https://github.com/peterhinch/micropython-samples
294 wiersze
12 KiB
Python
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
|
|
|