Repository navigation
Touch Support
Touch support in TinyGPU is split into three independent layers, each usable on its own:
-
TouchDriver- a small abstract interface (begin()/isTouched()/getPoint()) that every concrete touch controller implements. This is the only layer you need if you just want raw touch points. -
Calibration & coordinate mapping - built into the
TouchDriverbase class itself: raw controller coordinates are turned into logical display-spacePoints (inversion, per-axis scaling, axis swap, rotation), so application code never deals with raw ADC/register values. -
GestureDetector- an optional layer on top that turns a stream of raw touch samples from anyTouchDriverinto higher-level events (tap, double-tap, long-press, swipe, drag/pan/scroll, pinch/rotate).
#include <TinyGPU.h>
#include <TinyGPU/Input/TouchDriverArduino.h>
using namespace tinygpu;
TouchDriverFT6236 touch(Wire, /*rstPin=*/-1, /*irqPin=*/-1);
void setup() {
Wire.begin();
touch.begin();
CalibrationData cal;
cal.screenWidth = 240;
cal.screenHeight = 320;
touch.setCalibration(cal);
touch.setRotation(Rotation::Deg90);
}
void loop() {
if (touch.isTouched()) {
Point p;
if (touch.getPoint(p)) {
// p.x / p.y are already in logical display space
}
}
}| Header | Contents | Dependencies |
|---|---|---|
TouchDriver.h |
Picks the right implementation automatically: TouchDriverArduino.h under ARDUINO, TouchDriverSDL.h when <SDL.h> is available, otherwise TouchDriverCommon.h
|
- |
TouchDriverCommon.h |
The actual platform-independent pieces: Point, CalibrationData, Rotation, and the TouchDriver base class (calibration storage + raw-to-logical coordinate mapping) |
None - no Arduino.h, no bus library. Safe to include transitively (e.g. via TinyGPU.h) without pulling in an Arduino core |
TouchDriverArduino.h |
Concrete controller drivers: TouchDriverXPT2046, TouchDriverFT6236/TouchDriverFT6206, TouchDriverCST816S, TouchDriverGT911, TouchDriverBitBang
|
Arduino API (pinMode/digitalWrite/SPIClass/TwoWire) via TinyGPU/Emulation.h - include this file explicitly to use any of these drivers |
TouchDriverSDL.h |
TouchDriverSDL - maps the desktop mouse to a TouchDriver
|
SDL2 |
GestureDetector.h |
GestureDetector - gesture recognition on top of any TouchDriver
|
None beyond TouchDriver.h
|
TouchDriver.h (not TouchDriverArduino.h/TouchDriverSDL.h directly) is what TinyGPU.h itself includes, so a plain #include <TinyGPU.h> already gets you the right base types for whatever platform you're building for - include TouchDriverArduino.h explicitly only when you need one of its concrete controller classes.
class TouchDriver {
public:
virtual bool begin() = 0;
virtual bool isTouched() = 0;
virtual bool getPoint(Point& outPoint) = 0;
virtual bool getSecondPoint(Point& outPoint) { return false; }
void setRotation(Rotation rotation);
Rotation getRotation() const;
bool setCalibration(const CalibrationData& cal);
const CalibrationData& getCalibration() const;
bool hasCalibration() const;
};-
begin()never initializes the SPI/I2C bus itself - the application is responsible forWire.begin()/SPI.begin()and any board-specific bus pin configuration, the same division of responsibilityDisplayDriveruses. -
isTouched()is the once-per-loop-iteration entry point. For controllers without an IRQ pin it may perform an actual controller read; forTouchDriverGT911specifically, it's where the chip's status register gets read and cleared - see that driver's notes below for why calling it more than once per loop iteration is unsafe. -
getPoint()returnsfalseif no valid touch point is currently available - always check its return value, don't assume a precedingisTouched()==trueguarantees a point (GT911's cached-result design is the exception that makes this matter in practice). -
getSecondPoint()defaults tofalse(single-touch). OnlyTouchDriverGT911overrides it with a genuine second simultaneous contact - see the table below.
struct Point {
int16_t x = 0;
int16_t y = 0;
uint16_t pressure = 0;
};pressure semantics vary by controller: TouchDriverXPT2046 reports an estimated Z/pressure-related value (0..4095, clamped to 12 bits) computed from Z1 + 4095 - Z2, useful for thresholding but not a calibrated physical pressure. Every capacitive controller (FT6236, CST816S, GT911) just reports 255 while touched - "touched," not a real pressure reading.
TouchDriver::mapCoordinates() (called internally by every concrete driver's getPoint()) turns raw controller coordinates into logical display-space coordinates, in this fixed order:
raw coordinates -> inversion -> independent X/Y calibration -> swap XY -> display rotation
That order matters - in particular, swapping raw X/Y before calibration would incorrectly apply the X calibration range to Y and vice versa, which is why swap happens after.
struct CalibrationData {
int16_t rawXMin = 300, rawXMax = 3800; // raw controller coordinate ranges
int16_t rawYMin = 300, rawYMax = 3800;
uint16_t screenWidth = 240, screenHeight = 320; // unrotated display dimensions
bool invertX = false, invertY = false;
bool swapXY = false; // applied AFTER raw X/Y calibration
};Call setCalibration() with your panel's real raw ranges and screen size - the defaults above are just placeholders and setCalibration() returns false (rejecting the call) if rawXMin >= rawXMax, rawYMin >= rawYMax, or either screen dimension is 0. Until setCalibration() has been called successfully, mapCoordinates() passes raw coordinates straight through (still applying rotation) rather than silently guessing a calibration.
setRotation(Rotation) (Deg0/Deg90/Deg180/Deg270) rotates the already-calibrated point to match the display's current orientation - keep this in sync with whatever rotation you set on the matching DisplayDriver (see DisplayDrivers.md), they are two independent settings that both need to agree for touch coordinates to line up with what's on screen.
| Class | Controller | Bus | Touch type | Notes |
|---|---|---|---|---|
TouchDriverXPT2046 |
XPT2046 | SPI | Resistive, single-touch | Z1/Z2-based pressure threshold (setZThreshold()/getZThreshold(), default 400) gates whether a read counts as "touched"; discards the first ADC conversion per axis for stability |
TouchDriverFT6236 |
FT6236/FT6336 | I2C (addr 0x38) |
Capacitive, single-touch |
TouchDriverFT6206 is a drop-in alias (register-compatible part) |
TouchDriverCST816S |
CST816S | I2C (addr 0x15) |
Capacitive, single-touch | |
TouchDriverGT911 |
GT911 | I2C (addr 0x5D, alt 0x14) |
Capacitive, up to 5 touches (2 cached) | The only built-in driver where getSecondPoint() returns a real second contact - see below |
TouchDriverBitBang |
Any 4-wire resistive panel (X-/X+/Y-/Y+ wired straight to GPIO) | Bit-banged GPIO (pinMode/digitalWrite/digitalRead/analogRead) |
Resistive, single-touch | No controller chip at all - for panels whose resistive layer pins go straight to MCU pins. Requires analogRead(); see platform note below |
#include <TinyGPU/Input/TouchDriverArduino.h>
using namespace tinygpu;
TouchDriverXPT2046 touch(SPI, /*csPin=*/17, /*irqPin=*/16);
TouchDriverFT6236 touch(Wire, /*rstPin=*/-1, /*irqPin=*/-1);
TouchDriverCST816S touch(Wire, /*rstPin=*/-1, /*irqPin=*/13);
TouchDriverGT911 touch(Wire, /*rstPin=*/-1, /*irqPin=*/-1);
TouchDriverBitBang touch(/*xPlusPin=*/32, /*xMinusPin=*/33, /*yPlusPin=*/25, /*yMinusPin=*/26);GT911's status register (bit 7 = buffer ready, bits [3:0] = touch point count) must be cleared unconditionally on every isTouched() call, not only when a touch was actually found - clearing it only after a detected touch is a deadlock, since a stale, unacknowledged flag prevents the chip from ever reporting a new one. Because of that, isTouched() does the chip's real read/clear I/O and caches whatever point data was present; getPoint()/getSecondPoint() just return the cached result rather than re-querying the chip. This is the same "call isTouched() exactly once per loop iteration" contract every other driver in this file already expects, but on TouchDriverGT911 violating it (calling isTouched() more than once between getPoint() calls) will actively consume/clear a pending touch before getPoint() ever sees it, where on the other drivers it's merely redundant work.
For 4-wire resistive panels wired directly to GPIO instead of through a controller chip:
-
Touch presence (
isTouched()) is a cheap digital check: Y+ configuredINPUT_PULLUP, X- drivenLOW. Untouched, Y+'s internal pull-up holds it high; a touch shorts the two resistive layers together, pulling Y+ low. -
Position (
getPoint()) drives one axis's plates to create a voltage gradient, floats the other axis, andanalogRead()s the resulting position - the classic resistive-touchscreen measurement technique (the same one used by, e.g., Adafruit'sTouchScreenlibrary), discarding the first ADC conversion per axis for stability, matchingTouchDriverXPT2046's own approach. - Pressure is always reported as
255while touched (no calibrated Z measurement, unlike XPT2046) - this driver only needs to know whether the panel is touched at all. -
Platform note: requires a real Arduino core's
analogRead(). The ESP-IDF-nativeTinyGPU/Emulationfallback (EmulationIDF.h, used when building against plain ESP-IDF without thearduino-esp32component) does not emulateanalogRead()/ADC, so this driver won't compile against that fallback - it will against a real Arduino core (arduino-esp32, AVR, ...) on any board with the four GPIOs to spare.
TouchDriverSDL maps the desktop mouse to a TouchDriver for exercising touch-driven UI (buttons, sliders, gestures) on the SDL desktop backend without any physical touch hardware - pairs naturally with DisplayDriverSDL for a fully hardware-free UI test loop.
- The left mouse button stands in for a finger: button down = touched, cursor position = touch point.
- Like the other drivers,
isTouched()doesn't implicitly loop - it's where the SDL event queue gets drained (SDL_PollEvent), so call it exactly once perloop()iteration.SDL_QUIT(closing the window) exits the process, since there's no hardware equivalent to hand back to the sketch.
#include <TinyGPU/Input/TouchDriverSDL.h>
using namespace tinygpu;
TouchDriverSDL touch;TouchDriver.h picks TouchDriverSDL.h automatically (over TouchDriverArduino.h) whenever <SDL.h> is available and ARDUINO isn't defined - a desktop CMake build of this library's own examples gets this for free without needing to name the header explicitly.
GestureDetector sits on top of any TouchDriver (it doesn't replace it - feed it the same driver you'd otherwise poll directly) and turns raw touch samples into a single event stream:
#include <TinyGPU/Input/GestureDetector.h>
using namespace tinygpu;
GestureDetector gestures;
void handleGesture(GestureEvent& ev) {
switch (ev.type) {
case GestureType::kTap: /* ... */ break;
case GestureType::kSwipeLeft: /* ... */ break;
default: break;
}
}
void setup() {
gestures.onGesture = handleGesture;
gestures.isDraggable = [](int16_t x, int16_t y) { return isOverDraggableWidget(x, y); }; // optional
}
void loop() {
gestures.update(touch); // same TouchDriver instance you'd call isTouched()/getPoint() on
}GestureType |
Kind | Trigger |
|---|---|---|
kTap |
Discrete | Touch released within tapMaxDurationMs (default 300ms) and tapMaxMovePx (default 12px) of where it started |
kDoubleTap |
Discrete | A kTap following a previous one within doubleTapMaxGapMs (default 350ms) |
kLongPress |
Discrete | Held past longPressMinDurationMs (default 600ms) without enough movement to start a drag |
kSwipeLeft/kSwipeRight/kSwipeUp/kSwipeDown
|
Discrete | A drag/pan/scroll released within swipeMaxDurationMs (default 500ms) having covered at least swipeMinDistancePx (default 40px); direction is whichever axis moved further. Reported in addition to the drag/pan/scroll's own kEnded event, so swipe-only callers don't need to track distance/duration themselves |
kDrag / kPan / kScroll
|
Continuous (kBegan/kChanged/kEnded) |
Movement past dragStartThresholdPx (default 8px). Reported as kDrag if isDraggable is set and returns true for the touch's starting position; otherwise kScroll if vertical movement dominates, else kPan
|
kPinchIn/kPinchOut
|
Continuous (kChanged only) |
Two-finger distance changing by more than 0.5px since the last sample - requires a genuine second touch point |
kRotate |
Continuous (kChanged only) |
Two-finger angle changing by more than 1 degree since the last sample - requires a genuine second touch point |
All tuning thresholds above are public fields on GestureDetector (tapMaxDurationMs, tapMaxMovePx, doubleTapMaxGapMs, longPressMinDurationMs, swipeMinDistancePx, swipeMaxDurationMs, dragStartThresholdPx) - adjust them to taste before your first update() call.
Pinch/rotate need a real second touch point (TouchDriver::getSecondPoint() returning true). Of the built-in controller drivers, only TouchDriverGT911 can supply one - kPinchIn/kPinchOut/kRotate will simply never fire against TouchDriverXPT2046, TouchDriverFT6236/TouchDriverFT6206, TouchDriverCST816S, TouchDriverBitBang, or TouchDriverSDL (all inherently single-touch), no matter how the panel is actually touched.
GestureEvent carries the current point, the point where the touch/gesture began, cumulative and step deltas, pinch scale/rotation, and elapsed duration - see the struct in GestureDetector.h for the exact fields.
A touch panel's calibration and rotation are set independently from the paired DisplayDriver's own rotation (see DisplayDrivers.md) - nothing keeps them in sync automatically. When you call a display driver's setRotation(), call the matching TouchDriver::setRotation() with the same logical rotation too, or touch coordinates will stop lining up with what's rendered on screen. LCDBoard setups in TinyGPU/Boards/ pair a specific touch controller with a specific display driver/panel per board (e.g. TouchDriverFT6236 with the FT6336G on one board, TouchDriverGT911 with two others) as a reference for wiring both up together correctly.
TouchDriverBitBang (the newest addition here) was build-verified with a real compile against this project's Arduino-Emulator toolchain. The pre-existing I2C/SPI controller drivers (TouchDriverXPT2046/FT6236/CST816S/GT911) and GestureDetector were reviewed against their source but not independently re-verified as part of writing this document - see each driver's own doc comments in TouchDriverArduino.h for hardware-specific caveats already captured there.