基于战舰V3的电阻型触摸实验

基于战舰V3的电阻型触摸实验

原理简介

电阻屏幕用到的是XPT2046芯片,其原理比较简单:当我们触摸到某个点位时,X,Y轴的电阻分布会发生改变:

 

当我们触摸下屏幕的一点时,芯片的引脚会发生电平变化,此时我们等待一段时间后,就可以读取到我们想要的坐标数据。

芯片引脚简介

 

这里最重要的是上面这几个标注了的引脚,这些引脚的含义如下:

1.串行时钟:时钟信号传输线

2.片选信号:就是使能信号,低电平有效;

3.串行数据输入:向XPT2046芯片写入指令/数据;

3.状态转换信号:当我们向芯片传输指令完毕后,BUSY信号可以指示我们:已接收指令,正在处理中,此时,我们需要等待大约6us,我们才可以接收到数据;

4.串行数据输出:用于输出转换后的X,Y坐标;

5.笔中断:用于指示“触摸屏已被触摸”,当我们读取X,Y坐标后,笔中断信号立即失效,该信号低电平有效;

在我们一些外围设备中,一般用电平信号的转变来触发MCU读取该设备想要输出的信息,这里XPT2046的笔中断引脚信号起的就是这个作用。

XPT2046与MCU的硬件连接

//电阻/电容屏芯片连接引脚
#definePENPFin(10)//PF10INT-笔中断信号接受引脚
#defineDOUTPBin(2)//PB2MISO-串行数据接受引脚
#defineTDINPFout(9)//PF9MOSI-串行指令输出引脚
#defineTCLKPBout(1)//PB1SCLK-串行时钟输出引脚
#defineTCSPFout(11)//PF11CS-片选输出引脚

对应的硬件连接图如下所示:

 

代码解析

我们这里使用GPIO电平变化来模拟SPI信号,具体实现如下所示:

结构体参数解析

//触摸屏控制器  
typedef struct  
{  
    u8 (*init)(void);           //初始化触摸屏控制器  
    u8 (*scan)(u8);             //扫描触摸屏.0,屏幕扫描;1,物理坐标;  
    void (*adjust)(void);       //触摸屏校准  
    u16 x[CT_MAX_TOUCH];        //当前坐标  
    u16 y[CT_MAX_TOUCH];        //电容屏有最多5组坐标,电阻屏则用x[0],y[0]代表:此次扫描时,触屏的坐标,用x[4],y[4]存储第一次按下时的坐标.  
    u8  sta;                    //笔的状态  
    //b7:按下1/松开0;  
    //b6:0,没有按键按下;1,有按键按下.  
    //b5:保留  
    //b4~b0:电容触摸屏按下的点数(0,表示未按下,1表示按下)  
/触摸屏校准参数(电容屏不需要校准)//  
    float xfac;  
    float yfac;  
    short xoff;  
    short yoff;  
//新增的参数,当触摸屏的左右上下完全颠倒时需要用到.  
//b0:0,竖屏(适合左右为X坐标,上下为Y坐标的TP)  
//   1,横屏(适合左右为Y坐标,上下为X坐标的TP)  
//b1~6:保留.  
//b7:0,电阻屏  
//   1,电容屏  
    u8 touchtype;  
} _m_tp_dev;  

这里我们的屏幕是电阻屏,因此触摸点坐标的保存变量是成员变量x[4],y[4];xfac,yfac是一次拟合表达式的斜率,xoff,yoff是一次拟合表达式的偏移量。电阻屏中,变量sta用到的各个位含义如下:

b6:代表此时有无触摸;

b7:代表此时有无有效的触摸;

有效触摸和触摸的区别:如果我们一直按着触摸点那么就相当于我们一直再触摸,但是我们认为不管一次触摸时长是多少,这仅仅只是一次有效触摸而已。

数据写操作

 

MCU传递给XTP2046的信息在XT92046串行时钟线上的信号出现上升沿时,XTP2046将串行数据输入线上的电平信号所存在自己的寄存器中,相当于在时钟线上出现上升沿时,XTP2046才会成功接受数据。实现代码如下:

//SPI写数据
//向触摸屏IC写入1byte数据
//num:要写入的数据
voidTP_Write_Byte(u8num)
{
    u8count=0;
    for(count=0;count<8;count++)
    {
        if(num&0x80)TDIN=1;
        else TDIN=0;
        num<<=1;
        TCLK=0;
        delay_us(1);
        TCLK=1;//上升沿有效
    }
}

