UCGUI中的中的设备驱动解析

来源:互联网 发布:sqlserver连接oracle 编辑:程序博客网 时间:2024/06/05 02:30
目录

一.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]. Offset = 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驱动接口,从而让移值的用户能够更加明白移值过程.

1. UCGUI在驱动层的接口如下:

void LCD_L0_SetPixelIndex(int x, int y, int PixelIndex);//画点..

unsigned int LCD_L0_GetPixelIndex(int x, int y) ; //取点..

void LCD_L0_XorPixel(int x, int y);//反色画点…

void LCD_L0_DrawHLine  (int x0, int y,  int x1); //水平线…

void LCD_L0_DrawVLine  (int x, int y0,  int y1); //竖直线…

void LCD_L0_FillRect(int x0, int y0, int x1, int y1); //矩形填充…

void LCD_L0_DrawBitmap(int x0, int y0,

                       int xsize, int ysize,

                       int BitsPerPixel,

                       int BytesPerLine,

                       const U8* pData, int Diff,

                       const LCD_PIXELINDEX* pTrans);//位图…

static void  _DrawBitLine1BPP(int x, int y, U8 const*p, int Diff, int xsize, const LCD_PIXELINDEX*pTrans);//单色位图…

static void  _DrawBitLine2BPP(int x, int y, U8 const * p, int Diff, int xsize, const LCD_PIXELINDEX * pTrans); //2色位图…

static void  _DrawBitLine4BPP(int x, int y, U8 const * p, int Diff, int xsize, const LCD_PIXELINDEX * pTrans); //4色位图…

static void  _DrawBitLine8BPP(int x, int y, U8 const * p, int xsize, const LCD_PIXELINDEX * pTrans); //8色位图…

static void  DrawBitLine16BPP(int x, int y, U16 const * p, int xsize, const LCD_PIXELINDEX * pTrans); //16色位图…

 

在写驱动时最基础的是画点函数,其它所有的图形都可以通过调用画点函数完成,这可以是初期的时候;但是到优化阶段的话,则必须进行优化.

 

2.驱动的优化问题

 

[1].水平线/竖直线的优化:

如果是控制器上点是横排的,则可以优化水平线;如果是纵排的,则可以优化竖直线,比如以13xx系列的一个象素占用1 Bit的,总线宽度是8 Bit,则优化水平线一直画八个点;如是是15xx系列的,则可以优化竖直线一次画八个点。优化时有三点原则:

(一)           尽量避免多次的调用函数,使用宏来定义画点的代码.

(二)           尽量根据总线宽度,画多个点.

(三)            如果硬件上有加速功能则使用硬件上的加速.

 

水平线或竖直线优化过后,则矩形填充就可以直接调用他们进行填充,效率就高很多了,不过有些控制器支持块填充的,对于矩形填充有硬件上的支持,可能参见LCD13xx.c这个驱动文件.

 

[2].位图的优化:

位图的优化比较复杂一些,所支持的位图位数越多,要优化的函数就越多;通常的情况下有1/2/4/8/16位这几种.

2. 与LCD控制器读写通信的宏如下:

 

 

void LCD_X_Write00(char c);

void LCD_X_Write01(char c);

char LCD_X_Read00(void);

char LCD_X_Read01(void);

#define LCD_WRITE_A1(Byte) LCD_X_Write01(Byte)

#define LCD_WRITE_A0(Byte) LCD_X_Write00(Byte)

#define LCD_READ_A1(Byte)  Byte = LCD_X_Read01()

#define LCD_READ_A0(Byte)  Byte = LCD_X_Read00()

 

原创粉丝点击