STM32电阻触摸屏应用

目录

STM32电阻触摸屏应用

触摸屏原理图

电阻触摸屏控制芯片

XPT2046芯片框图

程序分析

触摸屏硬件相关宏定义

XPT2046 初始化函数

延时函数

 写时序

读时序

状态机编程

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

多次采样求平均值

根据原始数据计算坐标值

触摸校正

触摸坐标获取及处理

main 函数

实验现象


STM32电阻触摸屏应用

触摸屏原理图

我们使用的板载3.2寸电阻屏的原理图如下所示:

STM32电阻触摸屏应用_第1张图片

在左边3.2寸TFT中为26-29脚

STM32电阻触摸屏应用_第2张图片

        在实际使用中,TFT左边的X+到Y-四根线与电阻触摸屏控制器的X+到Y-四根线相连,电阻触摸屏控制器的GPIO1到GPIO5连接到排针的GPIO1到GPIO5。

        触摸检测的主体是型号为 XPT2046 的芯片,它接收触摸屏的 X+/X-/Y+/Y-信号进行处理,把触摸 信息使用 SPI 接口输出到 STM32 等控制器,注意,由于控制 XPT2046 芯片的并不是 STM32 专 用的硬件 SPI 接口,所以在编写程序时,需要使用软件模拟 SPI 时序与触摸芯片进行通讯。

        由于使用软件模拟SPI,所以STM32液晶接口使用了五个普通的GPIO

STM32电阻触摸屏应用_第3张图片

电阻触摸屏控制芯片

        为了方便检测触摸的坐标,一些芯片厂商制作了电阻屏专用的控制芯片,控制上述采集过程、采集电压,外部微控制器直接与触摸控制芯片通讯直接获得触点的电压或坐标。

        秉火3.2寸电阻触摸屏就是采用XPT2046芯片作为触摸控制芯片,XPT2046芯片控制4线电阻触摸屏,STM32与XPT2046采用SPI通讯获取采集得的电压,然后转换成坐标。

        XPT2046是专用在四线电阻屏的触摸屏控制器,STM32可通过SPI接口向它写入控制字,由它测得X、Y方向的触点电压返回给STM32。

XPT2046芯片框图

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

STM32电阻触摸屏应用_第4张图片

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

STM32电阻触摸屏应用_第5张图片

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

STM32电阻触摸屏应用_第6张图片

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

STM32电阻触摸屏应用_第7张图片

程序分析

触摸屏硬件相关宏定义

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 )

XPT2046 初始化函数

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微秒    

    }
    
}

STM32电阻触摸屏应用_第8张图片

 写时序

/**
  * @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 个数据位。

状态机编程

STM32电阻触摸屏应用_第9张图片

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

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

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

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

 

//触屏信号有效电平
#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 调用一次),其状态转换

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

STM32电阻触摸屏应用_第10张图片

        在代码中,通过使用 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 数据。

/******************************* 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 函数是一个接口,用户可以根据自己的应用编写相应 的触摸处理程序,把触摸按下和释放的处理加入到上述函数即可。

main 函数

#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 函数完成画板相关的操作。

实验现象

你可能感兴趣的:(STM32_LCD原理及应用,stm32,嵌入式硬件,单片机,LCD)