在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 ); }