Porting XPT2046 / ADS7843 Touch Sensor to Photon

Hi,

I am porting this library for the XP2046 resistive touch chip.

Whenever I call SPI1.beginTransaction(), the Photon turns green after about 10 seconds.

I read that it may mean the loop is not returning, but I am not sure why SPI would do that.

Does somebody have a suggestion?

Kind regards,

Are you using SPI or SPI1? You mentioned both in your post.
Which pins have you connected?

I am using both.

The XP2046 Touch chip on SPI1 and the visual display (Il9341) on SPI.

I made sure to not mix up the SPI and SPI1 calls.

… the pins, I am using the ones defined in the reference manual, exactly the same and I defined the CS lines

When you say you are porting the library, can you share what you have now?

Hi,

Here's my (so far) adapted library.

Things I did:

change

#define SPI_SETTING     SPISettings(2000000, MSBFIRST, SPI_MODE0)

to

#define SPI_SETTING     __SPISettings(4*MHZ, MSBFIRST, SPI_MODE0)

I also changed all occurrences of SPI.transfer16() to SPI1.transfer().

(I am using SPI1)

The Photon currently goes green as soon as this function is called:

XPT2046_Touchscreen::update()

More specifically, as soon as this line runs

SPI1.beginTransaction(SPI_SETTING);

Heres the code so far:

XPT2046_Touchscreen.cpp

/* Touchscreen library for XPT2046 Touch Controller Chip
 * Copyright (c) 2015, Paul Stoffregen, paul@pjrc.com
 *
 * 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, development funding 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.
 */

#include "XPT2046_Touchscreen.h"

#define Z_THRESHOLD     400
#define Z_THRESHOLD_INT	75
#define MSEC_THRESHOLD  3
#define SPI_SETTING     __SPISettings(4*MHZ, MSBFIRST, SPI_MODE0)

static XPT2046_Touchscreen 	*isrPinptr;
void isrPin(void);

bool XPT2046_Touchscreen::begin()
{
	SPI1.begin();
	pinMode(csPin, OUTPUT);
	digitalWrite(csPin, HIGH);
	if (255 != tirqPin) {
		pinMode( tirqPin, INPUT );
		attachInterrupt(tirqPin, isrPin, FALLING);
		isrPinptr = this;
	}
	return true;
}

#ifdef ESP32
void IRAM_ATTR isrPin( void )
#else
void isrPin( void )
#endif
{
	XPT2046_Touchscreen *o = isrPinptr;
	o->isrWake = true;
}

TS_Point XPT2046_Touchscreen::getPoint()
{
	update();
	return TS_Point(xraw, yraw, zraw);
}

bool XPT2046_Touchscreen::tirqTouched()
{
	return (isrWake);
}

bool XPT2046_Touchscreen::touched()
{
	update();
	return (zraw >= Z_THRESHOLD);
}

void XPT2046_Touchscreen::readData(uint16_t *x, uint16_t *y, uint8_t *z)
{
	update();
	*x = xraw;
	*y = yraw;
	*z = zraw;
}

bool XPT2046_Touchscreen::bufferEmpty()
{
	return ((millis() - msraw) < MSEC_THRESHOLD);
}

static int16_t besttwoavg( int16_t x , int16_t y , int16_t z ) {
  int16_t da, db, dc;
  int16_t reta = 0;
  if ( x > y ) da = x - y; else da = y - x;
  if ( x > z ) db = x - z; else db = z - x;
  if ( z > y ) dc = z - y; else dc = y - z;

  if ( da <= db && da <= dc ) reta = (x + y) >> 1;
  else if ( db <= da && db <= dc ) reta = (x + z) >> 1;
  else reta = (y + z) >> 1;   //    else if ( dc <= da && dc <= db ) reta = (x + y) >> 1;

  return (reta);
}

// TODO: perhaps a future version should offer an option for more oversampling,
//       with the RANSAC algorithm https://en.wikipedia.org/wiki/RANSAC

