STM32触摸屏学习手记.docx

上传人:b****6 文档编号:8849001 上传时间:2023-02-02 格式:DOCX 页数:19 大小:320.83KB
下载 相关 举报
STM32触摸屏学习手记.docx_第1页
第1页 / 共19页
STM32触摸屏学习手记.docx_第2页
第2页 / 共19页
STM32触摸屏学习手记.docx_第3页
第3页 / 共19页
STM32触摸屏学习手记.docx_第4页
第4页 / 共19页
STM32触摸屏学习手记.docx_第5页
第5页 / 共19页
点击查看更多>>
下载资源
资源描述

STM32触摸屏学习手记.docx

《STM32触摸屏学习手记.docx》由会员分享,可在线阅读,更多相关《STM32触摸屏学习手记.docx(19页珍藏版)》请在冰豆网上搜索。

STM32触摸屏学习手记.docx

STM32触摸屏学习手记

STM32触摸屏学习手记

1、触摸屏结构和原理

想驱动触摸屏首先要对触摸屏有一个深刻的了解和认识才行,必须要搞懂触摸屏的原理,才能去很好的驱动。

搜集了一些触摸屏资料,会在附件中列出。

这里只对原理进行讲解。

触摸屏的结构如下图1所示。

这里省略了屏的材质,网上有很多的介绍,但是个人觉得这些东西和驱动它没有什么本质的联系。

通过图1我们可以看到,触摸屏本质上就是两个电阻层。

只是在这两个电阻层之间夹杂了一些其他的材质,我们并不需要关心这个。

图1触摸屏结构

触摸屏工作时,两个电阻层的工作原理如下图2所示。

图2电阻层工作原理

当某一层电极加上电压时,会在该网络上形成电压梯度。

如有外力使得上下两层在某一点接触,则在未加电压的另一层可以测得接触点处的电压,从而知道接触点处的坐标。

比如,在顶层的电极(Y+,Y-)上加上电压,则会在顶层导体层上形成电压梯度,当有外力使得上下两层在某一点接触时,在底层X层就可以测得接触点处的电压,再根据该电压与电极(Y+)之间的距离关系,就可得到该处的Y坐标。

然后,将电压切换到底层电极(X+,X-)上,并在顶层Y层上测量接触点处的电压,从而得到X坐标。

2、控制芯片ADS7843

2.1ADS7843引脚及功能描述

在了解了触摸屏的原理之后会感觉驱动触摸屏其实很简单。

只需要在电阻层(X+,X-)上加上固定的电压,在(Y+,Y-)层读出触摸时电压值,根据比例换算就可知道笔在X方向的触摸位置了。

然后在电阻层(Y+,Y-)上加上固定的电压,在(X+,X-)层读出触摸时的电压值,再根据比例换算就可知道笔在Y方向的触摸位置了。

有了X,Y方向的位置,触摸点的位置就唯一确定了。

因此要想确定笔触摸时的位置,需要两次AD转换:

X方向和Y方向,只是固定电压加给的电极要跟着切换而已。

这些事情现在很多专用的控制芯片都可以完成了,我们的MCU只需要控制这些芯片进行AD转换,并读出转换结果就可以了。

ADS7843是TI公司生产的4线电阻触摸屏转换接口芯片。

具有内置12位模数转换、低导通电阻模拟开关的串行接口等特点。

下图3是它的引脚图。

表1为引脚功能说明。

图3ADS7843引脚图

表1ADS7843引脚功能描述

引脚号

功能

引脚号

功能

1

电源输入端

9

参考电压输入端

2

X+位置输入端

10

供电电源

3

Y+位置输入端

11

笔接触中断引脚

4

X-位置输入端

12

串行数据输出端。

数据在DCLK的下降沿移出,当CS高电平时为高阻态

5

Y-位置输入端

13

忙信号。

当CS为高电平时为高阻态

6

电源地

14

串行数据输入端。

当CS为低电平时,数据在DCLK上升沿所存进来

7

附属AD通道

15

片选信号,控制转换时序和使能串行输入输出寄存器

8

附属AD通道

