一、环境介绍
单片机采用: STM32F103ZET6
编程软件: keil5
编程语言: C语言
编程风格: 寄存器开发.
目标芯片: XPT2046---标准SPI接口时序
二、XPT2046芯片介绍
2.1 功能
XPT2046是一颗12位的ADC芯片,可以当做普通的ADC芯片使用,但是一般都是用在电阻触摸屏上,方便定位触摸屏坐标。
图1: XPT2046内部原理图
图2:电阻触摸屏---引出的4条线就接在XPT2046的YNXNYPXP上
(XPT2046支持笔中断输出--低电平有效,这个引脚可以配置到单片机的中断脚上,或者轮询判断这个引脚状态,判断触摸屏是否已经按下)
可以单独买一个触摸屏+一个XPT2046就可以自己做手画板、触摸按键(自己用一张纸在下面画个模型就行)、等等很多小玩意。
图3:采用的电阻触摸屏的LCD屏(上面盖的哪一层薄膜就是触摸用的)
2.2 特性
1. 工作电压范围为 2.2V~5.25V
2. 支持 1.5V~5.25V 的数字 I/O 口
3. 内建 2.5V 参考电压源
4. 电源电压测量(0V~6)
5. 内建温度测量功能
6. 触摸压力测量
7. 采用 SPI 3线控制通信接口
8. 具有自动 power-down 功能
9. 封装:QFN-16、 TSSOP-16 和 VFBGA-48与 TSC2046、 AK4182A 完全兼容
10. XPT2046 在 125KHz 转换速率和 2.7V 电压下的功耗仅为750 µW。 XPT2046 11. 以其低功耗和高速率等特性,被广泛应用在采用电池供电的小型手持设备上,比如 PDA、手机等。
12. XPT2046 有 TSSOP-16、 QFN-16 和 VFBGA 三种封装形
式,温度范围是 - 40 ~ + 85℃ 。
2.3工作原理
XPT2046 是一种典型的逐次逼近型模数转换器(SAR ADC),包含了采样/保持、模数转换、串口数据输出等功能。同时芯片集成有一个 2.5V的内部参考电压源、温度检测电路,工作时使用外部时钟。 XPT2046 可以单电源供电,电源电压范围为 2.7V~5.5V。参考电压值直接决定ADC的输入范围,参考电压可以使用内部参考电压,也可以从外部直接输入1V~VCC范围内的参考电压(要求外部参考电压源输出阻抗低)。 X、 Y、 Z、 VBAT、 Temp和AUX模拟信号经过片内的
控制寄存器选择后进入ADC, ADC可以配置为单端或差分模式。选择VBAT、 Temp和AUX时可以配置为单端模式;作为触摸屏应用时,可以配置为差分模式,这可有效消除由于驱动开关的寄生电阻及外部的干扰带来的测量误差,提高转换准确度。
典型的应用:
单端工作模式
SER/DFR置为高电平时, XPT2046 工作在为单端模式,单端工作模式的应用原理如下图所示。
单端模式简单,在采样过程完成后,转换过程中可以关闭驱动开关,降低功耗。但这种模式的缺点是精度直接受参考电压源的精度限制,同时由于内部驱动开关的导通电阻存在,导通电阻与触摸屏电阻的分压作用,也会带来测量误差。
(图片里的A2 A1 A0 ,还有上面说的SER/DFR就是XPT2046的配置命令,具体使用方法在后面会讲到)
差分工作模式
SER/DFR置为低电平时, XPT2046 为差分工作模式.
差分模式的优点是: +REF 和-REF 的输入分别直接接到 YP、 YN 上,可消除由于驱动开关的导通电阻引入的坐标测量误差。
缺点是:无论是采样还是转换过程中,驱动开关都需要接通,相对单端模式而言,功耗增加了。
如果不考虑功耗的话,当前就选择差分工作模式了。
(图片里的A2 A1 A0 ,还有上面说的SER/DFR就是XPT2046的配置命令,具体使用方法在后面会讲到)
2.3 XPT2046采集并转换一次数据的时序介绍
XPT2046 数据接口是串行接口,处理器和转换器之间的通信需要 8 个时钟周期,可采用 SPI、 SSI 和 Microwire 等同步串行接口。一次完整的转换需要 24 个串行同步时钟(DCLK)来完成。
前 8 个时钟用来通过DIN引脚输入控制字节。当转换器获取有关下一次转换的足够信息后,接着根据获得的信息设置输入多路选择器和参考源输入,并进入采样模式,如果需要,将启动触摸面板驱动器。 3 个多时钟周期后,控制字节设置完成,转换器进入转换状态。这时,输入采样-保持器进入保持状态,触摸面板驱动器停止工作(单端工作模式)。
接着的12 个时钟周期将完成真正的模数转换。如果是度量比率转换方式(SER/DFR ——=0),驱动器在转换过程中将一直工作,第13 个时钟将输出转换结果的最后一位。剩下的 3 个多时钟周期将用来完成被转换器忽略的最后字节(DOUT置低)。
时序图如下:
时序图里的控制命令字节:
位 7(MSB) | 位 6 | 位 5 | 位 4 | 位 3 | 位 2 | 位 1 | 位 0(LSB) |
S | A2 | A1 | A0 | MODE | SER/DFR | PD1 | PD0 |
控制字节每个位的含义如下:
位 | 名称 | 功能描述 |
7 | S | 开始位。为 1 表示一个新的控制字节到来,为 0 则忽略 PIN 引脚上数据 |
6-4 | A2-A0 | 通道选择位。这个在上面已经介绍过了 |
3 | MODE | 12 位/8 位转换分辨率选择位。为 1 选择 8 位为转换分辨率,为 0 选择 12 位分辨率 |
2 | SER/DFR | 单端输入方式/ 差分输入方式选择位。为 1 是单端输入方式,为 0 是差分输入方式 |
1-0 | PD1-PD0 | 低功率模式选择位。若为11,器件总处于供电状态;若为00,器件在变换之间处于低 功率模式 |
注意: 差分模式仅用于 X 坐标、 Y 坐标和触摸压力的测量,其它测量要求采用单端模式。
根据上面表格的介绍,可以得到在差分模式下,选择12位分辨率,测量X和Y坐标的两个命令:0xD0 和 0x90
XPT2046还有其他模式,可以测量温度,笔中断的开关(默认是开着的),16时钟周期转换,15时钟周期转换,这些就不再介绍。 根据前面的介绍用在触摸屏上测量XY坐标的功能已经满足了。
2.4 SPI时序介绍
这里的XPT2046支持标准3线SPI接口,关于SPI时序的介绍,在前面文章里有介绍过。
参考这里:STM32入门开发: 介绍SPI总线、读写W25Q64(FLASH)(硬件+模拟时序)_DS小龙哥的专栏-CSDN博客_w25q64
2.5 物理坐标与屏幕坐标的转换
正常在LCD屏上使用触摸屏,肯定是需要将采集的原始X、Y值转为LCD屏的屏幕坐标才好使用。
转换的方法有很多,这里采用最简单的角系数计算方法转换。
比如,我使用的LCD屏是3.5寸的,分辨率是320*480。
1. 得到触摸屏左上角和右下角的坐标XY极限值
x=3831,y=3934
x=155,y=168
2. 转换坐标值
x坐标:3831~155 --> 3676~0
y坐标:3934~168 --> 3766~0
3. 计算斜率
x坐标的斜率: 3676/320=11.4875
y坐标的斜率: 3766/480=7.84583
4. 得到实际的像素坐标
x坐标: 320-(实时采集的当前X模拟量-155)/11.4875
y坐标: 480-(实时采集的当前Y模拟量-168)/7.84583
这里相减的原因: 因为我测试用的触摸屏采集出来的X、Y值大小和LCD屏的屏幕坐标值大小是反过来的。
三、示例代码
采用SPI模拟时序驱动,其他平台都可以移植。
3.1 xpt2046.c
#include "xpt2046_touch.h"
struct XPT2046_TOUCH xpt2046_touch;
/*
函数功能: 初始化
硬件连接:
T_MOSI--PF9
T_MISO--PB2
T_SCK---PB1
T_PEN---PF10
T_CS----PF11
*/
void XPT2046_TouchInit(void)
{
/*1. 时钟初始化*/
RCC->APB2ENR|=1<<3; //PB
RCC->APB2ENR|=1<<7; //PF
/*2. 初始化GPIO口*/
GPIOB->CRL&=0xFFFFF00F;
GPIOB->CRL|=0x00000830;
GPIOF->CRH&=0xFFFF000F;
GPIOF->CRH|=0x00003830;
/*3. 上拉*/
GPIOB->ODR|=0x3<<1;
GPIOF->ODR|=0x7<<9;
}
/*
函数功能: SPI底层写一个字节
*/
void XPT2046_SPI_WriteOneByte(u8 cmd)
{
u8 i;
for(i=0;i<8;i++)
{
XPT2046_SCK=0; //低电平写
if(cmd&0x80)XPT2046_MOSI=1;
else XPT2046_MOSI=0;
cmd<<=1;
XPT2046_SCK=1; //高电平读,保证数据线稳定
}
}
/*
函数功能: 读2个字节
说明: 读取16位数据,最低4位数据无效,有效数据是高12位
*/
u16 XPT2046_ReadData(u8 cmd)
{
u16 data;
u8 i;
XPT2046_CS=0; //选中XPT2046
XPT2046_MOSI=0;
XPT2046_SCK=0;
XPT2046_SPI_WriteOneByte(cmd);
DelayUs(8); //0.008ms ,等待XPT2046转换完成。
//消除忙信号
XPT2046_SCK=0;
DelayUs(1);
XPT2046_SCK=1;
//连续读取16位的数据
for(i=0;i<16;i++)
{
XPT2046_SCK=0; //通知XPT2046,主机需要数据
XPT2046_SCK=1;
data<<=1;
if(XPT2046_MISO)data|=0x01;
}
data>>=4; //丢弃最低4位
XPT2046_CS=1; //取消选中
return data;
}
/*
XPT2046的命令:
10010000 :测试Y的坐标 0x90
11010000 :测试X的坐标 0xD0
返回值: 0表示没有读取到坐标,1表示读取到当前坐标
//1. 得到左上角和右下角的坐标XY极限值
x=3831,y=3934
x=155,y=168
//2. 转换坐标值
x坐标:3831~155 --> 3676~0
y坐标:3934~168 --> 3766~0
//3. 计算斜率
x坐标的斜率: 3676/320=11.4875
y坐标的斜率: 3766/480=7.84583
//4. 得到实际的像素坐标
x坐标: 320-(模拟量-155)/11.4875
y坐标: 480-(模拟量-168)/7.84583
*/
u8 XPT2046_ReadXY(void)
{
if(XPT2046_PEN==0) //判断触摸屏是否按下
{
/*1. 得到物理坐标*/
xpt2046_touch.x0=XPT2046_ReadData(0xD0);
xpt2046_touch.y0=XPT2046_ReadData(0x90);
/*2. 得到像素坐标*/
xpt2046_touch.x=320-(xpt2046_touch.x0-155)/11.4875;
xpt2046_touch.y=480-(xpt2046_touch.y0-168)/7.84583;
return 1;
}
return 0;
}
poYBAGDYdXCAWkKMAAAAK8RNs4s030.png
3.2 xpt2046.h
#ifndef XPT2046_TOUCH_H
#define XPT2046_TOUCH_H
#include "stm32f10x.h"
#include "sys.h"
#include "delay.h"
//触摸屏引脚定义
#define XPT2046_MOSI PFout(9)
#define XPT2046_MISO PBin(2)
#define XPT2046_SCK PBout(1)
#define XPT2046_CS PFout(11)
#define XPT2046_PEN PFin(10)
//函数声明
void XPT2046_TouchInit(void);
void XPT2046_SPI_WriteOneByte(u8 cmd);
u8 XPT2046_ReadXY(void);
//存放触摸屏信息的结构体
struct XPT2046_TOUCH
{
u16 x0; //物理坐标x
u16 y0; //物理坐标y
u16 x; //像素坐标x
u16 y; //像素坐标y
};
extern struct XPT2046_TOUCH xpt2046_touch;
#endif