STM32 SPI驱动触摸屏(XPT2046)(下)

接口/总线/驱动

1138人已加入

描述

电阻屏是通过检测触点处的电压来确定位置的,电压受到电阻材料的影响,而生产中不同批次的电阻材料可能会有偏差,因此需要先定位几个点来确定屏幕的偏移量 ( 也就是校准 ) ,以后通过校准得来的偏移量调整坐标输出,才能准确通过电压反映坐标。而电容屏是直接由多个电容组成的矩阵,检测时可获知整个电容矩阵中哪些电容发生了改变,而且各个电容在生产时就确认了它在触摸屏中的坐标,所以只要获知哪些电容发生了变化,就可直接得出触点位置,无须校准。

由于用的是电阻屏,所以这里需要进行校准。

屏幕坐标与AD转换结果也不是相等的,面是存在一定比例关系,也就是说AD转换结果与实际屏幕坐标之间是一条不过坐标原点,且斜率不为1的直线,线性关系可表示为:

**LCD_X=KxADC_x+offset_x

LCD_Y=KyADC_y+offset_y *

式中LCD_X和LCD_ Y是LCD屏幕上的坐标位置,ADC_x和ADCy为点击屏上(LCDX.,LCD Y)时所采集的AD值,Kx和Ky是两者之同的比例关系,而 offset_x和 offset_y则为两者相对于坐标原点时的的偏移量。屏幕上点的坐标与其AD值之间的关系可用图表示:

电容器

由图可知,只要确定线性关系当中的Kx、Ky及 Offset_x和 Offset_y,当获取到触摸点对应的AD值时,即可确定触摸点的位置,比较简单的方法是采集屏幕的左上角(0,0)位置和右下角(239,319)位置的AD值,假定为(ad_x0,ad_y0)和(ad_x1,ad_y1),有了坐标和AD值,即可确定线性关系,Kx=(ad_x1- ad_x1)/239;Ky=( ade y1-ad_y0)/319,确定了Kx和Ky即可确定 Offset_x和Offset_y,从而最终确定线性关系,以后点击屏幕任意点,都可根据线性关系确定屏幕坐标位置。

通常,由于LCD屏幕边界处存在较多的噪点,为了更为准确地校准屏幕,并确定线性关系,往往采用四点校屏法或五点校屏法,但本质都是一样的。四点校屏即在屏幕靠近四个角落的地方给定四个点,以提示用户点击这四点,并假定用户点击后所得的AD值就是这四个点的AD值(当然,实际触摸位置总是会存在一定偏差,若在一定误差范围内,则认为触摸点就是给定点),从而根据给定位置和所得AD值,就可确定线性关系,原理与点击左上角与右下角是一样的。至于五点校屏,无非就是在屏幕中心位置又增加一参考点而已 。

先为校准的参数定义一个结构体:

typedef struct{
  float kx;
  float ky;
  float dx;
  float dy;
  u8 flag;        //用来标识,是否校准过
}TouchParam_Typedef;

校准思路:在在屏幕上给出参考的点击点,当用户触摸屏幕时,假定所得的触点AD值就是参考点的ADC,利用实际所得的AD值和参考坐标,计算出一个线性关系。然后,再把触点AD值代入所得的线性关系中,计算实际触点的坐标,若实际坐标与给定坐标满足的误差条件,则认为该直线即为线性关系。下面给出校准示意图:

电容器

这里采用四点校屏法,来确定线性关系中的比例系数和偏移量,即屏幕校准。选择4个参考点,参考点选取越靠边界,范围就越大,这样校准精度就越高,但是越靠近边界,噪声就越大,所以我们去距离边界20的4个点来校准:(20,20)、(220,20)、(20,300)、(220,300)。

void Touch_Adjust()
{
  u16 ref_Point[4][2] = {{20,20},{220,20},{20,300},{220,300}};
  u16 real_Point[4][2] = {0};
  Point_Typedef pt = {0xffff,0xffff};
  u16 err[4] = {0};
  u8 i = 0;
  s8 error = 0;

  LCD_ClearScreen(0xffff);

  while(1)
  {
    for(i=0;i< 4;i++)
    {
      //画点
      //画水平线
      LCD_DrawLine(ref_Point[i][0]-7,ref_Point[i][1],ref_Point[i][0]+7,ref_Point[i][1],RED);
      //画垂直线
      LCD_DrawLine(ref_Point[i][0],ref_Point[i][1]-7,ref_Point[i][0],ref_Point[i][1]+7,RED);
      //画圆圈
      LCD_DrawCircle(ref_Point[i][0],ref_Point[i][1],5,RED);
      while(GPIOB- >IDR & (1< < 1)){}    //未触摸
      Delay_ms(30);
      while(GPIOB- >IDR & (1< < 1)){}  //未触摸
      //暂时把采用的ADC值,存储到real_Point中
      pt = Touch_GetPointADC();
      real_Point[i][0] = pt.x;
      real_Point[i][1] = pt.y;
        printf("%d,%drn",real_Point[i][0],real_Point[i][1]);
      //等待松手
      while((GPIOB- >IDR & (1< < 1)) == 0){}

      //清除已触摸的点
      LCD_DrawLine(ref_Point[i][0]-7,ref_Point[i][1],ref_Point[i][0]+7,ref_Point[i][1],0xffff);
      //画垂直线
      LCD_DrawLine(ref_Point[i][0],ref_Point[i][1]-7,ref_Point[i][0],ref_Point[i][1]+7,0xffff);
      //画圆圈
      LCD_DrawCircle(ref_Point[i][0],ref_Point[i][1],5,0xffff);
    }
    //4个点的ADC值已经获取,根据AD值和参考坐标,求线性关系
    //利用第1点和第4点,求线性关系
    /*
        ref_Point[0][0] = Kx * real_Point[0][0] + dx;    (1)
        ref_Point[3][0] = Kx * real_Point[3][0] + dx;   (2)
      由(1)和(2)可得:
        Kx = (float)(ref_Point[3][0] - ref_Point[0][0])/(real_Point[3][0] - real_Point[0][0]);
        dx = (float)(ref_Point[3][0]*real_Point[0][0] - ref_Point[0][0]*real_Point[3][0])/(real_Point[0][0] - real_Point[3][0]);

        ref_Point[0][1] = Ky * real_Point[0][1] + dy;    (3)
        ref_Point[3][1] = Ky * real_Point[3][1] + dy;   (4)
      由(3)和(4)可得:
        Ky = (float)(ref_Point[3][1] - ref_Point[0][1])/(real_Point[3][1] - real_Point[0][1]);
        dy = (float)(ref_Point[3][1]*real_Point[0][1] - ref_Point[0][1]*real_Point[3][1])/(real_Point[0][1] - real_Point[3][1]);

    */
      touchParam.kx = (float)(ref_Point[3][0] - ref_Point[0][0])/(real_Point[3][0] - real_Point[0][0]);
      touchParam.dx = (float)(ref_Point[3][0]*real_Point[0][0] - ref_Point[0][0]*real_Point[3][0])/(real_Point[0][0] - real_Point[3][0]);

      touchParam.ky = (float)(ref_Point[3][1] - ref_Point[0][1])/(real_Point[3][1] - real_Point[0][1]);
      touchParam.dy = (float)(ref_Point[3][1]*real_Point[0][1] - ref_Point[0][1]*real_Point[3][1])/(real_Point[0][1] - real_Point[3][1]);

      //利用计算的线性关系,求实际触点坐标
      for(i=0;i< 4;i++)
      {
        real_Point[i][0] = touchParam.kx * real_Point[i][0] + touchParam.dx;
        real_Point[i][1] = touchParam.ky * real_Point[i][1] + touchParam.dy;

        printf("(%d,%d)rn",real_Point[i][0],real_Point[i][1] );
      }

      //引入误差条件(如果4个实际点都在小圆内,此认为校准通过,)
      for(i=0;i< 4;i++)
      {
        //这里计算距离平方
        err[i] = (real_Point[i][0] - ref_Point[i][0]) * (real_Point[i][0] - ref_Point[i][0])
                + (real_Point[i][1] - ref_Point[i][1]) * (real_Point[i][1] - ref_Point[i][1]);
        printf("err[%d]=%drn",i,err[i]);
      }

      //误差判断
      if(err[0]< 100 && err[1]< 100 && err[2]< 100 && err[3]< 100)
      {
        //认为校准通过(存储到24c02或w25q64中,以24c02为例)
        touchParam.flag = 0xaa;  
        AT24C02_ContinueWrite(TOUCH_PARAM_ADD,(u8 *)&touchParam,sizeof(touchParam),&error);
        break;
      }
  }
  //校准通过
  LCD_Print(50,160,(u8 *)"Adjust ok",0,0xffff,1,24);
  Delay_ms(3000);
  LCD_ClearScreen(0xffff);
}

校准函数编写完成,需要添加到触摸屏初始化函数中,初始化GPIO时判断是否已经校准过,没有校准过就进行校准。

void Touch_Init()
{
  s8 error = 0;

  Touch_gpio_Init();
  AT24C02_ContinueRead(TOUCH_PARAM_ADD,(u8 *)&touchParam,sizeof(touchParam),&error);

  if(touchParam.flag != 0x77)    //没有校准
    Touch_Adjust();
}

接着编写主函数进行测试。

#include "stm32f4xx.h"
#include "usart.h"
#include "delay.h"
#include "stdio.h"
#include "touch.h"
#include "AT24C02.h"
#include "ili9341.h"
#include "lcd.h"


int main()
{
  Usart1_Init(115200);
  AT24C02_Init();
  LCD_Init();
  Touch_Init();

  while(1)
  {}
}

运行后,显示一个校准点,点击该点校准后,第一个校准点被擦除,显示第二个校准点,这样依次校准四个校准点,校准结果在误差允许范围内,屏幕显示Adjust ok,延时3秒后,屏幕被清为白色,触摸屏校准成功,接下来就可以在校准好的基础上在触摸屏上做其他应用了。

测试结果如下图所示:

电容器

电容器

电容器

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分