基于S3C6410的Touch驱动详解(之二)
2009-12-26 17:40
218 查看
从基于S3C6410的Touch驱动详解(之一)可以看到,在触摸屏驱动程序中DdsiTouchPanelEnable、DdsiTouchPanelDisable和DdsiTouchPanelGetPoint三个DDSI接口函数是驱动实现的关键所在。
这段代码有点难理解,要通过两个中断源以及事件一起理解。 我们先看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 (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按下的点。具体代码如下:
这里用到了一组简单的数学算法,详细见程序的注释。ADC采样一起采样8次,舍掉最小值,最大值,再加权求平均,最终得到一组TP采样值,回传。
采样的数据会继续过滤一次,通过调用Touch_Pen_Filtering(&TmpX, &TmpY)函数实现。具体代码如下:
看到这里也许会觉得奇怪,为什么会是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中可以看到。
// 对触摸屏数据的采样,采样的数据以*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, //@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 (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中可以看到。
相关文章推荐
- 基于S3C6410的Touch驱动详解(之一)
- 基于S3C6410的Touch驱动详解
- linux驱动由浅入深系列:显示子系统之二(基于android的分析)
- ALSA声卡驱动中的DAPM详解之二:widget-具备路径和电源管理信息的kcontrol
- 详解Linux2.6内核中基于platform机制的驱动模型
- 基于Android系统的多点触摸屏(MultiTouchScreen)驱动
- 基于S3C2450 + WINCE的背光驱动及背光亮度调节应用程序移植详解之驱动篇
- 详解Linux2.6内核中基于platform机制的驱动模型
- 详解Linux2.6内核中基于platform机制的驱动模型
- 详解Linux2.6内核中基于platform机制的驱动模型(续)
- 基于S3C6410&WINCE6.0的LCD驱动详解
- 详解Linux2.6内核中基于platform机制的驱动模型
- WinCE驱动之Touch Panel(开发详解)
- ALSA声卡驱动中的DAPM详解之二:widget-具备路径和电源管理信息的kcontrol
- 详解Linux2.6内核中基于platform机制的驱动模型
- 详解Linux2.6内核中基于platform机制的驱动模型 (经典)
- 详解Linux2.6内核中基于platform机制的驱动模型
- 基于S3C6410的KT0801A的驱动
- 【嵌入式Linux学习七步曲之第四篇 Linux内核移植】详解Linux2.6内核中基于platform机制的驱动模型
- 基于s3c6410的74hc595驱动