赞
踩
目录
我们使用的板载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 <string.h>
-
-
-
-
- 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 函数完成画板相关的操作。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。