上述代码的大致含义如下:

1.先将数据位设置完毕,然后在SCK时钟线上模拟上升沿;

2.不断循环,直至8个位发送完毕。

读取AD转换结果

XTP2046的指令不复杂,无非就是:发送一个字节的指令设置XTP2046的工作状态,然后XTP2046等待6us之后就会向MCU传递数据了,这个函数完成的就是这个作用。切记:XTP2046向MCU传输的只能是AD转换结果,我们要得到X/Y坐标需要进行直线拟合(X/Y轴的AD转换电压值和与原点的距离成y=kx+b的一次线性关系)。

这里,我们重点说一下这一个字节的指令包含什么有什么用:

表格1

位序号

名称

0

起始位:S

1

地址位:A2

2

地址位:A1

3

地址位:A0

4

模式选择位:MODE

5

控制参考源位:SER=1/DFR=0

6

掉电和内部参考电压配置位:PD1

7

掉电和内部参考电压配置位:PD0

功能如下:

 

 

这里要提一下:差分输入,我们一共有两种模式——差分/单端输入,单端输入虽然速度快功耗低,但是易受外界干扰影响,在模电的差分放大器那一节,我们知道:当当放大器的两个引脚差模输入时,外界干扰由于对两个引脚影响一样,一旦做差就会去除外界干扰信号使得处理器得到精准的电压信号。

 

在测量Y轴坐标时,XP作为差分输入源,此时YN输入需要与XP输入的信号做差才可以进入转换器中,在模拟转换过程中XP通道的开关要一直处于开启状态,这样就会增加一部分功耗。实现代码如下:

//SPI读数据  
//从触摸屏IC读取adc值  
//CMD:指令  
//返回值:读到的数据  
u16 TP_Read_AD(u8 CMD)  
{  
    u8 count=0;  
    u16 Num=0;  
    TCLK=0;     //先拉低时钟  
    TDIN=0;     //拉低数据线  
    TCS=0;      //选中触摸屏IC  
    TP_Write_Byte(CMD);//发送命令字  
    delay_us(6);//ADS7846的转换时间最长为6us=15*0.4us  
    TCLK=0;  
    delay_us(1);  
    TCLK=1;     //给1个时钟,清除BUSY  
    delay_us(1);  
    TCLK=0;  
    for(count=0; count<16; count++) //读出16位数据,只有高12位有效  
    {  
        Num<<=1;  
        TCLK=0; //下降沿有效  
        delay_us(1);  
        TCLK=1;  
        if(DOUT)Num++;  
    }  
    Num>>=4;      //只有高12位有效.  
    TCS=1;      //释放片选  
    return(Num);  
}

代码实现的大致流程如下:

1. 将控制XTP2046的串行数据输入线,时钟输入线(对于MCU来说是输出)的引脚拉低;

2. 拉低片选CS端(低电平有效)选中XTP2046,此时MCU与XTP2046建立了联系;

3. 将8位指令CMD传输至XTP2046中;

4. 等待6us左右MCU再从XTP2046中接收传其回传的数据,那为何这里需要等待6us呢?

XTP2046的输出AD转换位数可以是8位/12位,那我们就按照最大的输出位数去考虑,我们这里取num为16位的,用于接收AD转换的有效数据位(注意:当设置为12b时,输出的2个字节中只有高12位有效)。首先时钟的周期为400ns=0.4us:

 

然后,我们读取转换值需要等待12+3个周期,即T=15*0.4us=6us:

 

前8个时钟用来通过DIN引脚输入控制字节。当转换器获取有关下一次转换的足够信息后,接着根据获得的信息设置输入多路选择器和参考源输入,并进入采样模式,如果需要,将启动触摸面板驱动器。3个多时钟周期后,控制字节设置完成,转换器进入转换状态。这时,接着的12个时钟周期将完成真正的模数转换。如果是度量比率转换方式(差分输入),驱动器在转换过程中将一直工作,第13个时钟将输出转换结果的最后一位。剩下的3个多时钟周期将用来完成被转换器忽略的最后字节(DOUT置低)。

注意:

 

5. 对于传回数据格式为12b的情况,我们取回传数据的高12位作为数据有效位,然后拉高片选,从而切断MCU与XTP2046芯片之间的联系。

