diff --git a/adafruit_stmpe610.py b/adafruit_stmpe610.py old mode 100644 new mode 100755 index 9862efa..d72eb89 --- a/adafruit_stmpe610.py +++ b/adafruit_stmpe610.py @@ -1,17 +1,27 @@ -# SPDX-FileCopyrightText: 2017 Jerry Needell for Adafruit Industries +# SPDX-FileCopyrightText: 2017, 2022 Jerry Needell for Adafruit Industries # # SPDX-License-Identifier: MIT - """ `adafruit_stmpe610` ==================================================== - This is a CircuitPython Driver for the STMPE610 Resistive Touch sensor -* Author(s): Jerry Needell -""" +* Author(s): Jerry Needell, CedarGroveMakerStudios + +Implementation Notes +-------------------- +**Hardware:** + +* 2.4" 320x240 TFT FeatherWing display: https://www.adafruit.com/product/3315 +* 3.5" 480x320 TFT FeatherWing display: https://www.adafruit.com/product/3651 +* Resistive Touch Screen Controller - STMPE610 breakout board: + https://www.adafruit.com/product/1571 (discontinued) -# imports +**Software and Dependencies:** + +* Adafruit CircuitPython firmware for the supported boards: + https://github.com/adafruit/circuitpython/releases +""" import time from micropython import const @@ -21,6 +31,29 @@ __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_STMPE610.git" +def map_range(x, in_min, in_max, out_min, out_max): + """ + Maps a value from one range to another. Values beyond the input minimum or + maximum will be limited to the minimum or maximum of the output range. + + :return: Returns value mapped to new range + :rtype: float + """ + in_range = in_max - in_min + in_delta = x - in_min + if in_range != 0: + mapped = in_delta / in_range + elif in_delta != 0: + mapped = in_delta + else: + mapped = 0.5 + mapped *= out_max - out_min + mapped += out_min + if out_min <= out_max: + return max(min(mapped, out_max), out_min) + return min(max(mapped, out_max), out_min) + + _STMPE_ADDR = const(0x41) _STMPE_VERSION = const(0x0811) @@ -111,12 +144,19 @@ class Adafruit_STMPE610: - """ - A driver for the STMPE610 Resistive Touch sensor. - """ + """A class (driver) for the STMPE610 Resistive Touch controller used by the + 2.4" 320x240 TFT FeatherWing display (#3315), 3.5" 480x320 TFT FeatherWing + display (#3651), and the Resistive Touch Screen Controller - STMPE610 + breakout board (#1571). This class acts as a super class for the I2C and + SPI interface classes. + + This class was modified from the original to add the Displayio Button + compatible touch_point property to the existing functionality. + + See the examples folder for instantiation kwargs and properties.""" def __init__(self): - """Reset the controller""" + """Reset the controller.""" self._write_register_byte(_STMPE_SYS_CTRL1, _STMPE_SYS_CTRL1_RESET) time.sleep(0.001) @@ -138,15 +178,15 @@ def __init__(self): self._write_register_byte(_STMPE_TSC_FRACTION_Z, 0x6) self._write_register_byte(_STMPE_FIFO_TH, 1) self._write_register_byte(_STMPE_FIFO_STA, _STMPE_FIFO_STA_RESET) - self._write_register_byte(_STMPE_FIFO_STA, 0) # unreset + self._write_register_byte(_STMPE_FIFO_STA, 0) # Unreset self._write_register_byte(_STMPE_TSC_I_DRIVE, _STMPE_TSC_I_DRIVE_50MA) - self._write_register_byte(_STMPE_INT_STA, 0xFF) # reset all ints + self._write_register_byte(_STMPE_INT_STA, 0xFF) # Reset all ints self._write_register_byte( _STMPE_INT_CTRL, _STMPE_INT_CTRL_POL_HIGH | _STMPE_INT_CTRL_ENABLE ) def read_data(self): - """Request next stored reading - return tuple containing (x,y,pressure) """ + """Request next stored reading - return tuple containing (x,y,pressure).""" d_1 = self._read_byte(0xD7) d_2 = self._read_byte(0xD7) d_3 = self._read_byte(0xD7) @@ -154,32 +194,30 @@ def read_data(self): x_loc = d_1 << 4 | d_2 >> 4 y_loc = (d_2 & 0xF) << 8 | d_3 pressure = d_4 - # reset all ints (not sure what this does) + # Reset all ints (not sure what this does) if self.buffer_empty: self._write_register_byte(_STMPE_INT_STA, 0xFF) return (x_loc, y_loc, pressure) def _read_byte(self, register): - """Read a byte register value and return it""" + """Read a byte register value and return it.""" return self._read_register(register, 1)[0] def _read_register(self, register, length): - # Read an arbitrarily long register (specified by length number of - # bytes) and return a bytearray of the retrieved data. - # Subclasses MUST implement this! + """Read an arbitrarily long register (specified by length number of + bytes) and return a bytearray of the retrieved data. + Subclasses MUST implement this!""" raise NotImplementedError def _write_register_byte(self, register, value): - # Write a single byte register at the specified register address. - # Subclasses MUST implement this! + """Write a single byte register at the specified register address. + Subclasses MUST implement this!""" raise NotImplementedError @property def touches(self): - """ - Returns a list of touchpoint dicts, with 'x' and 'y' containing the - touch coordinates, and 'pressure' - """ + """Returns a list of touchpoint dicts, with 'x' and 'y' containing the + touch coordinates, and 'pressure'.""" touchpoints = [] while (len(touchpoints) < 4) and not self.buffer_empty: (x_loc, y_loc, pressure) = self.read_data() @@ -189,7 +227,7 @@ def touches(self): @property def get_version(self): - "Read the version number from the sensosr" + """Read the version number from the sensor.""" v_1 = self._read_byte(0) v_2 = self._read_byte(1) version = v_1 << 8 | v_2 @@ -198,39 +236,84 @@ def get_version(self): @property def touched(self): - "Report if any touches have been detectd" + """Report if any touches were detected.""" touch = self._read_byte(_STMPE_TSC_CTRL) & 0x80 return touch == 0x80 @property def buffer_size(self): - "The amount of touch data in the buffer" + """The amount of touch data in the buffer.""" return self._read_byte(_STMPE_FIFO_SIZE) @property def buffer_empty(self): - "Buffer empty status" + """Buffer empty status.""" empty = self._read_byte(_STMPE_FIFO_STA) & _STMPE_FIFO_STA_EMPTY return empty != 0 @property def get_point(self): - "Read one touch from the buffer" + """Read one touch from the buffer.""" (x_loc, y_loc, pressure) = self.read_data() point = {"x": x_loc, "y": y_loc, "pressure": pressure} return point class Adafruit_STMPE610_I2C(Adafruit_STMPE610): - """ - I2C driver for the STMPE610 Resistive Touch sensor. + """I2C interface class for the STMPE610 Resistive Touch sensor. + + :param i2c: I2C interface bus + :param int address: I2C address. Defaults to 0x41 + :param None, (int, int) calibration: touchscreen calibration tuple. + Defaults to None. + :param None, (int, int) size: display size tuple (width, height). + Defaults to None. + :param int disp_rotation: display rotation in degrees. Values allowed are + 0, 90, 180, and 270. Defaults to 0. + :param (bool, bool) touch_flip: swap touchscreen axis range minimum and + maximum values for (x, y) axes as referenced to display 0-degree rotation. + Defaults to (False, False). + + ** Quickstart: Importing and instantiating Adafruit_STMPE610_I2C** + + Import the Adafruit_STMPE610_I2C class and instantiate for the 2.4" TFT Wing + after instantiating the display: + + .. code-block:: python + + import adafruit_stmpe610 + ts = adafruit_stmpe610.Adafruit_STMPE610_I2C(board.I2C(), address=0x41, + calibration=((357, 3812), (390, 3555)), + size=(display.width, display.height), disp_rotation=display.rotation, + touch_flip=(False, False)) + """ - def __init__(self, i2c, address=_STMPE_ADDR): - """ - Check the STMPE610 was founnd - Default address is 0x41 but another address can be passed in as an argument - """ + def __init__( # pylint: disable=too-many-arguments + self, + i2c, + address=_STMPE_ADDR, + calibration=None, + size=None, + disp_rotation=0, + touch_flip=(False, False), + ): + + self._calib = calibration + self._disp_size = size + self._disp_rotation = disp_rotation + self._touch_flip = touch_flip + + # Check the touchscreen calibration and display size kwargs. + if not self._calib: + self._calib = ((0, 4095), (0, 4095)) + if not self._disp_size: + self._disp_size = (4095, 4095) + + if self._disp_rotation not in (0, 90, 180, 270): + raise ValueError("Display rotation value must be 0, 90, 180, or 270") + + # Check that the STMPE610 was found. import adafruit_bus_device.i2c_device as i2cdev # pylint: disable=import-outside-toplevel self._i2c = i2cdev.I2CDevice(i2c, address) @@ -240,8 +323,53 @@ def __init__(self, i2c, address=_STMPE_ADDR): raise RuntimeError("Failed to find STMPE610! Chip Version 0x%x" % version) super().__init__() + @property + def touch_point(self): # pylint: disable=too-many-branches + """Read latest touched point value and convert to calibration-adjusted + and rotated display coordinates. Commpatible with Displayio Button. + :return: x, y, pressure + rtype: int, int, int + """ + if not self.buffer_empty: + while not self.buffer_empty: + x_loc, y_loc, pressure = self.read_data() + # Swap touch axis range minimum and maximum if needed + if self._disp_rotation in (0, 180): + if self._touch_flip and self._touch_flip[0]: + x_c = (self._calib[0][1], self._calib[0][0]) + else: + x_c = (self._calib[0][0], self._calib[0][1]) + if self._touch_flip and self._touch_flip[1]: + y_c = (self._calib[1][1], self._calib[1][0]) + else: + y_c = (self._calib[1][0], self._calib[1][1]) + if self._disp_rotation in (90, 270): + if self._touch_flip[1]: + x_c = (self._calib[1][1], self._calib[1][0]) + else: + x_c = (self._calib[1][0], self._calib[1][1]) + if self._touch_flip[0]: + y_c = (self._calib[0][1], self._calib[0][0]) + else: + y_c = (self._calib[0][0], self._calib[0][1]) + # Adjust to calibration range; convert to display size and rotation + if self._disp_rotation == 0: + x = int(map_range(y_loc, x_c[0], x_c[1], 0, self._disp_size[0])) + y = int(map_range(x_loc, y_c[0], y_c[1], 0, self._disp_size[1])) + elif self._disp_rotation == 90: + x = int(map_range(x_loc, x_c[0], x_c[1], 0, self._disp_size[0])) + y = int(map_range(y_loc, y_c[0], y_c[1], self._disp_size[1], 0)) + elif self._disp_rotation == 180: + x = int(map_range(y_loc, x_c[0], x_c[1], self._disp_size[0], 0)) + y = int(map_range(x_loc, y_c[0], y_c[1], self._disp_size[1], 0)) + elif self._disp_rotation == 270: + x = int(map_range(x_loc, x_c[0], x_c[1], self._disp_size[0], 0)) + y = int(map_range(y_loc, y_c[0], y_c[1], 0, self._disp_size[1])) + return (x, y, pressure) + return None + def _read_register(self, register, length): - """Low level register reading over I2C, returns a list of values""" + """Low level register reading over I2C, returns a list of values.""" with self._i2c as i2c: i2c.write(bytearray([register & 0xFF])) result = bytearray(length) @@ -250,54 +378,149 @@ def _read_register(self, register, length): return result def _write_register_byte(self, register, value): - """Low level register writing over I2C, writes one 8-bit value""" + """Low level register writing over I2C, writes one 8-bit value.""" with self._i2c as i2c: i2c.write(bytes([register & 0xFF, value & 0xFF])) # print("$%02X <= 0x%02X" % (register, value)) class Adafruit_STMPE610_SPI(Adafruit_STMPE610): - """ - SPI driver for the STMPE610 Resistive Touch sensor. + """SPI interface class for the STMPE610 Resistive Touch sensor. + + :param spi: SPI interface bus + :param pin cs: touchscreen SPI interface chip select pin + :param int baudrate: SPI interface clock speed in Hz. + Defaults to 1000000 (1MHz). + :param None, (int, int) calibration: touchscreen calibration tuple. + Defaults to None. + :param None, (int, int) size: display size tuple (width, height). + Defaults to None. + :param int disp_rotation: display rotation in degrees. Values allowed are + 0, 90, 180, and 270. Defaults to 0. + :param (bool, bool) touch_flip: swap touchscreen axis range minimum and + maximum values for (x, y) axes as referenced to display 0-degree rotation. + Defaults to (False, False). + + ** Quickstart: Importing and instantiating Adafruit_STMPE610_I2C** + + Import the Adafruit_STMPE610_SPI class and instantiate for the 2.4" TFT Wing + after instantiating the display: + + .. code-block:: python + + import adafruit_stmpe610 + ts = adafruit_stmpe610.Adafruit_STMPE610_SPI(spi, cs=cs_pin, + baudrate=1000000, + calibration=((357, 3812), (390, 3555)), + size=(display.width, display.height), disp_rotation=display.rotation, + touch_flip=(False, False)) + """ - def __init__(self, spi, cs, baudrate=1000000): - """ - Check the STMPE610 was found,Default clock rate 1000000 - can be changed with 'baudrate' - """ + def __init__( # pylint: disable=too-many-arguments + self, + spi, + cs, + baudrate=1000000, + calibration=None, + size=None, + disp_rotation=0, + touch_flip=(False, False), + ): + + self._calib = calibration + self._disp_size = size + self._disp_rotation = disp_rotation + self._touch_flip = touch_flip + + # Check the touchscreen calibration and display size kwargs. + if not self._calib: + self._calib = ((0, 4095), (0, 4095)) + if not self._disp_size: + self._disp_size = (4095, 4095) + + if self._disp_rotation not in (0, 90, 180, 270): + raise ValueError("Display rotation value must be 0, 90, 180, or 270") + + # Check that the STMPE610 was found. import adafruit_bus_device.spi_device as spidev # pylint: disable=import-outside-toplevel self._spi = spidev.SPIDevice(spi, cs, baudrate=baudrate) # Check device version. version = self.get_version if _STMPE_VERSION != version: - # if it fails try SPI MODE 1 -- that is what Arduino does + # If it fails try SPI MODE 1 -- that is what Arduino does self._spi = spidev.SPIDevice( spi, cs, baudrate=baudrate, polarity=0, phase=1 ) version = self.get_version if _STMPE_VERSION != version: raise RuntimeError( - "Failed to find STMPE610! Chip Version 0x%x. " + "Failed to find STMPE610 controller! Chip Version 0x%x. " "If you are using the breakout, verify you are in SPI mode." % version ) super().__init__() + @property + def touch_point(self): # pylint: disable=too-many-branches + """Read latest touched point value and convert to calibration-adjusted + and rotated display coordinates. Commpatible with Displayio Button. + :return: x, y, pressure + rtype: int, int, int + """ + if not self.buffer_empty: + while not self.buffer_empty: + x_loc, y_loc, pressure = self.read_data() + # Swap touch axis range minimum and maximum if needed + if self._disp_rotation in (0, 180): + if self._touch_flip and self._touch_flip[0]: + x_c = (self._calib[0][1], self._calib[0][0]) + else: + x_c = (self._calib[0][0], self._calib[0][1]) + if self._touch_flip and self._touch_flip[1]: + y_c = (self._calib[1][1], self._calib[1][0]) + else: + y_c = (self._calib[1][0], self._calib[1][1]) + if self._disp_rotation in (90, 270): + if self._touch_flip[1]: + x_c = (self._calib[1][1], self._calib[1][0]) + else: + x_c = (self._calib[1][0], self._calib[1][1]) + if self._touch_flip[0]: + y_c = (self._calib[0][1], self._calib[0][0]) + else: + y_c = (self._calib[0][0], self._calib[0][1]) + # Adjust to calibration range; convert to display size and rotation + if self._disp_rotation == 0: + x = int(map_range(y_loc, x_c[0], x_c[1], 0, self._disp_size[0])) + y = int(map_range(x_loc, y_c[0], y_c[1], 0, self._disp_size[1])) + elif self._disp_rotation == 90: + x = int(map_range(x_loc, x_c[0], x_c[1], 0, self._disp_size[0])) + y = int(map_range(y_loc, y_c[0], y_c[1], self._disp_size[1], 0)) + elif self._disp_rotation == 180: + x = int(map_range(y_loc, x_c[0], x_c[1], self._disp_size[0], 0)) + y = int(map_range(x_loc, y_c[0], y_c[1], self._disp_size[1], 0)) + elif self._disp_rotation == 270: + x = int(map_range(x_loc, x_c[0], x_c[1], self._disp_size[0], 0)) + y = int(map_range(y_loc, y_c[0], y_c[1], 0, self._disp_size[1])) + return (x, y, pressure) + return None + # pylint: disable=no-member # Disable should be reconsidered when refactor can be tested. def _read_register(self, register, length): - """Low level register reading over SPI, returns a list of values""" - register = (register | 0x80) & 0xFF # Read single, bit 7 high. + """Low level register reading over SPI, returns a list of values.""" + register = (register | 0x80) & 0xFF # Read single byte, bit 7 high. with self._spi as spi: spi.write(bytearray([register])) result = bytearray(length) spi.readinto(result) - # print("$%02X => %s" % (register, [hex(i) for i in result])) + # print("$%02X => %s" % (register, [hex(i) for i in result])) return result def _write_register_byte(self, register, value): - """Low level register writing over SPI, writes one 8-bit value""" + """Low level register writing over SPI, writes one 8-bit value.""" register &= 0x7F # Write, bit 7 low. with self._spi as spi: spi.write(bytes([register, value & 0xFF])) diff --git a/examples/stmpe610_button_demo.py b/examples/stmpe610_button_demo.py new file mode 100755 index 0000000..db3c71c --- /dev/null +++ b/examples/stmpe610_button_demo.py @@ -0,0 +1,94 @@ +# SPDX-FileCopyrightText: 2022 ladyada for Adafruit Industries +# SPDX-License-Identifier: MIT +""" +Simple button demonstration/example. +STMPE610 touch controller with TFT FeatherWing Display + +Author(s): ladyada, CedarGroveMakerStudios + +""" + +import time +import board +import digitalio +import displayio +import terminalio + +# from adafruit_hx8357 import HX8357 +from adafruit_ili9341 import ILI9341 +from adafruit_button import Button +import adafruit_stmpe610 + +# --| Button Config |------------------------------------------------- +BUTTON_X = 50 +BUTTON_Y = 50 +BUTTON_WIDTH = 100 +BUTTON_HEIGHT = 50 +BUTTON_STYLE = Button.ROUNDRECT +BUTTON_FILL_COLOR = 0x00FFFF +BUTTON_OUTLINE_COLOR = 0xFF00FF +BUTTON_LABEL = "HELLO WORLD" +BUTTON_LABEL_COLOR = 0x000000 +# --| Button Config |------------------------------------------------- + +# Release any resources currently in use for the displays +displayio.release_displays() +disp_bus = displayio.FourWire( + board.SPI(), command=board.D10, chip_select=board.D9, reset=None +) + +# Instantiate the 2.4" 320x240 TFT FeatherWing (#3315). +display = ILI9341(disp_bus, width=320, height=240) +_touch_flip = (False, False) + +"""# Instantiate the 3.5" 480x320 TFT FeatherWing (#3651). +display = HX8357(disp_bus, width=480, height=320) +_touch_flip = (False, True)""" + +# Always set rotation before instantiating the touchscreen +display.rotation = 0 + +# Instantiate touchscreen +ts_cs = digitalio.DigitalInOut(board.D6) +ts = adafruit_stmpe610.Adafruit_STMPE610_SPI( + board.SPI(), + ts_cs, + calibration=((357, 3812), (390, 3555)), + size=(display.width, display.height), + disp_rotation=display.rotation, + touch_flip=_touch_flip, +) + +# Create the displayio group and show it +splash = displayio.Group() +display.show(splash) + +# Defiine the button +button = Button( + x=BUTTON_X, + y=BUTTON_Y, + width=BUTTON_WIDTH, + height=BUTTON_HEIGHT, + style=BUTTON_STYLE, + fill_color=BUTTON_FILL_COLOR, + outline_color=BUTTON_OUTLINE_COLOR, + label=BUTTON_LABEL, + label_font=terminalio.FONT, + label_color=BUTTON_LABEL_COLOR, +) + +# Add button to the displayio group +splash.append(button) + +# Loop and look for touches +while True: + p = ts.touch_point + if p: + if button.contains(p): + button.selected = True + # Perform a task related to the button press here + time.sleep(0.25) # Wait a bit so we can see the button color change + else: + button.selected = False # When touch moves outside of button + else: + button.selected = False # When button is released diff --git a/examples/stmpe610_touch_point_paint_demo.py b/examples/stmpe610_touch_point_paint_demo.py new file mode 100755 index 0000000..aadf017 --- /dev/null +++ b/examples/stmpe610_touch_point_paint_demo.py @@ -0,0 +1,43 @@ +# SPDX-FileCopyrightText: 2022 CedarGroveMakerStudios for Adafruit Industries +# SPDX-License-Identifier: MIT + +""" +Simple painting demo that draws on an Adafruit 2.4" TFT FeatherWing +display (#3315) with the STMPE610 resistive touch controller. +""" + +import board +import digitalio +import displayio +from adafruit_rgb_display import ili9341, color565 +import adafruit_stmpe610 + +# Release any resources currently in use for the display +displayio.release_displays() + +# Instantiate the 2.4" 320x240 TFT FeatherWing Display(#3315). +cs_pin = digitalio.DigitalInOut(board.D9) +dc_pin = digitalio.DigitalInOut(board.D10) +display = ili9341.ILI9341(board.SPI(), cs=cs_pin, dc=dc_pin) + +# Fill the screen with black! +display.fill(color565(0, 0, 0)) + +# Instantiate the touchpad +ts_cs_pin = digitalio.DigitalInOut(board.D6) +ts = adafruit_stmpe610.Adafruit_STMPE610_SPI( + board.SPI(), + ts_cs_pin, + calibration=((350, 3500), (350, 3500)), + size=(display.width, display.height), + disp_rotation=90, + touch_flip=(True, True), +) + +while True: + point = ts.touch_point + if point: + # Display the touched point + x = point[0] + y = point[1] + display.fill_rectangle(x - 2, y - 2, 4, 4, color565(255, 0, 0)) diff --git a/examples/stmpe610_touch_point_simpletest.py b/examples/stmpe610_touch_point_simpletest.py new file mode 100755 index 0000000..d7c88d0 --- /dev/null +++ b/examples/stmpe610_touch_point_simpletest.py @@ -0,0 +1,21 @@ +# SPDX-FileCopyrightText: 2022 CedarGroveMakerStudios for Adafruit Industries +# SPDX-License-Identifier: MIT + +""" +Simple print-to-REPL demo using the STMPE610 resistive touch controller. +""" + +import board +import digitalio +import adafruit_stmpe610 + +# Instantiate the touchpad +ts_cs_pin = digitalio.DigitalInOut(board.D6) +ts = adafruit_stmpe610.Adafruit_STMPE610_SPI(board.SPI(), ts_cs_pin) + +print("Go Ahead - Touch the Screen - Make My Day!") +print("(x, y, pressure)") +while True: + point = ts.touch_point + if point: + print(point) diff --git a/examples/touch_calibrator_stmpe610.py b/examples/touch_calibrator_stmpe610.py new file mode 100755 index 0000000..1377bb6 --- /dev/null +++ b/examples/touch_calibrator_stmpe610.py @@ -0,0 +1,203 @@ +# SPDX-FileCopyrightText: 2022 CedarGroveMakerStudios for Adafruit Industries +# SPDX-License-Identifier: MIT + +""" +touch_calibrator_stmpe610.py 2022-01-21 v1.1 + +Author(s): CedarGroveMakerStudios + +On-screen touchscreen calibrator for TFT FeatherWing displays. + +When the test screen appears, use a stylus to swipe to the four edges +of the visible display area. As the screen is calibrated, the small red +square tracks the stylus tip (REPL_ONLY=False). Minimum and maximum +calibration values will display on the screen and in the REPL. The calibration +tuple can be copied and pasted into the calling code's touchscreen +instantiation statement. + +NOTE: When instantiating the STMPE610 controller, enter the 0-degree display +rotation raw touch calibration value regardless of screen rotation value. +The controller code will automatically adjust the calibration as needed. + +DISPLAY_ROTATION: Display rotation value in degrees. Only values of +None, 0, 90, 180, and 270 degrees are accepted. Defaults to None, the +previous orientation of the display. + +REPL_ONLY: If False, calibration values are shown graphically on the screen +and printed to the REPL. If True, the values are only printed to the REPL. +Default value is False. + +RAW_DATA: If True, measure and display the raw touchscreen values. If False, +display the touch value in screen coordinates; requires a previously measured +calibration tuple for screen coordinate conversion accuracy. +""" + +import time +import board +import digitalio +import displayio +import vectorio +import terminalio +from adafruit_display_text.label import Label + +# from adafruit_hx8357 import HX8357 +from adafruit_ili9341 import ILI9341 +from simpleio import map_range +import adafruit_stmpe610 + +# Operational parameters: +# Specify 0, 90, 180, or 270 degrees; +# use 0 for instantiation calibration tuple. +DISPLAY_ROTATION = 0 +REPL_ONLY = False # True to disable graphics +RAW_DATA = True # Use touchscreen raw values; False to use display coordinates + +# Previously measured raw calibration tuple for +# display coordinate mode (RAW_DATA = False): +CALIBRATION = ((357, 3812), (390, 3555)) + +# A collection of colors used for graphic objects +BLUE_DK = 0x000060 # Screen fill +RED = 0xFF0000 # Boundary +WHITE = 0xFFFFFF # Text + +# Release any resources currently in use for the displays +displayio.release_displays() + +# Define the display's SPI bus connection +disp_bus = displayio.FourWire( + board.SPI(), command=board.D10, chip_select=board.D9, reset=None +) + +# Instantiate the 2.4" 320x240 TFT FeatherWing (#3315). +display = ILI9341(disp_bus, width=320, height=240) +_touch_flip = (False, False) + +"""# Instantiate the 3.5" 480x320 TFT FeatherWing (#3651). +display = HX8357(disp_bus, width=480, height=320) +_touch_flip = (False, True)""" + +# Check rotation value and update display. +# Always set rotation before instantiating the touchscreen. +if DISPLAY_ROTATION is not None and DISPLAY_ROTATION in (0, 90, 180, 270): + display.rotation = DISPLAY_ROTATION +else: + print("Warning: invalid rotation value -- defalting to zero") + display.rotation = 0 + time.sleep(1) + +# Activate the display graphics unless REPL_ONLY=True. +if not REPL_ONLY: + display_group = displayio.Group() + display.show(display_group) + +# Instantiate touchscreen. +ts_cs = digitalio.DigitalInOut(board.D6) +if RAW_DATA: + # Display raw touchscreen values; calibration tuple not required. + ts = adafruit_stmpe610.Adafruit_STMPE610_SPI( + board.SPI(), ts_cs, disp_rotation=display.rotation, touch_flip=_touch_flip + ) +else: + # Display calibrated screen coordinates. + # Update the raw calibration tuple with previously measured values. + ts = adafruit_stmpe610.Adafruit_STMPE610_SPI( + board.SPI(), + ts_cs, + calibration=CALIBRATION, + size=(display.width, display.height), + disp_rotation=display.rotation, + touch_flip=_touch_flip, + ) + +# Define the graphic objects if REPL_ONLY = False. +if not REPL_ONLY: + # Define the text graphic objects + font_0 = terminalio.FONT + + coordinates = Label( + font=font_0, + text="calib: ((x_min, x_max), (y_min, y_max))", + color=WHITE, + ) + coordinates.anchor_point = (0.5, 0.5) + coordinates.anchored_position = (display.width // 2, display.height // 4) + + display_rotation = Label( + font=font_0, + text="rotation: " + str(display.rotation), + color=WHITE, + ) + display_rotation.anchor_point = (0.5, 0.5) + display_rotation.anchored_position = (display.width // 2, display.height // 4 - 30) + + # Define graphic objects for the screen fill, boundary, and touch pen. + target_palette = displayio.Palette(1) + target_palette[0] = BLUE_DK + screen_fill = vectorio.Rectangle( + pixel_shader=target_palette, + x=2, + y=2, + width=display.width - 4, + height=display.height - 4, + ) + + target_palette = displayio.Palette(1) + target_palette[0] = RED + boundary = vectorio.Rectangle( + pixel_shader=target_palette, + x=0, + y=0, + width=display.width, + height=display.height, + ) + + pen = vectorio.Rectangle( + pixel_shader=target_palette, + x=display.width // 2, + y=display.height // 2, + width=10, + height=10, + ) + + display_group.append(boundary) + display_group.append(screen_fill) + display_group.append(pen) + display_group.append(coordinates) + display_group.append(display_rotation) + +# Reset x and y values to raw or display size mid-point before measurement. +x = y = 0 +if RAW_DATA: + x_min = y_min = x_max = y_max = 4096 // 2 +else: + x_min = y_min = x_max = y_max = min(display.width, display.height) // 2 + +print("Touchscreen Calibrator") +print(" Use a stylus to swipe slightly beyond the") +print(" four edges of the visible display area.") +print(" ") +print(f" display rotation: {display.rotation} degrees") +print(" Calibration values follow:") +print(" ") + +while True: + time.sleep(0.100) + touch = ts.touch_point # Check for touch + if touch: + x = touch[0] # Raw touchscreen x value + y = touch[1] # Raw touchscreen y value + if not REPL_ONLY: + pen.x = int(round(map_range(x, x_min, x_max, 0, display.width), 0)) - 5 + pen.y = int(round(map_range(y, y_min, y_max, 0, display.height), 0)) - 5 + + # Remember minimum and maximum values for the calibration tuple. + x_min = min(x_min, touch[0]) + x_max = max(x_max, touch[0]) + y_min = min(y_min, touch[1]) + y_max = max(y_max, touch[1]) + + # Show the calibration tuple. + print(f"(({x_min}, {x_max}), ({y_min}, {y_max}))") + if not REPL_ONLY: + coordinates.text = f"calib: (({x_min}, {x_max}), ({y_min}, {y_max}))"