16

外部时钟信号输入

2.2ADS7843的内部结构及参考电压模式选择

ADS7843之所以能实现对触摸屏的控制,是因为其内部结构很容易实现电极电压的切换,并能进行快速A/D转换。

由于本人能力有限,内部结构图看的不是很懂,这里就不贴出来误导读者了。

但是电压参考模式还是很有必要弄明白。

ADS7843支持两种参考电压输入模式:

一种是参考电压固定为VREF,另一种采取差动模式,参考电压来自驱动电极。

两种模式分别如图4,图5所示。

图4固定参考电压模式图5差动输入模式

这两种模式可供用户自由选择。

固定参考电压模式中参考电压固定为VREF,采样完成后,可以关闭驱动开关以降低功耗。

也许有人会有疑惑,这种模式不是很好了吗?

为什么还要有差动模式输入呢?

这是因为你忽略了驱动开关的导通电阻,由于驱动开关的导通电阻和触摸屏电阻是串联的关系,有分压作用,因此会带来测量误差,所有就有了差动输入模式。

这种模式+REF和-REF的输入分别接在了Y+,Y-上,或者X+,X-上。

这样就消除了驱动开关的导通电阻引入的测量误差。

但是这种方式的缺点就是无论是采样还是转换过程,驱动开关都需要接通,这样功耗就增加了。

因此两种方式各有利弊,鱼和熊掌不可兼得啊!

2.3ADS7843控制字及数据传输格式

ADS7843的控制字如下表2所示。

表2ADS7843的控制字

Bit7

Bit6

Bit5

Bit4

Bit3

Bit2

Bit1

Bit0

S

A2

A1

A0

MODE

SER/DFR

PD1

PD0

Bit7:

S:

数据传输起始标志位,必须为1

Bit6~Bit4:

A2~A0:

通道选择位

Bit3:

MODE:

选择AD精度

1:

选择8位

0:

选择12位

Bit2:

SER/DFR:

选择参考电压模式

1:

固定参考电压模式

2:

差动输入模式

Bit1~Bit0:

PD1~PD0选择省电模式

00:

省电模式,允许中断

01:

省电模式,不允许中断

10:

保留

11:

禁止省电模式

为了完成一次电极电压切换和AD转换,需先往ADS7843发送控制字,转换完成后再读出电压转换值。

标准的一次转换需要24个时钟周期。

转换时序如下图6所示。

图6AD转换时序

3、程序设计

在这里声明一下,代码参考了正点原子的触摸程序。

有些地方做了改进,融入了一些自己的思想。

程序最终在正点原子的开发板上通过验证。

软件设计和硬件是密不可分,首先我们得弄清楚STM32的那些IO口和ADS7843相连接。

硬件连接如下图7所示。

在这里我没有用到笔接触中断引脚,程序设计中也没有用到中断。

图7ADS7843与STM32连接图

首先肯定是IO口初始化函数,由上图可以看出,要把PC0,PC13,PC3配置为输出,PC2配置为输入。

代码如下:

voidTouch_init()

{

RCC->APB2ENR|=(1<<4);//PC口时钟使能

GPIOC->CRL&=0xFFFF0000;

GPIOC->CRL|=0x00003883;//PC1,PC2配置为带上/下拉的输入

//PC0,PC3配置为推挽输出

GPIOC->CRH&=0xFF0FFFFF;

GPIOC->CRH|=0x00300000;//PC13配置为推挽输出

GPIOC->ODR|=(1<<0)|(1<<1)|(1<<2)|(1<<3)|(1<<13);//PC0,PC1,PC2,PC3,PC13全部上拉

}

紧接着就是通过SPI向ADS7843写一个字节,由于SPI本人理解的不是很透彻,所以这里就直接用了正点原子的程序。

代码如下:

voidADS7843_WriteByte(unsignedchardata)

{

unsignedcharcount=0;

for(count=0;count<8;count++)

{

if(data&0x80)TDIN=1;

elseTDIN=0;

data=data<<1;

TCLK=0;

TCLK=1;//数据在上升沿锁存

}

}