这里,我们采取的读取方式是:输入一次指令接收一次回传数据,为什么会这样呢?我们仔细看如下的时序图:

 

我们看到:两个读取周期重叠了,就相当于当XTP2046进行AD转换时,我们在发送指令让其进行AD转换,人家还没完成任务呢就给人家派任务,这本身就是不可能的,因此我们为了避免这种错误操作,我们采取“一条指令->一次回传数据“的方式操作XTP2046。

读取更加准确的X/Y轴AD转换结果的算法

这里用到了“冒泡排序“的算法,取5次转换的XY坐标结果,去掉最大值和最小值,取中间3次的XY轴坐标平均值。

//读取一个坐标值(x或者y)  
//连续读取READ_TIMES次数据,对这些数据升序排列,  
//然后去掉最低和最高LOST_VAL个数,取平均值  
//xy:指令(CMD_RDX/CMD_RDY)  
//返回值:读到的数据  
#define READ_TIMES 5    //读取次数  
#define LOST_VAL 1      //丢弃值  
u16 TP_Read_XOY(u8 xy)  
{  
    u16 i, j;  
    u16 buf[READ_TIMES];  
    u16 sum=0;  
    u16 temp;  
    for(i=0; i<READ_TIMES; i++)buf[i]=TP_Read_AD(xy);  
    for(i=0; i<READ_TIMES-1; i++) //排序  
    {  
        for(j=i+1; j<READ_TIMES; j++)  
        {  
            if(buf[i]>buf[j])//升序排列  
            {  
                temp=buf[i];  
                buf[i]=buf[j];  
                buf[j]=temp;  
            }  
        }  
    }  
    sum=0;  
    for(i=LOST_VAL; i<READ_TIMES-LOST_VAL; i++)sum+=buf[i];  
    temp=sum/(READ_TIMES-2*LOST_VAL);  
    return temp;  
} 

使用算法获取更加精准的X/Y轴AD转换结果

//默认为touchtype=0的数据.  
u8 CMD_RDX=0XD0;  
u8 CMD_RDY=0X90;  
  
//读取x,y坐标  
//最小值不能少于100.  
//x,y:读取到的坐标值  
//返回值:0,失败;1,成功。  
u8 TP_Read_XY(u16 *x,u16 *y)  
{  
    u16 xtemp,ytemp;  
    xtemp=TP_Read_XOY(CMD_RDX);  
    ytemp=TP_Read_XOY(CMD_RDY);  
    //if(xtemp<100||ytemp<100)return 0;//读数失败  
    *x=xtemp;  
    *y=ytemp;  
    return 1;//读数成功  
}  

这里,我们要解析一下:指令CMD_RDX和CMD_RDY的结构,结构如下:

表格 2

C7

C6

C5

C4

C3

C2

C1

C0

CMD_RDX

1

1

0

1

0

0

0

0

CMD_RDY

1

0

0

1

0

0

0

0

两者不一样的就是C6~C4位,这三位分别对应这A2~A0地址位:

 

确定读取X/Y坐标是否合格并输出合格结果

//连续2次读取触摸屏IC,且这两次的偏差不能超过  
//ERR_RANGE,满足条件,则认为读数正确,否则读数错误.  
//该函数能大大提高准确度  
//x,y:读取到的坐标值  
//返回值:0,失败;1,成功。  
#define ERR_RANGE 50 //误差范围   
u8 TP_Read_XY2(u16 *x,u16 *y)  
{  
    u16 x1,y1;  
    u16 x2,y2;  
    u8 flag;  
    flag=TP_Read_XY(&x1,&y1);  
    if(flag==0)return(0);  
    flag=TP_Read_XY(&x2,&y2);  
    if(flag==0)return(0);  
    if(((x2<=x1&&x1<x2+ERR_RANGE)||(x1<=x2&&x2<x1+ERR_RANGE))//前后两次采样在+-50内  
            &&((y2<=y1&&y1<y2+ERR_RANGE)||(y1<=y2&&y2<y1+ERR_RANGE)))  
    {  
        *x=(x1+x2)/2;  
        *y=(y1+y2)/2;  
        return 1;  
    } else return 0;  
} 

这个函数的作用是:检测连续两次检测得到的X/Y轴的AD转换值之间的偏差是否在容许误差范围之内,如果在则返回两次读取X/Y坐标的平均值;如果不在则返回0。

