当前位置:   article > 正文

STM32电阻触摸屏应用_stm32 电阻屏

stm32 电阻屏

目录

STM32电阻触摸屏应用

触摸屏原理图

电阻触摸屏控制芯片

XPT2046芯片框图

程序分析

触摸屏硬件相关宏定义

XPT2046 初始化函数

延时函数

 写时序

读时序

状态机编程

采集触摸原始数据(读取ADC采样结果)

多次采样求平均值

根据原始数据计算坐标值

触摸校正

触摸坐标获取及处理

main 函数

实验现象


STM32电阻触摸屏应用

触摸屏原理图

我们使用的板载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芯片框图

XPT2046芯片的内部结构框图如下:

由于我们没有使用到AUX引脚,所以我们安装SER/DFR非为低电平时候来配置。

因此可以得到Y+通道的选择控制字为10010000(0x90)

X+通道的选择控制字为11010000(0xD0)

程序分析

触摸屏硬件相关宏定义

bsp_xpt2046_lcd.h&bsp_xpt2046_lcd.c

根据触摸屏与 STM32 芯片的硬件连接

  1. /******************************* XPT2046 触摸屏触摸信号指示引脚定义(不使用中断) ***************************/
  2. #define XPT2046_PENIRQ_GPIO_CLK RCC_APB2Periph_GPIOE
  3. #define XPT2046_PENIRQ_GPIO_PORT GPIOE
  4. #define XPT2046_PENIRQ_GPIO_PIN GPIO_Pin_4
  5. //触屏信号有效电平
  6. #define XPT2046_PENIRQ_ActiveLevel 0
  7. #define XPT2046_PENIRQ_Read() GPIO_ReadInputDataBit ( XPT2046_PENIRQ_GPIO_PORT, XPT2046_PENIRQ_GPIO_PIN )
  8. /******************************* XPT2046 触摸屏模拟SPI引脚定义 ***************************/
  9. #define XPT2046_SPI_GPIO_CLK RCC_APB2Periph_GPIOE| RCC_APB2Periph_GPIOD
  10. #define XPT2046_SPI_CS_PIN GPIO_Pin_13
  11. #define XPT2046_SPI_CS_PORT GPIOD
  12. #define XPT2046_SPI_CLK_PIN GPIO_Pin_0
  13. #define XPT2046_SPI_CLK_PORT GPIOE
  14. #define XPT2046_SPI_MOSI_PIN GPIO_Pin_2
  15. #define XPT2046_SPI_MOSI_PORT GPIOE
  16. #define XPT2046_SPI_MISO_PIN GPIO_Pin_3
  17. #define XPT2046_SPI_MISO_PORT GPIOE
  18. #define XPT2046_CS_ENABLE() GPIO_SetBits ( XPT2046_SPI_CS_PORT, XPT2046_SPI_CS_PIN )
  19. #define XPT2046_CS_DISABLE() GPIO_ResetBits ( XPT2046_SPI_CS_PORT, XPT2046_SPI_CS_PIN )
  20. #define XPT2046_CLK_HIGH() GPIO_SetBits ( XPT2046_SPI_CLK_PORT, XPT2046_SPI_CLK_PIN )
  21. #define XPT2046_CLK_LOW() GPIO_ResetBits ( XPT2046_SPI_CLK_PORT, XPT2046_SPI_CLK_PIN )
  22. #define XPT2046_MOSI_1() GPIO_SetBits ( XPT2046_SPI_MOSI_PORT, XPT2046_SPI_MOSI_PIN )
  23. #define XPT2046_MOSI_0() GPIO_ResetBits ( XPT2046_SPI_MOSI_PORT, XPT2046_SPI_MOSI_PIN )
  24. #define XPT2046_MISO() GPIO_ReadInputDataBit ( XPT2046_SPI_MISO_PORT, XPT2046_SPI_MISO_PIN )

XPT2046 初始化函数

