Convert UTouch To Use Hardware SPI

Hi, I have a project using an arduino and a TJCTM24024-SPI touch LCD.

The resistive touch screen uses the (ftp://imall.iteadstudio.com/LCD_Panel/IM120905004/DS_XPT2046.pdf) XPT2046 Touch Screen Controller chip to allow SPI access to the touches.

I have exhausted the amount of pins on my arduino with other sensors and so I need to use the hardware SPI to work with both the LCD and the touch chip.

I have searched high and low for a hardware SPI library for the XPT2046 chip but only found the UTouch Library which uses bit banging.

Having come across bit banging before but never really understood it I decided that I should convert the UTouch library to use hardware SPI, this will aid my understanding and give me a hardware SPI touch library.

So, I have started to rework it, my efforts I have included below in Fig 1.

Being a bit green, I would like others feedback on whether I am on the right track with this, it obviousely doesn’t work in its current form but once done, it should be useful to many others. Please could you guys give me a hand to get this working.

Thank you, Rick.

Fig 1

/************please see original github repo for forword, omited here for brevity ************/
#include "UTouch.h"
#include "UTouchCD.h"

UTouch::UTouch(byte tclk, byte tcs, byte din, byte dout, byte irq)
{
    //T_CLK        = tclk;
    T_CS         = tcs;
    //T_DIN        = din;
    //T_DOUT       = dout;
    T_IRQ        = irq;
}

void UTouch::InitTouch(byte orientation)
{
	orient					= orientation;
	_default_orientation	= CAL_S>>31;
	touch_x_left			= (CAL_X>>14) & 0x3FFF;
	touch_x_right			= CAL_X & 0x3FFF;
	touch_y_top				= (CAL_Y>>14) & 0x3FFF;
	touch_y_bottom			= CAL_Y & 0x3FFF;
	disp_x_size				= (CAL_S>>12) & 0x0FFF;
	disp_y_size				= CAL_S & 0x0FFF;
	prec					= 10;

	//pinMode(T_CLK,  OUTPUT);
    pinMode(T_CS,   OUTPUT);
    //pinMode(T_DIN,  OUTPUT);
    //pinMode(T_DOUT, INPUT);
    pinMode(T_IRQ,  OUTPUT);

	digitalWrite(T_CS,  HIGH);
	//digitalWrite(T_CLK, HIGH);
	//digitalWrite(T_DIN, HIGH);
	//digitalWrite(T_CLK, HIGH);
}

// transmit byte serially, MSB first
void UTouch::touch_WriteData(byte data)
{
	/*
	byte temp;
	byte nop;

	temp=data;
	digitalWrite(T_CLK,LOW);

	// send bits 7..0
	for(byte count=0; count<8; count++)
	{
		// consider leftmost bit
        // set line high if bit is 1, low if bit is 0
		if(temp & 0x80)
			digitalWrite(T_DIN, HIGH);
		else
			digitalWrite(T_DIN, LOW);

		// shift byte left so next bit will be leftmost
		temp = temp << 1;

		// pulse clock to indicate that bit value should be read
		digitalWrite(T_CLK, LOW);
		nop++;
		digitalWrite(T_CLK, HIGH);
		nop++;
	}
	*/
}

word UTouch::touch_ReadData(byte b)
{
	//the d_out will return 12 bits of data for every 8 bit command ?
	//so we need to send the 8 bit command plus some garbage ?
	return word(SPI.transfere(b), SPI.transfere(0x00));

	/*
	byte nop;
	word data = 0;

	// read bits 11..0
	for(byte count=0; count<12; count++)
	{
		// shift byte left so next bit will be leftmost
		data <<= 1;

		// pulse clock to indicate that bit value should be read
		digitalWrite(T_CLK, HIGH);
		nop++;
		digitalWrite(T_CLK, LOW);
		nop++;

		//if T_DOUT is high, this position in data will be 1, else it will be 0
		if (digitalRead(T_DOUT))
			data++;
	}

	return(data);
	*/


}

void UTouch::read()
{
	unsigned long tx=0, temp_x=0;
	unsigned long ty=0, temp_y=0;
	int datacount=0;

	digitalWrite(T_CS,LOW);

	for (int i=0; i<prec; i++)
	{
		//touch_WriteData(0x90);
		//digitalWrite(T_CLK,HIGH);
		//digitalWrite(T_CLK,LOW);
		//temp_x=touch_ReadData();
		temp_x=touch_ReadData(0x90);

		//touch_WriteData(0xD0);
		//digitalWrite(T_CLK,HIGH);
		//digitalWrite(T_CLK,LOW);
		//temp_y=touch_ReadData(0x90, 0xD0);
		temp_y=touch_ReadData(0xD0);

		if (!((temp_x>max(touch_x_left, touch_x_right)) or (temp_x==0) or (temp_y>max(touch_y_top, touch_y_bottom)) or (temp_y==0)))
		{
			ty+=temp_x;
			tx+=temp_y;
			datacount++;
		}
	}

	digitalWrite(T_CS,HIGH);
	if (datacount>0)
	{
		if (orient == _default_orientation)
		{
			TP_X=tx/datacount;
			TP_Y=ty/datacount;
		}
		else
		{
			TP_X=ty/datacount;
			TP_Y=tx/datacount;
		}
	}
	else
	{
		TP_X=-1;
		TP_Y=-1;
	}
}

bool UTouch::dataAvailable()
{
	bool avail;
	pinMode(T_IRQ,  INPUT);
	avail = !digitalRead(T_IRQ);
	pinMode(T_IRQ,  OUTPUT);
	return avail;
}

int UTouch::getX()
{
	long c;

	if (orient == _default_orientation)
	{
		c = long(long(TP_X - touch_x_left) * (disp_x_size)) / long(touch_x_right - touch_x_left);
		if (c<0)
			c = 0;
		if (c>disp_x_size)
			c = disp_x_size;
	}
	else
	{
		if (_default_orientation == PORTRAIT)
			c = long(long(TP_X - touch_y_top) * (-disp_y_size)) / long(touch_y_bottom - touch_y_top) + long(disp_y_size);
		else
			c = long(long(TP_X - touch_y_top) * (disp_y_size)) / long(touch_y_bottom - touch_y_top);
		if (c<0)
			c = 0;
		if (c>disp_y_size)
			c = disp_y_size;
	}
	return c;
}

int UTouch::getY()
{
	int c;

	if (orient == _default_orientation)
	{
		c = long(long(TP_Y - touch_y_top) * (disp_y_size)) / long(touch_y_bottom - touch_y_top);
		if (c<0)
			c = 0;
		if (c>disp_y_size)
			c = disp_y_size;
	}
	else
	{
		if (_default_orientation == PORTRAIT)
			c = long(long(TP_Y - touch_x_left) * (disp_x_size)) / long(touch_x_right - touch_x_left);
		else
			c = long(long(TP_Y - touch_x_left) * (-disp_x_size)) / long(touch_x_right - touch_x_left) + long(disp_x_size);
		if (c<0)
			c = 0;
		if (c>disp_x_size)
			c = disp_x_size;
	}
	return c;
}

void UTouch::setPrecision(byte precision)
{
	switch (precision)
	{
		case PREC_LOW:
			prec=1;
			break;
		case PREC_MEDIUM:
			prec=10;
			break;
		case PREC_HI:
			prec=25;
			break;
		case PREC_EXTREME:
			prec=100;
			break;
		default:
			prec=10;
			break;
	}
}

Ok, so I have figured this one out myself. Turns out I was nearly there with the code in the post above. I just needed to send the command and read the result separately. Also, returned data was 12 bits long so says the datasheet so I needed to right shift my 16 bit word by three bits.

Anyway, this is not the first time someone has been looking to do hardware SPI with these modules and so I have posted the UTouch library with my edits below so that it can be found and used by others.

I guess I could have made it so you can use it in the original bitbang form or HW but I didn’t hey ho. Remember you will need the header file along with the cpp below, see the post above for links to the github repo. Also, I have removed the write method from the cpp as it was not required, so remeber to remove it from the .h header :slight_smile:

I’m using this with a cheap chinese ILI9341, I have also modified the calibrate sketch to work with this display, it’s not pretty but it works so if anyone needs it I’ll be happy to send it over, be aware it is in a bit of a state though using serial output and the like so I won’t post that here.

/*
  UTouch.cpp - Arduino/chipKit library support for Color TFT LCD Touch screens
  Copyright (C)2010-2012 Henning Karlsen. All right reserved

  * Modified by Richard Bushill to use hardware SPI instead of bit bang
  * 06/04/2015

  Basic functionality of this library are based on the demo-code provided by
  ITead studio. You can find the latest version of the library at
  http://www.henningkarlsen.com/electronics

  If you make any modifications or improvements to the code, I would appreciate
  that you share the code with me so that I might include it in the next release.
  I can be contacted through http://www.henningkarlsen.com/electronics/contact.php

  This library is free software; you can redistribute it and/or
  modify it under the terms of the GNU Lesser General Public
  License as published by the Free Software Foundation; either
  version 2.1 of the License, or (at your option) any later version.

  This library is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  Lesser General Public License for more details.

  You should have received a copy of the GNU Lesser General Public
  License along with this library; if not, write to the Free Software
  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
*/
#include "SPI.h"
#include "UTouch.h"
#include "UTouchCD.h"

UTouch::UTouch(byte tcs, byte irq)
{
    T_CS         = tcs;
    T_IRQ        = irq;
}

void UTouch::InitTouch(byte orientation)
{
	orient					= orientation;
	_default_orientation	= CAL_S>>31;
	touch_x_left			= (CAL_X>>14) & 0x3FFF;
	touch_x_right			= CAL_X & 0x3FFF;
	touch_y_top				= (CAL_Y>>14) & 0x3FFF;
	touch_y_bottom			= CAL_Y & 0x3FFF;
	disp_x_size				= (CAL_S>>12) & 0x0FFF;
	disp_y_size				= CAL_S & 0x0FFF;
	prec					= 10;

    pinMode(T_CS,   OUTPUT);
    pinMode(T_IRQ,  OUTPUT);
	digitalWrite(T_CS,  HIGH);
}


//combined the write data method into the read seeing as HW SPI does both at the same time - RB 06/04/15
word UTouch::touch_ReadData(byte command)
{
	//first send the command, this will not return anything useful
	//so disregard the return byte
	SPI.transfer(command);

	//next we need to send 12 bits of nothing to get the 12 bits we need back
	//we can only send in batches of 8, ie 1 byte so do this twice
	byte f1 = SPI.transfer(0x00);
	byte f2 = SPI.transfer(0x00);
	
	//combine the 16 bits into a word
	word w = word(f1, f2);
	
	//the word is organised with MSB leftmost, shit right by 3 to make 12 bits
	w = w >> 3;

	//and return the result
	return w;
}

void UTouch::read()
{
	unsigned long tx=0, temp_x=0;
	unsigned long ty=0, temp_y=0;
	int datacount=0;
SPI.begin();
	digitalWrite(T_CS,LOW);

	for (int i=0; i<prec; i++)
	{
                //added param command - RB 6/4/15
		temp_x=touch_ReadData(0x90);
		temp_y=touch_ReadData(0xD0);

		if (!((temp_x>max(touch_x_left, touch_x_right)) or (temp_x==0) or (temp_y>max(touch_y_top, touch_y_bottom)) or (temp_y==0)))
		{
			ty+=temp_x;
			tx+=temp_y;
			datacount++;
		}
	}

	digitalWrite(T_CS,HIGH);
	SPI.end();
	if (datacount>0)
	{
		if (orient == _default_orientation)
		{
			TP_X=tx/datacount;
			TP_Y=ty/datacount;
		}
		else
		{
			TP_X=ty/datacount;
			TP_Y=tx/datacount;
		}
	}
	else
	{
		TP_X=-1;
		TP_Y=-1;
	}
}

bool UTouch::dataAvailable()
{
	bool avail;
	pinMode(T_IRQ,  INPUT);
	avail = !digitalRead(T_IRQ);
	pinMode(T_IRQ,  OUTPUT);
	return avail;
}

int UTouch::getX()
{
	long c;

	if (orient == _default_orientation)
	{
		c = long(long(TP_X - touch_x_left) * (disp_x_size)) / long(touch_x_right - touch_x_left);
		if (c<0)
			c = 0;
		if (c>disp_x_size)
			c = disp_x_size;
	}
	else
	{
		if (_default_orientation == PORTRAIT)
			c = long(long(TP_X - touch_y_top) * (-disp_y_size)) / long(touch_y_bottom - touch_y_top) + long(disp_y_size);
		else
			c = long(long(TP_X - touch_y_top) * (disp_y_size)) / long(touch_y_bottom - touch_y_top);
		if (c<0)
			c = 0;
		if (c>disp_y_size)
			c = disp_y_size;
	}
	
	return c;
}

int UTouch::getY()
{
	int c;

	if (orient == _default_orientation)
	{
		c = long(long(TP_Y - touch_y_top) * (disp_y_size)) / long(touch_y_bottom - touch_y_top);
		if (c<0)
			c = 0;
		if (c>disp_y_size)
			c = disp_y_size;
	}
	else
	{
		if (_default_orientation == PORTRAIT)
			c = long(long(TP_Y - touch_x_left) * (disp_x_size)) / long(touch_x_right - touch_x_left);
		else
			c = long(long(TP_Y - touch_x_left) * (-disp_x_size)) / long(touch_x_right - touch_x_left) + long(disp_x_size);
		if (c<0)
			c = 0;
		if (c>disp_x_size)
			c = disp_x_size;
	}
	return c;
}

void UTouch::setPrecision(byte precision)
{
	switch (precision)
	{
		case PREC_LOW:
			prec=1;
			break;
		case PREC_MEDIUM:
			prec=10;
			break;
		case PREC_HI:
			prec=25;
			break;
		case PREC_EXTREME:
			prec=100;
			break;
		default:
			prec=10;
			break;
	}
}

void UTouch::calibrateRead()
{
	unsigned long tx=0;
	unsigned long ty=0;

	SPI.begin();
	digitalWrite(T_CS,LOW);

	tx=touch_ReadData(0x90);
    ty=touch_ReadData(0xD0);

	digitalWrite(T_CS,HIGH);
	SPI.end();

	TP_X=ty;
	TP_Y=tx;
}

Hi, I found very interesting your post.
I’m trying to do the same with hy28b LCD display that has the same touch controller chip.
I use Arduino Due with SPI extended (and I modified your code in this way), but it doesn’t work.

Have you a github account where find this software?

Here the code UTouch.cpp and after the UTouch.h

#include "SPI.h"
#include "UTouch.h"
#include "UTouchCD.h"

UTouch::UTouch(byte tcs, byte irq)
{
    T_CS = tcs;
    T_IRQ = irq;
}

void UTouch::InitTouch(byte orientation)
{
	orient					= orientation;
	_default_orientation	= CAL_S>>31;
	touch_x_left			= (CAL_X>>14) & 0x3FFF;
	touch_x_right			= CAL_X & 0x3FFF;
	touch_y_top				= (CAL_Y>>14) & 0x3FFF;
	touch_y_bottom			= CAL_Y & 0x3FFF;
	disp_x_size				= (CAL_S>>12) & 0x0FFF;
	disp_y_size				= CAL_S & 0x0FFF;
	prec					= 10;

    pinMode(T_IRQ, OUTPUT);

    //pinMode(T_CS, OUTPUT);
	//digitalWrite(T_CS,  HIGH);
	
	SPI.begin(T_CS);
	SPI.setDataMode(T_CS, SPI_MODE3);
    SPI.setClockDivider(T_CS, SPI_CLOCK_DIV4);
    SPI.setBitOrder(T_CS, MSBFIRST);
}


//combined the write data method into the read seeing as HW SPI does both at the same time - RB 06/04/15
word UTouch::touch_ReadData(byte command)
{
	//first send the command, this will not return anything useful
	//so disregard the return byte
	SPI.transfer(T_CS, command, SPI_CONTINUE);

	//next we need to send 12 bits of nothing to get the 12 bits we need back
	//we can only send in batches of 8, ie 1 byte so do this twice
	byte f1 = SPI.transfer(T_CS, 0x00, SPI_CONTINUE);
	byte f2 = SPI.transfer(T_CS, 0x00);
	
	//combine the 16 bits into a word
	word w = word(f1, f2);
	
	//the word is organised with MSB leftmost, shift right by 3 to make 12 bits
	w = w >> 3;

	//and return the result
	return w;
}

void UTouch::read()
{
	unsigned long tx=0, temp_x=0;
	unsigned long ty=0, temp_y=0;
	int datacount=0;
	//digitalWrite(T_CS,LOW);

	for (int i=0; i<prec; i++)
	{
        //added param command - RB 6/4/15
		temp_x = touch_ReadData(0x90);
		temp_y = touch_ReadData(0xD0);

	    //Serial.println(temp_x);
	    //Serial.println(temp_y);

		if (!((temp_x>max(touch_x_left, touch_x_right)) or (temp_x==0) or (temp_y>max(touch_y_top, touch_y_bottom)) or (temp_y==0)))
		{
			ty+=temp_x;
			tx+=temp_y;
			datacount++;
		}
	}

	//digitalWrite(T_CS,HIGH);
	if (datacount>0)
	{
		if (orient == _default_orientation)
		{
			TP_X=tx/datacount;
			TP_Y=ty/datacount;
		}
		else
		{
			TP_X=ty/datacount;
			TP_Y=tx/datacount;
		}
	}
	else
	{
		TP_X=-1;
		TP_Y=-1;
	}
}

bool UTouch::dataAvailable()
{
	bool avail;
	pinMode(T_IRQ,  INPUT);
	avail = !digitalRead(T_IRQ);
	pinMode(T_IRQ,  OUTPUT);
	return avail;
}

int16_t UTouch::getX()
{
	long c;

	if (orient == _default_orientation)
	{
		c = long(long(TP_X - touch_x_left) * (disp_x_size)) / long(touch_x_right - touch_x_left);
		if (c<0)
			c = 0;
		if (c>disp_x_size)
			c = disp_x_size;
	} else {
		if (_default_orientation == PORTRAIT)
			c = long(long(TP_X - touch_y_top) * (-disp_y_size)) / long(touch_y_bottom - touch_y_top) + long(disp_y_size);
		else
			c = long(long(TP_X - touch_y_top) * (disp_y_size)) / long(touch_y_bottom - touch_y_top);
		if (c<0)
			c = 0;
		if (c>disp_y_size)
			c = disp_y_size;
	}
	
	return c;
}

int16_t UTouch::getY()
{
	int c;

	if (orient == _default_orientation)
	{
		c = long(long(TP_Y - touch_y_top) * (disp_y_size)) / long(touch_y_bottom - touch_y_top);
		if (c<0)
			c = 0;
		if (c>disp_y_size)
			c = disp_y_size;
	} else {
		if (_default_orientation == PORTRAIT)
			c = long(long(TP_Y - touch_x_left) * (disp_x_size)) / long(touch_x_right - touch_x_left);
		else
			c = long(long(TP_Y - touch_x_left) * (-disp_x_size)) / long(touch_x_right - touch_x_left) + long(disp_x_size);
		if (c<0)
			c = 0;
		if (c>disp_x_size)
			c = disp_x_size;
	}
	return c;
}

void UTouch::setPrecision(byte precision)
{
	switch (precision)
	{
		case PREC_LOW:
			prec=1;
			break;
		case PREC_MEDIUM:
			prec=10;
			break;
		case PREC_HI:
			prec=25;
			break;
		case PREC_EXTREME:
			prec=100;
			break;
		default:
			prec=10;
			break;
	}
}

void UTouch::calibrateRead()
{
	unsigned long tx=0;
	unsigned long ty=0;

	SPI.begin(T_CS);
	//digitalWrite(T_CS,LOW);

	tx=touch_ReadData(0x90);
    ty=touch_ReadData(0xD0);

	//digitalWrite(T_CS,HIGH);
	SPI.end(T_CS);

	TP_X=ty;
	TP_Y=tx;
}

UTouch.h

#define PORTRAIT			0
#define LANDSCAPE			1

#define PREC_LOW			1
#define PREC_MEDIUM			2
#define PREC_HI				3
#define PREC_EXTREME		4

class UTouch
{
	public:
		int16_t	TP_X ,TP_Y;

				UTouch(byte tcs, byte irq);

		void	InitTouch(byte orientation = LANDSCAPE);
		void	read();
		bool	dataAvailable();
		int16_t	getX();
		int16_t	getY();
		void	setPrecision(byte precision);

		void	calibrateRead();
    
    private:
		byte	T_CLK, T_CS, T_DIN, T_DOUT, T_IRQ;
		long	_default_orientation;
		byte	orient;
		byte	prec;
		byte	display_model;
		long	disp_x_size, disp_y_size, default_orientation;
		long	touch_x_left, touch_x_right, touch_y_top, touch_y_bottom;

		word	touch_ReadData(byte data);
};

Any suggestions
Thanks

rickpbush: Ok, so I have figured this one out myself. Turns out I was nearly there with the code in the post above. I just needed to send the command and read the result separately. Also, returned data was 12 bits long so says the datasheet so I needed to right shift my 16 bit word by three bits.

Anyway, this is not the first time someone has been looking to do hardware SPI with these modules and so I have posted the UTouch library with my edits below so that it can be found and used by others.

I guess I could have made it so you can use it in the original bitbang form or HW but I didn't hey ho. Remember you will need the header file along with the cpp below, see the post above for links to the github repo. Also, I have removed the write method from the cpp as it was not required, so remeber to remove it from the .h header :)

I'm using this with a cheap chinese ILI9341, I have also modified the calibrate sketch to work with this display, it's not pretty but it works so if anyone needs it I'll be happy to send it over, be aware it is in a bit of a state though using serial output and the like so I won't post that here.

Hi

I have the exact same screen and would love for you to post a zip file of the complete modified hardware SPI Utouch library, and also the modified calibrate sketch, if you wouldn't mind.

Thanks in advance

Hi rickpbush

Very promising as I would like the cheap Chinese board with a ESP8266. Unfortunately I struggle completely. :roll_eyes:

Could you please post your UTouch.h and the example for the calibration?

Thanks a lot