http://blog.sina.com.cn/s/blog_4b4b54da01012gbz.html
Wince6.0 s3c6410触摸屏驱动分析
一、wince 触摸屏驱动概述... 3
二、触摸屏驱动接口分析... 4
2.1 Windows CE触摸屏驱动工作过程... 4
2.2 触摸屏驱动程序的实现... 4
2.3四线电阻式触摸屏的工作原理... 4
三、wince 6.0 触摸屏驱动分析... 6
3.1 TouchPanelEnable() 函数分析... 6
3.2 TouchPanelpISR()函数分析... 8
3.3 DdsiTouchPanelGetPoint()函数分析... 13
3.5 TSP_GetXY()函数分析... 17
3.6 Touch_Pen_Filtering()函数分析... 19
3.7 触摸屏校准分析... 21
一、wince 触摸屏驱动概述
在Windows CE操作系统中触摸屏驱动是一种分层驱动。其驱动模型如下图所示。上层是模型设备驱动程序(Model Device Driver, MDD),下层是依赖平台的驱动程序(Platform Dependent Driver, PDD)。MDD通常无需修改直接使用,MDD链接PDD层并定义它希望调用的函数接口:设备驱动程序提供器接口(Device Driver Service Provider Interface, DDSI)。同时MDD把不同的函数集提供给操作系统,这些函数叫做设备驱动程序接口(Device Driver Interface, DDI),这部分为也就是我们通常驱动需要实现的部分。
Windows CE的触摸屏驱动链接了tch_cal.lib和tchmdd.lib两个静态链接库。触摸屏驱动由GWES加载,GWES通过DDI调用驱动程序获取设备状态,设置驱动功能等,而驱动本身通过DDSI直接获得硬件信息来确定当前触摸屏的状态。
Windows CE触摸屏驱动要求的DDI接口包括:TouchPanelGetDeviceCaps、TouchPanelEnable、TouchPanelDisable、TouchPanelSetMode、TouchPanelReadCalibrationPoint、TouchPanelReadCalibrationAbort、TouchPanelSetCalibration、TouchPanelCalibrateAPoint、TouchPanelPowerHandler。
Windows CE触摸屏驱动要求的DDSI接口包括:DdsiTouchPanelAttach、DdsiTouchPanelDetach、DdsiTouchPanelDisable、DdsiTouchPanelEnable、DdsiTouchPanelGetDeviceCaps、DdsiTouchPanelGetPoint、DdsiTouchPanelPowerHandler。
2.2 触摸屏驱动程序的实现
Windows CE6.0触摸屏驱动程序采用中断方式对触摸笔的按下状态进行检测,如果检测到触摸笔按下将产生中断并触发一个事件通知一个工作线程开始采集数据。同时,驱动将打开一个硬件定时器,只要检测到触摸笔仍然在按下状态将定时触发同一个事件通知工作线程采集数据,直到触摸笔抬起后关闭该定时器,并重新检测按下状态。驱动中采用了触摸屏中断以及定时器中断2个中断源,不仅可以监控触摸笔按下和抬起状态,而且可以检测触摸笔按下时的拖动轨迹。
2.3四线电阻式触摸屏的工作原理
四线电阻式触摸屏的结构如下图,在玻璃或丙烯酸基板上覆盖有两层透平,均匀导电的ITO层,分别做为X电极和Y电极,它们之间由均匀排列的透明格点分开绝缘。其中下层的ITO与玻璃基板附着,上层的ITO附着在PET薄膜上。X电极和Y电极的正负端由“导电条”(图中黑色条形部分)分别从两端引出,且X电极和Y电极导电条的位置相互垂直。引出端X-,X+,Y-,Y+一共四条线,这就是四线电阻式触摸屏名称的由来。当有物体接触触摸屏表面并施以一定的压力时,上层的ITO导电层发生形变与下层ITO发生接触,该结构可以等效为相应的电路
计算触点的X,Y坐标分为如下两步:
1. 计算Y坐标,在Y+电极施加驱动电压Vdrive, Y-电极接地,X+做为引出端测量得到接触点的电压,由于ITO层均匀导电,触点电压与Vdrive电压之比等于触点Y坐标与屏高度之比。
2. 计算X坐标,在X+电极施加驱动电压Vdrive, X-电极接地,Y+做为引出端测量得到接触点的电压,由于ITO层均匀导电,触点电压与Vdrive电压之比等于触点X坐标与屏宽度之比。
测得的电压由ADC转化为触摸点的原始坐标(数值范围由所选用的A/D转换器位数决定)后,还要根据具体使用的液晶屏实际像素进行转换,转换后通过校准直接转化为屏幕上的坐标,供GWES使用
Windows CE触摸屏驱动程序采用中断方式对触摸笔的按下状态进行检测,如果检测到触摸笔按下将产生中断并触发一个事件通知一个工作线程开始采集数据。同时,驱动将打开一个硬件定时器,只要检测到触摸笔仍然在按下状态将定时触发同一个事件通知工作线程采集数据,直到触摸笔抬起后关闭该定时器,并重新检测按下状态。驱动中采用了触摸屏中断以及定时器中断两个中断源,不仅可以监控触摸笔按下和抬起状态,而且可以检测触摸笔按下时的拖动轨迹。
触摸屏驱动在初始化过程调用TouchPanelEnable函数使能触摸屏。该函数调用的DDSI函数为:DdsiTouchPanelEnable和DdsiTouchPanelDisable。该函数实现如下内容:
1) 创建事件hTouchPanelEvent和hCalibrationSampleAvailable。hTouchPanelEvent事件在正常状态下当有触摸笔按下或者按下后需要定时采集数据时被触发。而hCalibrationSampleAvailable事件在校准状态下当有校准数据输入时被触发;
2) 检查并初始化所需的中断gIntrTouch(触摸屏中断)和gIntrTouchChanged(定时器中断),并将中断gIntrTouch、gIntrTouchChanged关联到事件hTouchPanelEvent。当gIntrTouch,gIntrTouchChanged中断产生时将触发hTouchPanelEvent事件;
3) 创建一个ISR线程TouchPanelpISR。TouchPanelpISR用于等待和处理触摸屏事件hTouchPanelEvent,它是整个驱动程序中唯一的事件源。
TouchPanelEnable()实现 C:\WINCE600\PUBLIC\COMMON\OAK\DRIVERS\TOUCH目录下
代码如下:
BOOL
TouchPanelEnable(
PFN_TOUCH_PANEL_CALLBACK pfnCallback
)
{
BOOL ReturnValue;
//创建hTouchPanelEvent 触摸屏事件和hCalibrationSampleAvaiable校准采样事件,确保设备被禁用,设置采样频率
TouchPanelpAttach();
EnterCriticalSection( &csMutex );
//
// Insure the device is disabled and no one is attached to the logical
// interrupt.
// Power on the device.
// Connect the logical interrupt to the device.
//
InterruptDone( gIntrTouch );
InterruptDisable( gIntrTouch );
if( SYSINTR_NOP != gIntrTouchChanged ) {
InterruptDone( gIntrTouchChanged );
InterruptDisable( gIntrTouchChanged );
}
v_pfnCgrPointCallback = pfnCallback;
if (v_pfnCgrCallback != NULL)
v_pfnPointCallback = v_pfnCgrCallback;
else
v_pfnPointCallback = pfnCallback;
ghevCalibrationActivity = NULL;
//检查并初始化所需的中断gIntrTouch(触摸屏中断)和gIntrTouchChanged(定时器中断),
//并将中断gIntrTouch、gIntrTouchChanged关联到事件hTouchPanelEvent。
ReturnValue = DdsiTouchPanelEnable();
// 将hTouchPanelEvent事件与中断号gIntrTouch绑定,并使能中断
if (ReturnValue && !InterruptInitialize(gIntrTouch, hTouchPanelEvent, NULL, 0)) {
DEBUGMSG(ZONE_ERROR, (TEXT("TouchPanelEnable: InterruptInitialize(gIntrTouch %d failed\r\n"),
gIntrTouch));
DdsiTouchPanelDisable();
ReturnValue = FALSE;
}
// 将hTouchPanelEvent事件与中断号gIntrTouchChanged绑定,并使能中断
if ( ( SYSINTR_NOP != gIntrTouchChanged ) &&
ReturnValue && !InterruptInitialize( gIntrTouchChanged, hTouchPanelEvent, NULL, 0)) {
DEBUGMSG(ZONE_ERROR, (TEXT("TouchPanelEnable: InterruptInitialize(gIntrTouchChanged %d failed\r\n"),
gIntrTouchChanged));
InterruptDisable(gIntrTouch);
DdsiTouchPanelDisable();
ReturnValue = FALSE;
}
if (ReturnValue) {
// Create the ISR thread. If creation fails, perform cleanup and return failure.
//创建一个ISR线程TouchPanelpISR。TouchPanelpISR用于等待和处理
//触摸屏事件hTouchPanelEvent,它是整个驱动程序中唯一的事件源。
bTerminate=FALSE;
if (!(hThread = CreateThread( NULL, 0, TouchPanelpISR, 0, 0, NULL))) {
TouchPanelpDetach();
InterruptDisable(gIntrTouch);
if( SYSINTR_NOP != gIntrTouchChanged )
InterruptDisable(gIntrTouchChanged);
DdsiTouchPanelDisable();
ReturnValue = FALSE;
} else {
// Get thread priority from registry
TouchPanelpGetPriority(&gThreadPriority, &gThreadHighPriority);
// Set our interrupt thread's priority
CeSetThreadPriority(hThread, gThreadPriority);
}
}
LeaveCriticalSection(&csMutex);
return(ReturnValue);
}
TouchPanelpISR函数是实现触摸屏数据采集关键函数,它实现的内容为:
1) 等待循环,用于接收hTouchPanelEvent事件,并构成函数的主体;
2) 通过调用DdsiTouchPanelGetPoint函数获取当前触摸屏位置和状态信息;
3) 在获取有效数据且在校准状态下,收集并提交按下的位置信息;
4) 在正常状态下,校准数据,并检查校准后数据的有效性;
5) 最后调用由GWES传入的回调函数,提交位置信息和状态信息。
TouchPanelpISR()代码如下
// 中断服务函数,采样频率10ms
static ULONG
TouchPanelpISR(
PVOID Reserved //@parm Reserved, not used.
)
{
TOUCH_PANEL_SAMPLE_FLAGS SampleFlags = 0;
INT32 RawX, CalX;
INT32 RawY, CalY;
UINT32 MaxX = DisplayWidth * X_SCALE_FACTOR;
UINT32 MaxY = DisplayHeight * Y_SCALE_FACTOR;
UINT32 CurrentDown = 0;
static LONG CX;
static LONG CY;
static LONG XBase;
static LONG YBase;
static int CalibrationSampleCount;
static BOOL fSetBase;
static DWORD BaseTime;
static BOOL fGotSample;
PFN_TOUCH_PANEL_CALLBACK pfnCallback;
// Need to be all kmode so that we can write to shared memory.
//
while ( !bTerminate )
{
//等待触摸屏产生中断,用于接收hTouchPanelEvent事件,并构成函数的主体
WaitForSingleObject( hTouchPanelEvent, gdwTouchIstTimeout );
EnterCriticalSection( &csMutex );
DEBUGMSG(ZONE_THREAD, (TEXT("TCH_INTR\r\n")) );
// Give the pdd the down state of the previous sample
if ( CurrentDown )
SampleFlags |= TouchSamplePreviousDownFlag;
else
SampleFlags &= ~TouchSamplePreviousDownFlag;
//调用DdsiTouchPanelGetPoint函数获取当前触摸屏位置[RawX,RawY]和状态信息[SampleFlags]
DdsiTouchPanelGetPoint( &SampleFlags, &RawX, &RawY ); // Get the point info
//忽略采集到的数据
if ( SampleFlags & TouchSampleIgnore )
{
// do nothing, not a valid sample
LeaveCriticalSection( &csMutex );
continue;
}
if ( SampleFlags & TouchSampleValidFlag )
{
// 之前有校准笔针按下时,会将SampleFlags的TouchSamplePreviousDownFlag
位置1
if ( CurrentDown )
SampleFlags |= TouchSamplePreviousDownFlag;
else
SampleFlags &= ~TouchSamplePreviousDownFlag;
// 当笔针有按下动作时,会将SampleFlags的TouchSampleDownFlag位置1.
// 这时CurrentDown的TouchSampleDownFlag位也会置1
CurrentDown = SampleFlags & TouchSampleDownFlag;
}
if ( CalibrationState )
{
//
// At this point we know that calibration is active.
//
// Typically, the user touches the panel then converges to the
// displayed crosshair. When the tip state transitions to
// the up state, we forward the last valid point to the callback
// function.
//
DEBUGMSG(ZONE_SAMPLES, (TEXT("**** Calibration point (%d, %d), flags 0x%4.4X\r\n"),
RawX, RawY, SampleFlags) );
// Skip if not valid. 采样数据无效
if ( !(SampleFlags & TouchSampleValidFlag) )
{
LeaveCriticalSection( &csMutex );
continue;
}
// Signal the Power Manager activity event if one has been set up
if ( ghevCalibrationActivity != NULL)
{
SetEvent(ghevCalibrationActivity); //通知校准事件
}
// Must see down transition.
if ( (SampleFlags & (TouchSampleDownFlag|TouchSamplePreviousDownFlag)) ==
TouchSampleDownFlag )
{// 进入笔针校准状态
CalibrationState = CalibrationDown;
fSetBase = TRUE;
CalibrationSampleCount = 0;
fGotSample = FALSE;
}
// Only look at stuff if we saw a down transition.
if ( (CalibrationState == CalibrationDown) && !fGotSample )
{
if ( SampleFlags & TouchSampleDownFlag )
{
long DeltaX, DeltaY;
//校准次数累加。需校准5次
CalibrationSampleCount++;
CX = RawX;
CY = RawY;
if ( fSetBase )
{
XBase = CX;
YBase = CY;
BaseTime = GetTickCount();
fSetBase = FALSE;
}
DeltaX = CX - XBase;
DeltaY = CY - YBase;
if ( (GetTickCount() - BaseTime) > CAL_HOLD_STEADY_TIME )
{
fGotSample = TRUE;
}
else if ( ( ABS(DeltaX) > CAL_DELTA_RESET ) ||
( ABS(DeltaY) > CAL_DELTA_RESET ) )
{
RETAILMSG(1, (TEXT("M %ld,%ld %ld,%ld %ld,%ld"),
XBase,YBase, CX,CY, DeltaX,DeltaY));
fSetBase = TRUE;
}
}
else
{
// They lifted the pen, see if we will accept coordinate.
if ( CalibrationSampleCount >= MIN_CAL_COUNT )
{
fGotSample = TRUE;
}
else
{
CalibrationState = CalibrationWaiting;
}
}
//收集并提交校准按下的位置信息
if ( fGotSample )
{
//保存校验数据到lCalibrationXCoord,lCalibrationYCoord
CalibrationState = CalibrationValid;
lCalibrationXCoord = CX;
lCalibrationYCoord = CY;
SetEvent(hCalibrationSampleAvailable);
}
}
LeaveCriticalSection( &csMutex );
}
else
{//在正常状态下,校准数据,并检查校准后数据的有效性;
pfnCallback = v_pfnPointCallback;
if ( pfnCallback != NULL )
{ // XY坐标植已经校准过,直接读取采样的值。
if( SampleFlags & TouchSampleIsCalibratedFlag )
{ // Sample already calibrated by PDD
CalX = RawX;
CalY = RawY;
}
else // 之前没有校准屏幕,调用校准函数进行校准。这里只校准一个点.
{ // Not previously calibrated, do it now.
TouchPanelCalibrateAPoint( RawX, RawY, &CalX, &CalY ); //看看是如何校准的.不是要校准5个点吗?
// 校准完后将已经校准标志位置1,以后就不用校准了
// 为什么校准一个点就置校准标志位为1?
SampleFlags |= TouchSampleIsCalibratedFlag;
}
LeaveCriticalSection( &csMutex );
// Bounds check this value
if( CalX < 0 )
CalX = 0;
else if( MaxX && ((UINT32)CalX >= MaxX) )
CalX = MaxX - X_SCALE_FACTOR;
if( CalY < 0 )
CalY = 0;
else if( MaxY && ((UINT32)CalY >= MaxY) )
CalY = MaxY - Y_SCALE_FACTOR ;
DEBUGMSG( ZONE_SAMPLES,
(TEXT("**** Queuing point (%d, %d), flags 0x%4.4X\r\n"),
CalX, CalY, SampleFlags) );
#ifdef DEBUG
{
static DWORD SampleCt;
if( SampleFlags & TouchSampleDownFlag )
SampleCt++;
else
{
DEBUGMSG( ZONE_TIMING,
(TEXT("%d down samples queued\r\n"),
SampleCt) );
SampleCt = 0;
}
}
#endif
(pfnCallback)( SampleFlags, CalX, CalY); // 调用由GWES传入的回调函数,提交位置信息和状态信息
}
else
{
LeaveCriticalSection( &csMutex );
}
}
}
ExitThread(1);
return ( TRUE );
}
在触摸屏驱动程序中DdsiTouchPanelEnable、DdsiTouchPanelDisable和DdsiTouchPanelGetPoint三个DDSI接口函数是驱动实现的关键所在。
// 对触摸屏数据的采样,采样的数据以*pUncalX,*pUncalY形式回传
VOID
DdsiTouchPanelGetPoint(TOUCH_PANEL_SAMPLE_FLAGS *pTipState, INT *pUncalX, INT *pUncalY)
{
static int PrevX=0;
static int PrevY=0;
int TmpX = 0;
int TmpY = 0;
TSPMSG((_T("[TSP] ++DdsiTouchPanelGetPoint()\r\n")));
// 处理触摸屏中断.触摸笔按下时产生触摸屏中断gIntrTouch时触发
if (g_pVIC1Reg->VICRAWINTR & (1<<(PHYIRQ_PENDN-VIC1_BIT_OFFSET))) // gIntrTouch Interrupt Case
{
TSPMSG((_T("[TSP] gIntrTouch(PHYIRQ_PENDN) Case\r\n")));
*pTipState = TouchSampleValidFlag;
if ((g_pADCReg->ADCDAT0 & D_UPDOWN_UP)
|| (g_pADCReg->ADCDAT1 & D_UPDOWN_UP)) //笔针抬起
{
TSPMSG((_T("[TSP] Pen Up\r\n")));
g_bTSP_DownFlag = FALSE;
g_pADCReg->ADCTSC = ADCTSC_WAIT_PENDOWN;
*pUncalX = PrevX;
*pUncalY = PrevY;
TSP_SampleStop();
}
else//笔针按下状态
{
TSPMSG((_T("[TSP] Pen Down\r\n")));
g_bTSP_DownFlag = TRUE;
g_pADCReg->ADCTSC = ADCTSC_WAIT_PENUP;
*pTipState |= TouchSampleIgnore;
*pUncalX = PrevX;
*pUncalY = PrevY;
*pTipState |= TouchSampleDownFlag;
//笔针按下后会开启定时器
TSP_SampleStart();
}
g_pADCReg->ADCCLRWK = CLEAR_ADCWK_INT;
// 通知触摸中断已经完成
InterruptDone(gIntrTouch); // Not handled in MDD
}
// 当触摸屏中断产生时,同时会打开一个定时器,当笔针未松开时定时触发同一个事件通知这个工作线程继续采集数据,直到触摸笔抬起后关闭该定时器。故上面的if虽然执行了,也会继续执行后面的else。处理定时器中断.触摸笔按下后,定时器被打开,定时器将定时产生中断gIntrTouchChanged,并触发事件,直到触摸笔抬起为止。
else// if (g_pVIC0Reg->VICRAWINTR & (1<<(PHYIRQ_TIMER3))) // gIntrTouchTimer Interrupt Case
{
//TSPMSG((_T("[TSP] gIntrTouchChanged(PHYIRQ_TIMER3) Case\r\n")));
// 在开启定时器后检测到笔针已经抬起
// Check for Pen-Up case on the event of timer3 interrupt
if ((g_pADCReg->ADCDAT0 & D_UPDOWN_UP)
|| (g_pADCReg->ADCDAT1 & D_UPDOWN_UP))
{
TSPMSG((_T("[TSP] Pen Up +\r\n")));
g_bTSP_DownFlag = FALSE;
g_pADCReg->ADCTSC = ADCTSC_WAIT_PENDOWN;
// 将采样的笔针坐标存放起来
*pUncalX = PrevX;
*pUncalY = PrevY;
*pTipState = TouchSampleValidFlag;
TSP_SampleStop();//停止定时器中断
}
else if (g_bTSP_DownFlag) {
if (TSP_GetXY(&TmpX, &TmpY) == TRUE) //采集触摸笔按下的位置.一次采样8组数据,去掉最大两个,最小一个,处于中间大小的两个乘以2变为8个后采样数据除8,得平均值
{
//TSP_TransXY(&TmpX, &TmpY);
if(Touch_Pen_Filtering(&TmpX, &TmpY)) ) // 过滤采集的数据.这里通过定时器实现3次数据采集
{
*pTipState = TouchSampleValidFlag | TouchSampleDownFlag;
*pTipState &= ~TouchSampleIgnore; //数据过滤成功,清掉TouchSampleIgnore标识(标志)
}
else // Invalid touch pen
{
*pTipState = TouchSampleValidFlag;
*pTipState |= TouchSampleIgnore;
}
// 将采样的笔针坐标存放起来
*pUncalX = PrevX = TmpX;
*pUncalY = PrevY = TmpY;
g_pADCReg->ADCTSC = ADCTSC_WAIT_PENUP;
//TSPMSG((_T("[TSP] UncalX = %d, UncalY = %d\r\n"), *pUncalX,*pUncalY));
}
else
{
*pTipState = TouchSampleIgnore;
}
}
else
{
TSPERR((_T("[TSP] Unknown State\r\n")));
*pTipState = TouchSampleIgnore;
TSP_SampleStop();
}
// 清除定时器3中断位
// timer3 interrupt status clear
//g_pPWMReg->TINT_CSTAT |= (1<<8); // Do not use OR/AND operation on TINTC_CSTAT
g_pPWMReg->TINT_CSTAT = TINT_CSTAT_INTMASK(g_pPWMReg->TINT_CSTAT) | TIMER3_PENDING_CLEAR;
// 结束定时器3中断
InterruptDone(gIntrTouchChanged); // Not Handled in MDD
}
//TSPMSG((_T("[TSP] --DdsiTouchPanelGetPoint()\r\n")));
}
这段代码的分析,要通过两个中断源以及事件一起理解。
我们先看MDD层是如何调用这个函数的。
DdsiTouchPanelGetPoint( &SampleFlags, &RawX, &RawY );
在MDD层中,通过上面的调用方式,从DdsiTouchPanelGetPoint()函数中回传了三个参数,第一个用于表明触摸正处于的状态,RawX,RawY是调用的函数从TP触摸点取样得到的点的数据,这里的数据还是原始的ADC采样的数据,即直接从ADC转换的寄存器里面得到的,并没有对该数据进行处理。
先看看SampleFlags这个变量,一起有如下几种状态:
TouchSampleValidFlag = 0x01, //@EMEM The sample is valid.
TouchSampleDownFlag = 0x02, //@EMEM The finger/stylus is down.
TouchSampleIsCalibratedFlag = 0x04, //@EMEM The XY data has already been calibrated.
TouchSamplePreviousDownFlag = 0x08, //@EMEM The state of the previous valid sample.
TouchSampleIgnore = 0x10, //@EMEM Ignore this sample.
TouchSampleMouse = 0x40000000 // reserved
TouchSampleValidFlag标志位表示已经获得TP采样事件,准备开始采样;
TouchSampleDownFlag标志位表示检测到TP已经按下去;
TouchSampleIsCalibratedFlag标志位表示TP已经校准,没有必要再校准了;
TouchSamplePreviousDownFlag标志位是用于校准计数用的,每校准一次要采样五组数据;
TouchSampleIgnore标志位表示触摸失败,采样数据无效;
在这里,第一个大的if里面,是检测到了触摸屏中断,然后判断TP是否按下,按下则开起定时器3,用于定时产生中断,这时会结束触摸屏中断,中断转交给定时器。在大的else中,就是定时器中断所需执行的代码了。这里并不是第一个if语句判断有效,后面的else就不执行了,因为触摸屏中断结束后,定时器中断会定时的继续产生中断,从而又会继续判断前面的if标志位,这样就会执行到else语句中来。在else语句中,会调用TSP_GetXY(&TmpX, &TmpY)函数,用于采样TP按下的点。
static BOOL
TSP_GetXY(int *px, int *py)
{
int i,j,k;
int temp;
int x[TSP_SAMPLE_NUM], y[TSP_SAMPLE_NUM];
int dx, dy;
EnterCriticalSection(&g_csTouchADC);
//一起采样8次坐标值
for (i = 0; i < TSP_SAMPLE_NUM; i++)
{
g_pADCReg->ADCTSC = ADCTSC_AUTO_ADC; // Auto Conversion
g_pADCReg->ADCCON |= ENABLE_START_EN; // ADC Conversion Start
while (g_pADCReg->ADCCON & ENABLE_START_EN)
{ // Wait for Start Bit Cleared
Sleep(1);
}
while (!(g_pADCReg->ADCCON & ECFLG_END))
{ // Wait for ADC Conversion Ended
Sleep(1);
}
// 从ADCDAT0,ADCDAT1中读取转换的数据
x[i] = D_XPDATA_MASK(g_pADCReg->ADCDAT0);
y[i] = D_YPDATA_MASK(g_pADCReg->ADCDAT1);
}
LeaveCriticalSection(&g_csTouchADC);
// x[i],y[i]从小到大排序
for (j = 0; j < TSP_SAMPLE_NUM -1; ++j)
{
for (k = j+1; k < TSP_SAMPLE_NUM; ++k)
{
if(x[j]>x[k])
{
temp = x[j];
x[j]=x[k];
x[k]=temp;
}
if(y[j]>y[k])
{
temp = y[j];
y[j]=y[k];
y[k]=temp;
}
}
}
#ifdef DETAIL_SAMPLING
// 8 samples Interpolation (weighted 4 samples)
// 8次采样时最小一位,最大两位为无效数据
*px = (x[2] + ((x[3]+x[4])<<1) + (x[3]+x[4]) + x[5]);
*py = (y[2] + ((y[3]+y[4])<<1) + (y[3]+y[4]) + y[5]);
// 求8次采样的平均值
if ((*px & 0x7) > 3) *px = (*px>>3) + 1;
else *px = *px>>3;
if ((*py & 0x7) > 3) *py = (*py>>3) + 1;
else *py = *py>>3;
//求出有效值的最大偏差
dx = x[5] - x[2];
dy = y[5] - y[2];
#else
// 2 samples average
//求两次采样的平均值
*px = (x[1] + x[2] + 1)>>1;
*py = (y[1] + y[2] + 1)>>1;
//求出有效值的最大偏差
dx = x[2] - x[1];
dy = y[2] - y[1];
#endif
// 有效值的最大偏差大于40时,返回失败
if ((dx > TSP_INVALIDLIMIT) || (dy > TSP_INVALIDLIMIT))
{
return FALSE;
}
else
{
return TRUE;
}
}
这里用到了一组简单的数学算法,详细见程序的注释。ADC采样一起采样8次,舍掉最小值,最大值,再加权求平均,最终得到一组TP采样值。
采样的数据会继续过滤一次,通过调用Touch_Pen_Filtering(&TmpX, &TmpY)函数实现。具体代码如下:
static BOOL
Touch_Pen_Filtering(INT *px, INT *py)
{
#ifdef NEW_FILTER_SCHEME // New Scheme from PJR
BOOL RetVal = TRUE;
// TRUE : Valid pen sample
// FALSE : Invalid pen sample
INT Filter_Margin;
static int count = 0;
static INT x[2], y[2];
INT TmpX, TmpY;
INT dx, dy;
if(*px <0 && *py <0)
{
count = 0;
return FALSE;
}
else
{
count++;
}
if (count > 2) // 只采样两组数据,两次后开始下面的Filter算法
{
// apply filtering rule
count = 2;
// 第一组数据和第三组数据取平均
// average between x,y[0] and *px,y
TmpX = (x[0] + *px)>>1;
TmpY = (y[0] + *py)>>1;
// 求出第二组数据与第一,三组数据的平均值的差值
// difference between x,y[1] and TmpX,Y
dx = (x[1] > TmpX) ? (x[1] - TmpX) : (TmpX - x[1]);
dy = (y[1] > TmpY) ? (y[1] - TmpY) : (TmpY - y[1]);
// Filter_Margin = 第一,二组数据的X,Y坐标的差值 + TSP_FILTER_LIMIT(3)
Filter_Margin = (x[1] > x[0]) ? (x[1]-x[0]) : (x[0]-x[1]); // 求出第一组数据与第二组数据的X坐标的差值
Filter_Margin += (y[1] > y[0]) ? (y[1]-y[0]) : (y[0]-y[1]);
//求出第一组数据与第二组数据的Y坐标的差值并累加到Filter_Margin
Filter_Margin += TSP_FILTER_LIMIT;
// 如果dx,dy大于Filter_Margin,则保存上一组采样数据[第二组],并返回失败
// 这里可以拓展实现触摸屏的滑动
if ((dx > Filter_Margin) || (dy > Filter_Margin)) {
// Invalid pen sample
*px = x[1];
*py = y[1]; // previous valid sample
RetVal = FALSE;
count = 0;
}
else
{//否则保留后两组数据到x[0],y[0]以及x[1],y[1].程序执行到这里,笔针采样数据才有效
// Valid pen sample
x[0] = x[1]; y[0] = y[1];
x[1] = *px; y[1] = *py; // reserve pen samples
RetVal = TRUE;
}
}
else // (count > 2)
{ // till 2 samples, no filtering rule
// 保存前两组采样得到的数据。
// 第一组采样的数据放到x[0],y[0]
// 第二组采样的数据放到x[1],y[1]
x[0] = x[1]; y[0] = y[1];
x[1] = *px; y[1] = *py; // reserve pen samples
RetVal = FALSE; // <- TRUE jylee 2003.03.04
}
return RetVal;
#endif
}
看到这里也许会觉得奇怪,为什么会是count>2呢?
这里是联合定时器3的定时中断实现。只有当采样了两组数据之后,该函数才会返回真,否则采样的数据是不作数的。实际上是一起采样了三组数据,具体算法见程序中注释。经过过滤后的数据再回传给MDD,然后提交。
这里提交的数据其实仍然是直接从ADCDAT寄存器中读出的数据,并没有做相应处理。由于TP本身的非线性,程序为了兼容各种不同的非线性TP,微软通过一组数学算法将这个很复杂的问题轻而易举的通过程序解决了。
3.7 触摸屏校准分析
触摸屏的校准过程如下:
⑴ 调用TouchPanelEnable函数来启动触摸屏采样。
⑵ 调用TouchPanelGetDeviceCaps函数来获得校准触点的个数。
⑶ 去校准点,对于每一个校准点都有一下过程:
I:调用TouchPanelGetDeviceCaps函数来得到校准点的坐标。
II:在上述校准点处画十字,等待用户点击.
III:用户点击十字图形后,调用TouchPanelReadCalibrationPoint函数来获得上述校准点处触摸屏对应的坐标。
⑷ 当取完所需用的校准点,调用TouchPanelSetCalibration函数来计算校准系数A1,B1,C1和A2,B2,C2。