| 一旦完成了基本的硬件设置,接下来就需要一种可靠的方法判断屏幕是否被触摸了。如果用户没有触摸屏幕,那么运行ADC获得转换后的读数毫无意义。上述两个控制器都提供了屏幕是否被触摸的检测机制,并且当触摸事件发生时还可选择是否中断主处理器。判断屏幕是否被触摸的驱动程序的函数名叫WaitForTouchState()。 当控制器处于触摸检测模式时,Y轴触摸层通过一个上拉电阻上拉到高电平,X轴触摸层则连接到地。当用户触摸屏幕的任何地方时,这两层就发生短接,Y轴层被拉到低电平。该事件可以在驱动程序内部连接到名为PEN_OWN IRQ的中断发生机制。 在正常工作期间,当触摸事件发生时驱动程序利用PEN_DOWN IRQ唤醒触摸驱动任务。这样做可以让驱动程序在屏幕没有被触摸时中断自己的执行,而不消耗任何CPU资源,而一旦用户触摸屏幕,驱动程序就被唤醒并进入转换模式。我们也可以在转换模式没被激活时停止(disable)ADC时钟来节省功耗。 在校准和主动采样期间,驱动程序使用与上述基本相同的机制检测屏幕是否被触摸;不过在这些模式下驱动程序会屏蔽实际的中断,并通过人工方式简单的检查触摸状态。对于飞思卡尔的处理器,这时要求把控制器编程到触摸检测模式,并检查PEN_DOWN IRQ的数据位。对于夏普的处理器,触摸检测内建在ADC命令序列中,不需要额外的步骤。 读取触摸数据 在校准和正常操作期间,我们需要读取X和Y轴的原始数据并去抖动,然后确定屏幕被触摸时是否有稳定的读数。该过程在两个驱动程序中都叫TouchScan()。该过程的要点是: 1. 检查屏幕是否被触摸; 2. 采集每个轴上的多个原始读数用于以后的过滤; 3. 检查屏幕是否仍在被触摸。 在执行模数转换时,两个控制器都提供了由编程产生延迟的方法,以在给触摸层加电和开始实际的模数转换之间插入一段时延。飞思卡尔把这段时延称作数据建立计数(DSCNT),在两层切换后会有很多个ASP输入时钟长度的延时。夏普把这段时延称为预充时延。 两种CPU都需要这种时延,因为电阻触摸面板是二块由薄绝缘层隔离的大面积导体,正好形成一个电容。当从将要执行模数转换的层切换到正在加电的层时,需要一定的延时才能保证电容达到稳定状态。 对于飞思卡尔的i.MX1处理器来说,一旦我们启动转换过程,那么由ADC产生的数据将被保存在一个16位宽x12个条目深度的FIFO中。ADC产生9位无符号数据,因此每个16位条目的高7位将被忽略掉。这意味着这种触摸控制器的全部数据范围从0到511,不过实际上没有ADC或电阻触摸屏会产生接近这个极限值的数据。 我们可以通过编程让处理器在FIFO存有任何有效数据时就产生中断,或在输入FIFO装满时产生中断。由于我们通常会做多次读取,因此驱动程序一般会在FIFO装满时产生中断。当该中断产生时,会有12个原始的模数转换数据等待处理,分别对应于X轴的6次读数和Y轴的6次读数。 夏普LH79524处理器允许在产生中断前通过编程完成精确的步骤序列。在执行每个步骤时,结果同样会保存在输入FIFO中,等待驱动程序软件的读取。结果是以16位数值进行保存。每个结果的高10位是模数转换值,最低4位是序列索引。10位转换结果意味着这种触摸控制器的最大数值范围是0到1023,当然你永远也不会观察到接近极限值的结果。 一旦序列器控制字在LH79524上被编好程,驱动程序获取原始读数所需要做的就是命令序列器执行。当EOS(序列结束)中断产生时,我们获得的结果就可以用于采集和检查了。序列器可以被配置为当屏幕被触摸时自动触发、根据软件命令触发或连续触发三种模式。 要注意原始转换器读数中经常会有一些噪声和偏差,这是正常的。你只有紧紧压住电阻触摸屏才能得到两个连续的读数,并取得一致的9位或10位原始数据。然而你会发现当触控笔或手指按上或离开触摸屏时,读数的变化要比你保持稳定压力时大得多。要记住用户是以机械的方式连通二个平面电阻-触摸层。当用户按压和释放触摸屏时,在很短的一段时间内两层之间的电气连接处于临界状态。我们需要丢弃这些读数直到系统稳定下来,否则我们提交的触摸位置读数会产生大幅跳跃,导致更高层的软件无法进行合适的操作。 这里不可避免要进行折衷考虑。如果我们要求较窄的稳定窗口,那么驱动程序将无法跟踪快速的"拖曳"操作。对于在签名输入期间发生的滑动或笔划跟踪事件来说快速拖曳是非常重要的。如果我们加宽稳定窗口,我们就可能面临着风险,这些风险包括接收到不精确的触摸数据和上文描述过的处于临界状态的层连接结果。因此需要通过实验来确定适合自己系统的最佳值。智能化的触摸控制器同样允许你通过软件命令调整这些参数。 每个样值所需的读取次数、连续读取间允许的偏差以及采样速率是每个驱动程序的全部可编程参数。可以通过#defines调整这些参数以便在你的系统上产生最佳结果。智能化的外部触摸控制器一般会以很快的速度读取数十或数百个数据用以改善精度。由于我们是用核心CPU完成这种过滤,因此我们需要确定有多少时间可以合理地分配给触摸采样任务。嵌入式系统包含折衷,你的任务就是想出最佳的折衷办法,以产生能使用户满意的系统。 出于游戏目的,我喜欢测试日常生活中所遇到的商业触摸系统。下一次当你使用触摸屏进行购物签名或包裹签名时,你可以尝试快速大范围波浪形地移动触控笔,然后观察结果,查看屏幕跟踪你移动的程度如何。如果你能看到漂亮光滑的跟踪轨迹,你就知道驱动程序的采样速率相当快,可能在200Hz以上。经常你会观察到移动轨迹变成了一条直线(慢速采样)或完全丢失(由于数值改变过大而被拒绝输入)。当你在零售商店进行这种小测试的时候千万不要大呼小叫,否则人们会用异样的目光看你。正常人是不会理解什么东西会使工程师那么激动。 触摸屏的校准 到此我们已经介绍了驱动程序所支持的全部功能,这是我们进入下一步之前必须完成的繁琐工作。既然各种功能都已就绪,可以让用户实际触摸屏幕了。 电阻触摸屏需要校准。我们需要一些参考值,以便我们能够将接收到的原始模数转换值转换成高层软件所需的屏幕像素坐标。理想情况下校准程序只要在产品初次加电测试过程中运行一次就可以了,参考值被存储在非易失性存储器中。我已经安排好让触摸驱动程序在一启动时就运行校准程序,但要记住,你要把参考值保存起来,以免让用户在以后的加电启动期间再做校准。不过无论如何你仍然需要向用户提供一种进入校准例程的途径,从而在由于温度漂移或其它因素造成校准不准确时进行重新校准。 校准例程的名称是CalibrateTouchScreen(),它是一个简单的逐步操作过程,会在屏幕上向用户提供图形目标,并要求用户触摸目标,然后记录下原始的ADC读数,该读数将用于后面的原始数据转换到像素位置的调整例程。图形目标和用户提示通过使用便携式图形用户界面(PEG)图形软件API显示在屏幕上,不过这也可以通过类似的图形软件实现。 在理想情况下你只需两组(X和Y)原始数据,即在屏幕对角读取的最小和最大值。而在实际应用中,因为许多电阻触摸屏存在显著的非线性,因此如果在最小和最大值之间简单的插入位置数值会导致驱动程序非常的不精确。 非线性意味着在屏幕上的等距物理移动会导致原始数据的增量不等。更糟的情况下,即使我们只改变X轴的触摸位置,但从Y轴读取的数据也会发生很大的变化。为了演示这一现象,我用触控笔在一个典型的电阻触摸屏上从左到右移动,尽量保持Y轴位置不变,同时在图上记录Y轴的读数。你当然希望触控笔从左到右在X轴上滑动时Y轴读数能保持一定程度的恒定,但从图3可以看出完全不是这回事。 得出的结论是采用的校准点越多越好,尽量减小内插窗口的间距,才能产生可能的最佳精度。如果你能在工厂做一次校准,那么得到大量采样点并不是件难事。如果无法在工厂完成校准,那你必须确定用户需要输入多少个点才能产生足够精确的校准。本文提供的校准例程用了四个数据点,即屏幕的每个角一个。对于参考板上的VGA分辨率(640x480)显示屏幕来说,这样做的精度可达到一个或二个像素之内。对于更高的屏幕分辨率或其它触摸屏,要产生一个精确的驱动程序这些点也许过多,也许不够。做出准确判定的唯一途径只能是对具体的硬件进行大量反复测试。 在任何情况下,我的建议是尽可能多做些校准点。对用户来说,难得做一次较长时间的校准操作总比正常状态下系统无法对触摸输入做出精确响应要好。 正常操作 一旦校准过程完成,我们就可以开始正常的操作,并开始向更高层软件发送触摸事件。我把提供的每个触摸驱动程序在每种支持的RTOS环境中都作为低优先级任务加以执行。任务的入口名叫PegTouchTask,因为驱动程序需要与PEG图形软件进行交互操作。这些驱动程序修改后,可与其它图形软件甚至你自己编写的用户接口环境协同工作。在任何时候PegTouchTask总是先调用硬件配置例程,然后调用校准例程,最后进入等待触摸输入的无限循环中。 在MX1驱动程序中,无限循环通过等待前文描述的PEN_DOWN中断事件中止自身循环。当屏幕被触摸时,该任务会持续读取原始数据,将他们转换成屏幕像素坐标,并将触摸位置或状态的变化发送给更高层的软件。我把这称为"活动跟踪"模式。 LH79524驱动程序以相似的方式工作。当产生PEN_DOWN中断时,我们命令ADC序列器开始进行转换。驱动程序以20Hz的速度工作,检查位置的变化,直到屏幕不再处于被触摸的状态。 当屏幕被触摸时,我们需要对每个轴连续读取多个转换值以确定触摸位置是否稳定。如果任何两个连续读数中的增量或变化超出#defined定义的噪声窗口范围,我们就要重新开始。我们一直这样做,直到读取的多个连续值处于#defined定义的稳定范围内,此时我们可以调整该结果并向更高层软件报告更新。当屏幕不再被触摸时,我们又可以中断此任务,等待触摸输入事件的发生。 在每个转换过程的前后,驱动程序必须检查并确认屏幕仍处于被触摸状态。我们不希望向更高层的软件报告实际上是处于"开路状态"的稳定读数。我也看到过有的驱动程序在屏幕被初始触摸后会忽略掉N个读数。不过对于这两块参考电路板,我没有发现忽略掉一定数量的初始读数是有必要或有益的。 当屏幕被触摸时,驱动程序得到每个稳定的读数,并利用简单的线性插值法将原始数据转换成像素坐标。读取原始数据并将它们转换成屏幕坐标的例程名字叫GetScaleTouchPosition()。 最后部分 好了,我们终于调整好驱动程序,获得了精确、调整过的、可靠的触摸信息。这些重要的数据能用来干什么呢?如果你正在运行象PEG这样的图形用户接口系统,大部分工作到此就结束了。你只要简单的将这些触摸数据整理成消息,并将消息发送到PEG消息队列。PEG软件会对这些数据作出正确地处理。 PEG可以识别三种触摸输入事件类型,分别对应于向下触摸、向上触摸和拖曳。发送拖曳事件是可选的,但如果你希望向用户提供平滑的屏幕移动操作,那么发送拖曳事件就是必须的了。确定该发送哪种类型的消息给PEG消息队列的逻辑包含在所提供的源代码中名为SendTouchMessage()的函数中。 这里需要强调的一点是用于发送drag(PM_POINTERMOVE)消息的名为Fold()的函数的使用。这是一个方便使用的PEG API函数,可以防止用户接口的响应落后于用户的输入。例如,如果用户正在高分辨率显示器上滚动一个大窗口,那么用户接口很可能在重画滚动窗口时迟滞一段时间。在用户接口的响应能跟上时,用户一释放滚动条屏幕就应该立即停止滚动。但如果消息队列已经包含了一个PM_POINTERMOVE消息,我们只需要将这条消息更新到最新位置,而不用再发送新的消息。这样做的效果就是用户接口滚动到最新位置,跳过了对处理器来说太快的所有中间位置更新。 这就是PEG提供Fold函数的目的。它会检查这个消息类型是否已经在消息队列中,如果在,那么它只是简单的更新这条已有的消息,而不是发送全新的消息。如果你正在使用另外一种图形软件包,你也会希望实现类似的功能。 动手下载 本文主要介绍了如何为两个集成了触摸屏控制电路的主流CPU编写触摸屏驱动程序。你可以从ftp://ftp.embedded.com/pub/2005/07maxwell 网站免费下载每个驱动程序的源代码,并按照你的意图使用和修改。 提供精确可靠的触摸信息显然要花费大量的处理器时间。专门设计用于支持触摸屏输入的智能化的ADC可以显著地减轻核心CPU的负担,并有效地提高触摸屏输入系统的精度。 Ken Maxwell曾是一位应用软件工程师,在编写嵌入式软件方面有18多年的丰富经验。Ken目前是Swell软件公司总裁。该公司是用于嵌入式系统的PEG+和C/PEG图形软件开发商。Ken的email地址是:touch@swellsoftware.com。 作者:Kenneth G. Maxwell Swell软件公司总裁 |