转载请注明出处
作者:小马
前段时间移植 6.0 BSP,目前已移植到触摸屏部分了. 移植过程中学到了不少东西. 由其是关于触摸屏这部分, 掌握了很多以前不会的东西. 觉得有必要把这些知识点整理一下.
一 硬件部分
硬件上的原理不是本文的重点,只讲一下大概的原理(主要是我也只知道大概的原理, 毕竟咱不是搞硬件的. 嘻嘻!)
我移植用的这个屏是320*240 的TFT屏, 四线电阻式触屏. 这种触屏的原理是由两个电阻层组成, 一个实现X位置的测量,一个用于Y位置上的测量. 简单来说,就是当用触笔按下屏幕时,两个电阻层接触, 电阻发生变化,然后在X Y方向上产生信号, 这个信号是电压信号, 再经过CPU内部分AD转换为坐标值. 这个原理有点像高中物理课用的滑动电阻,有一个最大上限,滑动到不同的地方,阻值不同. 2410本身集成了touch的控制器,通过简单的配置和读取相关的寄存器,就可以实现触摸屏的操作.
二 驱动部分
Wince下的touch驱动跟很多其它的驱动一样, 是分层的, 有MDD 和PDD两层. MDD层被系统隐藏起来, 一般不用我们来修改. 而我们真正关心的是PDD 层. 也就是要由开发者来修改的这一层.
分析touch驱动时,以我最近刚刚移植到一个基于2410的板子上的6.0的BSP包的触屏驱动为例.到C:\WINCE600\PLATFORM\DEVICEEMULATOR\SRC\DRIVERS\TOUCH下. 找到s3c2410x_touch.cpp文件. 这里面正是PDD层的实现代码. 容易发现这里面的函数分为两类,一类是以TSP开头的函数,一类是以DDSI开头的函数. TSP开头的函数为内部私有的函数,是被DDSI调用的, 而DDSI开头的函数则是对外的接口, 也就是被MDD层的函数调用的接口.
DdsiTouchPanelEnable是首先被调用的一个外部接口, 它的实现可参见源程序, 它主
要做了下面几个事情:
1 通过调用TSP_VirtualAlloc函数为驱动所用的IO,中断等硬件中断分配内存空间.
2 通过调用KernelIoControl向系统申请两个中断,如果申请成功,赋予相应的逻辑中断号. KernelIoControl向底层是调用OEMIoControl函数, OEMIoControl根据KernelIoControl传进来的IOCTL代码,做相应的操作,比如这里, IOCTL是IOCTL_HAL_REQUEST_SYSINTR, 它是向内核申请一个物理中断和逻辑中断的映射.
3 通过调用TSP_PowerOn来初始化中断控制器,ADC寄存器,定时器等, 在TSP_PowerOn的实现中,有几点要说明一下:
ADCDLY 这个值在不同的模式下意义不同, 因为前面通过ADCTSC已经配置为wait for interrupt mode, 所以这个值的意义和你的触笔按下时, 从产生中断信号到开始自动转换X,Y时的时间间隔是相关的,它的单位是ms
v_pPWMregs->TCNTB3 = g_timer3_sampleticks
TCNTB3是timer3的count buffer, 当定时器启动时, 0,这个值以一个设置好的频率递减,直到减到0, 这时会产生一个定时器中断. 这个有什么用呢. 要理解它,得知道触摸屏在中断模式下是如何工作的.
当我们按下的触摸屏时,会产生一个ADC的中断, 同时我们的驱动还会启动一个定时器, 这个定时器触发一个事件做数据采集, 在我们的手或触笔抬起来前,这个定时器不断的触发采集事件,直到它被关闭, 而它什么时候会被关闭呢,就是在触笔的抬起来时. 下面截取的代码很好的说明的这个原理:
if ( (v_pADCregs->ADCDAT0 & (1 << 15)) |(v_pADCregs->ADCDAT1 & (1 << 15)) )
{
bTSP_DownFlag = FALSE;
DEBUGMSG(ZONE_TIPSTATE, (TEXT("up\r\n")));
v_pADCregs->ADCTSC &= 0xff;
*pUncalX = x;
*pUncalY = y;
TSP_SampleStop();
……
}
上面的代码,if判断的正是是否抬起.
而g_timer3_sampleticks的值是这样计算出来的.
g_timer3_freq = (g_s3c2410_pclk / TIMER3_DIVIDER);
g_timer3_sampleticks = (g_timer3_freq / TSP_SAMPLE_RATE_LOW);
TIMER3_DIVIDER 的值是2, TSP_SAMPLE_RATE_LOW的值是100, 由
v_pPWMregs->TCFG1 &= ~(0xf << 12);
v_pPWMregs->TCFG1 |= (0 << 12);
可知定时器1/2分频, 所以,很容易计算出,所设置的定时器是每10ms产生一次定时器中断
而触摸屏中断是在你按下和抬起时产生的.
DdsiTouchPanelGetPoint是采样的主要实现函数,当MDD检测到中断事件发生时,该函数会被调用. 触摸屏的中断是SYSINTR_TOUCH, 而定时器的中断是SYSINTR_TOUCH_CHANGED
该函数用if else分别处理两种中断, 如下:
if (v_pINTregs->SUBSRCPND & (1<<IRQ_SUB_TC)) /* 触摸屏中断*/
{
……
}
else /*定时器中断 */
{
}
DdsiTouchPanelGetPoint函数的实现代码中,调用了两个很重要的函数TSP_TransXY和TSP_GetXY
需要说明的是,这两个函数的实现跟LCD本身的分辨率是没有关系的.
TSP_GetXY用来获到AD采样值,TSP_TransXY把它转化为屏上的坐标. 我移植touch驱动时,遇到过点屏幕上面,下面有反应,或者点左上角,右上角有反应等类似的问题, 都是因为这两个函数没实现好.
先来看TSP_GetXY函数.它的实现如下:
TSP_GetXY(INT *px, INT *py)
{
INT i;
INT xsum, ysum;
INT x, y;
INT dx, dy;
xsum = ysum = 0;
for (i = 0; i < TSP_SAMPLE_NUM; i++)
{
v_pADCregs->ADCTSC = (0 << 8) | /* UD_Sen*/
(1 << 7) | /* YMON 1 (YM = GND)*/
(1 << 6) | /* nYPON 1 (YP Connected AIN[n])*/
(0 << 5) | /* XMON 0 (XM = Z)*/
(1 << 4) | /* nXPON 1 (XP = AIN[7])*/
(1 << 3) | /* Pull Up Enable*/
(1 << 2) | /* Auto ADC Conversion Mode*/
(0 << 0); /* No Operation Mode*/
v_pADCregs->ADCCON |= (1 << 0); /* Start Auto conversion*/
while (v_pADCregs->ADCCON & 0x1); /* check if Enable_start is low*/
while (!(v_pADCregs->ADCCON & (1 << 15))); /* Check ECFLG*/
y = (0x3ff & v_pADCregs->ADCDAT1);
x = (0x3ff & v_pADCregs->ADCDAT0);
xsum += x;
ysum += y;
}
*px = xsum / TSP_SAMPLE_NUM;
*py = ysum / TSP_SAMPLE_NUM;
v_pADCregs->ADCTSC = (1 << 8) | /* UD_Sen*/
(1 << 7) | /* YMON 1 (YM = GND)*/
(1 << 6) | /* nYPON 1 (YP Connected AIN[n])*/
(0 << 5) | /* XMON 0 (XM = Z)*/
(1 << 4) | /* nXPON 1 (XP = AIN[7])*/
(0 << 3) | /* Pull Up Disable*/
(0 << 2) | /* Normal ADC Conversion Mode*/
(3 << 0); /* Waiting Interrupt*/
dx = (*px > x) ? (*px - x) : (x - *px);
dy = (*py > y) ? (*py - y) : (y - *py);
return((dx > TSP_INVALIDLIMIT || dy > TSP_INVALIDLIMIT) ? FALSE : TRUE);
}
关于这个函数有几点要说明.
根据2410的手册, ADCDAT0 保存是X方向上采样的结果, ADCDAT1 保存是Y方向上采样的结果, 所以, 我们看到下面的两行代码
y = (0x3ff & v_pADCregs->ADCDAT1);
x = (0x3ff & v_pADCregs->ADCDAT0);
与上0x3ff, 是因为, ADCDAT寄存器只用了前面 10位来保存AD采样的结果, 而这和2410内部的AD模块只有10位精度是相一致的.所以,AD转换后的最大值不会超过1024-1.
当然上在那种计算方法并不是绝对的 , 根据硬件构造的不同, 比如有可能你x方向的坐标值和采样值成反比,就要按下面的方式计算:
x = 0x3ff - (0x3ff & v_pADCregs->ADCDAT0);
再看TSP_TransXY函数. 我移植的版本的实现如下:
PRIVATE void
TSP_TransXY(INT *px, INT *py)
{
*px = (*px >= TSP_MAXX) ? (TSP_MAXX) : *px;
*py = (*py >= TSP_MAXY) ? (TSP_MAXY) : *py;
*px = (*px - TSP_MINX);
*py = (*py - TSP_MINY);
*px = (*px >= 0) ? *px : 0;
*py = (*py >= 0) ? *py : 0;
*px = *px * TSP_LCDY / (TSP_MAXX - TSP_MINX);
*py = *py * TSP_LCDX / (TSP_MAXY - TSP_MINY);
*px = (*px >= TSP_LCDY)? (TSP_LCDY - 1) : *px;
*py = (*py >= TSP_LCDX)? (TSP_LCDX - 1) : *py;
*px = TSP_LCDY - *px - 1;
*py = TSP_LCDX - *py - 1;
}
这个实现是我在模拟器的实现代码基础上修改的. 这个函数计算X,Y的坐标用的是一个公式,至于这个公式是怎么来的,我就不太清楚了. 只说明一点.
#define TSP_MINX 88
#define TSP_MINY 84
#define TSP_MAXX 952
#define TSP_MAXY 996
上面四个值是定义X+, X-, Y+, Y-四个有效的采样值, 理论上应该是0和1023(10 bit ADC), 但实际肯定有偏差,准确来讲, 换了不同的硬件平台,这四个值应该是要重新测过的. 我就直接沿用原BSP中的值了.