Adafruit TFT LCD: Touch Screen Interface

The Adafruit STMPE610 touch screen library wrangles data from the touch screen interface, but the raw results require cleanup before they’re useful. Here’s a start on the problem.

Incidentally, the resistive touch screen works better than the capacitive one for a sewing machine interface used by a quilter, because cotton mesh quilter gloves have grippy silicone fingertips.

During startup, my code dumps the touch values if it detects a press and stalls until the touch goes away:

Kenmore 158 UI - startup

Kenmore 158 UI – startup

That’s actually green-on-black, the only colors a dot-matrix character display should have, but the image came out a bit overexposed.

In this landscape orientation, the touch digitizer coordinate system origin sits in the lower left, with X vertical and Y horizontal, as shown by the (1476,1907) raw coordinate; that matches the LCD’s default portrait orientation. The digitizer’s active area is bigger than the LCD, extending a smidge in all directions and 6 mm to the right.

The rotated LCD coordinate system has its origin in the upper left corner, with dot coordinates from (0,0) to (319,239); note that Y increases downward. The touch point sits about the same distance from the left and top edges, as indicated by the (154,157) cleaned coordinate.

How this all works…

This chunk defines the hardware and calibration constants, with the touch screen set up to use hardware SPI on an Arduino Mega1280:

// Adafruit ILI9341 TFT LCD ...
#define TFT_CS 10
#define TFT_DC 9

// ... with STMPE610 touch screen ...
#define STMPE_CS 8

// ... and MicroSD Card slot
#define SD_CS 4

// Globals

Adafruit_STMPE610 ts =  Adafruit_STMPE610(STMPE_CS);
Adafruit_ILI9341  tft = Adafruit_ILI9341(TFT_CS, TFT_DC);

// Touchscreen extents in unrotated digitizer values
// These should be in EEPROM to allow per-unit calibration

TS_Point TS_Min(220,220,0);
TS_Point TS_Max(3800,3700,0);

The TS_Min and TS_Max values come from experimental twiddling with the Adafruit TouchTest demo code and correspond to the LCD’s active area in the digitizer’s raw coordinate system.

Although the digitizer also produces a Z axis value corresponding to the touch pressure, I don’t use it, and, in any event, it has a tiny dynamic range.

The initialization goes in setup():

	Serial.print(F("TS... "));	// start touch screen early to let it wake up
	if (ts.begin()) {
	else {
		Serial.println(F("** NG"));
		while(1) continue;

As noted, the digitizer takes a while to wake up from a cold start, so I overlapped that time with the SD card and LCD initializations. Most likely, I should use a fixed delay, but I don’t know what would be a good value.

With the LCD up & running, this code produces the touch screen values shown in the picture:

	if (ts.touched() && !ts.bufferEmpty()) {	// hold display while touched
		tp = ts.getPoint();
		tft.print(" TS raw: (");
		CleanTouch(&tp);						// different point, but should be close
		tft.print(" TS clean: (");
		while (ts.touched()) continue;
		while (!ts.bufferEmpty()) ts.getPoint();

The first while stalls until you stop pressing on the screen, whereupon the second drains the digitizer’s queue. You can reach inside the digitizer and directly reset the hardware, but that seems overly dramatic.

CleanTouch() fetches the next point from the digitizer and returns a boolean indicating whether it got one. If it did, you also get back touch point coordinates in the LCD’s rotated coordinate system:

#define TS_TRACE true

boolean CleanTouch(TS_Point *p) {

TS_Point t;

// Sort out possible touch and data queue conditions

	if (ts.touched())							// screen touch?
		if (ts.bufferEmpty())					//  if no data in queue
			return false;						//   bail out
		else									//  touch and data!
			t = ts.getPoint();					//   so get it!
	else {										// no touch, so ...
		while (!ts.bufferEmpty())				//  drain the buffer
		return false;

	printf("Raw touch (%d,%d)\r\n",t.x,t.y);

	t.x = constrain(t.x,TS_Min.x,TS_Max.x);					// clamp to raw screen area
	t.y = constrain(t.y,TS_Min.y,TS_Max.y);

    printf(" constrained (%d,%d)\r\n",t.x,t.y);
    printf(" TFT (%d,%d)\r\n",tft.width(),tft.height());

	p->x = map(t.y, TS_Min.y, TS_Max.y, 0, tft.width());		// rotate & scale to TFT boundaries
	p->y = map(t.x, TS_Min.x, TS_Max.x, tft.height(), 0);		//   ... flip Y to put (0,0) in upper left corner

	p->z = t.z;

	printf(" Clean (%d,%d)\r\n",p->x,p->y);

	return true;

The hideous conditional block at the start makes sure that the point corresponds to the current touch coordinate, by the simple expedient of tossing any and all stale data overboard. I think you could trick the outcome by dexterous finger dancing on the screen, but (so far) it delivers the expected results.

The constrain() functions clamp the incoming data to the boundaries, to prevent the subsequent map() functions from emitting values beyond the LCD coordinate system.

Note that t is in raw digitizer coordinates and p is in rotated LCD coordinates. The simple transform hardcoded into the map() functions sorts that out; you get to figure out different rotations on your own.

The results of touching all four corners, starting in the upper left near the LCD origin and proceeding counterclockwise:

Raw touch (3750,258)
 constrained (3750,258)
 TFT (320,240)
 Clean (3,4)
 No hit!
Raw touch (274,231)
 constrained (274,231)
 TFT (320,240)
 Clean (1,237)
 No hit!
Raw touch (145,3921)
 constrained (220,3700)
 TFT (320,240)
 Clean (320,240)
 No hit!
Raw touch (3660,3887)
 constrained (3660,3700)
 TFT (320,240)
 Clean (320,10)
 No hit!

The No hit! comments come from the button handler, which figures out which button sits under the touch point: all four touches occur outside of all the buttons, so none got hit. More on that later.

Inside the main loop(), it goes a little something like this:

if (CleanTouch(&pt)) {
	BID = FindHit(pt);
	if (BID) {
	while (ts.touched())									// stall waiting for release

The while() loop stalls until the touch goes away, which ensures that you don’t get double taps from a single press; it may leave some points in the queue that CleanTouch() must discard when it encounters them without a corresponding touch.

All in all, it seems to work pretty well…



  1. #1 by madbodger on 2015-01-21 - 10:40

    When C coding, I have a habit of creating structs and then using typedef to name them, so I can declare variables like “touch_t curtouch;”. Unfortunately, this conflicts with the Arduino variant of C++.

    • #2 by Ed on 2015-01-21 - 12:05

      Yup! The solution involves tucking various code snippets into other IDE tabs / header files.

      Fortunately, my toy programs aren’t nearly complex enough to warrant that sort of thing.

  2. #3 by david on 2015-01-22 - 18:10

    Silly rabbit, everyone knows the opposite of raw is not clean but . :)

    • #4 by Ed on 2015-01-22 - 20:29

      Ooooh, that stings… [grin]