按键扫描

电阻屏的缺点就在于:需要校准。为何校准:因为我们不知道触摸屏的位置和AD转换值的结果,虽然输出的X/Y轴的AD电压值与触摸点在X/Y轴上与原点的距离Xd/Yd呈一次线性关系,但是y=kx+b方程式中k,b仍是待定系数。待定系数确定的方法:

两点确定一条直线,因此我们先测两个触摸点的输出的AD转换的电压值作为因变量,然后根据这两个点之间的Xd/Yd(作为自变量)来拟合直线X/Y轴专属的y=kx+b一次线性关系。

物理坐标:没有拟合之前的A/D转换电压值;

屏幕坐标:通过拟合得到的X/Y坐标值;

//触摸按键扫描  
//tp:0,屏幕坐标;1,物理坐标(校准等特殊场合用)  
//返回值:当前触屏状态.  
//0,触屏无触摸;1,触屏有触摸  
u8 TP_Scan(u8 tp)  
{  
    if(PEN==0)//有按键按下  
    {  
        if(tp)TP_Read_XY2(&tp_dev.x[0],&tp_dev.y[0]);//读取物理坐标  
        else if(TP_Read_XY2(&tp_dev.x[0],&tp_dev.y[0]))//读取屏幕坐标  
        {  
            tp_dev.x[0]=tp_dev.xfac*tp_dev.x[0]+tp_dev.xoff;//将结果转换为屏幕坐标  
            tp_dev.y[0]=tp_dev.yfac*tp_dev.y[0]+tp_dev.yoff;  
        }  
        if((tp_dev.sta&TP_PRES_DOWN)==0)//之前没有被按下  
        {  
            tp_dev.sta=TP_PRES_DOWN|TP_CATH_PRES;//按键按下  
            tp_dev.x[4]=tp_dev.x[0];//记录第一次按下时的坐标  
            tp_dev.y[4]=tp_dev.y[0];  
        }  
    } else  
    {  
        if(tp_dev.sta&TP_PRES_DOWN)//之前是被按下的  
        {  
            tp_dev.sta&=~(1<<7);//标记按键松开  
        } else//之前就没有被按下  
        {  
            tp_dev.x[4]=0;  
            tp_dev.y[4]=0;  
            tp_dev.x[0]=0xffff;  
            tp_dev.y[0]=0xffff;  
        }  
    }  
    return tp_dev.sta&TP_PRES_DOWN;//返回当前的触屏状态  
} 

注意:不管多长时间触摸显示屏仅相当于一次触摸。

上述程序的大致执行流程如下:

1. 如果有按键按下(笔中断信号PEN=0,笔中断信号低电平有效),输入参数tp=1时,电阻屏只执行X/Y轴AD电压值的读取,不进行X/Y轴坐标拟合操作(主要是此时,没使用“两点确定一条直线”的定理来确定待定系数k,b,那还算个啥);反之,如果参数tp=0,则测量X/Y轴AD转换值并且根据求解出来的待定系数k,b求解实际的触摸点在X/Y轴的坐标;

2. 如果有按键按下(笔中断信号PEN=0,笔中断信号低电平有效),如果之前没有触摸过,说明这次触摸有效,则标记触摸屏触摸过且记录计算出的X/Y坐标;

3. 如果笔中断信号PEN=1且按键按下,说明长时间触摸显示屏,此时我们标记“触摸屏没有被再次触摸”;当我们完全松开显示屏时,将用于保存触摸点的X/Y坐标变量中的信息清空。

① 如何确保长时间触摸只按照触摸一次动作呢?

1. 如果触摸标志位一直有效,但是笔中断信号无效,说明:有效触摸次数为1,则执行如下程序:

if(tp_dev.sta&TP_PRES_DOWN)//之前是被按下的  
{  
    tp_dev.sta&=~(1<<7);//标记按键松开  
}  

2. 如果触摸标志位无效且笔中断信号无效,说明:此时触摸屏没有被触摸,则执行如下程序:

tp_dev.x[4]=0;  
tp_dev.y[4]=0;  
tp_dev.x[0]=0xffff;  
tp_dev.y[0]=0xffff; 

这段程序用于清空用于记录X/Y坐标的变量和计算X/Y坐标的中间变量。

注意,触摸点的X/Y坐标只在第一次触摸,即一次有效触摸时赋值给变量:

