IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 嵌入式 -> 基于战舰V3的电阻型触摸实验 -> 正文阅读

[嵌入式]基于战舰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。

  嵌入式 最新文章
基于高精度单片机开发红外测温仪方案
89C51单片机与DAC0832
基于51单片机宠物自动投料喂食器控制系统仿
《痞子衡嵌入式半月刊》 第 68 期
多思计组实验实验七 简单模型机实验
CSC7720
启明智显分享| ESP32学习笔记参考--PWM(脉冲
STM32初探
STM32 总结
【STM32】CubeMX例程四---定时器中断(附工
上一篇文章      下一篇文章      查看所有文章
加:2021-07-13 17:37:47  更:2021-07-13 17:38:26 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/4 17:34:12-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码