CLK、CS、MOSI、MISO作为输出模拟SPI时序,INT作为输入检测屏幕是否产生触摸

  1. /**
  2. * @brief XPT2046 初始化函数
  3. * @param 无
  4. * @retval 无
  5. */
  6. void XPT2046_Init ( void )
  7. {
  8. GPIO_InitTypeDef GPIO_InitStructure;
  9. /* 开启GPIO时钟 */
  10. RCC_APB2PeriphClockCmd ( XPT2046_SPI_GPIO_CLK|XPT2046_PENIRQ_GPIO_CLK, ENABLE );
  11. /* 模拟SPI GPIO初始化 */
  12. GPIO_InitStructure.GPIO_Pin=XPT2046_SPI_CLK_PIN;
  13. GPIO_InitStructure.GPIO_Speed=GPIO_Speed_10MHz ;
  14. GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_PP;
  15. GPIO_Init(XPT2046_SPI_CLK_PORT, &GPIO_InitStructure);
  16. GPIO_InitStructure.GPIO_Pin = XPT2046_SPI_MOSI_PIN;
  17. GPIO_Init(XPT2046_SPI_MOSI_PORT, &GPIO_InitStructure);
  18. GPIO_InitStructure.GPIO_Pin = XPT2046_SPI_MISO_PIN;
  19. GPIO_InitStructure.GPIO_Speed = GPIO_Speed_10MHz ;
  20. GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
  21. GPIO_Init(XPT2046_SPI_MISO_PORT, &GPIO_InitStructure);
  22. GPIO_InitStructure.GPIO_Pin = XPT2046_SPI_CS_PIN;
  23. GPIO_InitStructure.GPIO_Speed = GPIO_Speed_10MHz ;
  24. GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
  25. GPIO_Init(XPT2046_SPI_CS_PORT, &GPIO_InitStructure);
  26. /* 拉低片选,选择XPT2046 */
  27. XPT2046_CS_DISABLE();
  28. //触摸屏触摸信号指示引脚,不使用中断
  29. GPIO_InitStructure.GPIO_Pin = XPT2046_PENIRQ_GPIO_PIN;
  30. GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; // 上拉输入
  31. GPIO_Init(XPT2046_PENIRQ_GPIO_PORT, &GPIO_InitStructure);
  32. }

延时函数

  1. /**
  2. * @brief 用于 XPT2046 的简单微秒级延时函数
  3. * @param nCount :延时计数值,单位为微妙
  4. * @retval 无
  5. */
  6. static void XPT2046_DelayUS ( __IO uint32_t ulCount )
  7. {
  8. uint32_t i;
  9. for ( i = 0; i < ulCount; i ++ )
  10. {
  11. uint8_t uc = 12; //设置值为12,大约延1微秒
  12. while ( uc -- ); //延1微秒
  13. }
  14. }

 写时序

  1. /**
  2. * @brief XPT2046 的写入命令
  3. * @param ucCmd :命令
  4. * 该参数为以下值之一:
  5. * @arg 0x90 :通道Y+的选择控制字
  6. * @arg 0xd0 :通道X+的选择控制字
  7. * @retval 无
  8. */
  9. static void XPT2046_WriteCMD ( uint8_t ucCmd )
  10. {
  11. uint8_t i;
  12. XPT2046_MOSI_0();
  13. XPT2046_CLK_LOW();
  14. for ( i = 0; i < 8; i ++ )
  15. {
  16. ( ( ucCmd >> ( 7 - i ) ) & 0x01 ) ? XPT2046_MOSI_1() : XPT2046_MOSI_0();
  17. XPT2046_DelayUS ( 5 );
  18. XPT2046_CLK_HIGH();
  19. XPT2046_DelayUS ( 5 );
  20. XPT2046_CLK_LOW();
  21. }
  22. }

在写入命令完成后,紧接着会执行读命令,在读命令前要有一个时钟周期的等待信号。

读时序

  1. /**
  2. * @brief XPT2046 的读取命令
  3. * @param 无
  4. * @retval 读取到的数据
  5. */
  6. static uint16_t XPT2046_ReadCMD ( void )
  7. {
  8. uint8_t i;
  9. uint16_t usBuf=0, usTemp;
  10. XPT2046_MOSI_0();
  11. XPT2046_CLK_HIGH();
  12. for ( i=0;i<12;i++ )
  13. {
  14. XPT2046_CLK_LOW();
  15. usTemp = XPT2046_MISO();
  16. usBuf |= usTemp << ( 11 - i );
  17. XPT2046_CLK_HIGH();
  18. }
  19. return usBuf;
  20. }

        SPI 协议的读写时序都比较简单,只要驱动好一个时钟信号传输一个数据位即可,发送数据时使 用 MOSI 引脚输出电平,读取数据时从 MISO 引脚获取状态。

        代码中的 XPT2046_WriteCMD 函数主要在后面用于发送控制触摸芯片的命令代码,发送不同的 命令可以控制触摸芯片检测 X 坐标或 Y 坐标的触摸信号,该命令代码一般为 8 个数据位;而 XPT2046_ReadCMD 函数主要在后面用于读取触摸芯片输出的 ADC 电压值,这些 ADC 电压值一 般为 12 个数据位。

状态机编程

        电容触摸屏与电子触摸屏不同,电阻触摸屏按下需要进行消抖,而电容触摸屏根据电容特性不需要消抖。

        使用状态机的原理,就可以来判断按键或者触摸屏是短按、长按还是双击等等。

        通过将状态机函数放在循环里调用(配合定时器来用),实现消抖并判断出长按、短按还是双击等等。