if((tp_dev.sta&TP_PRES_DOWN)==0)//之前没有被按下  
{  
    tp_dev.sta=TP_PRES_DOWN|TP_CATH_PRES;//按键按下  
    tp_dev.x[4]=tp_dev.x[0];//记录第一次按下时的坐标  
    tp_dev.y[4]=tp_dev.y[0];  
}  

只有第一次触摸LCD时sta的b7才会置一,其他均为0,即只有第一次触摸才为有效触摸。只有当有效触摸时,才会记录下此时触摸点的X/Y坐标。扫描函数的返回值正是“是否为有效触摸。是则返回真,不是则返回0”:

return tp_dev.sta&TP_PRES_DOWN;//返回当前的触屏状态  

② 输入参数tp的意义是什么?

if(tp)TP_Read_XY2(&tp_dev.x[0],&tp_dev.y[0]);//读取物理坐标  
else if(TP_Read_XY2(&tp_dev.x[0],&tp_dev.y[0]))//读取屏幕坐标  
{  
    tp_dev.x[0]=tp_dev.xfac*tp_dev.x[0]+tp_dev.xoff;//将结果转换为屏幕坐标  
    tp_dev.y[0]=tp_dev.yfac*tp_dev.y[0]+tp_dev.yoff;  
}  

tp的意义在于:确定是只读取A/D转换值(用于校准),还是读取A/D转换值并且拟合出X/Y的实际坐标。

校准数据的保存

//保存在EEPROM里面的地址区间基址,占用14个字节(RANGE:SAVE_ADDR_BASE~SAVE_ADDR_BASE+13)  
#define SAVE_ADDR_BASE 40  
//保存校准参数  
void TP_Save_Adjdata(void)  
{  
    AT24CXX_Write(SAVE_ADDR_BASE,(u8*)&tp_dev.xfac,14); //强制保存&tp_dev.xfac地址开始的14个字节数据,即保存到tp_dev.touchtype  
    AT24CXX_WriteOneByte(SAVE_ADDR_BASE+14,0X0A);       //在最后,写0X0A标记校准过了  
}  

为什么这里保存数据的偏移量是0~14呢?那得看定义的结构体中我们想要保存的数据的存储地址了:

//触摸屏控制器  
typedef struct  
{  
    u8 (*init)(void);           //初始化触摸屏控制器  
    u8 (*scan)(u8);             //扫描触摸屏.0,屏幕扫描;1,物理坐标;  
    void (*adjust)(void);       //触摸屏校准  
    u16 x[CT_MAX_TOUCH];        //当前坐标  
    u16 y[CT_MAX_TOUCH];        //电容屏有最多5组坐标,电阻屏则用x[0],y[0]代表:此次扫描时,触屏的坐标,用x[4],y[4]存储第一次按下时的坐标.  
    u8  sta;                    //笔的状态  
    //b7:按下1/松开0;  
    //b6:0,没有按键按下;1,有按键按下.  
    //b5:保留  
    //b4~b0:电容触摸屏按下的点数(0,表示未按下,1表示按下)  
/触摸屏校准参数(电容屏不需要校准)//  
    float xfac;  
    float yfac;  
    short xoff;  
    short yoff;  
//新增的参数,当触摸屏的左右上下完全颠倒时需要用到.  
//b0:0,竖屏(适合左右为X坐标,上下为Y坐标的TP)  
//   1,横屏(适合左右为Y坐标,上下为X坐标的TP)  
//b1~6:保留.  
//b7:0,电阻屏  
//   1,电容屏  
    u8 touchtype;  
} _m_tp_dev;  

这里,我们想保存的变量是:xfac,yfac,xoff,yoff分别可以拟合X/Y坐标和X/Y轴A/D转换所得的电压值的关系:

Y轴:y = yfac*x+yoff;

X轴:x = xfac*x+xoff;

这里保存了14个字节的数据:

//在AT24CXX里面的指定地址开始写入指定个数的数据  
//WriteAddr :开始写入的地址 对24c02为0~255  
//pBuffer   :数据数组首地址  
//NumToWrite:要写入数据的个数  
void AT24CXX_Write(u16 WriteAddr,u8 *pBuffer,u16 NumToWrite)  
{  
    while(NumToWrite--)  
    {  
        AT24CXX_WriteOneByte(WriteAddr,*pBuffer);  
        WriteAddr++;  
        pBuffer++;  
    }  
}  