下面就可以通过voidADS7843_WriteByte(unsignedchardata)这个函数先给ADS7843发送命令控制字,然后读出AD转换的结果。

代码如下:

unsignedintADS7843_ReadAD(unsignedcharcmd)

{

unsignedcharcount=0;

unsignedintnumber=0;

TCLK=0;//先拉低时钟

TCS=0;//选中ADS7843

ADS7843_WriteByte(cmd);//发送命令字

delay_us(6);//ADS7843转换时长最长为6us

TCLK=1;//给一个时钟清楚BUSY位

TCLK=0;

for(count=0;count<16;count++)

{

number<<=1;

TCLK=0;//下降沿有效

TCLK=1;

if(DOUT)number++;

}

number>>=4;//取高12位

TCS=1;//释放ADS7843

returnnumber;

}

调用一次ADS7843_ReadAD(unsignedcharcmd)这个函数,我们就可以读回AD转换的结果。

这个函数中cmd是什么呢?

它就是命令控制字,详细介绍请看表2。

在这里我们可以令CMD_RDX=0xD0,CMD_RDY=0x90。

其实就是读X方向AD值时把控制字的A2~A0配置为101,读Y方向AD值时把控制字的A2~A0配置为001。

都选择12位模式,差动输入,省电模式并允许中断,因此剩余的各位都是一样的。

调用一次这个函数就返回一次AD转换结果,但是往往转换一次与真实值之间存在很大的偏差,所以我们就需要转换多次,除掉最大值和最小值,然后求平均值的方法。

这样得到的结果才有意义。

前段时间在一本书中看到一句话,说进行AD转换,一次转换结果是毫无意义的,必须转换多次求平均值。

因此以后再遇到同样的问题,我就会用这种方式处理了。

求平均值代码如下:

#defineREAD_TIMES15//读取次数

#defineLOST_VAL5//丢弃数据个数

unsignedintADS7843_ReadAvgAD(unsignedcharcmd)

