分类: 嵌入式
2009-08-03 21:00:59
文章发表于:2009-08-02 22:55
目录
一.UCGUI的消息处理机制.
二.UCGUI中支持的几种输入设备.
三.UCGUI中的触摸屏校正分析.
四.UCGUI中的图形驱动分析.
一.UCGUI的消息处理机制.
1.UCGUI的消息流转泵, UCGUI的执行路径是单一执行绪的, 并没有专门的消息收集以及消息处理的线程, 它是先收集到消息并马上同步处理,紧接着根据消息引起的屏幕画面变化(窗体移动/销毁/生成/尺寸及Z序变化等), 绘制变化后的屏幕画面, 主要是处理无效窗体的重绘. 在MainTask()这个用户入口点上, 调用WM_Exec1(),就可以让UCGUI的消息流转起来.
2.消息队列问题, UCGUI消息处理不用队列, 而是采取两个变量, 一个变量记载当前消息,一个变量记载先前消息, 比较两者是决定是否要处理该消息, 处理完消息再将当前消息更新到先前消息变量中.这样处理起来,比起采用消息队列, 就省了不知道多少空间了,因为在启用MOSUE类设备时,移动消息是非常多的.不采用消息队列, 也导致了UGGUI中的所有的消息处理都是同步的, 但这对于UCGUI来说也不是什么严重的问题, 并不是必须的功能; 没有消息队列, 则每个消息的处理不能占用大量的时间, 否则严重影响消息处理的灵敏度,造成消息的丢失.但是这些问题都是可以接受的, 因为带来的空间效率太有帮助了.
3.消息多层挂起的问题, 由于是单一执行绪而且是同步处理所有消息, 所以如果在消息处理过程中又导致了再次调用WM_Exec1(), 进入到新的消息LOOP当中, 那么旧的消息LOOP就被永远挂起来了(对于那些芯片中CALL栈有限的情形, 是绝对应该避免的, 因为平白的占用了多层的CALL栈而无法返回),在打开多话框时即产生此问题, 避免的办法很简单:
[1].仅创建对话框,不进入对话框消息LOOP;
[2].将消息LOOP放在MainTask()任务入口中调用.
注意:有朋友会以为会什么不为每一个消息LOOP在多任务系统中开启单独的线程? 虽然UCGUI的消息处理已经加入了互斥机制, 但是UCGUI中各个窗体没有单独队列, 消息都是一些全局变量, 所以开启多个任务没有实质上的意义, 因为一个任务处理完消息时,另外一个任务只能在空转, 因为消息已经处理完毕了.
具体看看WM_Exec1()函数, 使用while(1) WM_Exec1();即可建立消息LOOP.
int WM_Exec1(void){
// Poll PID if necessary
if (WM_pfPollPID) { //检测是否设置了读取pointer input device(touch-screen or mouse).)的接口函数.
WM_pfPollPID(); //比如触摸屏的GUI_TOUCH_Exec()或者PS2的GUI_MOUSE_DRIVER_PS2_OnRx(),但可不设置.
}
if (WM_pfHandlePID) { //处理pointer input device输入的消息.
if (WM_pfHandlePID())
return 1; // We have done something ..
}
if (GUI_PollKeyMsg()) {//处理按键式设备输入的消息.
return 1; // We have done something ...
}
if (WM_IsActive && WM__NumInvalidWindows){
WM_LOCK();
_DrawNext(); //绘制无效窗体.
WM_UNLOCK();
return 1; // We have done something ...
}
return 0; // There was nothing to do ...
}
二.UCGUI中支持的几种输入设备.
以前在多篇文章都有介绍过UCGUI中的消息处理机制, 其中将消息输入设备分成了两类:
1.按键式的键盘类输入设备.
键盘内设备的处理比较简单,UCGUI中也是采用了节约空间的微型方案, 只求可以正常处理设备消息即可; 具体的思路:向外提供一个键盘驱动接口, 让具体实现键盘输入设备驱动的用户, 在自己处理键盘输入驱动处调用此函数, 将得到的硬件键盘值传送给UCGUI, 即完成一次键盘事件,UCGUI就会在消息处理LOOP当中处理按键, 将其发发到UCGUI中的当前焦点窗体.
UCGUI中的键盘消息, 采用一个的全局键盘消息变量以及是否有按键的全局变量,如下所示:
static int _KeyMsgCnt; //是否有按键消息.
static struct {
int Key;
int PressedCnt;
} _KeyMsg; //启用win窗体时,记载当前键盘消息.
static int _Key; //未启用窗体管理时,记载当前键盘消息.
键盘消息的驱动接口如下所示, 要使UCGUI中的键盘驱动起来, 有两种形式:一种是建立一个任务, 以一定的频率去检测并读取键盘;另一种是直接在驱动中读取键盘. 这两种都必须调用下面的UCGUI提供的键盘驱动接口来将从设备上读取到的键盘值传送给UCGUI.
[1].启用win窗体管理时, 使用_KeyMsg作为键盘消息记录变量, 在使用窗体管理模块时, 通过在消息LOOP当中调用GUI_PollKeyMsg()函数检测_KeyMsg, 并将收集到的键盘消息以WM_KEY传送到当前焦点窗体.
void GUI_StoreKeyMsg(int Key, int PressedCnt);//第一个参数为按键值, 第二个参数为该键状态,1为按下,0为弹起.
[2].未启用WIN窗体管理时, 使用_Key作为键盘消息记录变量, 没有使用窗体管理模块时, 用户在应用中通过调用GUI_WaitKey()或者GUI_GetKey()来读取按键值.
void GUI_StoreKey(int Key);
[注意:按键式输入设备在UCGUI中处理得相当简单, 仅用了一个全局变量而没有用到队列来缓冲键盘输入, 这有可能会造键盘输入的丢失,所以如果要求比严格的情况下, 最好可以自己建立一个数组来缓冲键盘输入值.]
2.滑动式的MOUSE类输入设备.
UCGUI中的滑动类输入设备可以具体分为两类,具体情况如下:
[1].两类设备均相同的使用相同的接口, 来最终向UCGUI传送从设备中读取的硬件消息.
//pointer input device类设备最终都调用这两个接口函数来设置及获取当前的消息.
void GUI_PID_StoreState(const GUI_PID_STATE *pState); //传送给UCGUI从设备层读取的pointer input device消息.
int GUI_PID_GetState(GUI_PID_STATE *pState); //UCGUI中读取当前pointer input device消息.
[2].MOUSE设备,UCGUI提供了PS2串口MOUSE的驱动支持,并建立了一个基础的将MOUSE消息传送给UCGUI的API集,具体不同的MOUSE设备可以调用这套API接口来将接收到的MOUSE消息分发出去.
//基础MOUSE驱动接口.
void GUI_MOUSE_StoreState(const GUI_PID_STATE *pState);
int GUI_MOUSE_GetState(GUI_PID_STATE *pState);
UCGUI中提示了串口的PS2的驱动,具体在GUI_MOUSE_DriverPS2.c文件当中, 参考一下那个驱动就可以很清楚的了解其它类型的MOUSE驱动应该如何写, 最终都是处理与硬件相关的部分后, 调用如下接口来传送消息.
[3].触摸屏设备, 除了读取设备的具体处理方法与Mouse不一样之外, 其收集后消息的传送与处理全部都是一样的, 均会被UCGUI处理成同一杰消息分发出去(WM_TOUCH). 相比起MOUSE设备, UCGUI中对于触摸屏的具体驱动接口稍微复杂一些. UCGUI中的触摸屏驱动须提供四个底层驱动接口函数, 这四个函数在GUI_TOUCH_Exec()中调用, 具体的实现必须由用户根据实际的触摸屏来写驱动.GUI_TOUCH_Exec()的功能就是读取触摸屏坐标, 并且传送到UCGUI当中(调用GUI_TOUCH_StoreStateEx()).
以下为UCGUI触摸屏提供的三层接口.
1.底层触摸屏驱动接口.
//触摸屏读的时候只能Y/X轴分别读, 所以UCGUI提供了两个函数, 用于开启读X轴还是读Y轴, 意即激活读X或者是激活读Y轴.
GUI_TOUCH_X_ActivateX() Prepares measurement for Y-axis.
GUI_TOUCH_X_ActivateY() Prepares measurement for X-axis.
//实际读取X或者是Y轴的坐标.
GUI_TOUCH_X_MeasureX() Returns the X-result of the A/D converter.
GUI_TOUCH_X_MeasureY() Returns the Y-result of the A/D converter
2.中间层触摸屏驱动接口.
void GUI_TOUCH_StoreStateEx(const GUI_PID_STATE *pState);
void GUI_TOUCH_StoreState(int x, int y);
int GUI_TOUCH_GetState(GUI_PID_STATE *pState);
3.触摸屏高层的应用接口
void GUI_TOUCH_Exec(void);//读取触摸屏.
int GUI_TOUCH_Calibrate(int Coord, int Log0, int Log1, int Phys0, int Phys1);//校正触摸屏.
void GUI_TOUCH_SetDefaultCalibration(void); //设置默认的校正参数.
void GUI_TOUCH_GetCalData(int Coord, int* pMin,int* pMax); //获取校正参数.
三. UCGUI中的触摸屏校正分析.
问题: 关于触摸屏校准问题
我用串口打印出来来的触摸屏的坐标是AD转换后的值,还没有转换成LCD的坐标 我的LAD是320*240的,怎么校准呀,希望高手能够指点给我个例子.
深入的解析UCGUI中触摸屏坐标与LCD坐标的转换计算:
1.为什么要进行校正.
传统的鼠标是一种相对定位系统, 只和前一次鼠标的位置坐标有关, 而触摸屏则是一种绝对坐标系统. 通常应用程序中使用的LCD坐标是以像素为单位, 而从触摸屏中读出的是点的物理坐标,其坐标轴的方向、XY值的比例因子、偏移量、缩放因子都与LCD坐标不同, 所以必须将从驱动中读取的触摸屏绝对坐标转换成LCD的坐标, 然后才能正确处理触摸屏消息.
2.UCGUI中如何进行触摸屏校正.
UCGUI中可以用两种方法进行校正配置,一是配置一个宏的值;二是使用运行时校正支持,在触摸屏使用前进行校正,如下:
[1].使用配置宏进行UCGUI中的触摸屏校正.
以下为触摸屏驱动返回的触摸屏坐标系统相对于LCD坐标系统的范围值(可以理解为触摸屏的边界在LCD坐标系统中的坐标),这些值都是可以经过校正计算出来的,一般情况下也可以校正出来后通过如下宏进行默认的配置.如下:
#define GUI_TOUCH_AD_LEFT 30 //Minimum value returned by the A/D converter.
#define GUI_TOUCH_AD_RIGHT 220 //Maximum value returned by the A/D converter.
#define GUI_TOUCH_AD_TOP 30 //Minimum value returned by the A/D converter.
#define GUI_TOUCH_AD_BOTTOM 220 //Maximum value returned by the A/D converter.
[注:简单的可以理解为触摸屏四个边界在屏幕坐标系中的坐标值,触摸屏左上角(30,220),右下角(220,220)]
//触摸屏精度,其意义相当于LCD屏幕之解析度.
#define GUI_TOUCH_XSIZE LCD_XSIZE //Horizontal area covered by touch-screen.
#define GUI_TOUCH_YSIZE LCD_YSIZE //Vertical area covered by touch-screen.
[2].调用函数进行触摸屏运行时校正.
//参数意义依次为:要校正的坐标轴(0为校正x,1为较正y)/lcd点1坐标及点2坐标/触摸屏点1及点2坐标.
int GUI_TOUCH_Calibrate(int Coord, int Log0, int Log1, int Phys0, int Phys1) {
int l0 = 0;
int l1 = (Coord == GUI_COORD_X) ? LCD_XSIZE - 1 : LCD_YSIZE - 1;
if (labs(Phys0 - Phys1) < 20) {
return 1;
}
if (labs(Log0 - Log1) < 20) {
return 1;
}
xyMinMax[Coord].Min = _Log2Phys(l0, Log0, Log1, Phys0, Phys1);
xyMinMax[Coord].Max = _Log2Phys(l1, Log0, Log1, Phys0, Phys1);
return 0;
}
/*********************************************************************
* _Log2Phys
*/
static int _Log2Phys(int l, I32 l0, I32 l1, I32 p0, I32 p1) {
return p0+ ((p1 - p0) * (l - l0)) / (l1 - l0);
}
以上校正计算两个坐标系统偏移值Offset的原理如下,设Offset/K为触摸屏坐标系统相对LCD坐标系统的在X轴的偏移值即转换比率,触摸屏Xp,LCD坐标Xl.则根据待定系数法有:
Xp = Offset + X1*Kx;
亦即:
公式[1]. ffset = Xp + (0-X1)*Kx; //这个公式也就是_Log2Phys()中的求两坐标系统偏移的算法原理.
据此,触摸屏左边界GUI_TOUCH_AD_LEFT就是如此计算出来的,但要注意其右边界GUI_TOUCH_AD_RIGHT,则:
GUI_TOUCH_AD_RIGHT = Offset + LCD_XSIZE * Kx
即:
GUI_TOUCH_AD_RIGHT = Xp + (LCD_XSIZE-X1)*Kx
GUI_TOUCH_AD_LEFT = Xp + (0-X1)*Kx
同理可得:
GUI_TOUCH_AD_TOP = Yp + (0-Yl) * Ky
GUI_TOUCH_AD_BOTTOM = Yp + (LCD_YSIZE-Y1)*Ky
上面基本就讲清了GUI_TOUCH_Calibrate()这个校正函数的计算原理,用户可以调用这个函数进行运行时校正,只须提高一个点相应的两种坐标系统下面的坐标值,即可以计算出触摸屏边界相对于屏幕坐标系统的坐标.
公式[2]. Xl = (Xp - Offset)/Kx //这个公式就是从触摸屏坐标转换成LCD坐标的原理.
这个公式将在第3点中说明.
3.触摸屏坐标与LCD屏幕坐标的转换计算.
[1].UCGUI中使用如下函数来进行转换, 其中xyMinMax数中存放的是触摸屏校正后的触摸屏边界相对屏幕坐标系统的坐标.
static int _AD2X(int adx);//Convert physical value into coordinates.
static int _AD2Y(int ady);//Convert physical value into (logical) coordinates.
static int _AD2X(int adx) {
I32 r = adx - xyMinMax[GUI_COORD_X].Min;
r *= GUI_TOUCH_XSIZE - 1;
return r / (xyMinMax[GUI_COORD_X].Max - xyMinMax[GUI_COORD_X].Min);
}
static int _AD2Y(int ady) {
I32 r = ady - xyMinMax[GUI_COORD_Y].Min;
r *= GUI_TOUCH_YSIZE - 1;
return r/(xyMinMax[GUI_COORD_Y].Max - xyMinMax[GUI_COORD_Y].Min);
}
由第3点推出一个公式[2]:
Xl = (Xp - Offset)/Kx //参见前面第2点所述
可以理解 _AD2X/_AD2Y这两个转换触摸屏坐标到屏幕LCD坐标的转换关系:
[1].xyMinMax[GUI_COORD_X].Min/xyMinMax[GUI_COORD_Y].Min触摸屏坐标相对屏幕LCD坐标系的偏移值OffSet.
[2].(GUI_TOUCH_YSIZE - 1)/(xyMinMax[GUI_COORD_Y].Max - xyMinMax[GUI_COORD_Y].Min)即为转换比率Kx.
四.UCGUI中的图形驱动分析.
相比于上面的触摸屏校正计算,UCGUI中的图形驱动没有什么计算方法问题,须要移值UCGUI时提供相应的图形驱动接口实现即可,但针对多种不同的LCD控制器,因为提供的功能强弱不同,所以差别还是比较大的,而且UCGUI中LCD控制器有大量的配置宏.
本文将对实际的LCD驱动为例来讲解UCGUI中的LCD的配置以及LCD驱动接口,从而让移值的用户能够更加明白移值过程
chinaunix网友2009-09-17 15:48:29
推荐视频会议、视频直播、视频面试、视频招聘、视频监控等视频系统: http://www.eyesom.com/products/var.htm