代码中显示:24C02保存了NumToWrite个数据,因为当NumToWrite自减至0时,自动跳出循环,因此保存了字节序号:14~1这14个字节的数据。

在_m_tp_dev结构体中,,yoff的地址相较于xfac的地址偏移了12个字节,各变量地址分布如下:

表格 3

变量名称

起始地址(相对xfac的地址)

终止地址(相对xfac的地址)

xfac

0

3

yfac

4

7

xoff

8

9

yoff

10

11

touchtype

11

12

标记变量

14

14

注意:STM32中float占用4个字节,short占用2个字节。这里,标记变量就是表征着“是否校准过”的标志。

读取X/Y轴一次线性拟合所需的待定系数

//得到保存在EEPROM里面的校准值  
//返回值:1,成功获取数据  
//        0,获取失败,要重新校准  
u8 TP_Get_Adjdata(void)  
{  
    u8 temp;  
    temp=AT24CXX_ReadOneByte(SAVE_ADDR_BASE+14);//读取标记字,看是否校准过!  
    if(temp==0X0A)//触摸屏已经校准过了  
    {  
        AT24CXX_Read(SAVE_ADDR_BASE,(u8*)&tp_dev.xfac,14);//读取之前保存的校准数据  
        if(tp_dev.touchtype)//X,Y方向与屏幕相反  
        {  
            CMD_RDX=0X90;  
            CMD_RDY=0XD0;  
        } else                 //X,Y方向与屏幕相同  
        {  
            CMD_RDX=0XD0;  
            CMD_RDY=0X90;  
        }  
        return 1;  
    }  
    return 0;  
} 

该程序执行流程如下:

1. 将保存在24C02的校准参数(X/Y轴拟合所需的待定参数kx/ky,dx/dy)读取出来;

2. 然后,根据横竖屏标志变量tp_dev.touchtype进行设置,其实横竖屏的实质就是:将X轴拟合的坐标值装入用于存储Y轴拟合坐标值的数组中,即存入tp_dev.y[CT_MAX_TOUCH]中;反之,将X轴拟合的坐标值装入用于存储X轴拟合坐标值的数组中,即存入tp_dev.x[CT_MAX_TOUCH]中。

LCD显示

//与LCD部分有关的函数  
//画一个触摸点  
//用来校准用的  
//x,y:坐标  
//color:颜色  
void TP_Drow_Touch_Point(u16 x,u16 y,u16 color)  
{  
    POINT_COLOR=color;  
    LCD_DrawLine(x-12,y,x+13,y);//横线  
    LCD_DrawLine(x,y-12,x,y+13);//竖线  
    LCD_DrawPoint(x+1,y+1);  
    LCD_DrawPoint(x-1,y+1);  
    LCD_DrawPoint(x+1,y-1);  
    LCD_DrawPoint(x-1,y-1);  
    LCD_Draw_Circle(x,y,6);//画中心圈  
} 

上述程序在TFTLCD中的(x,y)坐标位置显示出如下图形:

 

画点函数

//画一个大点(2*2的点)  
//x,y:坐标  
//color:颜色  
void TP_Draw_Big_Point(u16 x,u16 y,u16 color)  
{  
    POINT_COLOR=color;  
    LCD_DrawPoint(x,y);//中心点  
    LCD_DrawPoint(x+1,y);  
    LCD_DrawPoint(x,y+1);  
    LCD_DrawPoint(x+1,y+1);  
}  

上述程序在TFTLCD中的(x,y)坐标位置显示出如下图形:

 

校准/待定系数的计算和保存

1. 我们需要指定4个已知的坐标点,然后我们触摸这4个点得到XPT2046传回的触摸点的实际X/Y轴的A/D转换值,从而计算交叉点之间的A/D转换值的差:

 

然后,理想状态下D1/D2应该是1,但是事与愿违,我们规定只要D1/D2在[0.95,1.05]以内,我们就认为使用这四个点计算出的拟合系数合格。

//对边相等  
tem1=abs(pos_temp[0][0]-pos_temp[1][0]);//x1-x2  
tem2=abs(pos_temp[0][1]-pos_temp[1][1]);//y1-y2  
tem1*=tem1;  
tem2*=tem2;  
d1=sqrt(tem1+tem2);//得到1,2的距离  
  
tem1=abs(pos_temp[2][0]-pos_temp[3][0]);//x3-x4  
tem2=abs(pos_temp[2][1]-pos_temp[3][1]);//y3-y4  
tem1*=tem1;  
tem2*=tem2;  
d2=sqrt(tem1+tem2);//得到3,4的距离  
fac=(float)d1/d2;  
if(fac<0.95||fac>1.05||d1==0||d2==0)//不合格  
{  
    cnt=0;  
    TP_Drow_Touch_Point(lcddev.width-20,lcddev.height-20,WHITE);    //清除点4  
    TP_Drow_Touch_Point(20,20,RED);                             //画点1  
    TP_Adj_Info_Show(pos_temp[0][0],pos_temp[0][1],pos_temp[1][0],pos_temp[1][1],pos_temp[2][0],pos_temp[2][1],pos_temp[3][0],pos_temp[3][1],fac*100);//显示数据  
 }

2. 我们需要指定4个已知的坐标点,然后我们触摸这4个点得到XPT2046传回的触摸点的实际X/Y轴的A/D转换值,从而计算交叉点之间的A/D转换值的差:

 

然后,理想状态下D1/D2应该是1,但是事与愿违,我们规定只要D1/D2在[0.95,1.05]以内,我们就认为使用这四个点计算出的拟合系数合格。

tem1=abs(pos_temp[0][0]-pos_temp[2][0]);//x1-x3  
tem2=abs(pos_temp[0][1]-pos_temp[2][1]);//y1-y3  
tem1*=tem1;  
tem2*=tem2;  
d1=sqrt(tem1+tem2);//得到1,3的距离  
  
tem1=abs(pos_temp[1][0]-pos_temp[3][0]);//x2-x4  
tem2=abs(pos_temp[1][1]-pos_temp[3][1]);//y2-y4  
tem1*=tem1;  
tem2*=tem2;  
d2=sqrt(tem1+tem2);//得到2,4的距离  
fac=(float)d1/d2;  
if(fac<0.95||fac>1.05)//不合格  
{  
    cnt=0;  
    TP_Drow_Touch_Point(lcddev.width-20,lcddev.height-20,WHITE);    //清除点4  
    TP_Drow_Touch_Point(20,20,RED);                             //画点1  
    TP_Adj_Info_Show(pos_temp[0][0],pos_temp[0][1],pos_temp[1][0],pos_temp[1][1],pos_temp[2][0],pos_temp[2][1],pos_temp[3][0],pos_temp[3][1],fac*100);//显示数据  
    continue;  
}//正确了 

3. 我们需要指定4个已知的坐标点,然后我们触摸这4个点得到XPT2046传回的触摸点的实际X/Y轴的A/D转换值,从而计算交叉点之间的A/D转换值的差:

 

然后,理想状态下D1/D2应该是1,但是事与愿违,我们规定只要D1/D2在[0.95,1.05]以内,我们就认为使用这四个点计算出的拟合系数合格。

//对角线相等  
tem1=abs(pos_temp[1][0]-pos_temp[2][0]);//x1-x3  
tem2=abs(pos_temp[1][1]-pos_temp[2][1]);//y1-y3  
tem1*=tem1;  
tem2*=tem2;  
d1=sqrt(tem1+tem2);//得到1,4的距离  
  
tem1=abs(pos_temp[0][0]-pos_temp[3][0]);//x2-x4  
tem2=abs(pos_temp[0][1]-pos_temp[3][1]);//y2-y4  
tem1*=tem1;  
tem2*=tem2;  
d2=sqrt(tem1+tem2);//得到2,3的距离  
fac=(float)d1/d2;  
if(fac<0.95||fac>1.05)//不合格  
{  
    cnt=0;  
    TP_Drow_Touch_Point(lcddev.width-20,lcddev.height-20,WHITE);    //清除点4  
    TP_Drow_Touch_Point(20,20,RED);                             //画点1  
    TP_Adj_Info_Show(pos_temp[0][0],pos_temp[0][1],pos_temp[1][0],pos_temp[1][1],pos_temp[2][0],pos_temp[2][1],pos_temp[3][0],pos_temp[3][1],fac*100);//显示数据  
    continue;  
}//正确了  

4. 当我们计算如上3种情况之后,如果都满足要求,那么我们就认为XPT2046芯片传回的触摸点的坐标大致就是我们指定的目标触摸点的坐标。然后,我们使用这四个点的实际坐标计算出X/Y轴坐标的拟合系数kx/ky,dx/dy,并存入24C02中结束轮询。