下面是我们定义的状态机函数

 

  1. //触屏信号有效电平
  2. #define XPT2046_PENIRQ_ActiveLevel 0
  3. #define XPT2046_PENIRQ_Read() GPIO_ReadInputDataBit ( XPT2046_PENIRQ_GPIO_PORT, XPT2046_PENIRQ_GPIO_PIN )
  4. /******触摸状态机相关******/
  5. typedef enum
  6. {
  7. XPT2046_STATE_RELEASE = 0, //触摸释放
  8. XPT2046_STATE_WAITING, //触摸按下
  9. XPT2046_STATE_PRESSED, //触摸按下
  10. }enumTouchState ;
  11. #define TOUCH_PRESSED 1
  12. #define TOUCH_NOT_PRESSED 0
  13. //触摸消抖阈值
  14. #define DURIATION_TIME 2
  15. /**
  16. * @brief 触摸屏检测状态机
  17. * @retval 触摸状态
  18. * 该返回值为以下值之一:
  19. * @arg TOUCH_PRESSED :触摸按下
  20. * @arg TOUCH_NOT_PRESSED :无触摸
  21. */
  22. uint8_t XPT2046_TouchDetect(void)
  23. {
  24. static enumTouchState touch_state = XPT2046_STATE_RELEASE;
  25. static uint32_t i;
  26. uint8_t detectResult = TOUCH_NOT_PRESSED;
  27. switch(touch_state)
  28. {
  29. case XPT2046_STATE_RELEASE:
  30. if(XPT2046_PENIRQ_Read() == XPT2046_PENIRQ_ActiveLevel) //第一次出现触摸信号
  31. {
  32. touch_state = XPT2046_STATE_WAITING;
  33. detectResult =TOUCH_NOT_PRESSED;
  34. }
  35. else //无触摸
  36. {
  37. touch_state = XPT2046_STATE_RELEASE;
  38. detectResult =TOUCH_NOT_PRESSED;
  39. }
  40. break;
  41. case XPT2046_STATE_WAITING:
  42. if(XPT2046_PENIRQ_Read() == XPT2046_PENIRQ_ActiveLevel)
  43. {
  44. i++;
  45. //等待时间大于阈值则认为触摸被按下
  46. //消抖时间 = DURIATION_TIME * 本函数被调用的时间间隔
  47. //如在定时器中调用,每10ms调用一次,则消抖时间为:DURIATION_TIME*10ms
  48. if(i > DURIATION_TIME)
  49. {
  50. i=0;
  51. touch_state = XPT2046_STATE_PRESSED;
  52. detectResult = TOUCH_PRESSED;
  53. }
  54. else //等待时间累加
  55. {
  56. touch_state = XPT2046_STATE_WAITING;
  57. detectResult = TOUCH_NOT_PRESSED;
  58. }
  59. }
  60. else //等待时间值未达到阈值就为无效电平,当成抖动处理
  61. {
  62. i = 0;
  63. touch_state = XPT2046_STATE_RELEASE;
  64. detectResult = TOUCH_NOT_PRESSED;
  65. }
  66. break;
  67. case XPT2046_STATE_PRESSED:
  68. if(XPT2046_PENIRQ_Read() == XPT2046_PENIRQ_ActiveLevel) //触摸持续按下
  69. {
  70. touch_state = XPT2046_STATE_PRESSED;
  71. detectResult = TOUCH_PRESSED;
  72. }
  73. else //触摸释放
  74. {
  75. touch_state = XPT2046_STATE_RELEASE;
  76. detectResult = TOUCH_NOT_PRESSED;
  77. }
  78. break;
  79. default:
  80. touch_state = XPT2046_STATE_RELEASE;
  81. detectResult = TOUCH_NOT_PRESSED;
  82. break;
  83. }
  84. return detectResult;
  85. }

        当触摸屏有触点按下时, PENIRQ 引脚会输出低电平,直到没有触摸的时候,它才会输出高电平; 而且 STM32 的中断只支持边沿触发(上升沿或下降沿),不支持电平触发,在触摸屏上存在类似 机械按键的信号抖动,所以如果使用中断的方式来检测触摸状态并不适合,难以辨别触摸按下及 释放的情况。

        状态机编程是一种非常高效的编程方式,它非常适合应用在涉及状态转换的过程控制中,上述代 码采用状态机的编程方式对触摸状态进行检测,主要涉及触摸的按下、消抖及释放这三种状态转 换。在应用时,本函数需要在循环体里调用,或定时调用(如每隔 10ms 调用一次),其状态转换

关系见图触摸检测状态转换图 :

        在代码中,通过使用 XPT2046_PENIRQ_Read 函数获取当前 PENIRQ 引脚的电平,再根据当 前的状态决定是否转换进入下一个状态,若经过消抖处理后进入“触摸确认按下/持续按下 (XPT2046_STATE_PRESSED)”状态时,函数会返回 TOUCH_PRESSED 表示触摸被按下,其余状 态返回 TOUCH_NOT_PRESSED 表示触摸无按下或释放状态。代码中的触摸消抖等待状态中,实 质是通过延时、多次检测 PENIRQ 引脚的电平达到消抖的目的,若 XPT2046_TouchDetect 函数每 隔 10ms 被调用一次,那么消抖的延时值则为 DURIATION_TIME*10 毫秒,可以根据实际情况适 当调整该消抖阈值。

采集触摸原始数据(读取ADC采样结果)

        利用 XPT2046_WriteCMD 及 XPT2046_ReadCMD 函数,可控制触摸屏检测并获取触摸的原始 ADC 数据。

  1. /******************************* XPT2046 触摸屏参数定义 ***************************/
  2. //校准触摸屏时触摸坐标的AD值相差门限
  3. #define XPT2046_THRESHOLD_CalDiff 2
  4. #define XPT2046_CHANNEL_X 0x90 //通道Y+的选择控制字
  5. #define XPT2046_CHANNEL_Y 0xd0 //通道X+的选择控制字
  6. /**
  7. * @brief 对 XPT2046 选择一个模拟通道后,启动ADC,并返回ADC采样结果
  8. * @param ucChannel
  9. * 该参数为以下值之一:
  10. * @arg 0x90 :通道Y+的选择控制字
  11. * @arg 0xd0 :通道X+的选择控制字
  12. * @retval 该通道的ADC采样结果
  13. */
  14. static uint16_t XPT2046_ReadAdc ( uint8_t ucChannel )
  15. {
  16. XPT2046_WriteCMD ( ucChannel );
  17. return XPT2046_ReadCMD ();
  18. }
  19. /**
  20. * @brief 读取 XPT2046 的X通道和Y通道的AD值(12 bit,最大是4096)
  21. * @param sX_Ad :存放X通道AD值的地址
  22. * @param sY_Ad :存放Y通道AD值的地址
  23. * @retval 无
  24. */
  25. static void XPT2046_ReadAdc_XY ( int16_t * sX_Ad, int16_t * sY_Ad )
  26. {
  27. int16_t sX_Ad_Temp, sY_Ad_Temp;
  28. sX_Ad_Temp = XPT2046_ReadAdc ( XPT2046_CHANNEL_X );
  29. XPT2046_DelayUS ( 1 );
  30. sY_Ad_Temp = XPT2046_ReadAdc ( XPT2046_CHANNEL_Y );
  31. * sX_Ad = sX_Ad_Temp;
  32. * sY_Ad = sY_Ad_Temp;
  33. }

多次采样求平均值

为了使得采样更精确,使用多次采样求取平均值(滤波)的方法来采集最终使用的数据。

  1. //校准触摸屏时触摸坐标的AD值相差门限
  2. #define XPT2046_THRESHOLD_CalDiff 2
  3. /**
  4. * @brief 在触摸 XPT2046 屏幕时获取一组坐标的AD值,并对该坐标进行滤波
  5. * @param 无
  6. * @retval 滤波之后的坐标AD值
  7. */
  8. //注意:校正较精准,但是相对复杂,速度较慢
  9. static uint8_t XPT2046_ReadAdc_Smooth_XY ( strType_XPT2046_Coordinate * pScreenCoordinate )
  10. {
  11. uint8_t ucCount = 0;
  12. int16_t sAD_X, sAD_Y;
  13. int16_t sBufferArray [ 2 ] [ 9 ] = { { 0 }, { 0 } }; //坐标X和Y进行9次采样
  14. int32_t lAverage [ 3 ], lDifference [ 3 ];
  15. do
  16. {
  17. XPT2046_ReadAdc_XY ( & sAD_X, & sAD_Y );
  18. sBufferArray [ 0 ] [ ucCount ] = sAD_X;
  19. sBufferArray [ 1 ] [ ucCount ] = sAD_Y;
  20. ucCount ++;
  21. } while ( ( XPT2046_EXTI_Read() == XPT2046_EXTI_ActiveLevel ) && ( ucCount < 9 ) ); //用户点击触摸屏时即TP_INT_IN信号为低 并且 ucCount<9*/
  22. /*如果触笔弹起*/
  23. if ( XPT2046_EXTI_Read() != XPT2046_EXTI_ActiveLevel )
  24. ucXPT2046_TouchFlag = 0; //触摸中断标志复位
  25. /* 如果成功采样9次,进行滤波 */
  26. if ( ucCount == 9 )
  27. {
  28. /* 为减少运算量,分别分3组取平均值 */
  29. lAverage [ 0 ] = ( sBufferArray [ 0 ] [ 0 ] + sBufferArray [ 0 ] [ 1 ] + sBufferArray [ 0 ] [ 2 ] ) / 3;
  30. lAverage [ 1 ] = ( sBufferArray [ 0 ] [ 3 ] + sBufferArray [ 0 ] [ 4 ] + sBufferArray [ 0 ] [ 5 ] ) / 3;
  31. lAverage [ 2 ] = ( sBufferArray [ 0 ] [ 6 ] + sBufferArray [ 0 ] [ 7 ] + sBufferArray [ 0 ] [ 8 ] ) / 3;
  32. /* 计算3组数据的差值 */
  33. lDifference [ 0 ] = lAverage [ 0 ]-lAverage [ 1 ];
  34. lDifference [ 1 ] = lAverage [ 1 ]-lAverage [ 2 ];
  35. lDifference [ 2 ] = lAverage [ 2 ]-lAverage [ 0 ];
  36. /* 对上述差值取绝对值 */
  37. lDifference [ 0 ] = lDifference [ 0 ]>0?lDifference [ 0 ]: ( -lDifference [ 0 ] );
  38. lDifference [ 1 ] = lDifference [ 1 ]>0?lDifference [ 1 ]: ( -lDifference [ 1 ] );
  39. lDifference [ 2 ] = lDifference [ 2 ]>0?lDifference [ 2 ]: ( -lDifference [ 2 ] );
  40. /* 判断绝对差值是否都超过差值门限,如果这3个绝对差值都超过门限值,则判定这次采样点为野点,抛弃采样点,差值门限取为2 */
  41. if ( lDifference [ 0 ] > XPT2046_THRESHOLD_CalDiff && lDifference [ 1 ] > XPT2046_THRESHOLD_CalDiff && lDifference [ 2 ] > XPT2046_THRESHOLD_CalDiff )
  42. return 0;
  43. /* 计算它们的平均值,同时赋值给strScreenCoordinate */
  44. if ( lDifference [ 0 ] < lDifference [ 1 ] )
  45. {
  46. if ( lDifference [ 2 ] < lDifference [ 0 ] )
  47. pScreenCoordinate ->x = ( lAverage [ 0 ] + lAverage [ 2 ] ) / 2;
  48. else
  49. pScreenCoordinate ->x = ( lAverage [ 0 ] + lAverage [ 1 ] ) / 2;
  50. }
  51. else if ( lDifference [ 2 ] < lDifference [ 1 ] )
  52. pScreenCoordinate -> x = ( lAverage [ 0 ] + lAverage [ 2 ] ) / 2;
  53. else
  54. pScreenCoordinate ->x = ( lAverage [ 1 ] + lAverage [ 2 ] ) / 2;
  55. /* 同上,计算Y的平均值 */
  56. lAverage [ 0 ] = ( sBufferArray [ 1 ] [ 0 ] + sBufferArray [ 1 ] [ 1 ] + sBufferArray [ 1 ] [ 2 ] ) / 3;
  57. lAverage [ 1 ] = ( sBufferArray [ 1 ] [ 3 ] + sBufferArray [ 1 ] [ 4 ] + sBufferArray [ 1 ] [ 5 ] ) / 3;
  58. lAverage [ 2 ] = ( sBufferArray [ 1 ] [ 6 ] + sBufferArray [ 1 ] [ 7 ] + sBufferArray [ 1 ] [ 8 ] ) / 3;
  59. lDifference [ 0 ] = lAverage [ 0 ] - lAverage [ 1 ];
  60. lDifference [ 1 ] = lAverage [ 1 ] - lAverage [ 2 ];
  61. lDifference [ 2 ] = lAverage [ 2 ] - lAverage [ 0 ];
  62. /* 取绝对值 */
  63. lDifference [ 0 ] = lDifference [ 0 ] > 0 ? lDifference [ 0 ] : ( - lDifference [ 0 ] );
  64. lDifference [ 1 ] = lDifference [ 1 ] > 0 ? lDifference [ 1 ] : ( - lDifference [ 1 ] );
  65. lDifference [ 2 ] = lDifference [ 2 ] > 0 ? lDifference [ 2 ] : ( - lDifference [ 2 ] );
  66. if ( lDifference [ 0 ] > XPT2046_THRESHOLD_CalDiff && lDifference [ 1 ] > XPT2046_THRESHOLD_CalDiff && lDifference [ 2 ] > XPT2046_THRESHOLD_CalDiff )
  67. return 0;
  68. if ( lDifference [ 0 ] < lDifference [ 1 ] )
  69. {
  70. if ( lDifference [ 2 ] < lDifference [ 0 ] )
  71. pScreenCoordinate ->y = ( lAverage [ 0 ] + lAverage [ 2 ] ) / 2;
  72. else
  73. pScreenCoordinate ->y = ( lAverage [ 0 ] + lAverage [ 1 ] ) / 2;
  74. }
  75. else if ( lDifference [ 2 ] < lDifference [ 1 ] )
  76. pScreenCoordinate ->y = ( lAverage [ 0 ] + lAverage [ 2 ] ) / 2;
  77. else
  78. pScreenCoordinate ->y = ( lAverage [ 1 ] + lAverage [ 2 ] ) / 2;
  79. return 1;
  80. }
  81. else if ( ucCount > 1 )
  82. {
  83. pScreenCoordinate ->x = sBufferArray [ 0 ] [ 0 ];
  84. pScreenCoordinate ->y = sBufferArray [ 1 ] [ 0 ];
  85. return 0;
  86. }
  87. return 0;
  88. }

        本函数有一个输入参数 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 即可计算出对应的触摸坐标。

  1. typedef struct //校准系数结构体(最终使用)
  2. {
  3. float dX_X,
  4. dX_Y,
  5. dX,
  6. dY_X,
  7. dY_Y,
  8. dY;
  9. } strType_XPT2046_TouchPara;
  10. //默认触摸参数,不同的屏幕稍有差异,可重新调用触摸校准函数获取
  11. strType_XPT2046_TouchPara strXPT2046_TouchPara[] = {
  12. -0.006464, -0.073259, 280.358032, 0.074878, 0.002052, -6.545977,//扫描方式0
  13. 0.086314, 0.001891, -12.836658, -0.003722, -0.065799, 254.715714,//扫描方式1
  14. 0.002782, 0.061522, -11.595689, 0.083393, 0.005159, -15.650089,//扫描方式2
  15. 0.089743, -0.000289, -20.612209, -0.001374, 0.064451, -16.054003,//扫描方式3
  16. 0.000767, -0.068258, 250.891769, -0.085559, -0.000195, 334.747650,//扫描方式4
  17. -0.084744, 0.000047, 323.163147, -0.002109, -0.066371, 260.985809,//扫描方式5
  18. -0.001848, 0.066984, -12.807136, -0.084858, -0.000805, 333.395386,//扫描方式6
  19. -0.085470, -0.000876, 334.023163, -0.003390, 0.064725, -6.211169,//扫描方式7
  20. };
  21. /**
  22. * @brief 获取 XPT2046 触摸点(校准后)的坐标
  23. * @param pDisplayCoordinate :该指针存放获取到的触摸点坐标
  24. * @param pTouchPara:坐标校准系数
  25. * @retval 获取情况
  26. * 该返回值为以下值之一:
  27. * @arg 1 :获取成功
  28. * @arg 0 :获取失败
  29. */
  30. uint8_t XPT2046_Get_TouchedPoint ( strType_XPT2046_Coordinate * pDisplayCoordinate, strType_XPT2046_TouchPara * pTouchPara )
  31. {
  32. uint8_t ucRet = 1; //若正常,则返回0
  33. strType_XPT2046_Coordinate strScreenCoordinate;
  34. if ( XPT2046_ReadAdc_Smooth_XY ( & strScreenCoordinate ) )
  35. {
  36. pDisplayCoordinate ->x = ( ( pTouchPara[LCD_SCAN_MODE].dX_X * strScreenCoordinate.x ) + ( pTouchPara[LCD_SCAN_MODE].dX_Y * strScreenCoordinate.y ) + pTouchPara[LCD_SCAN_MODE].dX );
  37. pDisplayCoordinate ->y = ( ( pTouchPara[LCD_SCAN_MODE].dY_X * strScreenCoordinate.x ) + ( pTouchPara[LCD_SCAN_MODE].dY_Y * strScreenCoordinate.y ) + pTouchPara[LCD_SCAN_MODE].dY );
  38. }
  39. else ucRet = 0; //如果获取的触点信息有误,则返回0
  40. return ucRet;
  41. }

        在实际应用中,并不会使用前面介绍触摸原理时讲解的直接按比例运算把 触摸原始数据物理坐 标转换成与液晶屏像素对应的 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函数。

  1. //触摸参数写到FLASH里的标志
  2. #define FLASH_TOUCH_PARA_FLAG_VALUE 0xA5
  3. //触摸标志写到FLASH里的地址
  4. #define FLASH_TOUCH_PARA_FLAG_ADDR (1*1024)
  5. //触摸参数写到FLASH里的地址
  6. #define FLASH_TOUCH_PARA_ADDR (2*1024)
  7. /**
  8. * @brief 从FLASH中获取 或 重新校正触摸参数(校正后会写入到SPI FLASH中)
  9. * @note 若FLASH中从未写入过触摸参数,
  10. * 会触发校正程序校正LCD_Mode指定模式的触摸参数,此时其它模式写入默认值
  11. *
  12. * 若FLASH中已有触摸参数,且不强制重新校正
  13. * 会直接使用FLASH里的触摸参数值
  14. *
  15. * 每次校正时只会更新指定的LCD_Mode模式的触摸参数,其它模式的不变
  16. * @note 本函数调用后会把液晶模式设置为LCD_Mode
  17. *
  18. * @param LCD_Mode:要校正触摸参数的液晶模式
  19. * @param forceCal:是否强制重新校正参数,可以为以下值:
  20. * @arg 1:强制重新校正
  21. * @arg 0:只有当FLASH中不存在触摸参数标志时才重新校正
  22. * @retval 无
  23. */
  24. void Calibrate_or_Get_TouchParaWithFlash(uint8_t LCD_Mode,uint8_t forceCal)
  25. {
  26. uint8_t para_flag=0;
  27. //初始化FLASH
  28. SPI_FLASH_Init();
  29. //读取触摸参数标志
  30. SPI_FLASH_BufferRead(&para_flag,FLASH_TOUCH_PARA_FLAG_ADDR,1);
  31. //若不存在标志或florceCal=1时,重新校正参数
  32. if(para_flag != FLASH_TOUCH_PARA_FLAG_VALUE | forceCal ==1)
  33. {
  34. //若标志存在,说明原本FLASH内有触摸参数,
  35. //先读回所有LCD模式的参数值,以便稍后强制更新时只更新指定LCD模式的参数,其它模式的不变
  36. if( para_flag == FLASH_TOUCH_PARA_FLAG_VALUE && forceCal == 1)
  37. {
  38. SPI_FLASH_BufferRead((uint8_t *)&strXPT2046_TouchPara,FLASH_TOUCH_PARA_ADDR,4*6*8);
  39. }
  40. //等待触摸屏校正完毕,更新指定LCD模式的触摸参数值
  41. while( ! XPT2046_Touch_Calibrate (LCD_Mode) );
  42. //擦除扇区
  43. SPI_FLASH_SectorErase(0);
  44. //设置触摸参数标志
  45. para_flag = FLASH_TOUCH_PARA_FLAG_VALUE;
  46. //写入触摸参数标志
  47. SPI_FLASH_BufferWrite(&para_flag,FLASH_TOUCH_PARA_FLAG_ADDR,1);
  48. //写入最新的触摸参数
  49. SPI_FLASH_BufferWrite((uint8_t *)&strXPT2046_TouchPara,FLASH_TOUCH_PARA_ADDR,4*6*8);
  50. }
  51. else //若标志存在且不强制校正,则直接从FLASH中读取
  52. {
  53. SPI_FLASH_BufferRead((uint8_t *)&strXPT2046_TouchPara,FLASH_TOUCH_PARA_ADDR,4*6*8);
  54. #if 0 //输出调试信息,注意要初始化串口
  55. {
  56. uint8_t para_flag=0,i;
  57. float *ulHeadAddres ;
  58. /* 打印校校准系数 */
  59. XPT2046_INFO ( "从FLASH里读取得的校准系数如下:" );
  60. ulHeadAddres = ( float* ) ( & strXPT2046_TouchPara );
  61. for ( i = 0; i < 6*8; i ++ )
  62. {
  63. if(i%6==0)
  64. printf("\r\n");
  65. printf ( "%12f,", *ulHeadAddres );
  66. ulHeadAddres++;
  67. }
  68. printf("\r\n");
  69. }
  70. #endif
  71. }
  72. }

触摸坐标获取及处理

  1. typedef struct //液晶坐标结构体
  2. {
  3. /*负数值表示无新数据*/
  4. int16_t x; //记录最新的触摸参数值
  5. int16_t y;
  6. /*用于记录连续触摸时(长按)的上一次触摸位置*/
  7. int16_t pre_x;
  8. int16_t pre_y;
  9. } strType_XPT2046_Coordinate;
  10. /**
  11. * @brief 触摸屏被按下的时候会调用本函数
  12. * @param touch包含触摸坐标的结构体
  13. * @note 请在本函数中编写自己的触摸按下处理应用
  14. * @retval 无
  15. */
  16. void XPT2046_TouchDown(strType_XPT2046_Coordinate * touch)
  17. {
  18. //若为负值表示之前已处理过
  19. if(touch->pre_x == -1 && touch->pre_x == -1)
  20. return;
  21. /***在此处编写自己的触摸按下处理应用***/
  22. /*处理触摸画板的选择按钮*/
  23. Touch_Button_Down(touch->x,touch->y);
  24. /*处理描绘轨迹*/
  25. Draw_Trail(touch->pre_x,touch->pre_y,touch->x,touch->y,&brush);
  26. /***在上面编写自己的触摸按下处理应用***/
  27. }
  28. /**
  29. * @brief 触摸屏释放的时候会调用本函数
  30. * @param touch包含触摸坐标的结构体
  31. * @note 请在本函数中编写自己的触摸释放处理应用
  32. * @retval 无
  33. */
  34. void XPT2046_TouchUp(strType_XPT2046_Coordinate * touch)
  35. {
  36. //若为负值表示之前已处理过
  37. if(touch->pre_x == -1 && touch->pre_x == -1)
  38. return;
  39. /***在此处编写自己的触摸释放处理应用***/
  40. /*处理触摸画板的选择按钮*/
  41. Touch_Button_Up(touch->pre_x,touch->pre_y);
  42. /***在上面编写自己的触摸释放处理应用***/
  43. }
  44. /**
  45. * @brief 检测到触摸中断时调用的处理函数,通过它调用tp_down 和tp_up汇报触摸点
  46. * @note 本函数需要在while循环里被调用,也可使用定时器定时调用
  47. * 例如,可以每隔5ms调用一次,消抖阈值宏DURIATION_TIME可设置为2,这样每秒最多可以检测100个点。
  48. * 可在XPT2046_TouchDown及XPT2046_TouchUp函数中编写自己的触摸应用
  49. * @param none
  50. * @retval none
  51. */
  52. void XPT2046_TouchEvenHandler(void )
  53. {
  54. static strType_XPT2046_Coordinate cinfo={-1,-1,-1,-1};
  55. if(XPT2046_TouchDetect() == TOUCH_PRESSED)
  56. {
  57. LED_GREEN;
  58. //获取触摸坐标
  59. XPT2046_Get_TouchedPoint(&cinfo,strXPT2046_TouchPara);
  60. //输出调试信息到串口
  61. XPT2046_DEBUG("x=%d,y=%d",cinfo.x,cinfo.y);
  62. //调用触摸被按下时的处理函数,可在该函数编写自己的触摸按下处理过程
  63. XPT2046_TouchDown(&cinfo);
  64. /*更新触摸信息到pre xy*/
  65. cinfo.pre_x = cinfo.x; cinfo.pre_y = cinfo.y;
  66. }
  67. else
  68. {
  69. LED_BLUE;
  70. //调用触摸被释放时的处理函数,可在该函数编写自己的触摸释放处理过程
  71. XPT2046_TouchUp(&cinfo);
  72. /*触笔释放,把 xy 重置为负*/
  73. cinfo.x = -1;
  74. cinfo.y = -1;
  75. cinfo.pre_x = -1;
  76. cinfo.pre_y = -1;
  77. }
  78. }

        由于 XPT2046_TouchEvenHandler 函数带有 XPT2046_TouchDetect 状态机检测,所以它需要被循 环或定时调用,以实现状态转换。当确认有触摸按下时,它会调用 XPT2046_Get_TouchedPoint 获 取当前触摸坐标,并使用 XPT2046_TouchDown 函数根据触摸坐标进行处理,当触摸释放时,调 用 XPT2046_TouchUp 函数处理释放坐标。

        XPT2046_TouchDown 和 XPT2046_TouchUp 函数是一个接口,用户可以根据自己的应用编写相应 的触摸处理程序,把触摸按下和释放的处理加入到上述函数即可。

main 函数

  1. #include "stm32f10x.h"
  2. #include "./usart/bsp_usart.h"
  3. #include "./lcd/bsp_ili9341_lcd.h"
  4. #include "./lcd/bsp_xpt2046_lcd.h"
  5. #include "./flash/bsp_spi_flash.h"
  6. #include "./led/bsp_led.h"
  7. #include "palette.h"
  8. #include <string.h>
  9. int main(void)
  10. {
  11. //LCD 初始化
  12. ILI9341_Init();
  13. //触摸屏初始化
  14. XPT2046_Init();
  15. //从FLASH里获取校正参数,若FLASH无参数,则使用模式3进行校正
  16. Calibrate_or_Get_TouchParaWithFlash(3,0);
  17. /* USART config */
  18. USART_Config();
  19. LED_GPIO_Config();
  20. printf("\r\n ********** 触摸画板程序 *********** \r\n");
  21. printf("\r\n 若汉字显示不正常,请阅读工程中的readme.txt文件说明,根据要求给FLASH重刷字模数据\r\n");
  22. //其中0、3、5、6 模式适合从左至右显示文字,
  23. //不推荐使用其它模式显示文字 其它模式显示文字会有镜像效果
  24. //其中 6 模式为大部分液晶例程的默认显示方向
  25. ILI9341_GramScan ( 3 );
  26. //绘制触摸画板界面
  27. Palette_Init(LCD_SCAN_MODE);
  28. while ( 1 )
  29. {
  30. //触摸检测函数,本函数至少10ms调用一次
  31. XPT2046_TouchEvenHandler();
  32. }
  33. }

        main 函 数 中 使 用 XPT2046_Init 初 始 化 触 摸 屏 相 关 的 引 脚, 然 后 调 用 Calibrate_or_Get_TouchParaWithFlash 进行触摸校正;关于触摸画板应用程序的初始化,都包含 在 Palette_Init 函数中,它会绘制触摸画板的按钮和白板界面;在 main 函数的 while 循环里调用了 XPT2046_TouchEvenHandler 函数,以实现状态机检测和对触摸进行处理,当有触摸按下和释放 时,都通过其内部调用的 XPT2046_TouchDown 和 XPT2046_TouchUp 函数完成画板相关的操作。

实验现象

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/不正经/article/detail/476972
推荐阅读
相关标签
  

闽ICP备14008679号