void XPT2046_Touchscreen::update()
{
	int16_t data[6];

	if (!isrWake) return;
	uint32_t now = millis();
	if (now - msraw < MSEC_THRESHOLD) return;
	
	SPI1.beginTransaction(SPI_SETTING);

	digitalWrite(csPin, LOW);
	SPI1.transfer(0xB1 /* Z1 */);

	int16_t z1 = SPI1.transfer(0xC1 /* Z2 */) >> 3;
	int z = z1 + 4095;
	int16_t z2 = SPI1.transfer(0x91 /* X */) >> 3;
	z -= z2;
	if (z >= Z_THRESHOLD) {
		SPI1.transfer(0x91 /* X */);  // dummy X measure, 1st is always noisy
		data[0] = SPI1.transfer(0xD1 /* Y */) >> 3;
		data[1] = SPI1.transfer(0x91 /* X */) >> 3; // make 3 x-y measurements
		data[2] = SPI1.transfer(0xD1 /* Y */) >> 3;
		data[3] = SPI1.transfer(0x91 /* X */) >> 3;
	}
	else data[0] = data[1] = data[2] = data[3] = 0;	// Compiler warns these values may be used unset on early exit.
	data[4] = SPI1.transfer(0xD0 /* Y */) >> 3;	// Last Y touch power down
	data[5] = SPI1.transfer(0) >> 3;
	digitalWrite(csPin, HIGH);
	SPI1.endTransaction();
	//Serial.printf("z=%d  ::  z1=%d,  z2=%d  ", z, z1, z2);
	if (z < 0) z = 0;
	if (z < Z_THRESHOLD) { //	if ( !touched ) {
		// Serial.println();
		zraw = 0;
		if (z < Z_THRESHOLD_INT) { //	if ( !touched ) {
			if (255 != tirqPin) isrWake = false;
		}
		return;
	}
	zraw = z;
	
	// Average pair with least distance between each measured x then y
	//Serial.printf("    z1=%d,z2=%d  ", z1, z2);
	//Serial.printf("p=%d,  %d,%d  %d,%d  %d,%d", zraw,
		//data[0], data[1], data[2], data[3], data[4], data[5]);
	int16_t x = besttwoavg( data[0], data[2], data[4] );
	int16_t y = besttwoavg( data[1], data[3], data[5] );
	
	//Serial.printf("    %d,%d", x, y);
	//Serial.println();
	if (z >= Z_THRESHOLD) {
		msraw = now;	// good read completed, set wait
		switch (rotation) {
		  case 0:
			xraw = 4095 - y;
			yraw = x;
			break;
		  case 1:
			xraw = x;
			yraw = y;
			break;
		  case 2:
			xraw = y;
			yraw = 4095 - x;
			break;
		  default: // 3
			xraw = 4095 - x;
			yraw = 4095 - y;
		}
	}
	
}

CPT2046_Touchscreen.h

/* Touchscreen library for XPT2046 Touch Controller Chip
 * Copyright (c) 2015, Paul Stoffregen, paul@pjrc.com
 *
 * 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, development funding 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.
 */

#ifndef _XPT2046_Touchscreen_h_
#define _XPT2046_Touchscreen_h_


#if defined (PARTICLE)
#include <Particle.h>
#else
#include "Arduino.h"
#include <SPI.h>
#if ARDUINO < 10600
#error "Arduino 1.6.0 or later (SPI library) is required"
#endif

#endif




class TS_Point {
public:
	TS_Point(void) : x(0), y(0), z(0) {}
	TS_Point(int16_t x, int16_t y, int16_t z) : x(x), y(y), z(z) {}
	bool operator==(TS_Point p) { return ((p.x == x) && (p.y == y) && (p.z == z)); }
	bool operator!=(TS_Point p) { return ((p.x != x) || (p.y != y) || (p.z != z)); }
	int16_t x, y, z;
};

class XPT2046_Touchscreen {
public:
	constexpr XPT2046_Touchscreen(uint8_t cspin, uint8_t tirq=255)
		: csPin(cspin), tirqPin(tirq) { }
	bool begin();
	TS_Point getPoint();
	bool tirqTouched();
	bool touched();
	void readData(uint16_t *x, uint16_t *y, uint8_t *z);
	bool bufferEmpty();
	uint8_t bufferSize() { return 1; }
	void setRotation(uint8_t n) { rotation = n % 4; }
// protected:
	volatile bool isrWake=true;

private:
	void update();
	uint8_t csPin, tirqPin, rotation=1;
	int16_t xraw=0, yraw=0, zraw=0;
	uint32_t msraw=0x80000000;
};

#endif

Before diving into your code: If I’d port this library, I’d not use a hardcoded SPI or SPI1 object, but rather make the library take a reference to an SPIClass reference and store it in a local reference which should be used throughout the implementation.

Could you also supply some sample code you test with?

I will make a refernce to an SPIClass reference and use that.

My testing code

// This #include statement was automatically added by the Particle IDE.
#include "XPT2046_Touchscreen.h"


// This #include statement was automatically added by the Particle IDE.
#include <Adafruit_mfGFX.h>




/***************************************************
  This is our GFX example for the Adafruit ILI9341 Breakout and Shield
  ----> http://www.adafruit.com/products/1651

  Check out the links above for our tutorials and wiring diagrams
  These displays use SPI to communicate, 4 or 5 pins are required to
  interface (RST is optional)
  Adafruit invests time and resources providing this open source code,
  please support Adafruit and open-source hardware by purchasing
  products from Adafruit!

  Written by Limor Fried/Ladyada for Adafruit Industries.
  MIT license, all text above must be included in any redistribution
 ****************************************************/