//计算结果  
tp_dev.xfac=(float)(lcddev.width-40)/(pos_temp[1][0]-pos_temp[0][0]);//得到xfac  
tp_dev.xoff=(lcddev.width-tp_dev.xfac*(pos_temp[1][0]+pos_temp[0][0]))/2;//得到xoff  
  
tp_dev.yfac=(float)(lcddev.height-40)/(pos_temp[2][1]-pos_temp[0][1]);//得到yfac  
tp_dev.yoff=(lcddev.height-tp_dev.yfac*(pos_temp[2][1]+pos_temp[0][1]))/2;//得到yoff  
if(abs(tp_dev.xfac)>2||abs(tp_dev.yfac)>2)//触屏和预设的相反了.  
{  
    cnt=0;  
    TP_Drow_Touch_Point(lcddev.width-20,lcddev.height-20,WHITE);    //清除点4  
    TP_Drow_Touch_Point(20,20,RED);                             //画点1  
    LCD_ShowString(40,26,lcddev.width,lcddev.height,16,"TP Need readjust!");  
    tp_dev.touchtype=!tp_dev.touchtype;//修改触屏类型.  
    if(tp_dev.touchtype)//X,Y方向与屏幕相反  
    {  
        CMD_RDX=0X90;  
        CMD_RDY=0XD0;  
    } else                 //X,Y方向与屏幕相同  
    {  
        CMD_RDX=0XD0;  
        CMD_RDY=0X90;  
    }  
    continue;  
}  
POINT_COLOR=BLUE;  
LCD_Clear(WHITE);//清屏  
LCD_ShowString(35,110,lcddev.width,lcddev.height,16,"Touch Screen Adjust OK!");//校正完成  
delay_ms(1000);  
TP_Save_Adjdata();  
LCD_Clear(WHITE);//清屏  
return;//校正完成  

最大轮询1000次,若轮询1000次还不合格那么将24C02中的数据读出来赋值给用于拟合的变量。

delay_ms(10);  
outtime++;  
if(outtime>1000)  
{  
    TP_Get_Adjdata();  
    break;  
}  

MCU引脚的初始化

//触摸屏初始化  
//返回值:0,没有进行校准  
//       1,进行过校准  
u8 TP_Init(void)  
{  
    GPIO_InitTypeDef  GPIO_InitStructure;  
  
    //注意,时钟使能之后,对GPIO的操作才有效  
    //所以上拉之前,必须使能时钟.才能实现真正的上拉输出  
  
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB|RCC_APB2Periph_GPIOF, ENABLE);   //使能PB,PF端口时钟  
  
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;                // PB1端口配置  
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;         //推挽输出  
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;  
    GPIO_Init(GPIOB, &GPIO_InitStructure);//B1推挽输出  
    GPIO_SetBits(GPIOB,GPIO_Pin_1);//上拉  
  
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;                // PB2端口配置  
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;        //上拉输入  
    GPIO_Init(GPIOB, &GPIO_InitStructure);//B2上拉输入  
    GPIO_SetBits(GPIOB,GPIO_Pin_2);//上拉  
  
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11|GPIO_Pin_9;                // F9,PF11端口配置  
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;         //推挽输出  
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;  
    GPIO_Init(GPIOF, &GPIO_InitStructure);//PF9,PF11推挽输出  
    GPIO_SetBits(GPIOF, GPIO_Pin_11|GPIO_Pin_9);//上拉  
  
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;               // PF10端口配置  
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;        //上拉输入  
    GPIO_Init(GPIOF, &GPIO_InitStructure);//PF10上拉输入  
    GPIO_SetBits(GPIOF,GPIO_Pin_10);//上拉  
  
    TP_Read_XY(&tp_dev.x[0],&tp_dev.y[0]);//第一次读取初始化  
    AT24CXX_Init();         //初始化24CXX  
    if(TP_Get_Adjdata())return 0;//已经校准  
    else                    //未校准?  
    {  
        LCD_Clear(WHITE);   //清屏  
        TP_Adjust();        //屏幕校准  
    }  
    TP_Get_Adjdata();  
    return 1;  
}  

这一步就是初始化一些引脚,并且调用校准函数计算并保存校准值,已校准返回0/未校准返回1。

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

肥肥胖胖是太阳

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值