XPT2046是电阻触摸驱动,本质是一个多通道的ADC转换器,通过SPI与单片机连接

应用电路及引脚说明


序号引脚说明
1VCC电源供电,2.7-5.5V,一般接单片机的3.3V,并并联0.1uF电容
2XP触摸屏X输入,接XP或XL
3YP触摸屏Y输入,接YP或YU
4XN触摸屏X输入,接XN或XR
5YN触摸屏Y输入,接XP或YD
6GND接地
7VBAT额外的ADC测量通道,内部带1/4分压器,可测电池电压(最高6V)
8AUX额外的ADC测量通道,不使用空置
9DCLK数据时钟,接MCU SPI的SCK
10CS#数据有效,接MCU的CS,拉低为有效数据
11DIN数据输入,接MCU的MOSI(主出从入)
12BUSY忙标志,ADC采样期间会拉高,可以不用接,程序层面控制延迟
13DOUT数据输出,接MCU的MISO(主入从出)
14PENIRQ#中断标志,在休眠模式下检测是否有触摸按下
(给YN接地,给XP上拉,触摸按下时会拉低XP,触发中断,把PENIRQ#拉低)
15IOVDDIO供电,与电源相同接3.3V
16VREF外部参考电压,不使用,留空

通信过程

基本过程

  1. 拉低CS,通信开始
  2. 发送1字节的控制命令
  3. 略微等待ADC采样
  4. 读取2字节的转换数据
  5. 拉高CS,通信完成

控制命令说明

控制命令的长度1字节

定义说明
7(MSB)S起始标志,必须为1
6,5,4A2-A0ADC通道选择
3MODE下一次ADC分辨率选择,0=12位,1=8位
2SER/DFRADC单端/差分选额,测量触摸屏时可以使用差分,其他情况使用单端
0=差分,1=单端
1PD1是否使用内部参考电压,0=关闭(使用外部参考电压),1=打开
0(LSB)PD0控制本次通信结束后是否进入休眠,0=休眠,1=不休眠
休眠模式不影响下次采样,而且可以使用PENIRQ

ADC通道表

SER/DFRA2A1A0说明
0001测量Y坐标(差分)
0011
0100
0101测量X坐标(差分)
1000测量TEMP0
1001测量Y坐标
1010测量VBAT输入
1011
1100
1101测量X坐标
1110测量AUX输入
1111测量TEMP1

上面留空的是测量Z的(按压屏幕的力度),没有研究

一般情况下,使用差分模式测量X,Y坐标,可额外通过单端模式测量电池电压、芯片温度

读取ADC转换结果

后两个字节是MISO,即XPT2046发出数据,单片机接收

第一位为0,随后12位为ADC转换结果(高位在前),然后3位的0,代码表示如下

uint16_t adc_value = buf[0] << 5 | buf[1] >> 3;

多次连续采样

单次采样需要占用3个字节(1发送 2接收),XPT2046支持在上次转换未完成之前就开启下次采样,即在接收第二字节的同时,发起下次采样的命令

SCLK: 第一字节       第二字节              第三字节               第四字节              第五字节
MOSI: 发送第一次命令  空闲                 发送第二次采样命令      空闲                 发送第三次采样命令    ...
MISO: 无效数据       接收第一次采样高字节   接收第一次采样低字节    接收第二次采样高字节   接收第二次采样低字节  ... 

代码示例

初始化

刚上电时,PD0的值是不确定的,先发送一次命令,让其变为0

void XTP2046_init(void) {
    gpio_cs_value(0);
    spi_transfer_byte(0b10000010);  // PD1=1, PD0=0
    gpio_cs_value(1);
}

采样函数

给定命令,返回对应的ADC值

#define XTP_COMMAND_DIFF_X     // 差分X
#define XTP_COMMAND_DIFF_Y    // 差分Y
#define XTP_COMMAND_VBAT      // 电池电压
#define XTP_COMMAND_TEMP0     // 测温度需要读取两次
#define XTP_COMMAND_TEMP1     // 测温度需要读取两次

uint16_t XTP2046_read_adc(int cmd) {
    gpio_cs_value(0);
    spi_transfer_byte(cmd);
    int high_byte = spi_transfer_byte(cmd);
    int low_byte = spi_transfer_byte(cmd);
    gpio_cs_value(1);
    
    return high_byte << 5 | low_byte >> 3;
}

读取电池电压

内部参考电压为2.5V,测量电阻时内部使用了1/4分压(上电阻7.5k,下电阻2.5k)

uint16_t XTP2046_read_vbat_mv(void) {
    uint16_t adc_raw = XTP2046_read_adc(XTP_COMMAND_VBAT);
    // return 2500.0 / 4096.0 * adc_raw * 4.0;  // 这里使用了浮点数
    return (adc_raw * 625) >> 8; // 优化为整数且避免除法
}

读取温度

根据文档,使用间接测量法,需要测两次电压

uint16_t XTP2046_read_temperature(void) {
    float u1_mv = XTP2046_read_adc(XTP_COMMAND_TEMP0) * 2500 / 4096;
    float u2_mv = XTP2046_read_adc(XTP_COMMAND_TEMP1) * 2500 / 4096;
    
    float dv = u2_mv - u1_mv;
    temperature = 2.573 * dv - 273;
}

读取触摸坐标

电阻触摸屏可以看作是两个滑动变阻器,通过读取中间抽头的电压反推滑动的位置,但是滑动变阻器的两端并不是0欧,而是有一定的电阻,所以电阻屏的使用一定需要先校准,也就是一个映射函数,把ADC的值映射为屏幕坐标

假设触摸屏的电阻分布均匀,则有可以认为坐标与ADC的值为一次函数关系(y = kx + b),即

coord_x = k1 * adc_x + b2
coord_y = k1 * adc_y + b2

这样只需提供两个不在同一横线和竖线上的两点,即可解出四个未知变量