cancel
Showing results for 
Search instead for 
Did you mean: 

xpt2046 touch controller

kob77
Associate III

Hi, I'm having trouble with the resistive touch screen on my display. Here's some info about my project - I'm using an stm32f401cdu6 microcontroller, a 7" display with an SSD1963 controller, and a touch controller XPT2046. I based my project on the offpic user libraries on GitHub. You can find my microcontroller configuration and source files at https://github.com/koob7/stm32-f401-display. The display works fine, but the touch only works in power mode (XPT PD1=0, PD0=1). The first issue is the synhronic generation of interrupts on the irq pin even when the screen isn't touched. This happens even when interrupts are disabled (XPT PD0=1). In the future, I'd like to only read touch input when it's actually pressed. The second problem occurs in power-down mode (XPT PD0=0): when I change the X position on the touch, both X and Y values change. A similar issue happens when changing Y.

1 ACCEPTED SOLUTION

Accepted Solutions

I have a solution! The issue was the delay after resetting the CS pin in the function communicating with XPT. I also changed the display mode to 11. In this case, the mentioned delay is not necessary, and all the problems were fixed. Regarding the delay in interrupt handling, the solution was to increase the priority of the systick interrupt so that it is more important than the touch interrupt. In this case, the delay in communicating with XPT also works (but I no longer need it). Thank you very much for your help and dedicated time. I found another problem - I connected an SD card SPI reader (without a card inserted, just physical connection without uploading code) and its presence interferes with reading from XPT. It's very strange because I set the CS pin of the reader to a high level. Do you have any idea what the problem might be?

View solution in original post

29 REPLIES 29

Taking a very cursory look at my code for the XPT2046, I do not switch power modes.  I enable interrupts (from the XPT), but do not enable them in the processor.  I'm using FreeRTOS, so there's a task that looks (when needed) for the interrupt pin being low.  Things happen after that. 

If the interrupts are being generated even if there is no touch, have you put a pullup on the IRQ pin?  A floating line would give you problems.  You might first try the processor's internal pullup. 

IIRC, the chip only enables interrupts in certain modes, and will not in other power modes. 

Since I don't change power modes, I can't comment on the power down mode.

Hope this helps a bit.

Thank you for your response. The pull-up resistor is already built-in into my ssd1963 display, so unfortunately that's not the issue. You mentioned that interrupts are only available in certain power modes, which is why I tried changing them, but even that didn't block the interrupts :C. However, that's not the biggest problem. The bigger issue is the dependency between the X and Y axes. One of the videos shows the correct solution, while the other shows the current one.

OK, have you looked at the IRQ pin from the chip with a scope to see what it's doing?  That may give you a few clues.

If the interrupts are always on, then something is very odd.  I'd check to see if the IRQ pin in the code agrees with the actual physical pin as well, and if possible, look at both pins with a scope.

I also wonder at the setup, since the power modes rewire how the pins are connected.  I do the following for a setup:

// sets basic parameters
void XPT_2046::init(uint16_t width, uint16_t height)
{
	uint8_t					ctrl;
	uint8_t					data[4];


	_width = width;
	_height = height;
	ctrl = 0;
		hal_spi[interface]->receive(1, &ctrl, 2, (unsigned char*) &data, 0,XPT_BAUDRATE_PRESCALER,
				CS.port, CS.pin, A0.port, A0.pin, RESET.port, RESET.pin);


	ctrl = CTRL_HI_Y | CTRL_LO_SER;
	hal_spi[interface]->receive(1, &ctrl, 2, (unsigned char*) &data, 0,XPT_BAUDRATE_PRESCALER,
			CS.port, CS.pin, A0.port, A0.pin, RESET.port, RESET.pin);

The SPI interface is a bit more complicated, but it sends out (one byte ctrl) of data with the A0 pin down, and 2 bytes of data with the A0 pin high.  No waiting between transmission/reception, CS down for all of it, reset (in this case) not used.  Baudrate is changed per call.  SPI interface is semaphore protected.  OK, so that explains the call. 

Note that in the setup, 0 is written to the chip, then the combination of CTRL_HI_Y and CTRL_LO_SER works out to writing 0xD4 to the chip.

Reading the chip is done by:

// reads data until either max_samples is exceeded or data is stable from one sample to the next
// reads binary xxxx xxxx xxxx 0000
// reading must be stable from one to the next
// note no current delay in readings

uint16_t XPT_2046::_readLoop(uint8_t ctrl, uint8_t max_samples, uint8_t* samples_taken) const
{
	uint16_t prev = {0};
	uint16_t cur = {0};

	uint8_t i = 0;
	uint8_t data[2] = {0};

	// get first reading for comparison
	hal_spi[interface]->receive(1, &ctrl, 2, (unsigned char*) &data, 0,XPT_BAUDRATE_PRESCALER,
			CS.port, CS.pin, A0.port, A0.pin, RESET.port, RESET.pin);

	prev = (data[0] << 8) | data[1];

	// force at least one more read
	cur = 0xffff;

	while ((cur != prev) && (i < max_samples))
	{
		prev = cur;
		hal_spi[interface]->receive(1, &ctrl, 2, (unsigned char*) &data, 0,XPT_BAUDRATE_PRESCALER,
				CS.port, CS.pin, A0.port, A0.pin, RESET.port, RESET.pin);
		cur = (data[0] << 8) | data[1];
//		Delay_us(100);
		vTaskDelay(2);
	}
	*samples_taken = i;
	return cur;
}

 

where the call to readloop is:

#define XPT_IDLE		0xD0
#define XPT_X			0XD3
#define XPT_Y			0X93
#define XPT_Z1			0XB3
#define XPT_Z2			0XC3

// uses bubble sort
#define SAMPLES			10

void XPT_2046::getRaw(int* x, int* y, int* z,  adc_ref_t mode,
		uint8_t max_samples)
{

	uint16_t				SY[SAMPLES] = {0};
	uint16_t				SX[SAMPLES] = {0};
	uint16_t				SZ[SAMPLES] = {0};
	uint16_t				tz1[SAMPLES] = {0};
	uint16_t				tz2[SAMPLES] = {0};
	uint16_t				data[SAMPLES] = {0};
	int						nx = {0}, ny = {0}, nz = {0};

	// Implementation based on TI Technical Note http://www.ti.com/lit/an/sbaa036/sbaa036.pdf

	// preferred mode is differential
	uint8_t	ctrl = {0};
	uint8_t samples_taken = {0};
//	uint8_t data = {0};

	// control mode is Mode (0x3) + control HIX = 0x93 or HIY 0xD3

	// NOTE: actually measures Y (when rotated) since panel is normally vertical to match display design
	// X mode is start bit, A0 selected, 12 bits, DFR, power mode 3

	// NOTE:
	// Y mode is start bit, A2, A0 selected, 12 bits, DFR, power mode 3

	// power mode 3 leaves xpt2046 constantly on


	// NOTE: data is formatted as 12 bits, 4 lsb set to zero

	for (int i = 0; i < SAMPLES; i++)
	{
		SX[i] = (_readLoop(XPT_X, max_samples, &samples_taken)) >> 4;
		SY[i] = (_readLoop(XPT_Y, max_samples, &samples_taken)) >> 4;
		tz1[i] = (_readLoop(XPT_Z1, max_samples, &samples_taken)) >> 4;
		tz2[i] = (_readLoop(XPT_Z2, max_samples, &samples_taken)) >> 4;
		SZ[i] = tz1[i] +  4095 - tz2[i];
		vTaskDelay(2);
	}
	nx =  bubble_sort (SX, SAMPLES);
	ny =  bubble_sort (SY, SAMPLES);
	nz =  bubble_sort (SZ, SAMPLES);

	// Turn off ADC by issuing one more read (throwaway)
	// This needs to be done, because PD=0b11 (needed for MODE_DFR) will disable PENIRQ
	ctrl = 0;
		hal_spi[interface]->receive(1, &ctrl, 2, (unsigned char*) &data, 0,XPT_BAUDRATE_PRESCALER,
				CS.port, CS.pin, A0.port, A0.pin, RESET.port, RESET.pin);


	ctrl = CTRL_HI_Y | CTRL_LO_SER;
	hal_spi[interface]->receive(1, &ctrl, 2, (unsigned char*) &data, 0,XPT_BAUDRATE_PRESCALER,
			CS.port, CS.pin, A0.port, A0.pin, RESET.port, RESET.pin);

	*x = SX[nx];
	*y = SY[ny];
	*z = SZ[nz];
}

 

Not at all sure what your driver does, but I'd consider checking to see if the right parameters are passed.  Note that Idle sets the enable for the chip to generate interrupts, and the measurement modes turn that off.

Can't comment too much more about the modes, but I wonder if the modes are wrong when you're reading. 

I'd also suspect that the constant generation of interrupts is messing up something, but I don't know how interrupts are handled. 

Since I poll for pen down (in a task), then go read data, the question of an IRQ is not relevant.  All I need to ensure is that the display is polled frequently enough.

Hope this helps a bit.

 

 

 

kob77
Associate III

Solution:
The issue lies in generating interrupts during communication with xpt2046. The solution is to disable interrupts before starting the communication. After the communication is finished, we need to clear the pending interrupts and then re-enable them. 
Currently, I am experiencing an issue with communication with xpt2046 from the interrupt handling function. During communication, the STM32 freezes.

The code I use for reading the chip is:

 

// **************************************************************************************************************************
// ************************************************ TOUCH DETECT ************************************************************
// **************************************************************************************************************************




// used IRQ pin, normally high, goes low with touch


bool XPT_2046::is_touch()
{
	if (HAL_GPIO_ReadPin(TOUCH_IRQ_GPIO_Port, TOUCH_IRQ_Pin) == GPIO_PIN_SET)
	{
		return false;
	}
	return true;
}

// **************************************************************************************************************************
// ************************************************ GET DISPLAY TOUCHED NO WAIT *********************************************
// **************************************************************************************************************************

//
// if touch, then return true and  raw data
// if no touch, then return false and P = { 0, 0}
// returns true/false
// DOES NOT WAIT
//
//
//modified for IRQ pin
// returns raw data


bool XPT_2046::get_touch_NOWAIT(tpoint* P)
{
	int			x = {0};
	int			y = {0};
	int			z = {0};


	P->x = -1;
	P->y = -1;
	if (is_touch())
	{
		// noted: default samples = 0xFF
		getPosition(&x, &y, &z, MODE_DFR,20);
		P->x = x & 0xFFF;
		P->y = y & 0xFFF;
		// return true or false depending on z threshold
	}
	else
	{
		return false;
	}
	return true;
}

 

Remember that this is run by a task, which means that the get_touch_NOWAIT routine is called periodically.

You cannot easily DMA or IRQ driven reads for getting the data from the chip, there's no "data ready". 

If you set up a DMA (there's not that much data, and speed is not of the essence), then you set up the DMA for 3 bytes (I think first is thrown away), then you have to poll to see that the DMA is over (or get the right callback).  If you were writing to an SPI display (not memory mapped), then the faster DMA is needed.

Did you enable all the right interrupts if you did DMA?  If you tried IRQ, what was the trigger?

 

 

I'm not currently using dma. I read the data when the touch is activated and an IRQ interrupt is detected. Interrupts seem to be working properly. I set their priority to 14. For some reason unknown to me, the xpt2046 reading functions hang in the interrupt handling function.

Interrupt handling code:

void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{

if(GPIO_Pin == T_IRQ_Pin)
{
was_touched=1;

NVIC_DisableIRQ(EXTI15_10_IRQn);
uint16_t touchx, touchy;
char buffer1[10]=""; 
char buffer2[10]=""; 
TFT_Draw_Fill_Round_Rect (280, 180, 200, 60, 10, 0xCFFF);
touchx = getX();
sprintf(buffer1, "X%d", touchx); 
touchy = getY();
sprintf(buffer2, "Y%d", touchy);

LCD_Font(300, 200, buffer1, _Open_Sans_Bold_28, 1, BLACK);
LCD_Font(300, 220, buffer2, _Open_Sans_Bold_28, 1, BLACK);
//HAL_Delay(100);
XPT2046_Init();//turn on IRQ
__HAL_GPIO_EXTI_CLEAR_IT(T_IRQ_Pin);//clearing interrupt flag
NVIC_EnableIRQ(EXTI15_10_IRQn);

}
}

XPT2046 lib:

#include "xpt2046.h"

#define XPT2046_ADDR_I 0xD0
#define XPT2046_ADDR_X 0b11010001//0xD0
#define XPT2046_ADDR_Y 0b10010001//0x90

extern SPI_HandleTypeDef hspi2;

inline static float remap(float x, float in_min, float in_max, float out_min, float out_max)
{
float new = ((x - in_min) / (in_max - in_min))* (out_max - out_min) + out_min;
return ((x - in_min) / (in_max - in_min))* (out_max - out_min) + out_min;
}

void XPT2046_Init(void)//turn on interrupts
{
uint8_t data;
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_13, GPIO_PIN_RESET);
HAL_Delay(1);
HAL_SPI_Transmit(&hspi2, (uint8_t*)XPT2046_ADDR_I, 1, 1000);
HAL_SPI_TransmitReceive(&hspi2, (uint8_t*)XPT2046_ADDR_I, &data, sizeof(data), 1000);
HAL_SPI_TransmitReceive(&hspi2, (uint8_t*)XPT2046_ADDR_I, &data, sizeof(data), 1000);
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_13, GPIO_PIN_SET);

}

uint16_t getRaw(uint8_t address)
{

uint8_t data;
uint16_t LSB, MSB;
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_13, GPIO_PIN_RESET);
HAL_Delay(1);
HAL_SPI_Transmit(&hspi2, &address, 1, 1000);
address = 0x00;
HAL_SPI_TransmitReceive(&hspi2, &address, &data, sizeof(data), 1000);
MSB = data;
address = 0x00;
HAL_SPI_TransmitReceive(&hspi2, &address, &data, sizeof(data), 1000);
LSB = data;
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_13, GPIO_PIN_SET);
return ((MSB << 8) | (LSB)) >> 3;


}

inline static uint16_t X(void)
{
uint16_t x;
x = (uint16_t) remap(getRaw(XPT2046_ADDR_X), RAW_MIN_X, RAW_MAX_X, OUT_MIN_X, OUT_MAX_X);
if (XPT2046_MIRROR_X) x = OUT_MAX_X - x;
if (x > OUT_MIN_X && x < OUT_MAX_X) return x;
else return 0;
}

inline static uint16_t Y(void)
{
uint16_t y;
y = (uint16_t) remap(getRaw(XPT2046_ADDR_Y), RAW_MIN_Y, RAW_MAX_Y, OUT_MIN_Y, OUT_MAX_Y);
if (XPT2046_MIRROR_Y) y = OUT_MAX_Y - y;
if (y > OUT_MIN_Y && y < OUT_MAX_Y) return y;
else return 0;
}

