声明:
本文是本人在韦东山笔记的基础上加了一些注释,方便理解。原文地址:
目录
- 声明:
- 第01节 ADC硬件原理
- 第02节 ADC编程
- 2.1 初始化ADC
- 第03节_电阻触摸屏硬件原理
- 3.1 电阻屏的原理
- 3.2 使用触摸屏的流程
- 3.2 触摸屏原理的进一步剖析
- 第04节_S3C2440触摸屏接口
- 第05节 触摸屏编程_按下松开检测
- 第06节_触摸屏编程_ADC中断
- 第07节 触摸屏编程_定时器程序优化
- 第08节 触摸屏编程_使用定时器支持长按
- 第09节 触摸屏编程_较准原理
- 9.1 方法一
- 9.2 方法二
- 第010节_触摸屏编程_较准与画线
- 10.1 编程思路
- 第011节_触摸屏编程_测试
- 第012节_触摸屏编程_完善
第01节 ADC硬件原理
ADC的概念:模数转换器即A/D转换器,或简称ADC,通常是指一个将模拟信号转变为数字信号的电子元件。
原理:通常的模数转换器是把经过与标准量比较处理后的模拟量转换成以二进制数值表示的离散信号的转换器。
故任何一个模数转换器都需要一个参考模拟量作为转换的标准,比较常见的参考标准为最大的可转换信号大小。而输出的数字量则表示输入信号相对于参考信号的大小。
如图,是把可变电阻上的电压值变换的模拟信号通过ADC转换,输出数字信号。
对于数字信号我们需要得到它的几个属性:
- 用多少位来存储这个数据(假设10bit)。(这个是你从2440芯片手册得出的)
- 最大值0b1111111111(10位的话,那就是10个1来表示)
- 它对应的电压是多少伏(模拟信号输入的最大值是多少)我们就可以根据模拟信号(电压)的最大值,来计算出对应的数值。(意思是说,比如你电压最大值为3.3V,则用0b1111111111表示3.3V,而0b0111111111表示1.65V)。
- 采样/转换速度。(从2440芯片手册得出)。
2440芯片手册中的相关信息如下图所示:
对于程序员,我们不关心ADC的内部机制(对上面的机理不用深究),我们只关心:
1、怎么启动ADC
2、启动之后怎么得到数据,
总之:我们都是通过寄存器操作的。
2440芯片手册有下图:
从上图可以看出ADC有8个多路选择器,显然,以后我们写程序的时候,我们可以8个多路选择之一, 下面是编写程序要做的步骤:
1、确定是哪一路信号:设置8:1MUX,选择要测量哪一个引脚,(看原理图选择要测量的引脚)—韦东山的是ain0)(对于韦东山的,他的板子原理图ain0什么也没有接,但需要你在空引脚ain0外接一个SPI模块,这个SPI模块里带有ADC,你可以在这个外接的ADC中调节电阻以充当模拟信号。你去看视频吧这一点)。
2、设置工作时钟(从工作时钟,可以算出,转换一次需要多长时间(Conversion time )。计算公式在2440芯片手册如下图:
注:freq是频率的意思。50MHz是GCLK的值。
3、启动ADC
4、读状态,判断ADC转换是否成功。
5、读数据
第02节 ADC编程
编程步骤:
1、初始化ADC(包括确定哪一路信号、设置工作时钟、启动ADC等等,你去读芯片手册去操作寄存器)
2、读数据,
3、在串口上显示出来。
由韦东山的原理图可知信号为ain0路:
2.1 初始化ADC
下面的函数实现对ADC的初始化。
03 void adc_init(void)
04 {
05 /* [15] : ECFLG, 1 = End of A/D conversion 这个是判断ADC是否在转换的状态的
06 * [14] : PRSCEN, 1 = A/D converter prescaler enable 预分频使能,以操作[13:6]
07 * [13:6]: PRSCVL, adc clk = PCLK / (PRSCVL + 1) ,从这里往上数,第二幅图片有一个公式,
可以看出PRSCVL为49.
08 * [5:3] : SEL_MUX, 000 = AIN 0 确定ain0路信号
09 * [2] : STDBM
10 * [0] : 1 = A/D conversion starts and this bit is cleared after the startup.AD开始转换
11 */
12 ADCCON = (1<<14) | (49<<6) | (0<<3);
13
14 ADCDLY = 0xff;
15 }
第12行:配置ADCCON寄存器,使能A/D 转换器预分频器,设置A/D 转换器预分频值,上拉使能。
第14行:设置ADC 转换启动延时值。
第03节_电阻触摸屏硬件原理
3.1 电阻屏的原理
这节课我们来讲电阻触摸屏的硬件原理。
假设有一个比较长的电阻,电阻是R 上面接3.3V电压,下面接地
假设整个电阻的阻值是R某一个触电它的阻值是R1 根据欧姆定律
3.3v/R = V/R1
V=3.3 *(R1/R)
假设R1是x坐标 R的长度是L这个电阻非常的均匀,那么这个电压就等于 3.3V * (x / L) 这个电压和这个触电的x坐标有一个线性关系 我使用ADC把这个电压算出来,就可以间接得到这个触电的x坐标, 电阻触摸屏就是使用欧姆定律使用电阻原理作出来的。
可以上百度图片搜索触摸屏,就知道了触摸屏的样子,它是一个透明的薄膜:
注意 :LCD是LCD 触摸屏是触摸屏它是两个设备, 我们只不过是把触摸屏做的和LCD大小一样,粘在LCD上面, 实际上触摸屏是由两层膜组成,他们靠的非常近。如下图所示:
上面这层右边引出来,代表xp ,p代表正极;上面这层左边引出来,代表xm, m代表负极。
下面这层膜 前面这条边引出来为yp,后面这层边为ym。
假设我们手指要点击触摸屏,那么上下就会粘贴在一起,我怎么算出这个 x y点的坐标呢?
答:
我们先来 测量触电x坐标:让xp接3.3v,xm接GND,让yp,ym不接电源。
测yp电压 。由于某处按压导致上下膜连接在一起,我就可以通过yp测量这个触电的电压, 这个yp就像探测一样(像前面讲的滑动变阻器的滑针一样),从前面的原理我们可以知道,当这个触电越靠近左边这个电压越小,越靠近右边电压越大, 因此这个yp的电压就可以认为是这个触电的坐标(x坐标)。如下图所示:
类似的我们怎么测量触电y坐标 ?
类似的xp xm不接电源,同样yp接3.3v, ym接GND,这时候电流就从 yp这里流向ym,然后我们就可以测量xp电压 ,当按下屏幕时,上下两层膜链接在一起,这个xp就像探针一样,这个触电越靠近yp电压值越大,越靠近ym电压值越小。yp接3.3V ym接GND,xp xm不接电源 测量xp电压,就是y坐标。如下图所示:
注意:测得的 x y坐标都是电压值,不是屏幕上480 * 272 这些值,我们需要把电压值转换为坐标值,这需要经过一些转换。我们测量xp yp可以得到触点的两个方向的电压值,这些电压值和坐标是线性关系。
3.2 使用触摸屏的流程
<1>按下触摸屏 按下触摸屏时,对于一个高效的系统,产生中断,这是触摸中断。
<2>在触摸中断程序中 ,去启动ADC,目的是获得数据(即xy坐标)。
<3>ADC完成, 又产生一个中断(我们称为ADC中断吧)。
<4>在ADC中断中去读取x y坐标。
好,我们来想想,在这4个流程里,启动触摸屏的源头是按下触摸屏,那如果要是长按触摸屏,我按下之后一直不松开或者滑动手指呢?那么谁来触发后续的多次ADC转换呢 ?不可能只启动一次吧, 为了支持 长按和滑动操作,我们需要启用定时器,因此有了步骤5:
<5> 启动定时器。
<6> 定时器中断发生,判断触摸屏是否仍被按下,如果按下就循环上述过程,即:
<6.1>在触摸中断程序中 启动ADC,以获得数据,即xy坐标。
<6.2>ADC完成, 产生中断
<6.3>在ADC中断 中读取x y坐标
<7> 松开就结束了一个流程
以上这就是整个触摸屏的使用流程。
3.2 触摸屏原理的进一步剖析
在《嵌入式Linux完全开发手册》14章里讲解了触摸屏,他抽象了几张图:
平时的时候上下两层膜并不连接,我们按下触摸屏的时候就会产生中断。
那么你怎么知道产生中断呢?答案肯定是由某个引脚的电平发生变化对不对,
平时 Y_ADC / XP是高电平,按下之后Y_ADC就接地了,就是被拉低了,就产生了低电平即电平发生变化产生了中断。原理图如下面两张图所示:
Y_ADC产生低电平后就知道触摸屏被按下了,这个时候就需要测量电压值读取x坐标,XP 、XM通电我就测量YP的电压,这就是 x 点的坐标。
同理:读取Y坐标:YP YM 通电,按下后XP通电,这就是y点的坐标。
第04节_S3C2440触摸屏接口
回顾上节触摸屏使用原理:
在不使用触摸屏的时候,必须要把 S1 S2 S3断开,S4 S5闭合,只有这样当我按下触摸屏,上面的电平才能从高变低,会产生一个中断信号:
而当我去读取X坐标的值时:必须让S1 S3闭合,这样电流才可以通过,同时让S2 S4 S5断开,这时候YP这层膜就相当于探针一样去测量电压:
当我读取y坐标值:必须让S2 S4闭合,这样电流才可以流 下来,同时S1 S3 S5断开,这个时候XP这层膜就相当于探针一样,我可以来测量这里的电压,从而得到Y坐标的电压值:
在测量x y坐标时,这个S5上拉电阻都要断开。
我们需要控制这几个开关(S5 ~ S5),实际上2440就提供了这几个开关的控制方法,。
打开2440的芯片手册看触摸屏是怎么操作的,ADC&Touch Screen Interface这一章是 从440到450总共10页不到。
看芯片手册中的ADC与触摸屏接口功能框图,我们看到有一个8:1 MUX的多路选择器,去设置多路选择器从而测量 XP YP 的电压,从而得到了xy的坐标:
下图是在442页的触摸屏各种的接口模式:
1、正常模式。在上节视频中我们有讲解过;
2、x y分离转换模式。
往上看看我们的X Y坐标原理图,我们可以单独转换X坐标、单独转换Y坐标,换句话说就是逐个去测量X Y坐标,这就是x y分离转换模式。
他首先会启动X坐标的ADC转换,转换成功后数据会保存在ADCDAT0里,同时会产生一个中断,在这个中断服务程序里,就可以把X坐标读取出来,然后可以启动Y坐标的转换,转换成功后数据会保存在ADCDAT1,同时会产生一个中断,进入这个中断把Y坐标读取出来 。
这种模式测量一次会产生2个中断,一个是X坐标中断,一个是Y坐标中断。
3、自动的(连续的)X/Y坐标转换模式。
也就是说不需要单独控制,即不需要像上面一样去单独地去读取X坐标Y坐标,而是可以设置寄存器,让它一次性的测量X坐标测量Y坐标,X坐标保存在ADCDAT0,Y坐标保存在ADCDAT1。这种模式最后产生一个中断,也就是读取X/Y坐标只需要产生一次中断。
4、等待中断模式。
所谓等待中断模式,即触屏控制器在触笔放下时产生中断信号。对于从这里往下数的第二幅图(即等待中断模式电路图),我按下的时候XP从高电平变为低电平,松开时,XP从低电平变为高电平,就是说按下松开都可以检测到。
我们要等待按下(等待着,直到你按下屏幕)或者等待松开(等待着知道你松开屏幕)时,需要设置rADCTSC =0xd3这个值。
看443页的编程要点如下图:
1、D转换数据时可以通过中断或者查询模式来得到数据,使用中断模式时,从AD转换开始,到得到数据可能会有些延迟,因为中断服务程序的进入和退出需要一定的时间,(也就是说,如果你对数据转换的速度要求的非常高,就可以使用查询方式),可以查询ADCCON[15](ECFLG)来判断是否转换结束。
从444页往后看, 剩下就是寄存器操作:
ADCCON寄存器:
ECFLG状态位 AD转换是否结束
PRSCEN 使能ADC转换
PRSCVL 设置A/D转换预分频值
SEL_MUX选择输入通道。对于触摸屏,后面我们使用的是自动转换XY坐标模式,所以这里不需要设置。
ENABLE_START 启动转换
看445页的ADCTSC,这个寄存器是重要的:
UD_SEN Bit8是用来判断触摸屏是被按下还是被松开:0表明被按下,1表明被松开;
YM_SEN Bit7 : YM开关使能控制S4,0表示断开 1闭合,看从这里数的第二幅图;
YP_SEN Bit6 : YP开关 0表示闭合、 1 表示断开;
XM_SEN Bit5 : XM开关 0 断开 、1 闭合;
XP_SEN Bit4: XP开关 0 闭合 、1 断开;
PULL_UP Bit3 :控制S5开关 0 上拉(即S5闭合(上拉电阻即通过一个电阻让一个不确定信号钳在高电平))、1 断开。
AUTO_PST Bit2 :自动连续转换X坐标Y坐标。上节视频里我们设置是 0 即设置正常的ADC转换。如果需要自动连续转换ADC坐标的话,需要设置为1 ,如果需要手动转换ADC坐标的话,需要设置为0。
XY_PST Bit[1:0] :对于手动转换X Y坐标,我们需要手动设置XY_PST 里面的位,以去设置是测量X坐标还是测量Y坐标, 也可以设置这两位等于11 让其等于等待模式, 也就是等待触摸屏被按下或者被松开。
如果想设置自动连续转换的话,则将Bit2 AUTO_PST设置为1 ,将XY_PST Bit[1:0]设置为00。
如果使用手动转换的话设置AUTO_PST为0, XY_PST设置为01 去手动转换X坐标模式 或者XY_PST设置为10 为Y坐标转换模式。
往下看,447页ADCDATA0 ADC数据寄存器:
UPDOWN Bit15 :可以读取这一位去判断触摸屏是按下还是松开。
AUTO_PST Bit14 :自动连续转换X坐标Y坐标
XY_PST Bit[13:12]: 手动转换X Y坐标
这和上面ADCTSC寄存器中的 AUTO_PST Bit2 、 XY_PST Bit[1:0]原理相同。
XPDATA Bit[9:0]最低10位用来保存X坐标的值。
448页ADCDAT1寄存器 和ADCDAT0功能一样的,只不过保存的数据不同:
这个寄存器的低10位YPDATA Bit[9:0]是用来保存 Y坐标的值。
接下来是ADCUPDN触摸屏按下或者松开检查寄存器:
TST_UP Bit1 触摸屏松开中断产生。1表示松开了。
TST_DN Bit0 触摸屏按下中断产生。1表示按下了。
好,手册看完了,涉及到中断,我们看一下这个图:
上图,它会涉及两个中断,一个是按下或者松开,触摸笔的状态中断,另外一个启动ADC以后,ADC结束时也会产生一个中断。
但是这个手册里我们没有看到这两个中断的使能寄存器。
那我们猜测一下,ADC模块、触摸屏模块一定会发出中断,这是一定的对吧。
首先是ADC或者触摸屏产生中断,然后通过中断控制器发送中断给CPU:
我们猜测,肯定有在中断控制器中肯定有寄存器去禁止/使能 ADC、触摸屏 中断。
我们来看看中断控制器芯片手册中都需要设置什么
从芯片手册中断控制器往下翻,看到下面的流程图,我们先放这,没准待会得用:
再往下翻,果然,看到了INT_ADC中断源,如下图。
INT_ADC的Descritions是:ADC结束中断和触摸屏中断。也就是说在这个芯片手册里看到了INT_ADC中断源就代表ADC结束中断、触摸屏中断。
看来ADC结束中断或者触摸屏中断合起来共用一个中断 。
既然合并必然还会有一个寄存器来分辨到底是ADC还是触摸屏发生的中断变化。
再往下看
是SRCPND寄存器 ,其31位为INT_ADC。
注意,该寄存器作用是指示哪个中断源正在等待服务请求。
再往下看
INTMOD寄存器 ,用来决定是普通中断还是快中断模式,设置Bit[31:
再往下看
INTMSK寄存器 ,用来表示是否屏蔽这个中断。设置Bit[31]:
再往下看,是优先级寄存器PRIORITY,我们不需要设置。
再往下看,是INTPND寄存器
中断挂起寄存器的32位在经过优先级逻辑之后,在INTPND寄存器中只有一个位可以设置为1(即只通过某一个中断),并且该中断请求生成IRQ到CPU。你可以读取这个寄存器状态以判断哪个中断正在执行(处理)。
我们设置Bit[31]表示中断是否正在处理
再往下看是中断偏移寄存器INTOFFSET ,中断偏移寄存器中的值显示哪个IRQ模式的中断请求在INTPND寄存器中。设置Bit[31]。
好,看到这里我们还没有发现到底是ADC中断还是触摸屏中断,但肯定有其他寄存器可以设置,我们接着往下看。
再往下看
发现了SUBSRCPND寄存器,其作用是读取指示中断请求状态。
INT_ADC_S Bit[10]表示ADC中断
INT_TC Bit[9]表示触摸屏中断
再往下看
INTSUBMSK ,中断子掩码(INTSUBMSK)寄存器
这个寄存器有11位,每一位都与中断源有关。如果特定位设置为1,则来自相应中断源的中断请求不由CPU提供服务(注意,即使在这种情况下,SUBSRCPND寄存器的相应位也设置为1)。如果掩码位为0,中断请求可以被服务。
也是设置同样的位:
INT_ADC_S Bit[10]表示ADC中断激活/屏蔽
INT_TC Bit[9]表示触摸屏中断激活/屏蔽
我们可以通过INTSUBMSK来屏蔽ADC中断或者TouchScreen中断, 当然也可以使能某个中断。
可以通过SUBSRCPND来分辨到底产生那个中断。
INTSUBMSK 和SUBSOURCPND这两个寄存器都会汇集到一起 ,变成一个叫做INT_ADC的中断来发送给CPU。
框图就是这样:
我们怎么写程序? 写出一个框架步骤:
1、 初始化ADC/TouchScreen接口ADCCON时钟接口;
2、 一开始触摸屏是没有被按下的,设置TS处于等待中断模式;
3 、设置中断:3.1、INTSUBMSK使能ADC中断和触摸屏中断3.2、还有INTMSK设置这个寄存器**使能INT_ADC中断**,让他能够发给CPU。(INT_ADC中断来自于ADC中断和触摸屏中断的合并)。
4 、按下触摸屏,进入TS中断4.1 进入自动采集模式(自动转换XY坐标)4.2 启动ADC
5、转换完之后产生ADC中断5.1 读数据5.2 再次进入 “'等待中断”'模式5.3 启动定时器,处理长按或者滑动
6 定时器中断6.1 判断是否松开,若松开结束6.2 若按下重新执行 4.2的 启动ADC步骤
第05节 触摸屏编程_按下松开检测
从这节课开始触摸屏编程。
关于触摸屏编程大概会分为以下3个小节:
第05节 触摸屏编程_按下松开检测
第006节_触摸屏编程_ADC中断
第007节_触摸屏编程_定时器程序优化
参考资料是韦东山的《嵌入式Linux应用开发完全手册》第14章 ADC和触摸屏接口
现在进行按下松开检测实验。
可以参考下面这张图,其是第4节最后的框架步骤的一个形象表达,只是其没有说定时器的事。
对这张图进行解释:
看懂这张图的关键点在于 :里面有个中断程序 AdcTsIntHandle ,它是总的中断,这里面要用if语句分辨 是ADC中断还是触摸屏中断。如果是ADC中断 那么就调用Isr_adc函数来处理中断。 else if, 如果是触摸屏中断,那么就调用Isr_tc函数中断,。这俩都是总中断具体的中断。
我们看看是怎么做的:
/*---------------------编程框架--------------------------------*/
一开始设置中断
初始化触摸屏控制器,进入等待中断模式
这个时候如果按下触摸屏就会进入Pen Down中断
就会进入AdcTsIntHandle这个总中断函数
这里面分辨出,你是按下了触摸屏,调用Isr_Tc函数
然后进入自动(连续) X/Y轴坐标转换模式,启动ADC
ADC结束之后会产生一个ADC中断
又再次进入这个AdcTsIntHandle总中断
这里面分辨出是ADC中断,这里面调用Isr_Adc函数
我可以 读出 这里面的数据,再次设置寄存器
进入等待Pen UP中断模式
松开触摸笔会再次产生一个中断
进入总中断AdcTsIntHandle这里面分辨,原来是松开了触摸笔,再次调用Isr_tc
这里面又会设置进入等待Pen Down中断模式
我们开始写代码,再上一个视频ADC代码上进行修改.我们在adc_touchscreen目录下添加几个文件
:touchscreen_test.c、touchscreen.c
现在我们写touchscreen.c文件:
下面这个是touchscreen.c源码:
#include "../s3c2440_soc.h"#define ADC_INT_BIT (10)
#define TC_INT_BIT (9)#define INT_ADC_TC (31)/* ADCTSC's bits */
#define WAIT_PEN_DOWN (0<<8)
#define WAIT_PEN_UP (1<<8)#define YM_ENABLE (1<<7)
#define YM_DISABLE (0<<7)#define YP_ENABLE (0<<6)
#define YP_DISABLE (1<<6)#define XM_ENABLE (1<<5)
#define XM_DISABLE (0<<5)#define XP_ENABLE (0<<4)
#define XP_DISABLE (1<<4)#define PULLUP_ENABLE (0<<3)
#define PULLUP_DISABLE (1<<3)#define AUTO_PST (1<<2)#define WAIT_INT_MODE (3)
#define NO_OPR_MODE (0)/*----------------第六步-------------------*/
/*要知道,对于触摸屏,其有down和up两种中断信号*/
void enter_wait_pen_down_mode(void)
{ADCTSC = WAIT_PEN_DOWN | PULLUP_ENABLE | YM_ENABLE | YP_DISABLE | XP_DISABLE | XM_DISABLE | WAIT_INT_MODE;//即设置ADCTSC寄存器,即设置了相应的开关(即上面电路图的S1 - S5),设置s1和s5闭合。//这个模式是等待着down,//WAIT_INT_MODE即等待中断模式//WAIT_PEN_DOWN即告诉硬件去检测笔down的中断信号。一旦检测出你down了,硬件便会发出down中断,从而执行AdcTsIntHandle()中的Isr_Tc(),并判断出中断为down,从而进入enter_wait_pen_up_mode();一直循环。
}void enter_wait_pen_up_mode(void)
{ADCTSC = WAIT_PEN_UP | PULLUP_ENABLE | YM_ENABLE | YP_DISABLE | XP_DISABLE | XM_DISABLE | WAIT_INT_MODE;//即设置ADCTSC寄存器,即设置了相应的开关(即上面电路图的S1 - S5),故设置s1和s5闭合。//这个模式是等待着up//WAIT_INT_MODE即等待中断模式//WAIT_PEN_DOWN即告诉硬件去检测笔up的中断信号。一旦检测出你up了,硬件便会发出up中断,从而执行AdcTsIntHandle()中的Isr_Tc(),并判断出中断为up,从而进入enter_wait_pen_down_mode();一直循环。
}/*------------------------------------第五步---------------------------------*/
void Isr_Tc(void)
{if (ADCDAT0 & (1<<15))//ADCDAT0 [15]判断笔down or up的状态。1表示笔up{printf("pen up\n\r");enter_wait_pen_down_mode();//即现在是up,等待着down}else {printf("pen down\n\r");/* 进入"等待触摸笔松开的模式" */enter_wait_pen_up_mode();//即现在是down,等待着up}
}
/*---------------------第四步-------------------------*/
void AdcTsIntHandle(int irq)
{if (SUBSRCPND & (1<<TC_INT_BIT)) /* 如果是触摸屏中断 */Isr_Tc();// if (SUBSRCPND & (1<<ADC_INT_BIT))/* 这节课先不讲ADC中断(本节只测试,pen笔 点到屏幕上即打
印pen down),抬起则打印pen up */
// Isr_Adc();SUBSRCPND = (1<<TC_INT_BIT) | (1<<ADC_INT_BIT);/*清SUBSRCPND寄存器。记住,这个寄存器清的时候是置1是清除。你记住就行了。并且芯片手册上也有解释。*//*以我的理解就是,对SUBSRCPND读取的时候相应位的1表示中断请求。而清寄存器的时候1表示清,0表示不清。这两个功能不关联。一个是对寄存器读,一个是对寄存器写*/
}/*---------------------第三步-------------------*/
void adc_ts_int_init(void)
{SUBSRCPND = (1<<TC_INT_BIT) | (1<<ADC_INT_BIT);//读取ADC中断和TC中断的状态/* 注册中断处理函数 */register_irq(31, AdcTsIntHandle);//register_irq()函数在interrupt.c里面;irq在2440手册里查得为31/*void register_irq(int irq, irq_func fp){irq_array[irq] = fp;INTMSK &= ~(1<<irq);
}
*/ /* 使能中断 */INTSUBMSK &= ~((1<<ADC_INT_BIT) | (1<<TC_INT_BIT));//INTSUBMSK 某位为0即请求相应位的中断被服务//INTMSK &= ~(1<<INT_ADC_TC);/*这个语句是让合并后的中断INT_ADC使能的,但是在register_irq()函数 里已经有了这个语句,因此这一句话不用要了*/
}/*---------------------第二步----------------------*/
void adc_ts_reg_init(void)
{/* [15] : ECFLG, 1 = End of A/D conversion* [14] : PRSCEN, 1 = A/D converter prescaler enable* [13:6]: PRSCVL, adc clk = PCLK / (PRSCVL + 1)* [5:3] : SEL_MUX, 000 = AIN 0* [2] : STDBM* [0] : 1 = A/D conversion starts and this bit is cleared after the startup.*/ADCCON = (1<<14) | (49<<6) | (0<<3);ADCDLY = 0xff;
}/*-------------------第一步-----------------*/
void touchscreen_init(void)
{/* 设置触摸屏接口:寄存器 */adc_ts_reg_init();/* 设置中断 */adc_ts_int_init();/* 让触摸屏控制器进入"等待中断模式" */enter_wait_pen_down_mode();
}
第06节_触摸屏编程_ADC中断
这节课我们加上ADC中断,目的是把触点的xy坐标读出来。
打开touchscreen.c,写出这个自动测量的函数
void enter_auto_measure_mode(void)
{
/*现在是自动测量,我们没有机会(不用)分别设置这些ADCTSC 中的开关(即电路图的S1 - S5)。
我们需要做的是:
1、设置AUTO_PST =1 即1 = Auto Sequential measurement of X-position, Y-position.即自动检测xy坐标模式
2、XY_PST = 00 即No operation mode
*/ADCTSC = AUTO_PST | NO_OPR_MODE;
}
根据上一节写的程序,我们知道,一旦按下屏幕就会触发down中断,执行AdcTsIntHandle()中的Isr_Tc(),并判断出中断为down,从而进入enter_wait_pen_up_mode(),一旦检测出up中断就又进入AdcTsIntHandle()中的Isr_Tc(),一直循环。
现在我们来加入ADC中断,将AdcTsIntHandle()中原来注释掉的ADC中断部分给取消注释:
void AdcTsIntHandle(int irq)
{if (SUBSRCPND & (1<<TC_INT_BIT)) /* 如果是触摸屏中断 */Isr_Tc();if (SUBSRCPND & (1<<ADC_INT_BIT)) /* ADC中断,则会进入Adc中断处理函数 */Isr_Adc();SUBSRCPND = (1<<TC_INT_BIT) | (1<<ADC_INT_BIT);//清寄存器
}
修改上节课的Isr_Tc(),即让进入触摸屏中断处理函数
void Isr_Tc(void)
{//printf("ADCUPDN = 0x%x, ADCDAT0 = 0x%x, ADCDAT1 = 0x%x, ADCTSC = 0x%x\n\r", ADCUPDN, ADCDAT0, ADCDAT1, ADCTSC);if (ADCDAT0 & (1<<15)){//printf("pen up\n\r");enter_wait_pen_down_mode();}else //就是说一旦down中断,就开始启动ADC中断,如下:{/* 进入"自动测量"模式 */enter_auto_measure_mode();/* 启动ADC *//*从而触发了ADC中断信号,然后执行AdcTsIntHandle()中的Isr_Adc()*//*ENABLE_START = 1就可以了*1 = A/D conversion starts and this bit is cleared after the startup.*/ADCCON |= (1<<0);}
}
写Adc中断处理函数Isr_Adc():
void Isr_Adc(void)
{//进入adc中断后,等待触摸笔松开int x = ADCDAT0 & 0x3ff;//取x坐标int y = ADCDAT1 & 0x3ff;//取y坐标printf("x = %08d, y = %08d\n\r", x, y);//等待触摸笔松开模式enter_wait_pen_up_mode();/*进入等待uo状态,以达up则又发出up信号,执行AdcTsIntHandle()中的Isr_Adc(),并执行enter_wait_pen_down_mode();。一旦按下发出doen则又发出down信号...... 如此循环往复,以让你可以一直连续的按屏幕甚至滑动屏幕*/
}
好,程序写完。
烧写 ,实验发现打印一堆乱码。如下图所示:
来解决这个问题:
应该是printf函数出了问题,现在我们来 打开my_printf.c文件,找到printf函数, 应该是处理第二个数据即y的时候,没有设置初始值。
/*reference : int vprintf(const char *format, va_list ap); */
static int my_vprintf(const char *fmt, va_list ap)
{char lead=' ';int maxwidth=0;for(; *fmt != '\0'; fmt++){if (*fmt != '%') {outc(*fmt);continue;}/*-------------------修改处------------------------------*///碰到 % 就重新处理, 初始值应该重新设置初始值上去lead=' ';//加入代码maxwidth=0;//加入代码//format : %08d, %8d,%d,%u,%x,%f,%c,%s fmt++;if(*fmt == '0'){lead = '0';fmt++; }while(*fmt >= '0' && *fmt <= '9'){maxwidth *=10;maxwidth += (*fmt - '0');fmt++;}switch (*fmt) {case 'd': out_num(va_arg(ap, int), 10,lead,maxwidth); break;case 'o': out_num(va_arg(ap, unsigned int), 8,lead,maxwidth); break; case 'u': out_num(va_arg(ap, unsigned int), 10,lead,maxwidth); break;case 'x': out_num(va_arg(ap, unsigned int), 16,lead,maxwidth); break;case 'c': outc(va_arg(ap, int )); break; case 's': outs(va_arg(ap, char *)); break; default: outc(*fmt);break;}}return 0;
}
重新烧写执行,解决了乱码的问题。执行结果如下图所示:
我们做一个实验,用手写笔在屏幕上从左到右依次点很多点,在从上到下依次点很多点。因此板子屏幕正确的状态应该是x值依次变化(线性增加或减小),y值也依次变化。
但经过我们操作发现,数据变化幅度很大,数据的变化并不是线性的按照我们想的那样。
好,来解决这个数据啊变化太夸张(即不线性)的问题:
可能应该是 触摸屏电压不稳定的问题, 我们之前不知道DELAY寄存器是用来干嘛的,来看看:
等待中断模式时,当触摸笔按下时我们会产生中断,但是可以通过 DELAY来延时产生中断
芯片手册的此处往前翻有一张图:
按下触摸笔,延迟A 才可以产生中断,你才可以测量X Y坐标 A = D(晶振的周期)。 D就是 DELAY就是那个寄存器的值 ,晶振周期时12M ,我们需要设置一下。修改adc_ts_reg_init()的ADCDLY :
void adc_ts_reg_init(void)
{/* [15] : ECFLG, 1 = End of A/D conversion* [14] : PRSCEN, 1 = A/D converter prescaler enable* [13:6]: PRSCVL, adc clk = PCLK / (PRSCVL + 1)* [5:3] : SEL_MUX, 000 = AIN 0* [2] : STDBM* [0] : 1 = A/D conversion starts and this bit is cleared after the startup.*/ADCCON = (1<<14) | (49<<6) | (0<<3);/* 按下触摸屏, 延时一会(我们定位5ms吧)再发出TC中断* 5ms为60000* 延时时间 = ADCDLY * 晶振周期 = ADCDLY * 1 / 12000000 = 5ms* 求得ADCDLY = 60000*/ADCDLY = 60000;
}
再次烧写,发现数据仍不规律 ,我们需要再次改进程序。
我们知道,我们按下触摸屏会产生触摸屏中断,启动自动测量,启动Adc,Adc成功后会进入Adc中断,在函数中打印数据。
那么也许是因为测量过程很长的原因(因为,你按着的时候,ADCDAT在测量xy,松开的时候xy也在在测量,那是否是因为松开的时候也在测量导致的错误呢)。因此只让xy在按下的时候才打印。
void Isr_Adc(void)
{int x = ADCDAT0;int y = ADCDAT1;
//松开的话打印也是错误的值,所以如果仍然按下才打印if (!(x & (1<<15))) /* 如果仍然按下才打印 *//*bit15为判断UPDOWN,0为down*/{x &= 0x3ff;y &= 0x3ff;//打印10进制printf("x = %08d, y = %08d\n\r", x, y);}enter_wait_pen_up_mode();
}
烧写执行 ,发现X Y轴输出称线性了。如下图:
但是有问题,观察上图:我们在屏幕上横着按一条线的轨迹点点,想让x不怎么变让y呈线性变化,而观察到的现象是y不怎么变让x呈线性变化。
找原因:
后来我们发现,厂家把X Y轴搞反了(在电路图中标反了)
TSYP和TSXP接反
TSYM和TSXM接反
我们后面使用触摸屏时会使用软件处理这点,不会导致任何问题
解决这个问题的方法如下,同时也是屏幕xy轴旋转倒置等随机改变的方法:
我们需要把触摸屏的坐标 XY坐标转换成LCD的XY坐标 ,需要用应用程序做 ,我们常使用Tslib库来做,这些旋转倒置都没有问题,各种方向的旋转都可以由软件转换。
好了这节课讲完了。
第07节 触摸屏编程_定时器程序优化
注:本节与ADC无关。本节是对以前的定时器章节的程序进行优化。优化完毕后改一下,会更完美的应用到我们的触摸屏编程的长按与滑动功能。
上一节的程序有一个缺点:我们按下触摸屏会输出一个数据,再按下触摸屏又输出一个数据。我长按并没有输出数据,我滑动也没有输出数据,我们需要使用定时器改进这个问题。
这个处理流程是怎么样的?
按下期间启动定时器
定时器每过10ms / 20ms就中断一次
在中断函数里测量触电的XY坐标
这样就可以得到连续的数据
好了,关于定时器应用到触摸屏我们一节课再讲。这节课我们讲对以前讲的定时器程序优化。开始:
首先打开以前的定时器文件Timer.c文件,对其进行修改。
首先我们看timer_irq()函数:应该让timer_irq()函数从某个数组(结构体数组,里面存着函数名和函数指针)里面把需要定时器处理的函数依次执行,这样做,我们以后添加定时器处理函数时就不需要修改Timer.c了。
//定义一个宏 TIMER_NUM = 32
#define TIMER_NUM 32
typedef void(*timer_func)(void);//定义一个结构体 ,既存放有函数指针又存放有数据
typedef struct timer_desc {char *name;timer_func fp;
}timer_desc, *p_timer_desc;//我们需要往这个结构体数组里面添加函数,注册Timer函数
timer_desc timer_array[TIMER_NUM];void timer_irq(void)
{int i;for (i = 0; i < TIMER_NUM; i++){
//判断指针是否为空,如果不是空的话就继续执行timer_array[i].fp();这个函数if (timer_array[i].fp){timer_array[i].fp();//即如果timer_array[i].fp()不为空则说明有函数,就去执行。}}
}
然后在timer.c中来写注册timer中的函数。即定时一到,去执行的那些函数。
//注册Timer函数
int register_timer(char *name, timer_func fp)
{int i;
//搜索这个数组,如果fp等于0的话,就表示没有占用这个数组项,我就把它填充进去for (i = 0; i < TIMER_NUM; i++){if (!timer_array[i].fp){ [i].name = name;timer_array[i].fp = fp;return 0;
//注册成功}}
//否则,表示已经满了,注册失败return -1;
}当我们不需要使用Timer定时器的时候,使用unregister_timer函数取消某timer函数, 考虑到我们需要从数组里面
把这个Timer去掉,我们怎么找到这个Timer?
答:传入一个函数指针,以后卸载使用名字找到对应的项.
void unregister_timer(char *name)
{
//对于unregister_timer就反过来操作,遍历每一项int i;for (i = 0; i < TIMER_NUM; i++){
//如果这个数组项里面的名字等于我传进来我名字if (!strcmp(timer_array[i].name, name)){
//也就表示我找到了这两项,设置成NULLtimer_array[i].name = NULL;timer_array[i].fp = NULL;return 0;}}
//否则return -1;找不到选择的项return -1;
}
好了,我们若想把上面这个把timer函数存进数组里的这个程序应用到LED电灯案例中,步骤如下:
注意,不是触摸屏的,这里是用led演示的。
往下看:
如果想继续点灯的话,需要在led.c文件里的led_init()中注册led_timer_irq()。则led_timer_irq()每10ms(这个10ms是随便定的)该函数被调用一次.
int led_init(void)
{/* 设置GPFCON让GPF4/5/6配置为输出引脚 */GPFCON &= ~((3<<8) | (3<<10) | (3<<12));GPFCON |= ((1<<8) | (1<<10) | (1<<12));
//led是名字,led_timer_irq是函数指针register_timer("led", led_timer_irq);
}/* 每10ms该函数被调用一次 * 每500ms操作一下LED实现计数*/
void led_timer_irq(void)
{/* 点灯计数 */static int timer_num = 0;static int cnt = 0;int tmp;timer_num++;if (timer_num < 50)return;timer_num = 0;
//操作ledcnt++;tmp = ~cnt;tmp &= 7;GPFDAT &= ~(7<<4);GPFDAT |= (tmp<<4);
}
main.c文件:
int main(void)
{
/*-------------------1------------------------*/led_init();//初始化led//interrupt_init(); /* 初始化中断控制器 */key_eint_init(); /* 初始化按键, 设为中断源 *//*-------------------2------------------------*/timer_init();//打开定时器,这个函数是设置timer参数的,如多长时间定时器响一次。puts("\n\rg_A = ");printHex(g_A);puts("\n\r");//nor_flash_test();lcd_test();//adc_test();touchscreen_test();while (1);return 0;
}
timer.c文件,把TCNTB0 设置为625已让定时器10ms中断一次。
void timer_init(void)
{/* 设置TIMER0的时钟 修改时钟频录让其10ms中断一次 *//* Timer clk = PCLK / {prescaler value+1} / {divider value} = 50000000/(49+1)/16= 62500*/TCFG0 = 49; /* Prescaler 0 = 49, 用于timer0,1 */TCFG1 &= ~0xf;TCFG1 |= 3; /* MUX0 : 1/16 *//* 设置TIMER0的初值 */TCNTB0 = 625; /* 10Ms中断一次 *//* 加载初值, 启动timer0 */TCON |= (1<<1); /* Update from TCNTB0 & TCMPB0 *//* 设置为自动加载并启动 */TCON &= ~(1<<1);TCON |= (1<<0) | (1<<3); /* bit0: start, bit3: auto reload *//* 设置中断 */register_irq(10, timer_irq);
}
烧写到nandflash发现无输出,可能是前重定位前的代码超出了4k,所以我们使用Norflash启动,发现可以正常运行。
我们修改Makefile把负责重定位代码往前移,其他无关代码往后放。并且看star.S,发现init.c 、nand_init
、sdram_init、sdram得放到前面:
修改Makefile 我们把 start.o init.o nand_flash.o放在最前面
objs = start.o init.o nand_flash.o led.o uart.o main.o exception.o interrupt.o timer.o nor_flash.o my_printf.o string_utils.o lib1funcs.oobjs += lcd/font.o
objs += lcd/framebuffer.o
objs += lcd/geometry.o
objs += lcd/lcd.o
objs += lcd/lcd_4.3.o
objs += lcd/lcd_controller.o
objs += lcd/lcd_test.o
objs += lcd/s3c2440_lcd_controller.o
objs += lcd/font_8x16.oobjs += adc_touchscreen/adc.o
objs += adc_touchscreen/adc_test.oobjs += adc_touchscreen/touchscreen.o
objs += adc_touchscreen/touchscreen_test.oall: $(objs)#arm-linux-ld -Ttext 0 -Tdata 0x30000000 start.o led.o uart.o init.o main.o -o sdram.elfarm-linux-ld -T sdram.lds $^ libgcc.a -o sdram.elfarm-linux-objcopy -O binary -S sdram.elf sdram.binarm-linux-objdump -D sdram.elf > sdram.dis
clean:rm -f *.bin $(objs) *.elf *.dis%.o : %.carm-linux-gcc -march=armv4 -c -o $@ $<%.o : %.Sarm-linux-gcc -march=armv4 -c -o $@ $<
这节课讲定时器的优化, 下节课讲怎么使用定时器来改进触摸屏。
第08节 触摸屏编程_使用定时器支持长按
我们根据触摸屏编程的流程图可知,可以使用定时器把长按或者滑动触摸屏的值读出来,我们按下触摸屏就会产生触摸屏中断,这个时候可以启动ADC,ADC成功后再次产生中断,在这个中断中启动定时器。当然了我们也可以在触摸屏中断里启动定时器。我们采用ADC中断来启动定时器。
首先从main.c函数开始
int main(void)
{led_init();//interrupt_init(); /* 初始化中断控制器 */key_eint_init(); /* 初始化按键, 设为中断源 */timer_init();puts("\n\rg_A = ");printHex(g_A);puts("\n\r");//nor_flash_test();lcd_test();//adc_test();//执行touchscreen_testtouchscreen_test();while (1);return 0;
}
进入touchscreen_test.c文件执行init初始化程序
void touchscreen_test(void)
{touchscreen_init();
}
以上这都是以前写的,是为了让你明白这个程序运行的流程。
接下来,我们需要先修改touchscreen_init();
首先,在touchscreen_init()函数里注册touchscreen_timer_irq()函数,即定时器一旦产生中断就去执行touchscreen_timer_irq()。
看touchscreen.c文件,一旦main.c执行到touchscreen_test()中的touchscreen_init()函数,其中有一步就得是注册定时器处理函数,如下:
void touchscreen_init(void)
{/* 设置触摸屏接口:寄存器 */adc_ts_reg_init();printf("ADCUPDN = 0x%x, SUBSRCPND = 0x%x, SRCPND = 0x%x\n\r", ADCUPDN, SUBSRCPND, SRCPND);/* 设置中断 */adc_ts_int_init();/* --------------注册定时器处理函数------------------ */
//首先是一个名字,其次是定时器处理函数register_timer("touchscreen", touchscreen_timer_irq);/* 让触摸屏控制器进入"等待中断模式" */enter_wait_pen_down_mode();
}
接下来写touchscreen_timer_irq()
//定义一个全局变量设置timer状态
static volatile int g_ts_timer_enable = 0;//触摸屏定时器处理函数
/* 每10ms该函数被调用一次 */
void touchscreen_timer_irq(void)
{
//如果定时器并没有被使能,则returnif (get_status_of_ts_timer() == 0)return;//如果松开,则什么事情都不做if (ADCDAT0 & (1<<15)) /* 如果松开 */{
//设置定时器状态ts_timer_disable();
//触摸笔进入等待模式enter_wait_pen_down_mode();return;}/* 如果触摸屏仍被按下, 进入"自动测量模式", 启动ADC */
//否则启动测量else /* 按下状态,启动下一次测量 */{/* 进入"自动测量"模式 */enter_auto_measure_mode();/* 启动ADC */ADCCON |= (1<<0);}
}static void ts_timer_enable(void)
{//我们使用定时器时把它的状态设置为1g_ts_timer_enable = 1;
}//有启用定时器就有关闭定时器
static void ts_timer_disable(void)
{//我们不使用定时器把定时器状态设置为0g_ts_timer_enable = 0;
}//我们如何获取定时器状态?
static int get_status_of_ts_timer(void)
{
//返回定时器状态return g_ts_timer_enable;
}
修改Isr_Adc()函数,
void Isr_Adc(void)
{int x = ADCDAT0;int y = ADCDAT1;if (!(x & (1<<15))) /* 如果仍然按下才打印 */{x &= 0x3ff;y &= 0x3ff;printf("x = %08d, y = %08d\n\r", x, y);//添加定时器函数/* 启动定时器以再次读取数据 */ts_timer_enable();} else//松开操作{ts_timer_disable();enter_wait_pen_down_mode();}//enter_wait_pen_up_mode();
}
整个流程(定时器优化的触摸屏),你看了源码就能明白了:
#include "../s3c2440_soc.h"#define ADC_INT_BIT (10)
#define TC_INT_BIT (9)#define INT_ADC_TC (31)/* ADCTSC's bits */
#define WAIT_PEN_DOWN (0<<8)
#define WAIT_PEN_UP (1<<8)#define YM_ENABLE (1<<7)
#define YM_DISABLE (0<<7)#define YP_ENABLE (0<<6)
#define YP_DISABLE (1<<6)#define XM_ENABLE (1<<5)
#define XM_DISABLE (0<<5)#define XP_ENABLE (0<<4)
#define XP_DISABLE (1<<4)#define PULLUP_ENABLE (0<<3)
#define PULLUP_DISABLE (1<<3)#define AUTO_PST (1<<2)#define WAIT_INT_MODE (3)
#define NO_OPR_MODE (0)static volatile int g_ts_timer_enable = 0;void enter_wait_pen_down_mode(void)
{ADCTSC = WAIT_PEN_DOWN | PULLUP_ENABLE | YM_ENABLE | YP_DISABLE | XP_DISABLE | XM_DISABLE | WAIT_INT_MODE;
}void enter_wait_pen_up_mode(void)
{ADCTSC = WAIT_PEN_UP | PULLUP_ENABLE | YM_ENABLE | YP_DISABLE | XP_DISABLE | XM_DISABLE | WAIT_INT_MODE;
}void enter_auto_measure_mode(void)
{ADCTSC = AUTO_PST | NO_OPR_MODE;
}void Isr_Tc(void)
{//printf("ADCUPDN = 0x%x, ADCDAT0 = 0x%x, ADCDAT1 = 0x%x, ADCTSC = 0x%x\n\r", ADCUPDN, ADCDAT0, ADCDAT1, ADCTSC);if (ADCDAT0 & (1<<15)){//printf("pen up\n\r");enter_wait_pen_down_mode();}else {//printf("pen down\n\r");/* 进入"自动测量"模式 */enter_auto_measure_mode();/* 启动ADC */ADCCON |= (1<<0);}
}static void ts_timer_enable(void)
{g_ts_timer_enable = 1;
}static void ts_timer_disable(void)
{g_ts_timer_enable = 0;
}static int get_status_of_ts_timer(void)
{return g_ts_timer_enable;
}/* 每10ms该函数被调用一次 */
void touchscreen_timer_irq(void)
{/* 如果触摸屏仍被按下, 进入"自动测量模式", 启动ADC */if (get_status_of_ts_timer() == 0)return;if (ADCDAT0 & (1<<15)) /* 如果松开 */{ts_timer_disable();enter_wait_pen_down_mode();return;}else /* 按下状态 */{/* 进入"自动测量"模式 */enter_auto_measure_mode();/* 启动ADC */ADCCON |= (1<<0);}
}void Isr_Adc(void)
{int x = ADCDAT0;int y = ADCDAT1;if (!(x & (1<<15))) /* 如果仍然按下才打印 */{x &= 0x3ff;y &= 0x3ff;printf("x = %08d, y = %08d\n\r", x, y);/* 启动定时器以再次读取数据 */ts_timer_enable();}else{ts_timer_disable();enter_wait_pen_down_mode();}enter_wait_pen_up_mode();
}void AdcTsIntHandle(int irq)
{if (SUBSRCPND & (1<<TC_INT_BIT)) /* 如果是触摸屏中断 */Isr_Tc();if (SUBSRCPND & (1<<ADC_INT_BIT)) /* ADC中断 */Isr_Adc();SUBSRCPND = (1<<TC_INT_BIT) | (1<<ADC_INT_BIT);
}void adc_ts_int_init(void)
{SUBSRCPND = (1<<TC_INT_BIT) | (1<<ADC_INT_BIT);/* 注册中断处理函数 */register_irq(31, AdcTsIntHandle); /* 使能中断 */INTSUBMSK &= ~((1<<ADC_INT_BIT) | (1<<TC_INT_BIT));//INTMSK &= ~(1<<INT_ADC_TC);
}void adc_ts_reg_init(void)
{/* [15] : ECFLG, 1 = End of A/D conversion* [14] : PRSCEN, 1 = A/D converter prescaler enable* [13:6]: PRSCVL, adc clk = PCLK / (PRSCVL + 1)* [5:3] : SEL_MUX, 000 = AIN 0* [2] : STDBM* [0] : 1 = A/D conversion starts and this bit is cleared after the startup.*/ADCCON = (1<<14) | (49<<6) | (0<<3);/* 按下触摸屏, 延时一会再发出TC中断* 延时时间 = ADCDLY * 晶振周期 = ADCDLY * 1 / 12000000 = 5ms*/ADCDLY = 60000;
}void touchscreen_init(void)
{/* 设置触摸屏接口:寄存器 */adc_ts_reg_init();printf("ADCUPDN = 0x%x, SUBSRCPND = 0x%x, SRCPND = 0x%x\n\r", ADCUPDN, SUBSRCPND, SRCPND);/* 设置中断 */adc_ts_int_init();/* 注册定时器处理函数 */register_timer("touchscreen", touchscreen_timer_irq);/* 让触摸屏控制器进入"等待中断模式" */enter_wait_pen_down_mode();
}
第09节 触摸屏编程_较准原理
所谓的校准原理,就是说,你先点击LCD上显示的几个十字架(可以称之为基准点),这就是校准。
校准的目的是:你以后再点击屏幕的时候,2440是如何把触摸屏上的xy坐标(xy的电压值)转换为lcd上的xy坐标的呢,就是靠那几个基准点。
更准确地说,校准的原理就是找到一个公式把电压值转换为坐标值。
好,我们来具体的讲其原理。
触摸屏和LCD是两个东西,触摸屏覆盖在LCD上:
问:在屏幕上随便点一个点,得到触电的(x3,y3)怎么换算出LCD的坐标值 (X,Y)?
9.1 方法一
x=479-0/x2-x1 * (x3-x1) + 0
注:上图这个方法不够好,因为你的x1能1对准0么?X2能百分百就是对准479的么?
因此,我们改进,让x1 和 x2 这两个基准点 是屏上的随便一点。
如下:
同理,比可以求出y的坐标。
我们只需要确定两个点就可以把lcd坐标确定下来,但是我们可以做的更好。看方法二。
9.2 方法二
假设由于制作工艺问题,导致触摸屏和LCD坐标并不相同,需要其他公式计算
X轴方向:
s1’ 、 s2’ 分别是TS上X轴两个点的距离
s1 、 s2 分别是LCD上X轴两个点的距离
则:
Kx= LCD距离/触摸屏距离
= (s1 + s2) / (s1’ + s2’)
= 2s/(s1’ + s2’)
Y轴方向
TS距离是d1’ 、 d2’
LCD距离是d1 、d2
Ky=(d1 + d2) / (d1’ + d2’)
= 2d / (d1’ + d2’)
我们现在有了斜率,我们需要定一个原点的触屏LCD坐标:原点我们选在最中间, 可以忽略掉上下两个屏幕安装的工艺导致的上下左右的偏差。
原点坐标在触摸屏上是xc’ yc’,在LCD上是 xc yc 。
那我们的校准公式,对于触摸屏上给定的x3(X3是一个电平值),我们如何求出x(x是真实坐标):
X= (x3 - xc' ) * Kx + xc
对于给定的y’我们如何算出Y轴坐标?
y = (y' - yc') * Ky + yc
综上,这节课你明白、记住这两个公式就可以了。
注意,要想使用上面这个公式求触摸屏上输入的任一点的坐标,我们需要先把公式中的几个定量参数(即那5个参考点)求出来,也就是说先对这个公式进行初始化,以后就可以用这个公式求点的真实坐标了。
校准时,我们需要点击触摸屏上这5个点,同时需要把这五个点坐标打印显示出来。
第010节_触摸屏编程_较准与画线
10.1 编程思路
第一步在A点显示 + ----->写一个fb_disp_cross(int x , int y)客户点击 +记录触摸屏的坐标 ------> 写一个ts_read_raw()在BCDE上 循环前三步操作(即显示、点击、读取)
第二步根据这些数据,确定公式 ------>写一个 ts_calibrate()
第三步以后得到TS触点时,就可以转换出LCD坐标 -------->写一个 ts_read()
我们先实现 显示+
我们既然画线就在geometry.c中实现,那我们就让 显示+ 功能在 geometry.c中实现吧。想画出的+如下,即我们给出一个坐标xy就可以在xy上显示一个 + :
geometry.c中写代码:
/*在xy坐标上显示color的十字架*/
void fb_disp_cross(int x, int y, unsigned int color)
{draw_line(x-10, y, x+10, y, color);draw_line(x, y-10, x, y+10, color);
}
好,完成。往下操作。
现在来编辑touchscreen.c,如下:
说一下这个程序的思路:
*外部函数(tslib.c文件)调用ts_read_raw(int *px, int *py)。
*执行到g_ts_data_valid == 0语句,因为全局变量g_ts_data_valid初始值为0,因此陷入死循环。
*此时如果你点击屏幕,便最终会产生ADC中断是吧,在ADC中断里会调用 report_ts_xy(int x, int y)函数,这个函
数会把你ADC所读取的xy电压值赋值给全局变量g_ts_x、g_ts_y,同时使g_ts_data_valid置1,而使刚刚的
ts_read_raw()中的死循环跳出,进而把g_ts_x、g_ts_y以指针参数的形式从ts_read_raw(int *px, int *py)传
出,并使g_ts_data_valid置0(置0的意义在于,你再次调用(循环点BCDE)的时候,又进入死循环,从而读取xy
值)。//+ 坐标的x值
static int g_ts_x;
//+ 坐标Y值
static int g_ts_y;//表示数据并未有效
//设置成volatile类型,有两个地方会用到一个是中断report_ts_xy
//另一个是程序ts_read_raw,我们一定确保这个值是从内存中读取出来
//让双方得到真实的值
static volatile char g_ts_data_valid = 0;void Isr_Adc(void)
{int x = ADCDAT0;int y = ADCDAT1;if (!(x & (1<<15))) /* 如果仍然按下才打印 */{x &= 0x3ff;y &= 0x3ff;
//我们现在不能打印 //printf("x = %08d, y = %08d\n\r", x, y);
//实现report_ts_xy函数来打印report_ts_xy(x, y);/* 启动定时器以再次读取数据 */ts_timer_enable();}else{ts_timer_disable();enter_wait_pen_down_mode();}
}//report_ts_xy函数的实现void report_ts_xy(int x, int y)
{//printf("x = %08d, y = %08d\n\r", x, y);
//一开始标记位=0表示没有数据if (g_ts_data_valid == 0){g_ts_x = x;g_ts_y = y;g_ts_data_valid = 1;}
}//读到原始数据
void ts_read_raw(int *px, int *py)
{
//当按下触摸屏时会产生ADC中断while (g_ts_data_valid == 0);*px = g_ts_x;*py = g_ts_y;
//读完数据清零g_ts_data_valid = 0;
}
简单的来说上面这块代码作用是把xy的电平值传递给ts_read_raw(int *px, int *py),而这个ts_read_raw(int *px, int *py)函数被tslib.c文件中的get_calibrate_point_data()调用:
void get_calibrate_point_data(int lcd_x, int lcd_y, int *px, int *py)
{fb_disp_cross(lcd_x, lcd_y, 0xffffff);/*这个函数在geometry.c文件中;cross是十字的意思。传入xy即可在xy位置显示一个十字架*//* 执行下面这个函数就会陷入一个死循环,等待你去点击,点击即传出xy的电平值,xy参数经ts_read_raw()最终传递给get_calibrate_point_data()函数参数 */ts_read_raw(px, py);
}geometry.c文件中的fb_disp_cross()函数:
void fb_disp_cross(int x, int y, unsigned int color)
{draw_line(x-10, y, x+10, y, color);draw_line(x, y-10, x, y+10, color);
}
好了,这节课以上的代码完成的任务是,点击某十字架,就会返回给get_calibrate_point_data()的xy电平值。
下面这个是tslib.c,我们来看:
/*
ts_calibrate()函数:
*调用get_calibrate_point_data()函数的目的:在lcd上显示一个坐标并进入死循环,一旦你按下屏幕点击这个十字
架,就会把十字架的电平值返回给get_calibrate_point_data()函数。
*确定触摸屏数据XY是否反转,即把A、B的xy电平值传入is_ts_xy_swap()函数,去判断触摸屏厂家是否把xy接反了,
如果接反了就返回1,从而进入下面的if语句,讲五个点的x和y电平值调换。
*再往下是把各点的xy坐标值赋值给全局变量,并计算出Kx。从而得到校准触摸屏的公式的所有参数。
*ts_calibrate()最终在touchscreen.c文件中被调用。*/static double g_kx;
static double g_ky;static int g_ts_xc, g_ts_yc;
static int g_lcd_xc, g_lcd_yc;
static int g_ts_xy_swap = 0;void get_calibrate_point_data(int lcd_x, int lcd_y, int *px, int *py)
{fb_disp_cross(lcd_x, lcd_y, 0xffffff);/* 等待点击 */ts_read_raw(px, py);
}int is_ts_xy_swap(int a_ts_x, int a_ts_y, int b_ts_x, int b_ts_y)
{int dx = b_ts_x - a_ts_x;int dy = b_ts_y - a_ts_y;if (dx < 0)dx = 0 - dx;if (dy < 0)dy = 0 - dy;if(dx > dy)return 0; /* xy没有反转 */elsereturn 1; /* xy反了 */
}void swap_xy(int *px, int *py)
{int tmp = *px;*px = *py;*py = tmp;
}/*
----------------------------
| |
| +(A) (B)+ |
| |
| |
| |
| +(E) |
| |
| |
| |
| +(D) (C)+ |
| |
----------------------------*/void ts_calibrate(void)
{unsigned int fb_base;int xres, yres, bpp;int a_ts_x, a_ts_y;int b_ts_x, b_ts_y;int c_ts_x, c_ts_y;int d_ts_x, d_ts_y;int e_ts_x, e_ts_y;/* X轴方向 */int ts_s1, ts_s2;int lcd_s;/* Y轴方向 */int ts_d1, ts_d2;int lcd_d;/* 获得LCD的参数: fb_base, xres, yres, bpp */get_lcd_params(&fb_base, &xres, &yres, &bpp);/* 对于ABCDE, 循环: 显示"+"、点击、读ts原始值 *//* A(50, 50) */get_calibrate_point_data(50, 50, &a_ts_x, &a_ts_y);/* B(xres-50, 50) */get_calibrate_point_data(xres-50, 50, &b_ts_x, &b_ts_y);/* C(xres-50, yres-50) */get_calibrate_point_data(xres-50, yres-50, &c_ts_x, &c_ts_y);/* D(50, yres-50) */get_calibrate_point_data(50, yres-50, &d_ts_x, &d_ts_y);/* E(xres/2, yres/2) */get_calibrate_point_data(xres/2, yres/2, &e_ts_x, &e_ts_y);/* 确定触摸屏数据XY是否反转 */g_ts_xy_swap = is_ts_xy_swap(a_ts_x, a_ts_y, b_ts_x, b_ts_y);if (g_ts_xy_swap){/* 对调所有点的XY坐标 */swap_xy(&a_ts_x, &a_ts_y);swap_xy(&b_ts_x, &b_ts_y);swap_xy(&c_ts_x, &c_ts_y);swap_xy(&d_ts_x, &d_ts_y);swap_xy(&e_ts_x, &e_ts_y);}/* 确定公式的参数并保存 */ts_s1 = b_ts_x - a_ts_x;ts_s2 = c_ts_x - d_ts_x;lcd_s = xres-50 - 50;ts_d1 = d_ts_y - a_ts_y;ts_d2 = c_ts_y - b_ts_y;lcd_d = yres-50-50;g_kx = ((double)(2*lcd_s)) / (ts_s1 + ts_s2);g_ky = ((double)(2*lcd_d)) / (ts_d1 + ts_d2);g_ts_xc = e_ts_x;g_ts_yc = e_ts_y;g_lcd_xc = xres/2;g_lcd_yc = yres/2;
}/** 读TS原始数据(即触摸屏返回的xy电平值),经过公式计算, 转换为LCD坐标*//*这个ts_read()函数咱先不管它,现在还用不到,往下看程序会用得到的,待会回来看*/
void ts_read(int *lcd_x, int *lcd_y)
{int ts_x, ts_y;ts_read_raw(&ts_x, &ts_y);if (g_ts_xy_swap)//判断是否反转{swap_xy(&ts_x, &ts_y);}/* 使用公式计算 */*lcd_x = g_kx * (ts_x - g_ts_xc) + g_lcd_xc;*lcd_y = g_ky * (ts_y - g_ts_yc) + g_lcd_yc;
}
好了,往下看,写
打开我们的 touchscreen_test.c文件
void touchscreen_test(void)
{unsigned int fb_base;int xres, yres, bpp;int x, y;/* 获得LCD的参数: fb_base, xres, yres, bpp */get_lcd_params(&fb_base, &xres, &yres, &bpp);touchscreen_init();/* -----------------清屏--------------------- */clear_screen(0);//写一个清屏函数,在下面。0表示把屏幕清为黑色
//我们在70像素的地方显示文字,背景白色显示文字/* ---------------显示文字提示较准--------------------- */fb_print_string(70, 70, "Touc cross to calibrate touchscreen", 0xffffff);/*----------------校准过程-------------------------*/ts_calibrate();/* ---------显示文字提示校准完成,可以开始绘画------------ */fb_print_string(70, yres - 70, "OK! To draw!", 0xffffff);/* ----------------------开始绘画------------ */while (1){ts_read(&x, &y);//函数在上一个代码块
//我们先打印值,即xy的在lcd上的真实坐标,而非电平值。printf(" x = %d, y = %d\n\r", x, y);
//描绿色的线fb_put_pixel(x, y, 0xff00);//把触摸笔经过的xy点画出来并定义绿色}
}注:清屏是我们在framebuffer.c中把清屏函数单独实现的,代码如下:
void clear_screen(unsigned int color)
{int x, y;unsigned char *p0;unsigned short *p;unsigned int *p2;/* 往framebuffer中写数据 */if (bpp == 8){/* bpp: palette[color] */p0 = (unsigned char *)fb_base;for (x = 0; x < xres; x++)for (y = 0; y < yres; y++)*p0++ = color;}else if (bpp == 16){/* 让LCD输出整屏的红色 *//* 565: 0xf700 */p = (unsigned short *)fb_base;for (x = 0; x < xres; x++)for (y = 0; y < yres; y++)*p++ = convert32bppto16bpp(color);}else if (bpp == 32){p2 = (unsigned int *)fb_base;for (x = 0; x < xres; x++)for (y = 0; y < yres; y++)*p2++ = color;}
}
最后,因为我们添加了 tslib.c,因此需要修改Makefile,添加:
objs += adc_touchscreen/tslib.o
第011节_触摸屏编程_测试
编译,发现一次性成功。然后烧写,运行。发现程序有bug:
我们点击坐标一次(A点),程序就完成执行(一下子显示出来了BCDE点),这说明我们点了A点之后程序一下子完成了。
分析原因,是因为我们虽然表面上是点击了一下,但是对于2440来说,可能就认为你点了很多下(因为你点击的那一瞬间可能2440执行了很对次你点击的操作)。
来解决这个问题:
我们需要修改触摸屏文件tsib.c的get_calibrate_point_data()函数:
void get_calibrate_point_data(int lcd_x, int lcd_y, int *px, int *py)
{int pressure;int x, y;fb_disp_cross(lcd_x, lcd_y, 0xffffff);/* 等待点击 */do {*px = x;*py = y;ts_read_raw(&x, &y, &pressure); printf("get raw data: x = %08d, y = %08d\n\r", x, y);} while (pressure);printf("return raw data: x = %08d, y = %08d\n\r", *px, *py);/* 直到松开才返回 */fb_disp_cross(lcd_x, lcd_y, 0);//点击之后,消除十字架(十字架设置为黑色)
}那么上面这个函数的ts_read_raw()修改经修改多了pressure参数,并且pressure参数是如何变化如何实现的呢?
现在我们来实现:
修改touchcreen.c文件添加压力值相关信息:
//定义压力值全局变量
static int g_ts_pressure;void ts_read_raw(int *px, int *py, int *ppressure)
{while (g_ts_data_valid == 0);*px = g_ts_x;*py = g_ts_y;/*------------将全局变量压力值让ts_read_raw()传递------------*/*ppressure = g_ts_pressure;g_ts_data_valid = 0;
}ts_read_raw()是经全局变量g_ts_pressure传递的,为了方便我们可以把g_ts_pressure写进report_ts_xy()函数
啊,你按下或松开,g_ts_pressure的值经report_ts_xy()去进行报告。
//我们需要report上报压力值数据
void report_ts_xy(int x, int y, int pressure);//别忘了在文件开头声明
void report_ts_xy(int x, int y, int pressure)
{//printf("x = %08d, y = %08d\n\r", x, y);if (g_ts_data_valid == 0){g_ts_x = x;g_ts_y = y;/*---------------把压力值上报给全局变量g_ts_pressure----------*/g_ts_pressure = pressure;g_ts_data_valid = 1;}
}那report_ts_xy中的pressure是如何变化的呢:
1、 如果没有按下屏幕,则在Isr_Tc()中报告压力值为0
2、 如果按下屏幕,则在Isr_Adc()中报告压力值
2.1 当我得到触电数据以后,如果当前仍是按下状态,会上报XY坐标值并且上报压力值,压力值等于1
2.2 如果数据转换完之前再次松开,这里我也会上报数据,0,0,0
void Isr_Tc(void)
{//printf("ADCUPDN = 0x%x, ADCDAT0 = 0x%x, ADCDAT1 = 0x%x, ADCTSC = 0x%x\n\r", ADCUPDN, ADCDAT0, ADCDAT1, ADCTSC);if (ADCDAT0 & (1<<15)){//printf("pen up\n\r");enter_wait_pen_down_mode();
//如果松开就上报数据xy坐标00 压力值0
/*------------------------报告处1-----------------------------*/report_ts_xy(0, 0, 0);}else {//printf("pen down\n\r");/* 进入"自动测量"模式 */enter_auto_measure_mode();/* 启动ADC */ADCCON |= (1<<0);}
}
void Isr_Adc(void)
{int x = ADCDAT0;//ADCDAT0的 bit15 为0代表downint y = ADCDAT1;static int adc_cnt = 0;static int adc_x = 0;static int adc_y = 0;if (!(x & (1<<15))) /* 如果仍然按下才打印 */{ x &= 0x3ff;y &= 0x3ff;//printf("x = %08d, y = %08d\n\r", x, y);/*-------------------------报告处2------------------*/
//当我得到触电数据以后,如果当前仍是按下状态,会上报XY坐标值并且上报压力值,压力值等于1report_ts_xy(x, y, 1);/* 启动定时器以再次读取数据 */ts_timer_enable();
}else{ts_timer_disable();enter_wait_pen_down_mode();
//如果数据转换完之前再次松开,这里我也会上报数据,0,0,0
/*-------------------------报告处3------------------*/report_ts_xy(0, 0, 0);}}
好了,编译运行,发现问题解决了,十字架是一个一个出现的。
但又发现了其他问题:
1、在屏幕上画线,线是横平竖直的而非我们随意画的曲线(并且看串口发现,x坐标值一直是同一个负数,只有y坐标在变化。
2、看串口输出的xy坐标值,发现其值在触摸笔将要松开时会出现值的波动,如下图所示。
好,先来解决问题2:
看Isr_Adc()函数,以前我们是如果有adc中断则执行Isr_Adc()函数,如果是按下状态则用report_ts_xy()上传xy值和压力状态,最后再启动ts_timer_enable(),ts_timer_enable()启动定时器再次读数据,是这样吧。
现在我们不这样做了,我们修改,在收到adc中断后,Isr_Adc为按下的状态的话,则把ADCDAT寄存器中的连续16个x值
相加存到adc_x ,连续16个y值相加存到adc_y,并让adc_cnt++。
如果adc_cnt不等于16,就触发一直ADC,每一次触发ADC中断都执行一次adc_cnt直到其值为16。
一旦adc_cnt等于了16,则取adc_x 、adc_y的平均值并用report_ts_xy()函数传递走,然后执行ts_timer_enable()函数,等10ms过后再发出ADC中断。注:关于ts_timer_enable()和启动16次ADC的关系,你去看下一节的第一幅图片就明白了。
关于那幅图,你可能会问了,为啥要中间隔10ms?
答:废话,定时器作用就是每隔一段时间再去做某件事情。难道每隔0ms???,别晕了兄弟。void Isr_Adc(void)
{int x = ADCDAT0;int y = ADCDAT1;static int adc_cnt = 0;static int adc_x = 0;static int adc_y = 0;if (!(x & (1<<15))) /* 如果仍然按下才打印 */{#if 0 x &= 0x3ff;y &= 0x3ff;//printf("x = %08d, y = %08d\n\r", x, y);
//当我得到触电数据以后,如果当前仍是按下状态,会上报XY坐标值并且上报压力值,压力值等于1report_ts_xy(x, y, 1);/* 启动定时器以再次读取数据 */ts_timer_enable();//这个作用就是隔10ms之后,再去发出ADC中断
#endif//防止数据在最后出现很大的误差/* 第1次启动ADC后:* a. 要连续启动N次, 获得N个数据, 求平均值并上报* b. 得到N次数据后, 再启动TIMER */
//我们直接累加adc_x += (x & 0x3ff);adc_y += (y & 0x3ff);adc_cnt++;
//我们取16的话右移4位比较容易操作if (adc_cnt == 16){
//右移4位adc_x >>= 4;//取平均值adc_y >>= 4;//取平均值
//上报report_ts_xy(adc_x, adc_y, 1);//恢复到初始值0adc_cnt = 0;adc_x = 0;adc_y = 0;/* 启动定时器以再次读取数据(其实这一句话的真实意义在于:adc_cnt == 16时,计时器重新开始从0计时) */ts_timer_enable();//这个作用就是每隔10ms读取一次数据}else{/* 否则再次启动ADC ,以实时测量xy在lcd位置*//* 进入"自动测量"模式 */enter_auto_measure_mode();/* 启动ADC */ADCCON |= (1<<0);}}else{adc_cnt = 0;adc_x = 0;adc_y = 0;ts_timer_disable();enter_wait_pen_down_mode();
//如果数据转换完之前再次松开,这里我也会上报数据,0,0,0report_ts_xy(0, 0, 0);}/*enter_wait_pen_up_mode();不需要写这一句。韦东山写了*/
}
好,编译,烧写,运行。发现了一个问题,如下图,即你点A点,虽然说return的值挺好的,但是下面又有一个get和return而且都是0,这说明程序在处理点A时上报了两次0。我们的解决方案是把点A点的第二个上报的0给他忽略:
方法如下,即修改get_calibrate_point_data.c:
void get_calibrate_point_data(int lcd_x, int lcd_y, int *px, int *py)
{int pressure;int x, y;fb_disp_cross(lcd_x, lcd_y, 0xffffff);/* 等待点击 *//*---------加入一个do while----------------------------------*/
/*目的是过滤掉第二个pressure(这个pressure就是点B刚开始执行时就遇到的,需要过滤掉)*/do {
//如果pressure一直是0的话我们丢掉这些数据ts_read_raw(&x, &y, &pressure); } while (pressure == 0);//让后再次读do {*px = x;*py = y;ts_read_raw(&x, &y, &pressure); printf("get raw data: x = %08d, y = %08d\n\r", x, y);} while (pressure);printf("return raw data: x = %08d, y = %08d\n\r", *px, *py);/* 直到松开才返回 */fb_disp_cross(lcd_x, lcd_y, 0);
}
编译烧写运行,发现ABCDE的值都很准确,误差很小。
好了,至此,第二个问题解决了。
但是还是不能画线,还是刚刚那个问题,来解决:
/*----------------1 在这个函数里面加入压力值-------------*/
int ts_read(int *lcd_x, int *lcd_y, int *lcd_pressure)
{int ts_x, ts_y, ts_pressure;int tmp_x, tmp_y;
//添加压力值参数 ts_read_raw(&ts_x, &ts_y, &ts_pressure);if (g_ts_xy_swap){swap_xy(&ts_x, &ts_y);}/* 使用公式计算 */tmp_x = g_kx * (ts_x - g_ts_xc) + g_lcd_xc;tmp_y = g_ky * (ts_y - g_ts_yc) + g_lcd_yc;/*----------------2 判断你所点的点不能超出屏幕,否则就返回-1 -------------*/
//如果值超出了LCD范围返回-1if (tmp_x < 0 || tmp_x >= xres || tmp_y < 0 || tmp_y >= yres)return -1;*lcd_x = tmp_x;*lcd_y = tmp_y;
//压力值等于全局变量ts_pressure*lcd_pressure = ts_pressure;return 0;
}
ts_read()函数是被touchscreen_test()函数应用的。在touchscreen_test()函数中,我们的改进是,确保ts_read()函数返回0而非-1(即确保所画点在屏幕范围内),并确保按下了(即确保压力值为1)
void touchscreen_test(void)
{unsigned int fb_base;int xres, yres, bpp;int x, y, pressure;/* 获得LCD的参数: fb_base, xres, yres, bpp */get_lcd_params(&fb_base, &xres, &yres, &bpp);touchscreen_init();/* 清屏 */clear_screen(0);/* 显示文字提示较准 */fb_print_string(70, 70, "Touc cross to calibrate touchscreen", 0xffffff);ts_calibrate();/* 显示文字提示绘画 */fb_print_string(70, yres - 70, "OK! To draw!", 0xffffff);while (1){
//如果结果=0则继续执行下面操作if (ts_read(&x, &y, &pressure) == 0){printf(" x = %d, y = %d\n\r", x, y);
//如果是按下状态,才会描点if (pressure){fb_put_pixel(x, y, 0xff00);}}}
}
编译运行,问题得到解决。最终效果图:
第012节_触摸屏编程_完善
我们触摸屏校准虽然可以正常运行,但是有些问题:
1、我们第一次点击触摸屏会出现两个点
2、长按,LCD上的点会越来越大
根源在于我们得到的LCD坐标值不稳定,根源ADC转换出来的xy坐标值不稳定。
运行的结果如下图:
下图是上报Isr_Adc()函数上报lcdx、lcdy的原理图:
关于这幅图,你可能会问了,为啥要中间隔10ms?
答:废话,定时器作用就是每隔一段时间再去做某件事情。难道每隔0ms???,别晕了兄弟。
首先需要你明白的一点是,在上图的1 ~ 16次adc中断期间是自动检测模式。而在10ms定时器期间我们应该设置为等待中断模式。而我们之前的程序在这个10ms期间仍是自动检测模式,这是不可以的。因此我们将10ms定时器使能之前,设置adc为等待中断模式。代码如下:
void Isr_Adc(void)
{省略代码............if (adc_cnt == 16){adc_x >>= 4;adc_y >>= 4;report_ts_xy(adc_x, adc_y, 1);adc_cnt = 0;adc_x = 0;adc_y = 0;/* 启动定时器以再次读取数据 *//* 先设置TS进入"等待中断模式" */
//有按下就会有松开
/*--------------------1 加上 设置 等待中断模式的函数-------------------------*/enter_wait_pen_up_mode();ts_timer_enable();}else{/* 再次启动ADC *//* 进入"自动测量"模式 */enter_auto_measure_mode();/* 启动ADC */ADCCON |= (1<<0);}}else{adc_cnt = 0;adc_x = 0;adc_y = 0;printf("adc report pen down\n\r");ts_timer_disable();enter_wait_pen_down_mode();report_ts_xy(0, 0, 0);}/*--------------------2 下面这句话不能要,要了就是一个bug。并且这个语句无意义啊-------------*///enter_wait_pen_up_mode(); /* 启动ADC时不应该进入这个模式, 它会影响数据 */
}
编译运行,还是不行。
我们看2440芯片手册发现:
只有在"等待中断模式"下才可以使用ADCDAT0’BIT 15来判断触摸笔状态 (up / down)。
如果在1 ~ 16adc阶段出现timer中断了怎么办,如下:
解决这个问题:
下面这个函数用到了ADCDAT0的bit15,我们需要判断touchscreen_timer_irq()是否处于等待中断模式,如果不是则在if (is_in_auto_mode())
语句中直接return。如果要是处于等待中断模式那么不满足条件从而执行就可以下面的语句了
void touchscreen_timer_irq(void)
{/* 如果触摸屏仍被按下, 进入"自动测量模式", 启动ADC */if (get_status_of_ts_timer() == 0)return;/*------------添加判断是否自动模式语句,不满足才可以往下执行。满足了直接return------------*/if (is_in_auto_mode())return;/* --------只有在"等待中断模式"下才可以使用ADCDAT0'BIT 15来判断触摸笔状态 ----------*/if (ADCDAT0 & (1<<15)) /* 如果松开 */{printf("timer set pen down\n\r");ts_timer_disable();enter_wait_pen_down_mode();report_ts_xy(0, 0, 0);return;}else /* 按下状态 */{/* 进入"自动测量"模式 */enter_auto_measure_mode();/* 启动ADC */ADCCON |= (1<<0);}
}/*-------------------添加is_in_auto_mode()函数---------------------*/
int is_in_auto_mode(void)
{return ADCTSC & AUTO_PST;
}
编译烧写运行,发现出现如下现象:
即,你点一下,就会出现“满天星”现象,并且发现ABCDE的lcd坐标值很不准确。这说明这个现象是测不准。
解决问题:
对下面这个函数进行改进:即把tslib.c中的ts_read_raw(&x, &y, &pressure); 传回来的xy值再次进行求平均值,代码如下:
void get_calibrate_point_data(int lcd_x, int lcd_y, int *px, int *py)
{int pressure;int x, y;int sum_x = 0, sum_y = 0;int cnt = 0;fb_disp_cross(lcd_x, lcd_y, 0xffffff);/* 等待点击 */do {ts_read_raw(&x, &y, &pressure); } while (pressure == 0);do {if (cnt < 128){sum_x += x;sum_y += y;cnt++;}ts_read_raw(&x, &y, &pressure);printf("get raw data: x = %08d, y = %08d, cnt = %d\n\r", x, y, cnt);} while (pressure);*px = sum_x / cnt;*py = sum_y / cnt;printf("return raw data: x = %08d, y = %08d\n\r", *px, *py);/* 直到松开才返回 */fb_disp_cross(lcd_x, lcd_y, 0);
}