在Windows CE操作系统中触摸屏驱动是一种分层驱动。其驱动模型如图1所示。上层是模型设备驱动程序(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。
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()代码如下:
BOOL TouchPanelEnable( PFN_TOUCH_PANEL_CALLBACK pfnCallback ) { BOOL ReturnValue; //
// Do the 'attach' code. Normally, this would have been
// done in the ThreadAttach block, but this driver is set
// up to be statically linked to GWE, in which case none of
// the DLL related calls would even be invoked.
//
//创建事件hTouchPanelEvent和hCalibrationSampleAvailable
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 ); // SYSINTR_NOP是什么意思?
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()代码如下
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 ) { // Set the previous down state for our use, since the pdd may not
// have preserved it.
// 之前有校准笔针按下时,会将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.
// SampleFlags的TouchSamplePreviousDownFlag位为0,表示还没有校准笔针按下
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; CalibrationSampleCount++;//校准次数累加。需校准5次
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 ) { CalibrationState = CalibrationValid; lCalibrationXCoord = CX;//保存校验数据到lCalibrationXCoord,lCalibrationYCoord
lCalibrationYCoord = CY; SetEvent(hCalibrationSampleAvailable); } } LeaveCriticalSection( &csMutex ); } else //在正常状态下,校准数据,并检查校准后数据的有效性;
{ pfnCallback = v_pfnPointCallback; if ( pfnCallback != NULL ) { if( SampleFlags & TouchSampleIsCalibratedFlag ) { // Sample already calibrated by PDD
// XY坐标植已经校准过,直接读取采样的值。
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 )// 最小坐标值为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 // 调用由GWES传入的回调函数,提交位置信息和状态信息???????????????
(pfnCallback)( SampleFlags, CalX, CalY);//这一句是什么意思???
} 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;//ADCTSC设置为等待抬起模式
*pTipState |= TouchSampleIgnore; *pUncalX = PrevX; *pUncalY = PrevY; //added by lqm.must be recovered later.
//RETAILMSG(1,(_T("[gIntrTouch] *pUncalX = %d, *pUncalY = %d\n"),*pUncalX,*pUncalY));
*pTipState |= TouchSampleDownFlag; TSP_SampleStart(); //笔针按下后会开启定时器
} g_pADCReg->ADCCLRWK = CLEAR_ADCWK_INT; // 通知触摸中断已经完成
InterruptDone(gIntrTouch); // Not handled in MDD
} // 当触摸屏中断产生时,同时会打开一个定时器,当笔针未松开时定时触发同一个
// 事件通知这个工作线程继续采集数据,直到触摸笔抬起后关闭该定时器。
// 故上面的if虽然执行了,也会继续执行后面的else。
// 处理定时器中断.触摸笔按下后,定时器被打开,
// 定时器将定时产生中断gIntrTouchChanged,并触发事件,直到触摸笔抬起为止。
else // 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组数据并取平均
{ #ifdef NEW_FILTER_SCHEME if(Touch_Pen_Filtering(&TmpX, &TmpY))// 过滤采集的数据.这里通过定时器实现3次数据采集
#else if(Touch_Pen_Filtering_Legacy(&TmpX, &TmpY)) #endif { *pTipState = TouchSampleValidFlag | TouchSampleDownFlag; *pTipState &= ~TouchSampleIgnore;//数据过滤成功,清掉TouchSampleIgnore标识
} else // Invalid touch pen.数据过滤不成功,则返回TouchSampleIgnore
{ // *pTipState = TouchSampleValidFlag;
// *pTipState |= TouchSampleIgnore;
*pTipState = TouchSampleIgnore; } // 将采样的笔针坐标存放起来
*pUncalX = PrevX = TmpX; *pUncalY = PrevY = TmpY; g_pADCReg->ADCTSC = ADCTSC_WAIT_PENUP;//等待笔针抬起
} else { *pTipState = TouchSampleIgnore; } } else { RETAILMSG(TSP_ZONE_ERROR,(_T("[TSP] Unknown State\r\n"))); *pTipState = TouchSampleIgnore; TSP_SampleStop(); } // timer3 interrupt status clear, Do not use OR/AND operation on TINTC_CSTAT directly
// 清除定时器3中断位???
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, The sample is valid.
TouchSampleDownFlag = 0x02, The finger/stylus is down.
TouchSampleIsCalibratedFlag = 0x04, The XY data has already been calibrated.
TouchSamplePreviousDownFlag = 0x08, The state of the previous valid sample.
TouchSampleIgnore = 0x10, Ignore this sample.
TouchSampleMouse = 0x40000000 // reserved
TouchSampleValidFlag标志位表示已经获得TP采样事件,准备开始采样;
TouchSampleDownFlag标志位表示检测到TP已经按下去;
TouchSampleIsCalibratedFlag标志位表示TP已经校准,没有必要再校准了;
TouchSamplePreviousDownFlag标志位是用于校准计数用的,每校准一次要采样五组数据;
TouchSampleIgnore标志位表示触摸失败,采样数据无效;
上面的程序中,大的框架如下:
if (g_pVIC1Reg->VICRAWINTR & (1<<(PHYIRQ_PENDN-VIC1_BIT_OFFSET)))
{
......
if ((g_pADCReg->ADCDAT0 & D_UPDOWN_UP)
|| (g_pADCReg->ADCDAT1 & D_UPDOWN_UP))
{
.........
TSP_SampleStop();
}
else
{
........
TSP_SampleStart();
}
}
else
{
if ((g_pADCReg->ADCDAT0 & D_UPDOWN_UP)
|| (g_pADCReg->ADCDAT1 & D_UPDOWN_UP))
{
........
TSP_SampleStop();//停止定时器中断
}
else
{
........
}
}
在这里,第一个大的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; int TimeOut = 100; // about 100ms
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)// ADC开始转换后100ms内等待该位清0,超过100ms将提示不能转换。
{ // Wait for Start Bit Cleared
if(TimeOut-- < 0) { RETAILMSG(ZONE_ERROR,(TEXT("ADC cannot start\n")));//ADC cannot start就是在这里打印出来的???
goto ADCfails; } Sleep(1); } TimeOut = 100; // about 100ms
while (!(g_pADCReg->ADCCON & ECFLG_END))//继续等待ADC转换100ms,超时还没转换完则提示不能转换完成。
{ // Wait for ADC Conversion Ended
if(TimeOut-- < 0) { RETAILMSG(ZONE_ERROR,(TEXT("ADC Conversion cannot be done\n"))); goto ADCfails; } Sleep(1); } // 从ADCDAT0,ADCDAT1中读取转换的数据
x[i] = D_XPDATA_MASK(g_pADCReg->ADCDAT0); y[i] = D_YPDATA_MASK(g_pADCReg->ADCDAT1); } ADCfails: 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) { 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; } return RetVal; }
|
看到这里也许会觉得奇怪,为什么会是count>2呢?
这里是联合定时器3的定时中断实现。只有当采样了两组数据之后,该函数才会返回真,否则采样的数据是不作数的。实际上是一起采样了三组数据,具体算法见程序中注释。
经过过滤后的数据再回传给MDD,然后提交。
这里提交的数据其实仍然是直接从ADCDAT寄存器中读出的数据,并没有做相应处理。由于TP本身的非线性,程序为了兼容各种不同的非线性TP,微软通过一组数学算法将这个很复杂的问题轻而易举的通过程序解决了。
在Windows CE中通过在函数DdsiTouchPanelGetDeviceCaps 中设置校准点的个数,在TouchDriverCalibrationPointGet中获取每个校准点的屏幕坐标。常用的校准点数量为5。校准UI将在校准点坐标处相应显示一个十字叉,用户需要精确地在该十字叉位置按下触摸屏,驱动通过TouchPanelReadCalibrationPoint函数读取相应的触摸屏坐标值,然后开始下一个校准点。循环设定的次数后,将采集到的触摸屏坐标值和校准点屏幕坐标送到TouchPanelSetCalibration函数中进行处理。该函数将产生校准基准参数。
TouchPanelSetCalibration函数执行的动作是一套数学算法,具体内容为:
在触摸屏数据与其位置偏移关系且屏幕像素与其位置偏移关系同为线性关系假设情况下,触摸屏返回的位置信息与像素位置信息之间成2D坐标变换关系。则对于触摸屏按下点的触摸屏坐标(Tx,Ty)与其在显示设备位置关系上匹配的点的屏幕坐标(Sx,Sy)之间的转换关系,可以通过下述坐标变换表示:
Sx = A1*Tx + B1*Ty + C1
Sy = A2*Tx + B2*Ty + C2
TouchPanelSetCalibration的具体工作就是通过校准的动作获取的屏幕坐标和触摸屏坐标TouchCoordinate来确定A1,B1,C1和A2, B2, C2。
在开发产品时,针对不同的屏,注册表中默认的校正值通常是不准的,这时就需要得到一组准确的值。这组值怎么样获得呢?很明显,上面的函数DdsiTouchPanelGetPoint()回传的点的数据并不是我们想要的,因为它并没有做任何的处理。我们真正需要的是经过上面的公式换算后得到的值。其实我们只需要校正一遍TP后,Touch驱动程序会自动将校正好的一组正确的值重刷到注册表,我们只需要打开我们手上机器的注册表,查看其键值就行了。具体在localmachine->hardware->touch中可以看到。