uint16_t getX(void)
{
if (XPT2046_ACCURACY)
{
uint16_t x[2] = { 1, 2 };
while (x[0] != x[1])
{
if (XPT2046_REVERSED) { x[0] = Y(); x[1] = Y(); }
else { x[0] = X(); x[1] = X(); }
}
return x[0];
}
else if (XPT2046_REVERSED) return Y(); else return X();
}

uint16_t getY(void)
{
if (XPT2046_ACCURACY)
{
uint16_t y[2] = { 1, 2 };
while (y[0] != y[1])
{
if (XPT2046_REVERSED) { y[0] = X(); y[1] = X(); }
else { y[0] = Y(); y[1] = Y(); }
}
return y[0];
}
else if (XPT2046_REVERSED) return X(); else return Y();

}

I can set global variable (was_touched) and read X, Y position in main loop. 

while (1)
{
if(was_touched==1){
was_touched=0;
NVIC_DisableIRQ(EXTI15_10_IRQn);
uint16_t touchx, touchy;
char buffer1[10]=""; 
char buffer2[10]=""; 
TFT_Draw_Fill_Round_Rect (280, 180, 200, 60, 10, 0xCFFF);
touchx = getX();
sprintf(buffer1, "X%d", touchx); 
touchy = getY();
sprintf(buffer2, "Y%d", touchy);

LCD_Font(300, 200, buffer1, _Open_Sans_Bold_28, 1, BLACK);
LCD_Font(300, 220, buffer2, _Open_Sans_Bold_28, 1, BLACK);
HAL_Delay(100);
XPT2046_Init();
__HAL_GPIO_EXTI_CLEAR_IT(T_IRQ_Pin);
NVIC_EnableIRQ(EXTI15_10_IRQn);

if(touchx >=696 && touchx<=696+88 && touchy>=9 && touchy<=9+47)// 696, pos_y, 88, 47,
{
uint16_t counter = TFT_Draw_List(400, 200, 100, "TYPE:", "powitanie", save, _Open_Sans_Bold_14);
//HAL_Delay(1000);//THIS DELAY
TFT_Restore_Area(400, 200, 100, 47+1+34+35*counter, save);
}
}

Unfortunately, there is also a problem here, I cannot insert a delay there because the interrupts start generating spontaneously. Without delay the screen works fine like in the video

There can be two interrupts.  One generated by the chip when PEN_IRQ is active.  For that to work successfully, you either have to disable/reset the PEN_IRQ  once triggered, or you need to do all the data transfer within the interrupt.  Best that within the interrupt, you reset the XPT's IRQ.  The second method is  how you handle the data reads from the XPT.  Since there's no data ready interrupt on a register basis (that I know of), you cannot have an interrupt driven data transfer from the XPT.  Since data is ready once the PEN_IRQ is generated, you can just go read the data.

That's PD1, 0 = 0,0 (PEN_IRQ = DISABLED), and you can read data, I think.  Then PD1, PD0 = 3, which enables everything but keeps PEN_IRQ disabled. 

You don't want the PEN_IRQ low when you enable interrupts, since it'll immediately create an interrupt.  Make sure that the IRQ is edge sensitive, not level sensitive.

 

 

I have a feeling that we don't understand each other :C. Interrupts generated and received are handled correctly (according to my tests - they are triggered when the screen is touched). The problem is that when I am in the interrupt handling function, for some strange reasons, I can't read data from XPT. Trying to communicate with XPT, the program freezes.

The the question becomes (to me) what method of SPI transfer are you using?  Programmed, IRQ driven, DMA?

The programmed option is likely the only one to work.  I have an NRF24L01 that generates an interrupt, and in the callback for the IRQ, I read the chip (SPI) but it is a programmed read, not using DMA or IRQ.

I do as little as possible in that callback.