#include "Adafruit_ILI9341.h"
#define TFT_DC A1
#define TFT_CS A2
#define TFT_BRIGHTNESS_PIN WKP

Adafruit_ILI9341 tft = Adafruit_ILI9341(TFT_CS, TFT_DC, D6);


//TOUCH
#define CS_PIN  D5
// MOSI=A5, MISO=A4, SCK=A3
#define TIRQ_PIN  D7

//XPT2046_Touchscreen ts(CS_PIN);  // Param 2 - NULL - No interrupts
//XPT2046_Touchscreen ts(CS_PIN, 255);  // Param 2 - 255 - No interrupts
XPT2046_Touchscreen ts(CS_PIN, TIRQ_PIN);  // Param 2 - Touch IRQ Pin - interrupt enabled polling
// END TOUCH




void setup() {
    
    //TFT Display
    tft.begin();
    tft.setRotation(3);
    //DISPLAY BRIGHNESS
    pinMode(TFT_BRIGHTNESS_PIN, OUTPUT);
    analogWrite(TFT_BRIGHTNESS_PIN, 255);  //0 (dark) to 255 (max brightness)
    //END TFT DISPLAY
    
    
    tft.fillScreen(ILI9341_BLACK);

    //TOUCH BEGIN
    ts.begin();
    ts.setRotation(1);
    // END TOUCH BEGIN

    
    
}


void loop(void) {

 if (ts.touched()) {
    TS_Point p = ts.getPoint();

      // tft.fillRect(120,7,120,30,ILI9341_BLACK);
   // tft.setCursor(120, 7);
        //tft.setTextColor(ILI9341_WHITE);
        //tft.setTextSize(2);

        //tft.print(p.z);

  }


}



You could take this for the selectable SPI
https://go.particle.io/shared_apps/5bddb922632d19dd940006b3

1 Like

Thank you for creating the shared library!

Its now working better.

I am receiving strange values for x,y and z.

Not touched:

Touched:
(The values do not change if I move my finger around)

Would you have an idea why it’s doing this?

Here’s my current code revision:
https://go.particle.io/shared_apps/5be67558502034938d000380

Hard to tell without having a display at hand to test, but these readings seem to be near the max 4095 of the 12bit ADC suggesting the coordintes aren’t actually sensed.

Are the functions transfer() and transfer16() the same?

In the original library for Arduino, its using transfer16()

data[0] = SPI.transfer16(0xD1) >> 3

now we are using transfer()

data[0] = xptSPI.transfer(0xD1) >> 3;

Could that be a problem?

SPI.transfer16 would send two bytes, if you want to replace it with SPI.transfer() you’d need to send two bytes too - either with two calls in a row, or via the version that takes a output buffer.

Thank you

Its working better now, I can read data.

But the values jump around like crazy.

The values are rocksteady on the Arduino version.

I have soldered the wires to both the display and the photon to exclude connection problems .
__

The behaviour changes when I alter the clockspeed.

    SPI1.setClockDividerReference(SPI_CLK_ARDUINO);
    SPI1.setClockDivider(SPI_CLOCK_DIV128);
    SPI1.setBitOrder(MSBFIRST);
    SPI1.setDataMode(SPI_MODE0);

But it doesn’t fully fix it.

Would you think the following function can replace Arduino’s SPI1.transfer16() ?

int16_t XPT2046_Touchscreen::SPItransfer16(int8_t a,int8_t b)
//this function added to library because photon SPI library does not have a transfer16 function as used in the Arduino lib
//two args used to make operation explicit but could be reduced to 1 arg once operation proven
{
    tx_buffer[0] = a;
    tx_buffer[1] = b;
    
    SPI1.transfer(tx_buffer, rx_buffer, sizeof(rx_buffer),NULL);
    return (rx_buffer[0]);
}

Just in case, here is my current revision

P.S. I will be happy to make this library publicly available once it (hopefully) works.

I’d rather think your function wants to take a uint16_t instead of two int8_t.
And if you want to stick with a non-buffer transfer you’d just call `SPI1.transfer()´ - once for each byte of the two-byte parameter.

Also if you want to return the value, you’d not return only a single byte (as you do there) but rather both bytes as the original SPI.transfer16() does.

Depending on endianness one of the following ways should fit your needs

uint16_t SPItransfer16(uint16_t data)
{
  uint16_t retVal = 0;
  // if endianness of both sides fits together
  // SPI1.transfer((uint8_t*)&data, (uint8_t*)&retVal, sizeof(data), NULL);

  // MSB before LSB
  retVal |= SPI1.transfer(data >> 8) << 8; 
  retVal |= SPI1.transfer(data & 0x00FF);

  // LSB before MSB
  // retVal |= SPI1.transfer(data & 0x00FF);
  // retVal |= SPI1.transfer(data >> 8) << 8; 

  return retVal;
}

It’s working.

Thank you for the support.

The made the library available here or in the IDE

:+1: