XPT2046触摸屏实验过程详解与STM32代码解析


  • 触摸屏的简介
  • 触摸屏的控制 XPT2046芯片简介
  • 1. XPT2046 的初始化
  • 2. XPT2046 读取 X、Y 值
  • 3. 物理坐标值的数据处理

学习目标:
1.复习 STM32 的硬件 SPI
2.学习触摸屏的原理做触摸屏实验
触摸屏的简介

现在的液晶屏大部分都带触摸了,一般我们使用比较多的是电阻式触摸屏(多点触摸属 于电容式触摸屏,比如几乎所有智能机都支持多点触摸,它们所用的屏就是电容式的触摸屏) 我们彩屏上面带的也是电阻式的触摸屏。
  电阻触摸屏的主要部分是一块与显示器表面非常配合的电阻薄膜屏,它是一种多层的复 合薄膜,它以一层玻璃或硬塑料平板作为基层,表面涂有一层透明氧化金属(透明的导电电 阻)导电层,上面再盖有一层外表面硬化处理、光滑防擦的塑料层、它的内表面也涂有一层 涂层、在他们之间有许多细小的(小于1/1000 英寸)的透明隔离点把两层导电层隔开绝缘。
  在这里插入图片描述
  当手指触摸屏幕时,两层导电层在触摸点位置就有了接触,电阻发生变化,在 X 和 Y 两个方向上的电压发生变化,产生信号,然后控制器读取信号,并计算出手指触摸的位置, 这就是电阻式触摸屏的原理。
  在这里插入图片描述

触摸屏的控制 XPT2046芯片简介

从上面的简介,我们知道触摸屏都需要一个 AD 转换器,也就是要将电压变化读取出 来,供主机求出触摸的位置。而我们彩屏上面使用的触摸芯片是XPT2046。XPT2046 的特点主要有:
1. 一款 4 导线制触摸屏控制器,采用 SPI 模式进行通信。
2. 内含 12 位分辨率 125KHz 转换速率逐步逼近型 A/D 转换器。
3. 支持从 1.5V 到 5.25V 的低电压 I/O 接口。
XPT2046 应该有 16 个引脚,如图:
在这里插入图片描述
其引脚说明如下:

在这里插入图片描述
从上面的引脚图,我们知道,XPT2046 跟单片机的主要引脚主要有:BUSY、DIN(单 片机 SPI 输出端)、CS、DCLK(单片机 SPI 时钟端)、PEN(笔触中断)、DOUT(单片机 SPI 输入端)

3PZ6808L触摸屏的原理图
在这里插入图片描述
在这里插入图片描述

1. XPT2046 的初始化

XPT2046 说起来其实就是一个 AD 转换器,所以它适合不需要什么初始化设置的, 而具体的初始化其实也就是单片机 IO 的初始化和 SPI 的初始化。
这次 STM32 是使用 SPI1 来进行操作,SPI 的设置其实在前几节课已经讲过了,这 里就不重复讲了,初始化的具体代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
    /**********************************************************************
    * Function Name      : TOUCH_Init
    * Description        : 初始化触摸屏
    * Input              : None
    * Output             : None
    * Return             : None
    **********************************************************************/
    void TOUCH_Init(void)
    {
        GPIO_InitTypeDef GPIO_InitStructure;
        /* SPI 的 IO 口和 SPI 外设打开时钟 */
         RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOD, ENABLE);
        /* TOUCH-CS 的 IO 口设置 */
        GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
        GPIO_InitStructure.GPIO_Mode =GPIO_Mode_Out_PP;
        GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
        GPIO_Init(GPIOD, &GPIO_InitStructure);
        /* TOUCH-PEN 的 IO 口设置 */
        GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7;
        GPIO_InitStructure.GPIO_Mode =GPIO_Mode_IPU;
        GPIO_Init(GPIOD, &GPIO_InitStructure); SPI1_Config();
        /* 要使用 FLASH 来存储校正参数,所以注意之前要初始化 */
        /* 检测是否有校正参数 */
        FLASH_ReadData(&TouchAdj.posState,TOUCH_ADJ_ADDR, sizeof(TouchAdj));
        if(TouchAdj.posState != TOUCH_ADJ_OK)
        {
            TOUCH_Adjust(); //校正
        }
    }

在这个函数中,调用了 SPI1 的初始化函数,和触摸屏的校正程序,下面是 SPI1 的初始化程序,校正原理我们在后面在讲述。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
    /**********************************************************************
    * Function Name      : SPI1_Config
    * Description        : 初始化 SPI2
    * Input              : None
    * Output             : None
    * Return             : None
    *********************************************************************/
    void SPI1_Config(void)
    {
        GPIO_InitTypeDef GPIO_InitStructure;
        SPI_InitTypeDef SPI_InitStructure;
   
        /* SPI 的 IO 口和 SPI 外设打开时钟 */
        RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
        RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE);
   
        /* SPI 的 IO 口设置 */
        GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5 | GPIO_Pin_6 | GPIO_Pin_7;
        GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
        GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
   
        GPIO_Init(GPIOA, &GPIO_InitStructure);
        GPIO_SetBits(GPIOA,GPIO_Pin_5|GPIO_Pin_6|GPIO_Pin_7); //PA5.6.7 上拉
        /********************************************************************/
        /******************* 设置 SPI 的参数 *********************************/
        /********************************************************************/
        SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;//选择全双工SPI 模式
       
        SPI_InitStructure.SPI_Mode = SPI_Mode_Master;              //主机模式
        SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;  //8 位 SPI
        SPI_InitStructure.SPI_CPOL = SPI_CPOL_High;        //时钟悬空高电平
        SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge;       //在第二个时钟采集数据
        SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;          //Nss 使用软件控制
        /* 选择波特率预分频为 256 */
        SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_256;
        SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;//从最高位开始传输
        SPI_InitStructure.SPI_CRCPolynomial = 7;
   
        SPI_Cmd(SPI1, ENABLE);
        SPI_Init(SPI1, &SPI_InitStructure);
    }

2. XPT2046 读取 X、Y 值

我们知道,触摸屏根据方向,分为 X 轴和 Y 轴两个部分,通过读取 X 轴和 Y 轴的 数据,我们就可以知道触摸屏触摸的位置了,就像数学上面的,知道了 x 坐标和 y 坐标, 那么就可以确定在坐标轴上面一个点的位置。
  如何读取 XPT2046 的数据呢?接下来我们来看一个时序图:在这里插入图片描述
  8 位总线接口,无 DCLK 时钟延迟,24 时钟周期转换时序,XPT2046 完成一个完整的转换需要 24 个串行时钟,也就是需要 3 个字节的 SPI 时 钟。对照上图,XPT2046 前 8 个串行时钟,是接收 1 个字节的转换命令,接收到转换 命令了之后,然后使用 1 个串行时钟的时间来完成数据转换(当然在编写程序的时候, 为了得到精确的数据,你可以适当的延时一下),然后返回 12 个字节长度(12 个字节 长度也计时 12 个串行时钟)的转换结果。然后最后 3 个串行时钟返回三个无效数据。所以读取一个完整转换过程为:
1.发送 1 个 8 字节的控制命令
2.在这里可以小延时一下,如果你 SPI 时钟周期比 XPT2046 转换周期慢许多,不用延时也可以。
3.读取 2 个字节的返回数据。
4.进行数据处理。也就是丢弃最后读取到的 3 位数据。 我们需要读取两个数据,一个 X 轴数据和一个 Y 轴数据,所以我们这里需要两个控
制命令。
一个完整的控制命令的结构为:
在这里插入图片描述
在这里插入图片描述
从上图,我们可以得到两个命令,读取 X 轴的命令为:0xD0。而读取 Y 轴的命令 为:0x90。程序实现为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
/**************************************************************************
* Function Name      : TOUCH_ReadData
* Description        : 采样物理坐标值
* Input              : cmd:选择要读取是 X 轴还是 Y 轴的命令
* Output             : None
* Return             : 读取到的物理坐标值
**************************************************************************/
static uint16_t TOUCH_ReadData(uint8_t cmd)
{
    uint8_t i, j;
    uint16_t readValue[TOUCH_READ_TIMES], value;
    uint32_t totalValue;
       
    /* SPI 的速度不宜过快 */
    SPI2_SetSpeed(SPI_BaudRatePrescaler_16);
    /* 读取 TOUCH_READ_TIMES 次触摸值 */
    for(i=0; i<touch_read_times; i++)
    {              
        /* 打开片选 */
        TOUCH_CS_CLR;
        /* 在差分模式下,XPT2046 转换需要 24 个时钟,8 个时钟输入命令,之后 1个时钟去除 */
        /* 忙信号,接着输出 12 位转换结果,剩下 3 个时钟是忽略位 */
        SPI1_WriteReadData(cmd); // 发送命令,选择 X 轴或者 Y 轴
        /* 读取数据 */
        readValue[i] = SPI1_WriteReadData(0xFF);
        readValue[i] <<= 8;
        readValue[i] |= SPI1_WriteReadData(0xFF);
        /* 将数据处理,读取到的 AD 值的只有 12 位,最低三位无用 */
        readValue[i] >>= 3; TOUCH_CS_SET;
    }
   
    /* 滤波处理 */
    /* 首先从大到小排序 */
    for(i=0; i<(TOUCH_READ_TIMES - 1); i++)
    {
        for(j=i+1; j<touch_read_times; j++)
        {
            /* 采样值从大到小排序排序 */
            if(readValue[i] < readValue[j])
            {
            value = readValue[i]; readValue[i] = readValue[j]; readValue[j] = value;
            }
        }
    }
       
    /* 去掉最大值,去掉最小值,求平均值 */
    j = TOUCH_READ_TIMES - 1;
    totalValue = 0;
    for(i=1; i<j; i++)="" 求="" y="" 的全部值
    {
        totalValue += readValue[i];
    }
    value = totalValue / (TOUCH_READ_TIMES - 2);
       
    return value;
}

在这个读取函数的程序中,为了获取数据值的准确性,进行多次读取,然后除去最大最 小值,求出平均值。这个就是所谓的程序滤波,接下来,再详细讲述程序滤波。

3. 物理坐标值的数据处理

在读取 X 轴和 Y 轴的物理坐标值,也就是 AD 值的时候,需要进行一些必要的数据处 理,这也是为了获取更准确的数据值,否则就会出现飞点等误差。
  比较常用的程序滤波的方法为平均值法。也就是多次读取结果,然后去掉它们的最大值 和最小值,最后求取它们的平均值。这种方法读取的次数越多,得到的数据就更准确。而上 面我们读取数据程序里面使用的滤波方法也是这种方法。
  不过为了更好的滤波,还使用了另外一种方式进行滤波。也就是当读取到两次数据之后, 然后检查两个数据之间的差值,如果超过理想的误差,那么丢弃数据。这种方法也是很多的 处理飞点的程序方法。
  我们来看一下我例程中的程序数据处理:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
    /**************************************************************************
    * Function Name      : TOUCH_ReadXY
    * Description        : 读取触摸屏的 X 轴 Y 轴的物理坐标值
    * Input              : *xValue:保存读取到 X 轴物理坐标值的地址
    *                   *yValue:保存读取到 Y 轴物理坐标值的地址
    * Output             : None
    * Return             : 0:读取成功;0xFF:读取失败
    **************************************************************************/
    static uint8_t TOUCH_ReadXY(uint16_t *xValue, uint16_t *yValue)
    {
        uint16_t xValue1, yValue1, xValue2, yValue2;
        xValue1 = TOUCH_ReadData(TOUCH_X_CMD);
        yValue1 = TOUCH_ReadData(TOUCH_Y_CMD);
        xValue2 = TOUCH_ReadData(TOUCH_X_CMD);
        yValue2 = TOUCH_ReadData(TOUCH_Y_CMD);
   
        /* 查看两个点之间的只采样值差距 */
        if(xValue1 > xValue2)
        {  
        }
        else
        {  
        }
        *xValue = xValue1 - xValue2;
        *xValue = xValue2 - xValue1;
        if(yValue1 > yValue2)
        {
        }
        else
        {
        }
        *yValue = yValue1 - yValue2;
        *yValue = yValue2 - yValue1;
        /* 判断采样差值是否在可控范围内 */
        if((*xValue > TOUCH_MAX) || (*yValue > TOUCH_MAX))
        {
            return 0xFF;
        }
       
        /* 求平均值 */
        *xValue = (xValue1 + xValue2) / 2;
        *yValue = (yValue1 + yValue2) / 2;
   
        /* 判断得到的值,是否在取值范围之内 */
        if((*xValue > TOUCH_X_MAX) || (*xValue < TOUCH_X_MIN)
        || (*yValue > TOUCH_Y_MAX) || (*yValue < TOUCH_Y_MIN))
        {
            return 0xFF;
        }
        return 0;
    }

在这里插入图片描述

完整源码+资料等内容下载地址:
http://www.51hei.com/bbs/dpj-92931-1.html