{

unsignedinti,j;

unsignedintAD_buffer[READ_TIMES];

unsignedintt;

unsignedintsum,AVG;

for(i=0;i

{

AD_buffer[i]=ADS7843_ReadAD(cmd);

}

for(j=0;j

{

for(i=0;i

{

if(AD_buffer[i]>AD_buffer[i+1])

{

t=AD_buffer[i];

AD_buffer[i]=AD_buffer[i+1];

AD_buffer[i+1]=t;

}

}

}

sum=0;

for(i=LOST_VAL;i

{

sum+=AD_buffer[i];

}

AVG=sum/(READ_TIMES-2*LOST_VAL);//求取平均值

returnAVG;

}

通过调用ADS7843_ReadAvgAD(unsignedcharcmd),就得到了多次转换的平均值。

READ_TIMES次数越多,得到的结果就越准确,但是次数也不宜太多,否则求一次平均值的时间就会变长,一般在10~20就可以了。

通过以上代码就可以实现调用一次ADS7843_ReadAvgAD(unsignedcharcmd),就返回一次X方向或者Y方向的AD转换平均值。

但是这样做还是显得比较麻烦,得X方向和Y方向各调用一次才能知道一个点的AD转换结果。

那么可不可以用一个函数把这两次调用封装在一起呢?

可以!

代码如下:

unsignedcharRead_ADS7843(int*x,int*y)

{

intxtemp,ytemp;

xtemp=ADS7843_ReadAvgAD(CMD_RDX);

ytemp=ADS7843_ReadAvgAD(CMD_RDY);

if(xtemp<100||ytemp<100)return0;//读取失败

else

{

*x=xtemp;

*y=ytemp;

return1;//读取成功

}

}

这个函数首先把读到的X方向平均值存到xtemp中,把读到的Y方向的平均值存到ytemp中,然后判断这两个值是否小于100,如果小于100,则返回0,认为读取失败。

因为我们的触摸屏是放在TFT上面,他们的起始坐标并不一样。

如下图8所示。

图8TFT和触摸屏位置示意图

从图上我们可以看到,触摸屏要比下面的TFT要大。

当我们的笔触摸到TFT的外面时,AD转换还是会有结果,一般会小于100,但是这样的结果是没有意义的,我们并不需要这样的结果,因此return0,认为读取失败。

当读取到的AD值大于100,则认为转换是有效的,然后把xtemp存入形参*x指定的地址中,把ytemp存入形参*y指定的地址中,并return1,表示读取成功。

在正点原子的教程中他称之为滤波,暂且这么叫吧。

那么调用一次Read_ADS7843(int*x,int*y),通过返回值就可知道读取的AD值是否有效。

如果有效就把数据存入*x,*y指定的地址中。

这样我们就知道了触点X,Y方向的AD值了。

但是这样做精确度还是不够高,那么就想出一种办法,调用两次Read_ADS7843(int*x,int*y)函数,把两次得到的X方向AD值和Y方向AD值进行比较,如果超过了误差允许范围则认为读取失败。

这样做精确度将大大的提高。

代码如下:

#defineERR_RANGE50

unsignedcharRead_ADS7843Two(int*x,int*y)

{

intx1,y1;

intx2,y2;

unsignedcharflag;

flag=Read_ADS7843(&x1,&y1);

if(flag==0)return0;

flag=Read_ADS7843(&x2,&y2);

if(flag==0)return0;

if(((x2<=x1&&x1

&&((y2<=y1&&y1

{

*x=(x1+x2)/2;//把两次采集到的数据求平均值存到*x,*y中

*y=(y1+y2)/2;

return1;

}

else

{

return0;

}

}

通过调用Read_ADS7843Two(int*x,int*y),得到的笔触点X,Y方向的AD值就很精确了,再通过相关的计算转化就可以得到实际的坐标了。

以上内容就是如何准确的得到一个触点X,Y方向的AD值。

有了上述的准备,下面就要把读到的AD值和实际坐标一一对应起来。

比方说我们的笔触摸到屏幕上一点,如下图9所示,返回的AD值为(1600,1200),即触点X方向AD值为1600,Y方向AD值为1200,那么我们怎么把AD值转换成实际坐标呢?

图9笔触摸到屏幕上一点示意图

这里就要引入校正了,因为我们在实际中没办法确定TFT屏的原点,那么我们只能在TFT屏上先确定4个点,如图10所示。

图104个校准点和实际坐标

这4个点的坐标我们是知道的,然后用笔去触摸这4个点,记录下这四个点的AD值,分别为(AD_x1,AD_y1),(AD_x2,AD_y2),(AD_x3,AD_y3),(AD_x4,AD_y4)。

假如触摸屏幕上某点,得到的AD坐标为(AD_X,AD_Y),那么实际坐标:

代码如下:

voidTouch_Adjust(void)

{

unsignedintPosition_Temp[4][2];//存放四个点的AD值

unsignedlongtemp1,temp2;//计算中间变量

unsignedintd1,d2,d3,d4;

floatfac1,fac2;

while

(1)

{

lcd_clear(WHITE);

lcd_draw_line(10,20,30,20);

lcd_draw_line(20,10,20,30);

while(!

Read_ADS7843Two(&AD_X,&AD_Y));

Position_Temp[0][0]=AD_X;//x1

Position_Temp[0][1]=AD_Y;//y1

delay_ms(400);

lcd_clear(WHITE);

lcd_draw_line(210,20,230,20);

lcd_draw_line(220,10,220,30);

while(!

Read_ADS7843Two(&AD_X,&AD_Y));

Position_Temp[1][0]=AD_X;//x2

Position_Temp[1][1]=AD_Y;//y2

delay_ms(400);

lcd_clear(WHITE);

lcd_draw_line(10,300,30,300);

lcd_draw_line(20,290,20,310);

while(!

Read_ADS7843Two(&AD_X,&AD_Y));

Position_Temp[2][0]=AD_X;//x3

Position_Temp[2][1]=AD_Y;//y3

delay_ms(400);

lcd_clear(WHITE);

lcd_draw_line(210,300,230,300);

lcd_draw_line(220,290,220,310);

while(!

Read_ADS7843Two(&AD_X,&AD_Y));

Position_Temp[3][0]=AD_X;//x4

Position_Temp[3][1]=AD_Y;//y4

delay_ms(400);

temp1=abs(Position_Temp[0][0]-Position_Temp[1][0]);//x1-x2

temp2=abs(Position_Temp[0][1]-Position_Temp[1][1]);//y1-y2

temp1*=temp1;

temp2*=temp2;

d1=sqrt(temp1+temp2);//得到(x1,y1)和(x2,y2)之间的距离

temp1=abs(Position_Temp[2][0]-Position_Temp[3][0]);//x3-x4

temp2=abs(Position_Temp[2][1]-Position_Temp[3][1]);//y3-y4

temp1*=temp1;

temp2*=temp2;

d2=sqrt(temp1+temp2);//得到(x3,y3)和(x4,y4)之间的距离

fac1=(float)d1/d2;//两条水平线长度之比

temp1=abs(Position_Temp[0][0]-Position_Temp[2][0]);//x1-x3

temp2=abs(Position_Temp[0][1]-Position_Temp[2][1]);//y1-y3

temp1*=temp1;

temp2*=temp2;

d3=sqrt(temp1+temp2);//得到(x1,y1)和(x3,y3)之间的距离

temp1=abs(Position_Temp[1][0]-Position_Temp[3][0]);//x2-x4

temp2=abs(Position_Temp[1][1]-Position_Temp[3][1]);//y2-y4

temp1*=temp1;

temp2*=temp2;

d4=sqrt(temp1+temp2);//得到(x2,y2)和(x4,y4)之间的距离

fac2=(float)d3/d4;//两条垂直线长度之比

if(fac1<0.95||fac1>1.05||fac2<0.95||fac2>1.05||d1==0||d2==0||d3==0||d4==0)

//任何一个条件不满足都认为是校验失败,则会继续循环执行校验程序,直到校验成功才会退出

{

lcd_show_string(100,150,"ERROR");//显示校验失败

}

else

{

fac_x=(float)200.0*2/(d1+d2);//x方向校准因数fac_x=200.0*2/(d1+d2)

fac_y=(float)280.0*2/(d3+d4);//y方向校准因数fac_y=280.0*2/(d3+d4)

AD_x1=Position_Temp[0][0];//把起点坐标(x1,y1)的AD值存入AD_x1和AD_y1中

AD_y1=Position_Temp[0][1];

/*把xy方向的校准因数和起点坐标(x1,y1)的AD值存入24C02*/

AT24CXX_WriteOneByte(0x00,(unsignedchar)(fac_x*1000));

AT24CXX_WriteOneByte(0x01,(unsignedchar)(fac_y*1000));

AT24CXX_WriteOneByte(0x02,(unsignedchar)(AD_x1/256));

AT24CXX_WriteOneByte(0x03,(unsignedchar)(AD_x1%256));

AT24CXX_WriteOneByte(0x04,(unsignedchar)(AD_y1/256));

AT24CXX_WriteOneByte(0x05,(unsignedchar)(AD_y1%256));

lcd_show_string(100,150,"RIGHT");//显示校验成功

delay_ms(1000);

delay_ms(1000);

lcd_clear(WHITE);

break;//退出校验循环程序

}

}

}

在main函数中有一个按键检测函数,当检测到按键按下时就进入了屏幕校准程序。

屏幕校准程序是一个while

(1)循环,首先会显示一个+的符号在屏幕左上角,然后有一个while等待,只有你校验了这个点,程序才能向下继续执行,否则就在那个位置循环等死。

重复四次后,四个点的AD值被存入了Position_Temp[4][2]这个数组中。

然后计算出(x1,y1)和(x2,y2)之间的距离d1和(x3,y3)和(x4,y4)之间的距离d2,把这两个水平距离相除得到一个比值fac1。

再计算出(x1,y1)和(x3,y3)之间的距离d3和(x2,y2)和(x4,y4)之间的距离d4,把这两个竖直方向的距离相除得到一个比值fac2。

如果0.95

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 高等教育 > 工学

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1