目录
STM32电阻触摸屏应用
触摸屏原理图
电阻触摸屏控制芯片
XPT2046芯片框图
程序分析
触摸屏硬件相关宏定义
XPT2046 初始化函数
延时函数
写时序
读时序
状态机编程
采集触摸原始数据(读取ADC采样结果)
多次采样求平均值
根据原始数据计算坐标值
触摸校正
触摸坐标获取及处理
main 函数
实验现象
我们使用的板载3.2寸电阻屏的原理图如下所示:
在左边3.2寸TFT中为26-29脚
在实际使用中,TFT左边的X+到Y-四根线与电阻触摸屏控制器的X+到Y-四根线相连,电阻触摸屏控制器的GPIO1到GPIO5连接到排针的GPIO1到GPIO5。
触摸检测的主体是型号为 XPT2046 的芯片,它接收触摸屏的 X+/X-/Y+/Y-信号进行处理,把触摸 信息使用 SPI 接口输出到 STM32 等控制器,注意,由于控制 XPT2046 芯片的并不是 STM32 专 用的硬件 SPI 接口,所以在编写程序时,需要使用软件模拟 SPI 时序与触摸芯片进行通讯。
由于使用软件模拟SPI,所以STM32液晶接口使用了五个普通的GPIO
为了方便检测触摸的坐标,一些芯片厂商制作了电阻屏专用的控制芯片,控制上述采集过程、采集电压,外部微控制器直接与触摸控制芯片通讯直接获得触点的电压或坐标。
秉火3.2寸电阻触摸屏就是采用XPT2046芯片作为触摸控制芯片,XPT2046芯片控制4线电阻触摸屏,STM32与XPT2046采用SPI通讯获取采集得的电压,然后转换成坐标。
XPT2046是专用在四线电阻屏的触摸屏控制器,STM32可通过SPI接口向它写入控制字,由它测得X、Y方向的触点电压返回给STM32。
XPT2046芯片的内部结构框图如下:
由于我们没有使用到AUX引脚,所以我们安装SER/DFR非为低电平时候来配置。
因此可以得到Y+通道的选择控制字为10010000(0x90)
X+通道的选择控制字为11010000(0xD0)
bsp_xpt2046_lcd.h&bsp_xpt2046_lcd.c
根据触摸屏与 STM32 芯片的硬件连接
/******************************* XPT2046 触摸屏触摸信号指示引脚定义(不使用中断) ***************************/
#define XPT2046_PENIRQ_GPIO_CLK RCC_APB2Periph_GPIOE
#define XPT2046_PENIRQ_GPIO_PORT GPIOE
#define XPT2046_PENIRQ_GPIO_PIN GPIO_Pin_4
//触屏信号有效电平
#define XPT2046_PENIRQ_ActiveLevel 0
#define XPT2046_PENIRQ_Read() GPIO_ReadInputDataBit ( XPT2046_PENIRQ_GPIO_PORT, XPT2046_PENIRQ_GPIO_PIN )
/******************************* XPT2046 触摸屏模拟SPI引脚定义 ***************************/
#define XPT2046_SPI_GPIO_CLK RCC_APB2Periph_GPIOE| RCC_APB2Periph_GPIOD
#define XPT2046_SPI_CS_PIN GPIO_Pin_13
#define XPT2046_SPI_CS_PORT GPIOD
#define XPT2046_SPI_CLK_PIN GPIO_Pin_0
#define XPT2046_SPI_CLK_PORT GPIOE
#define XPT2046_SPI_MOSI_PIN GPIO_Pin_2
#define XPT2046_SPI_MOSI_PORT GPIOE
#define XPT2046_SPI_MISO_PIN GPIO_Pin_3
#define XPT2046_SPI_MISO_PORT GPIOE
#define XPT2046_CS_ENABLE() GPIO_SetBits ( XPT2046_SPI_CS_PORT, XPT2046_SPI_CS_PIN )
#define XPT2046_CS_DISABLE() GPIO_ResetBits ( XPT2046_SPI_CS_PORT, XPT2046_SPI_CS_PIN )
#define XPT2046_CLK_HIGH() GPIO_SetBits ( XPT2046_SPI_CLK_PORT, XPT2046_SPI_CLK_PIN )
#define XPT2046_CLK_LOW() GPIO_ResetBits ( XPT2046_SPI_CLK_PORT, XPT2046_SPI_CLK_PIN )
#define XPT2046_MOSI_1() GPIO_SetBits ( XPT2046_SPI_MOSI_PORT, XPT2046_SPI_MOSI_PIN )
#define XPT2046_MOSI_0() GPIO_ResetBits ( XPT2046_SPI_MOSI_PORT, XPT2046_SPI_MOSI_PIN )
#define XPT2046_MISO() GPIO_ReadInputDataBit ( XPT2046_SPI_MISO_PORT, XPT2046_SPI_MISO_PIN )
CLK、CS、MOSI、MISO作为输出模拟SPI时序,INT作为输入检测屏幕是否产生触摸
/**
* @brief XPT2046 初始化函数
* @param 无
* @retval 无
*/
void XPT2046_Init ( void )
{
GPIO_InitTypeDef GPIO_InitStructure;
/* 开启GPIO时钟 */
RCC_APB2PeriphClockCmd ( XPT2046_SPI_GPIO_CLK|XPT2046_PENIRQ_GPIO_CLK, ENABLE );
/* 模拟SPI GPIO初始化 */
GPIO_InitStructure.GPIO_Pin=XPT2046_SPI_CLK_PIN;
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_10MHz ;
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_PP;
GPIO_Init(XPT2046_SPI_CLK_PORT, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = XPT2046_SPI_MOSI_PIN;
GPIO_Init(XPT2046_SPI_MOSI_PORT, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = XPT2046_SPI_MISO_PIN;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_10MHz ;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_Init(XPT2046_SPI_MISO_PORT, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = XPT2046_SPI_CS_PIN;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_10MHz ;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_Init(XPT2046_SPI_CS_PORT, &GPIO_InitStructure);
/* 拉低片选,选择XPT2046 */
XPT2046_CS_DISABLE();
//触摸屏触摸信号指示引脚,不使用中断
GPIO_InitStructure.GPIO_Pin = XPT2046_PENIRQ_GPIO_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; // 上拉输入
GPIO_Init(XPT2046_PENIRQ_GPIO_PORT, &GPIO_InitStructure);
}
/**
* @brief 用于 XPT2046 的简单微秒级延时函数
* @param nCount :延时计数值,单位为微妙
* @retval 无
*/
static void XPT2046_DelayUS ( __IO uint32_t ulCount )
{
uint32_t i;
for ( i = 0; i < ulCount; i ++ )
{
uint8_t uc = 12; //设置值为12,大约延1微秒
while ( uc -- ); //延1微秒
}
}
/**
* @brief XPT2046 的写入命令
* @param ucCmd :命令
* 该参数为以下值之一:
* @arg 0x90 :通道Y+的选择控制字
* @arg 0xd0 :通道X+的选择控制字
* @retval 无
*/
static void XPT2046_WriteCMD ( uint8_t ucCmd )
{
uint8_t i;
XPT2046_MOSI_0();
XPT2046_CLK_LOW();
for ( i = 0; i < 8; i ++ )
{
( ( ucCmd >> ( 7 - i ) ) & 0x01 ) ? XPT2046_MOSI_1() : XPT2046_MOSI_0();
XPT2046_DelayUS ( 5 );
XPT2046_CLK_HIGH();
XPT2046_DelayUS ( 5 );
XPT2046_CLK_LOW();
}
}
在写入命令完成后,紧接着会执行读命令,在读命令前要有一个时钟周期的等待信号。
/**
* @brief XPT2046 的读取命令
* @param 无
* @retval 读取到的数据
*/
static uint16_t XPT2046_ReadCMD ( void )
{
uint8_t i;
uint16_t usBuf=0, usTemp;
XPT2046_MOSI_0();
XPT2046_CLK_HIGH();
for ( i=0;i<12;i++ )
{
XPT2046_CLK_LOW();
usTemp = XPT2046_MISO();
usBuf |= usTemp << ( 11 - i );
XPT2046_CLK_HIGH();
}
return usBuf;
}
SPI 协议的读写时序都比较简单,只要驱动好一个时钟信号传输一个数据位即可,发送数据时使 用 MOSI 引脚输出电平,读取数据时从 MISO 引脚获取状态。
代码中的 XPT2046_WriteCMD 函数主要在后面用于发送控制触摸芯片的命令代码,发送不同的 命令可以控制触摸芯片检测 X 坐标或 Y 坐标的触摸信号,该命令代码一般为 8 个数据位;而 XPT2046_ReadCMD 函数主要在后面用于读取触摸芯片输出的 ADC 电压值,这些 ADC 电压值一 般为 12 个数据位。
电容触摸屏与电子触摸屏不同,电阻触摸屏按下需要进行消抖,而电容触摸屏根据电容特性不需要消抖。
使用状态机的原理,就可以来判断按键或者触摸屏是短按、长按还是双击等等。
通过将状态机函数放在循环里调用(配合定时器来用),实现消抖并判断出长按、短按还是双击等等。
下面是我们定义的状态机函数
//触屏信号有效电平
#define XPT2046_PENIRQ_ActiveLevel 0
#define XPT2046_PENIRQ_Read() GPIO_ReadInputDataBit ( XPT2046_PENIRQ_GPIO_PORT, XPT2046_PENIRQ_GPIO_PIN )
/******触摸状态机相关******/
typedef enum
{
XPT2046_STATE_RELEASE = 0, //触摸释放
XPT2046_STATE_WAITING, //触摸按下
XPT2046_STATE_PRESSED, //触摸按下
}enumTouchState ;
#define TOUCH_PRESSED 1
#define TOUCH_NOT_PRESSED 0
//触摸消抖阈值
#define DURIATION_TIME 2
/**
* @brief 触摸屏检测状态机
* @retval 触摸状态
* 该返回值为以下值之一:
* @arg TOUCH_PRESSED :触摸按下
* @arg TOUCH_NOT_PRESSED :无触摸
*/
uint8_t XPT2046_TouchDetect(void)
{
static enumTouchState touch_state = XPT2046_STATE_RELEASE;
static uint32_t i;
uint8_t detectResult = TOUCH_NOT_PRESSED;
switch(touch_state)
{
case XPT2046_STATE_RELEASE:
if(XPT2046_PENIRQ_Read() == XPT2046_PENIRQ_ActiveLevel) //第一次出现触摸信号
{
touch_state = XPT2046_STATE_WAITING;
detectResult =TOUCH_NOT_PRESSED;
}
else //无触摸
{
touch_state = XPT2046_STATE_RELEASE;
detectResult =TOUCH_NOT_PRESSED;
}
break;
case XPT2046_STATE_WAITING:
if(XPT2046_PENIRQ_Read() == XPT2046_PENIRQ_ActiveLevel)
{
i++;
//等待时间大于阈值则认为触摸被按下
//消抖时间 = DURIATION_TIME * 本函数被调用的时间间隔
//如在定时器中调用,每10ms调用一次,则消抖时间为:DURIATION_TIME*10ms
if(i > DURIATION_TIME)
{
i=0;
touch_state = XPT2046_STATE_PRESSED;
detectResult = TOUCH_PRESSED;
}
else //等待时间累加
{
touch_state = XPT2046_STATE_WAITING;
detectResult = TOUCH_NOT_PRESSED;
}
}
else //等待时间值未达到阈值就为无效电平,当成抖动处理
{
i = 0;
touch_state = XPT2046_STATE_RELEASE;
detectResult = TOUCH_NOT_PRESSED;
}
break;
case XPT2046_STATE_PRESSED:
if(XPT2046_PENIRQ_Read() == XPT2046_PENIRQ_ActiveLevel) //触摸持续按下
{
touch_state = XPT2046_STATE_PRESSED;
detectResult = TOUCH_PRESSED;
}
else //触摸释放
{
touch_state = XPT2046_STATE_RELEASE;
detectResult = TOUCH_NOT_PRESSED;
}
break;
default:
touch_state = XPT2046_STATE_RELEASE;
detectResult = TOUCH_NOT_PRESSED;
break;
}
return detectResult;
}
当触摸屏有触点按下时, PENIRQ 引脚会输出低电平,直到没有触摸的时候,它才会输出高电平; 而且 STM32 的中断只支持边沿触发(上升沿或下降沿),不支持电平触发,在触摸屏上存在类似 机械按键的信号抖动,所以如果使用中断的方式来检测触摸状态并不适合,难以辨别触摸按下及 释放的情况。
状态机编程是一种非常高效的编程方式,它非常适合应用在涉及状态转换的过程控制中,上述代 码采用状态机的编程方式对触摸状态进行检测,主要涉及触摸的按下、消抖及释放这三种状态转 换。在应用时,本函数需要在循环体里调用,或定时调用(如每隔 10ms 调用一次),其状态转换
关系见图触摸检测状态转换图 :
在代码中,通过使用 XPT2046_PENIRQ_Read 函数获取当前 PENIRQ 引脚的电平,再根据当 前的状态决定是否转换进入下一个状态,若经过消抖处理后进入“触摸确认按下/持续按下 (XPT2046_STATE_PRESSED)”状态时,函数会返回 TOUCH_PRESSED 表示触摸被按下,其余状 态返回 TOUCH_NOT_PRESSED 表示触摸无按下或释放状态。代码中的触摸消抖等待状态中,实 质是通过延时、多次检测 PENIRQ 引脚的电平达到消抖的目的,若 XPT2046_TouchDetect 函数每 隔 10ms 被调用一次,那么消抖的延时值则为 DURIATION_TIME*10 毫秒,可以根据实际情况适 当调整该消抖阈值。
利用 XPT2046_WriteCMD 及 XPT2046_ReadCMD 函数,可控制触摸屏检测并获取触摸的原始 ADC 数据。
/******************************* XPT2046 触摸屏参数定义 ***************************/
//校准触摸屏时触摸坐标的AD值相差门限
#define XPT2046_THRESHOLD_CalDiff 2
#define XPT2046_CHANNEL_X 0x90 //通道Y+的选择控制字
#define XPT2046_CHANNEL_Y 0xd0 //通道X+的选择控制字
/**
* @brief 对 XPT2046 选择一个模拟通道后,启动ADC,并返回ADC采样结果
* @param ucChannel
* 该参数为以下值之一:
* @arg 0x90 :通道Y+的选择控制字
* @arg 0xd0 :通道X+的选择控制字
* @retval 该通道的ADC采样结果
*/
static uint16_t XPT2046_ReadAdc ( uint8_t ucChannel )
{
XPT2046_WriteCMD ( ucChannel );
return XPT2046_ReadCMD ();
}
/**
* @brief 读取 XPT2046 的X通道和Y通道的AD值(12 bit,最大是4096)
* @param sX_Ad :存放X通道AD值的地址
* @param sY_Ad :存放Y通道AD值的地址
* @retval 无
*/
static void XPT2046_ReadAdc_XY ( int16_t * sX_Ad, int16_t * sY_Ad )
{
int16_t sX_Ad_Temp, sY_Ad_Temp;
sX_Ad_Temp = XPT2046_ReadAdc ( XPT2046_CHANNEL_X );
XPT2046_DelayUS ( 1 );
sY_Ad_Temp = XPT2046_ReadAdc ( XPT2046_CHANNEL_Y );
* sX_Ad = sX_Ad_Temp;
* sY_Ad = sY_Ad_Temp;
}
为了使得采样更精确,使用多次采样求取平均值(滤波)的方法来采集最终使用的数据。
//校准触摸屏时触摸坐标的AD值相差门限
#define XPT2046_THRESHOLD_CalDiff 2
/**
* @brief 在触摸 XPT2046 屏幕时获取一组坐标的AD值,并对该坐标进行滤波
* @param 无
* @retval 滤波之后的坐标AD值
*/
//注意:校正较精准,但是相对复杂,速度较慢
static uint8_t XPT2046_ReadAdc_Smooth_XY ( strType_XPT2046_Coordinate * pScreenCoordinate )
{
uint8_t ucCount = 0;
int16_t sAD_X, sAD_Y;
int16_t sBufferArray [ 2 ] [ 9 ] = { { 0 }, { 0 } }; //坐标X和Y进行9次采样
int32_t lAverage [ 3 ], lDifference [ 3 ];
do
{
XPT2046_ReadAdc_XY ( & sAD_X, & sAD_Y );
sBufferArray [ 0 ] [ ucCount ] = sAD_X;
sBufferArray [ 1 ] [ ucCount ] = sAD_Y;
ucCount ++;
} while ( ( XPT2046_EXTI_Read() == XPT2046_EXTI_ActiveLevel ) && ( ucCount < 9 ) ); //用户点击触摸屏时即TP_INT_IN信号为低 并且 ucCount<9*/
/*如果触笔弹起*/
if ( XPT2046_EXTI_Read() != XPT2046_EXTI_ActiveLevel )
ucXPT2046_TouchFlag = 0; //触摸中断标志复位
/* 如果成功采样9次,进行滤波 */
if ( ucCount == 9 )
{
/* 为减少运算量,分别分3组取平均值 */
lAverage [ 0 ] = ( sBufferArray [ 0 ] [ 0 ] + sBufferArray [ 0 ] [ 1 ] + sBufferArray [ 0 ] [ 2 ] ) / 3;
lAverage [ 1 ] = ( sBufferArray [ 0 ] [ 3 ] + sBufferArray [ 0 ] [ 4 ] + sBufferArray [ 0 ] [ 5 ] ) / 3;
lAverage [ 2 ] = ( sBufferArray [ 0 ] [ 6 ] + sBufferArray [ 0 ] [ 7 ] + sBufferArray [ 0 ] [ 8 ] ) / 3;
/* 计算3组数据的差值 */
lDifference [ 0 ] = lAverage [ 0 ]-lAverage [ 1 ];
lDifference [ 1 ] = lAverage [ 1 ]-lAverage [ 2 ];
lDifference [ 2 ] = lAverage [ 2 ]-lAverage [ 0 ];
/* 对上述差值取绝对值 */
lDifference [ 0 ] = lDifference [ 0 ]>0?lDifference [ 0 ]: ( -lDifference [ 0 ] );
lDifference [ 1 ] = lDifference [ 1 ]>0?lDifference [ 1 ]: ( -lDifference [ 1 ] );
lDifference [ 2 ] = lDifference [ 2 ]>0?lDifference [ 2 ]: ( -lDifference [ 2 ] );
/* 判断绝对差值是否都超过差值门限,如果这3个绝对差值都超过门限值,则判定这次采样点为野点,抛弃采样点,差值门限取为2 */
if ( lDifference [ 0 ] > XPT2046_THRESHOLD_CalDiff && lDifference [ 1 ] > XPT2046_THRESHOLD_CalDiff && lDifference [ 2 ] > XPT2046_THRESHOLD_CalDiff )
return 0;
/* 计算它们的平均值,同时赋值给strScreenCoordinate */
if ( lDifference [ 0 ] < lDifference [ 1 ] )
{
if ( lDifference [ 2 ] < lDifference [ 0 ] )
pScreenCoordinate ->x = ( lAverage [ 0 ] + lAverage [ 2 ] ) / 2;
else
pScreenCoordinate ->x = ( lAverage [ 0 ] + lAverage [ 1 ] ) / 2;
}
else if ( lDifference [ 2 ] < lDifference [ 1 ] )
pScreenCoordinate -> x = ( lAverage [ 0 ] + lAverage [ 2 ] ) / 2;
else
pScreenCoordinate ->x = ( lAverage [ 1 ] + lAverage [ 2 ] ) / 2;
/* 同上,计算Y的平均值 */
lAverage [ 0 ] = ( sBufferArray [ 1 ] [ 0 ] + sBufferArray [ 1 ] [ 1 ] + sBufferArray [ 1 ] [ 2 ] ) / 3;
lAverage [ 1 ] = ( sBufferArray [ 1 ] [ 3 ] + sBufferArray [ 1 ] [ 4 ] + sBufferArray [ 1 ] [ 5 ] ) / 3;
lAverage [ 2 ] = ( sBufferArray [ 1 ] [ 6 ] + sBufferArray [ 1 ] [ 7 ] + sBufferArray [ 1 ] [ 8 ] ) / 3;
lDifference [ 0 ] = lAverage [ 0 ] - lAverage [ 1 ];
lDifference [ 1 ] = lAverage [ 1 ] - lAverage [ 2 ];
lDifference [ 2 ] = lAverage [ 2 ] - lAverage [ 0 ];
/* 取绝对值 */
lDifference [ 0 ] = lDifference [ 0 ] > 0 ? lDifference [ 0 ] : ( - lDifference [ 0 ] );
lDifference [ 1 ] = lDifference [ 1 ] > 0 ? lDifference [ 1 ] : ( - lDifference [ 1 ] );
lDifference [ 2 ] = lDifference [ 2 ] > 0 ? lDifference [ 2 ] : ( - lDifference [ 2 ] );
if ( lDifference [ 0 ] > XPT2046_THRESHOLD_CalDiff && lDifference [ 1 ] > XPT2046_THRESHOLD_CalDiff && lDifference [ 2 ] > XPT2046_THRESHOLD_CalDiff )
return 0;
if ( lDifference [ 0 ] < lDifference [ 1 ] )
{
if ( lDifference [ 2 ] < lDifference [ 0 ] )
pScreenCoordinate ->y = ( lAverage [ 0 ] + lAverage [ 2 ] ) / 2;
else
pScreenCoordinate ->y = ( lAverage [ 0 ] + lAverage [ 1 ] ) / 2;
}
else if ( lDifference [ 2 ] < lDifference [ 1 ] )
pScreenCoordinate ->y = ( lAverage [ 0 ] + lAverage [ 2 ] ) / 2;
else
pScreenCoordinate ->y = ( lAverage [ 1 ] + lAverage [ 2 ] ) / 2;
return 1;
}
else if ( ucCount > 1 )
{
pScreenCoordinate ->x = sBufferArray [ 0 ] [ 0 ];
pScreenCoordinate ->y = sBufferArray [ 1 ] [ 0 ];
return 0;
}
return 0;
}
本函数有一个输入参数 strType_XPT2046_Coordinate 类型的结构体,它主要包含 x/y/pre_x/pre_y 四个结构体成员,其中 x/y 是用来存储最新的触摸参数值的,而 pre_x/pre_y 用于存储上一次的触 摸点。本函数中仅使用了 x/y 结构体成员值,且使用它存储的是触摸屏的原始触摸数据,即 ADC值。 代码中对 X、 Y 坐标各采样 10 次,然后去除极大极小值后再取平均,计算结果即存储在结构体 中的 x/y 成员值中。
由 XPT2046_ReadAdc_Smooth_XY 函数得到触摸原始数据后,再使用XPT2046_Get_TouchedPoint 即可计算出对应的触摸坐标。
typedef struct //校准系数结构体(最终使用)
{
float dX_X,
dX_Y,
dX,
dY_X,
dY_Y,
dY;
} strType_XPT2046_TouchPara;
//默认触摸参数,不同的屏幕稍有差异,可重新调用触摸校准函数获取
strType_XPT2046_TouchPara strXPT2046_TouchPara[] = {
-0.006464, -0.073259, 280.358032, 0.074878, 0.002052, -6.545977,//扫描方式0
0.086314, 0.001891, -12.836658, -0.003722, -0.065799, 254.715714,//扫描方式1
0.002782, 0.061522, -11.595689, 0.083393, 0.005159, -15.650089,//扫描方式2
0.089743, -0.000289, -20.612209, -0.001374, 0.064451, -16.054003,//扫描方式3
0.000767, -0.068258, 250.891769, -0.085559, -0.000195, 334.747650,//扫描方式4
-0.084744, 0.000047, 323.163147, -0.002109, -0.066371, 260.985809,//扫描方式5
-0.001848, 0.066984, -12.807136, -0.084858, -0.000805, 333.395386,//扫描方式6
-0.085470, -0.000876, 334.023163, -0.003390, 0.064725, -6.211169,//扫描方式7
};
/**
* @brief 获取 XPT2046 触摸点(校准后)的坐标
* @param pDisplayCoordinate :该指针存放获取到的触摸点坐标
* @param pTouchPara:坐标校准系数
* @retval 获取情况
* 该返回值为以下值之一:
* @arg 1 :获取成功
* @arg 0 :获取失败
*/
uint8_t XPT2046_Get_TouchedPoint ( strType_XPT2046_Coordinate * pDisplayCoordinate, strType_XPT2046_TouchPara * pTouchPara )
{
uint8_t ucRet = 1; //若正常,则返回0
strType_XPT2046_Coordinate strScreenCoordinate;
if ( XPT2046_ReadAdc_Smooth_XY ( & strScreenCoordinate ) )
{
pDisplayCoordinate ->x = ( ( pTouchPara[LCD_SCAN_MODE].dX_X * strScreenCoordinate.x ) + ( pTouchPara[LCD_SCAN_MODE].dX_Y * strScreenCoordinate.y ) + pTouchPara[LCD_SCAN_MODE].dX );
pDisplayCoordinate ->y = ( ( pTouchPara[LCD_SCAN_MODE].dY_X * strScreenCoordinate.x ) + ( pTouchPara[LCD_SCAN_MODE].dY_Y * strScreenCoordinate.y ) + pTouchPara[LCD_SCAN_MODE].dY );
}
else ucRet = 0; //如果获取的触点信息有误,则返回0
return ucRet;
}
在实际应用中,并不会使用前面介绍触摸原理时讲解的直接按比例运算把 触摸原始数据物理坐 标转换成与液晶屏像素对应的 XY 逻辑坐标(如触摸屏输出的原始数据范围为 0-2045,液晶屏的 像素 XY 坐标为 0-239 及 0-319),那种直接转换的方式误差比较大,所以通常会采用“多点触摸 校正法”来转换坐标,使用这种方式时,在应用前需要校正屏幕。校正时,使用液晶屏在特定的 位置显示几个点要求用户点击,根据触摸校准算法的数学关系把逻辑坐标与物理坐标转换公式 的各个系数计算出来。
这些触摸转换系数,在我们上述代码中使用 strType_XPT2046_TouchPara 类型来存储,一共有 6 个系数。利用这个数据类型,代码中定义了一个数组 strXPT2046_TouchPara,它存储了液晶屏在 8 个扫描方向时使用的转换系数,这些系数是我编写代码时使用某个液晶屏测试出来的,作为默 认转换系数,不同的液晶屏这些转换系数稍有差异,若在实际使用中你感觉触摸不准确,可以使 用校准函数 XPT2046_Touch_Calibrate 来重新计算自己屏幕的转换系数。
而本代码中列出的 XPT2046_Get_TouchedPoint 本函数可利用两用的转换系数计算出当前的触摸 逻辑坐标。它有两个输入参数,一个参数 pDisplayCoordinate 用于存储计算后得到的触摸逻辑坐 标,作为计算输出,这坐标与液晶屏对应;而参数 pTouchPara 即为校准系数,作为计算输入。在 函数的内部,它先调用 XPT2046_ReadAdc_Smooth_XY 检测触摸点的原始数据物理坐标,然后代 入公式中计算输出逻辑坐标。
触摸校正函数 XPT2046_Touch_Calibrate 的代码涉及到的都是数学函数映射关系的运算,比较复杂,此处作原理讲解,在工程应用需要校正时,采用Calibrate_or_Get_TouchParaWithFlash函数。
//触摸参数写到FLASH里的标志
#define FLASH_TOUCH_PARA_FLAG_VALUE 0xA5
//触摸标志写到FLASH里的地址
#define FLASH_TOUCH_PARA_FLAG_ADDR (1*1024)
//触摸参数写到FLASH里的地址
#define FLASH_TOUCH_PARA_ADDR (2*1024)
/**
* @brief 从FLASH中获取 或 重新校正触摸参数(校正后会写入到SPI FLASH中)
* @note 若FLASH中从未写入过触摸参数,
* 会触发校正程序校正LCD_Mode指定模式的触摸参数,此时其它模式写入默认值
*
* 若FLASH中已有触摸参数,且不强制重新校正
* 会直接使用FLASH里的触摸参数值
*
* 每次校正时只会更新指定的LCD_Mode模式的触摸参数,其它模式的不变
* @note 本函数调用后会把液晶模式设置为LCD_Mode
*
* @param LCD_Mode:要校正触摸参数的液晶模式
* @param forceCal:是否强制重新校正参数,可以为以下值:
* @arg 1:强制重新校正
* @arg 0:只有当FLASH中不存在触摸参数标志时才重新校正
* @retval 无
*/
void Calibrate_or_Get_TouchParaWithFlash(uint8_t LCD_Mode,uint8_t forceCal)
{
uint8_t para_flag=0;
//初始化FLASH
SPI_FLASH_Init();
//读取触摸参数标志
SPI_FLASH_BufferRead(¶_flag,FLASH_TOUCH_PARA_FLAG_ADDR,1);
//若不存在标志或florceCal=1时,重新校正参数
if(para_flag != FLASH_TOUCH_PARA_FLAG_VALUE | forceCal ==1)
{
//若标志存在,说明原本FLASH内有触摸参数,
//先读回所有LCD模式的参数值,以便稍后强制更新时只更新指定LCD模式的参数,其它模式的不变
if( para_flag == FLASH_TOUCH_PARA_FLAG_VALUE && forceCal == 1)
{
SPI_FLASH_BufferRead((uint8_t *)&strXPT2046_TouchPara,FLASH_TOUCH_PARA_ADDR,4*6*8);
}
//等待触摸屏校正完毕,更新指定LCD模式的触摸参数值
while( ! XPT2046_Touch_Calibrate (LCD_Mode) );
//擦除扇区
SPI_FLASH_SectorErase(0);
//设置触摸参数标志
para_flag = FLASH_TOUCH_PARA_FLAG_VALUE;
//写入触摸参数标志
SPI_FLASH_BufferWrite(¶_flag,FLASH_TOUCH_PARA_FLAG_ADDR,1);
//写入最新的触摸参数
SPI_FLASH_BufferWrite((uint8_t *)&strXPT2046_TouchPara,FLASH_TOUCH_PARA_ADDR,4*6*8);
}
else //若标志存在且不强制校正,则直接从FLASH中读取
{
SPI_FLASH_BufferRead((uint8_t *)&strXPT2046_TouchPara,FLASH_TOUCH_PARA_ADDR,4*6*8);
#if 0 //输出调试信息,注意要初始化串口
{
uint8_t para_flag=0,i;
float *ulHeadAddres ;
/* 打印校校准系数 */
XPT2046_INFO ( "从FLASH里读取得的校准系数如下:" );
ulHeadAddres = ( float* ) ( & strXPT2046_TouchPara );
for ( i = 0; i < 6*8; i ++ )
{
if(i%6==0)
printf("\r\n");
printf ( "%12f,", *ulHeadAddres );
ulHeadAddres++;
}
printf("\r\n");
}
#endif
}
}
typedef struct //液晶坐标结构体
{
/*负数值表示无新数据*/
int16_t x; //记录最新的触摸参数值
int16_t y;
/*用于记录连续触摸时(长按)的上一次触摸位置*/
int16_t pre_x;
int16_t pre_y;
} strType_XPT2046_Coordinate;
/**
* @brief 触摸屏被按下的时候会调用本函数
* @param touch包含触摸坐标的结构体
* @note 请在本函数中编写自己的触摸按下处理应用
* @retval 无
*/
void XPT2046_TouchDown(strType_XPT2046_Coordinate * touch)
{
//若为负值表示之前已处理过
if(touch->pre_x == -1 && touch->pre_x == -1)
return;
/***在此处编写自己的触摸按下处理应用***/
/*处理触摸画板的选择按钮*/
Touch_Button_Down(touch->x,touch->y);
/*处理描绘轨迹*/
Draw_Trail(touch->pre_x,touch->pre_y,touch->x,touch->y,&brush);
/***在上面编写自己的触摸按下处理应用***/
}
/**
* @brief 触摸屏释放的时候会调用本函数
* @param touch包含触摸坐标的结构体
* @note 请在本函数中编写自己的触摸释放处理应用
* @retval 无
*/
void XPT2046_TouchUp(strType_XPT2046_Coordinate * touch)
{
//若为负值表示之前已处理过
if(touch->pre_x == -1 && touch->pre_x == -1)
return;
/***在此处编写自己的触摸释放处理应用***/
/*处理触摸画板的选择按钮*/
Touch_Button_Up(touch->pre_x,touch->pre_y);
/***在上面编写自己的触摸释放处理应用***/
}
/**
* @brief 检测到触摸中断时调用的处理函数,通过它调用tp_down 和tp_up汇报触摸点
* @note 本函数需要在while循环里被调用,也可使用定时器定时调用
* 例如,可以每隔5ms调用一次,消抖阈值宏DURIATION_TIME可设置为2,这样每秒最多可以检测100个点。
* 可在XPT2046_TouchDown及XPT2046_TouchUp函数中编写自己的触摸应用
* @param none
* @retval none
*/
void XPT2046_TouchEvenHandler(void )
{
static strType_XPT2046_Coordinate cinfo={-1,-1,-1,-1};
if(XPT2046_TouchDetect() == TOUCH_PRESSED)
{
LED_GREEN;
//获取触摸坐标
XPT2046_Get_TouchedPoint(&cinfo,strXPT2046_TouchPara);
//输出调试信息到串口
XPT2046_DEBUG("x=%d,y=%d",cinfo.x,cinfo.y);
//调用触摸被按下时的处理函数,可在该函数编写自己的触摸按下处理过程
XPT2046_TouchDown(&cinfo);
/*更新触摸信息到pre xy*/
cinfo.pre_x = cinfo.x; cinfo.pre_y = cinfo.y;
}
else
{
LED_BLUE;
//调用触摸被释放时的处理函数,可在该函数编写自己的触摸释放处理过程
XPT2046_TouchUp(&cinfo);
/*触笔释放,把 xy 重置为负*/
cinfo.x = -1;
cinfo.y = -1;
cinfo.pre_x = -1;
cinfo.pre_y = -1;
}
}
由于 XPT2046_TouchEvenHandler 函数带有 XPT2046_TouchDetect 状态机检测,所以它需要被循 环或定时调用,以实现状态转换。当确认有触摸按下时,它会调用 XPT2046_Get_TouchedPoint 获 取当前触摸坐标,并使用 XPT2046_TouchDown 函数根据触摸坐标进行处理,当触摸释放时,调 用 XPT2046_TouchUp 函数处理释放坐标。
XPT2046_TouchDown 和 XPT2046_TouchUp 函数是一个接口,用户可以根据自己的应用编写相应 的触摸处理程序,把触摸按下和释放的处理加入到上述函数即可。
#include "stm32f10x.h"
#include "./usart/bsp_usart.h"
#include "./lcd/bsp_ili9341_lcd.h"
#include "./lcd/bsp_xpt2046_lcd.h"
#include "./flash/bsp_spi_flash.h"
#include "./led/bsp_led.h"
#include "palette.h"
#include
int main(void)
{
//LCD 初始化
ILI9341_Init();
//触摸屏初始化
XPT2046_Init();
//从FLASH里获取校正参数,若FLASH无参数,则使用模式3进行校正
Calibrate_or_Get_TouchParaWithFlash(3,0);
/* USART config */
USART_Config();
LED_GPIO_Config();
printf("\r\n ********** 触摸画板程序 *********** \r\n");
printf("\r\n 若汉字显示不正常,请阅读工程中的readme.txt文件说明,根据要求给FLASH重刷字模数据\r\n");
//其中0、3、5、6 模式适合从左至右显示文字,
//不推荐使用其它模式显示文字 其它模式显示文字会有镜像效果
//其中 6 模式为大部分液晶例程的默认显示方向
ILI9341_GramScan ( 3 );
//绘制触摸画板界面
Palette_Init(LCD_SCAN_MODE);
while ( 1 )
{
//触摸检测函数,本函数至少10ms调用一次
XPT2046_TouchEvenHandler();
}
}
main 函 数 中 使 用 XPT2046_Init 初 始 化 触 摸 屏 相 关 的 引 脚, 然 后 调 用 Calibrate_or_Get_TouchParaWithFlash 进行触摸校正;关于触摸画板应用程序的初始化,都包含 在 Palette_Init 函数中,它会绘制触摸画板的按钮和白板界面;在 main 函数的 while 循环里调用了 XPT2046_TouchEvenHandler 函数,以实现状态机检测和对触摸进行处理,当有触摸按下和释放 时,都通过其内部调用的 XPT2046_TouchDown 和 XPT2046_TouchUp 函数完成画板相关的操作。