STM32触摸屏学习手记.docx
- 文档编号:8849001
- 上传时间:2023-02-02
- 格式:DOCX
- 页数:19
- 大小:320.83KB
STM32触摸屏学习手记.docx
《STM32触摸屏学习手记.docx》由会员分享,可在线阅读,更多相关《STM32触摸屏学习手记.docx(19页珍藏版)》请在冰豆网上搜索。
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
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- STM32 触摸屏 学习 手记