心到 一切皆到
全部博文(149)
分类: IT职场
2013-08-17 21:33:50
来源:http://www.cnblogs.com/xilentz/archive/2010/05/18/1738710.html
UCGUI的模拟器UCGUISim详解
本文转自感谢原作者(最近发现该网站已经撤销,不知道作者近况如何.想当初,他对我理解ucgui提供了很多支持和帮助呢!)。
联系方式:
一、问题的由来-----模拟器完成了什么
在官方发布的UCGUI的源码包当中,附有很多的示例,源码与示例都打包成一个VC工程,在这个工程中我们可以编译和运行UCGUI的示例,进行UCGUI的图形编程,非常方便,这个工程结构下:在源码包390a-》start文件夹
Application-------UCGUI应用程序目录。
Config-------------UCGUI配制文件目录。
GUI----------------UCGUI源码文件。
Simulation--------模拟器库文件、模拟器头文件目录,主要有GUISim.lib这个提供模拟器的库文件。
System-------------应用程序的接口调用主文件,即调用用户的MainTask函数的main函数。
在这个官方提供的包中,我们进行UCGUI的图形编程时, 大多都是将要写的程序源文件放加到Application目录中, 其中应用程序中必不可少要提供的一个函数是MainTask(), 好奇的朋友会发觉, 我们程序并没创建窗口, 为什么一运行就有窗口界面并有一个LCD显示屏显示出自己的UCGUI程序的运行效果出来, 其实这些我们看不到的代码都是写在GUISim.lib这个库文件当中的,为了了解模拟器具体做了些什么,我通过反编译,还原了官方模拟器的源码,下面详细介绍模拟器的构成[以下均为官方模拟器采用的方法]。
GUISim.lib主要完成以下几件事,具体的模块划分将在文中后面描述:
1. 创建模拟器主窗口,这个窗体包括一些模拟器控制菜单,如打开LOG记录/打开调色板显示窗体/新建LCD模拟器窗体,以及暂停/继续模拟器。
2. 创建一个LCD模拟显示窗口并初始化其显示所需的各种数据结构,LCD窗口的大小由UCGUI中LCDConf.h中配制时指定(LCD_XSIZE/LCD_YSIZE),及其它的诸如每个象素占用多少位(LCD_BITSPERPIXEL),首先分配一块足够大的显存(4M)并初始化这块数据为0,每个象素占用不大于 8位时模拟显示屏幕需要用到8位图,还必须初始化要用到的调色板。
3. 提供操作LCD模拟显示屏幕的几个基本图形函数,UCGUI是一个设计层次非常清晰的图形系统,它将GUI的底层图形功能作为一层向上层提供最基本的图形功能,只有这一层才与具体的硬件相关,这一层包括基本的画点函数/矩形填充函数/调色板初始化函数/颜色索引与RGB的转换函数。
4. 提供上层的LOG记录接口的实现,主要完成UCGUI中调试信息的输出,是否输出调试信息可以由UCGUI中的一个开关设置。
5. 开启一个新的线程,在此线程中调用System\Main.c中的main()函数,此函数中再调用MainTask()函数,这个函数即为我们在模拟器中编程必须提供的一个函数,在单任务情形下UCGUI的程序均写在MainTask函数当中,供模拟器开启的线程调用。这里必须分清楚:主线程创建模拟器主窗口及LCD模拟窗口;UCGUI图形应用程序以新开的另外一个线程运行,这个线程结束时则UCGUI图形应用程序结束。
6. 从LCD模拟器窗口消息函数当中接收KEY消息及MOUSE消息,并通过UCGUI中的KEY及MOUSE接口传送到UCGUI内部以驱动UCGUI事件消息LOOP。
二、进一步入了解-----模拟器的基本实现原理
-----LCD模拟显示器的实现原理。
1. 上文中已经提到了UCGUI是一个设计层次分明的图形系统,具体的图形功能分为一层;再细化图形层,还可细分为两层:层一是最底层的直接实现基本的画点函数/矩形填充函数/调色板初始化函数/颜色索引与RGB的转换函数,这一层与直接的硬件及调色板相关,由模拟器中的LCDSIM.c文件实现;层二是位于上述 层一之上,提供更多更强图形功能的函数,如位图[1位/2位/4位/8位/16位]显示函数/水平垂直画线函数/矩形填充函数/画点函数,这些功能在\LCDDriver\LCDWin.c中完成,这一层当中的画点函数均以宏的形式提供,具体实现由上述更低的上述层一即模拟器的图形驱动来实现。
在WIN环境下,实现这个模拟器比较简单,要做的就是将GUI的结果显示给用户看,即做出一个LCD模拟显示器,将GUI画图的结果呈现在上面,
GUI画图的结果在显存中,所以也就是将显存中的GUI画图数据用位图显示出来,处理这个位图显示时,可以分两种情况:第一种情况是单个象素点占8位及8位以下的情况,此时显示位图需要用到调色板(所以初始化时必须初始化调色板),每个象素点简化处理为占用一个字节(8位以下实际情况下并非如此,实际是几位就占几位),值表示的是该象素点在调色板中的颜色索引,此时将显存中的数据以8位位图来显示处理;第二种情况是单个象素点占用8位以上的情况,这种情况下,每个象素点简化处理为占用4个字节(实际情况占几位就是几位),其值表示的是实际的该点的RGB颜色值,此时将显存中的数据以32位位图来显示处理。
2. 在LCD窗口中,将GUI画图的结果以上述位图形式画到LCD窗体当中并隔一定时间刷新显示,虽然这样做不一定在速度上非常准确实时,但基本上可以满足要求了,只要我们设定一个重画定时器,定时检测在显示中数据是否发生变化,有变化则刷新一次LCD窗口中显示的内容,可以满足要求。
3. 在解决了模拟LCD显示的问题后,还有要弄清的问题是,要显示器的一屏象素的存放位置,也即画图时所读写象素的内存,其实只需开一块足够大的内存来存放一屏LCD的象素,读写象素均在此块内存中,我们可以称此块内存为显存。如LCD宽XSize,高YSize, 显存起始地址为pFix,单个象素点占用8位或8位以下(此时一个象素占1字节),则象素点(x,y)的在显存中的地址即为 x + y * YSize + pFix;如果是单个象素点占8位以上(此时一个象素占四字节),则为(x + y * YSize)*4 + pFix。显存中象素是按行存放的,如果为了查看器(ucguiview)可以同样显示出独立运的模拟器程序的GUI画图结果,那么这个显存必须以内存映象文件的方式来实现多个进程之间内存共享访问支持。
4. 关于一个象素用多少位来表示的问题,其实这个问题与几位位图是一个意思:位图中,8位及8位以下的均会使用一个调色板,原因:用调色板[实际为一维组,表项个数由位图位数决定]可以以颜色索引来表示出一个象素点的RGB颜色,调色板中每一个表项存的是该表项索引对应的RGB颜色值,在位图存储时只须要将该位图对应的调色板数组存起来,那么存单个象素点时仅须要存其对应索引而不需要存4个字节的RGB颜色值,这样节约空间。位图中的数据为调色板中颜色的索引值,没有调色板是无法解析显示此位图的,所以8位及位以下位图对应的BMP文件会多出一个调色板内容;8位以上的位图,其位图数据即为RGB值或索引值(16位时),16位/24位/32位单象素分别占用2/3/4个字节,这里必须注意区别的是,我们这里所说的位图一个象素所占用情况分析,是指位图存储为文件时的占用情况,其中也并没有提到具体一个象素点中R/G/B分别占用多少位,特别对于16位的情况下,RGB占用情况一般分5/6/5或5/5/5,此时R/G/B中可以表示的最大值索引值为64,此时无法表示出0x0~0xff之间255种颜色,所以16位位图中的象素点数据还是直接的颜色索引值,并非直接RGB颜色值,但此时并不须要用调色板来辅助转化此索引值为RGB颜色值,而是通过一个转换算法,不过这样会对速度产生影响,没有使用调色板快,不采用调色板是因为需要太多调色板表项。
[注意:关于位图文件,这时就不多介绍了,介绍大家下载这篇文章看一下,"BMP档案结构及平滑缩放.doc",也可以自己通过WIN下面自带的画板来分析不同位数的位图,总之只要记住一点,要处理显示一个位图文件,必须知道位图文件大小及单个象素占用位数(即位图位数),然后显示时就是取得每个象素点的RGB值,这个RGB颜色值是根据调色板来取得或者由索引根据一定的转换算法取得或者象素点数据本身就表示RGB值]。
5. LCD_BITSPERPIXEL是UCGUI中定义的一个象素用多少位来表示的宏定义,则可以表示的颜色总数为1L<
同理,对于FixedPalette为555时,则R取值最好为(8*0,8*1,8*2,
8*3,...8*31=243)...
同理,对于FixedPalette为565时,则B取值最好为(4*0,4*1,4*2,
4*3,...4*63=252)知道这一点,则对于以下函数的理解就非常容易...
7. 在LCD_BITSPERPIXEL小于等于8时,颜色索引值则无须通过运算来转换,因为它所能表示的颜色数不多于256种,此时我们就会用一个调色板来表示这不多于256种的颜色,取用也非常方便,通过调色板数组就可以取得索引对应的RGB值。对FixedPalette为111时,调色板中只有八种颜色,不须要256个选项, 8种为(0x000000[0],0x0000ff[1],0x00ff00[2],0x00ffff[3], 0xff0000[4],0xff00ff[5],0xffff00[6],0xffffff[7]),即调色板索引为(0-7)其余对于FixedPalette为111时并未用于;初始化调色板时,要先调用LCD_L0_Index2Color(i)将Index转为RGB表示的颜色,再与黑白成比例综合. 这样了就可适应所有实际使用调色板为2项,4项,8项,16项,256项的所有用到调色板的情况。
另外还要提到的是在8位以下的灰度问题,灰度时,R/G/G三值相等,所以最多可以有256级灰度,其中二级灰度/四级灰度/十六级等,都是间隔着取256级灰度中的颜色值,取的方法是平均涵盖。
以上六点是LCD模拟显示器中用到的几点核心要点,要理解LCD模拟显示器,就必然先理解以上几点。
-----实现模拟LCD的内存布局。
[1]、首先是创建一个名为“emWinLCDMap”的大小为0x00400000(4M)的可读可写及其它所有权限的内存映象文件,如果已存在则是打开该内存映象文件, 设其分配所得初始地址为pSMemFix,类型字节。
[2]、pSMemFix+0x20开始,分别存放XSize、YSize、VXSize、VYSize、FixedPalette、BPP、NumColors(分别为LCD水平宽度/竖直高度、LCD虚拟屏水平及竖直大小、调色板模式、单个象素点倍数、可用颜色总数),其计28个字节,这里存入这些信息是为ucguiview查看器使用的,所有的对于查看器要使用的信息均可以在这里存放。
[3]、pSMemFix + 0x100开始,用于存放位图结构信息40(0x28)个字节及1000个字节(256*4)的调色板(最多可用表项为256项)信息,即如下结构:
typedef struct tagBITMAPINFO { // bmi
BITMAPINFOHEADER bmiHeader;
RGBQUAD bmiColors[1];
} BITMAPINFO;
[4]、pSMemFix + 0x1000开始,用作LCD的显存,存放LCD屏幕的象素点,大小总计为4M。
-----实现LCD功能的几个基本函数。
[1]、LCDSIM_SetPixelIndex--------画点.
///////////////////////////////////////////////////////////////////////
//
// 函数名 : LCDSIM_SetPixelIndex
// 功能描述 : 最基本的画点函数...
// 参数 : int x[点x坐标]
// 参数 : int y[点y坐标]
// 参数 : int Index[颜色索引值]
// 返回值 : void
//
///////////////////////////////////////////////////////////////////////
void LCDSIM_SetPixelIndex(int x, int y, int Index)
{
static int preIndex = 0, preLUT = 0, curLUT = 0, curColor = 0;
int pixPos = 0;
char* lptemp = 0;
if(paaPixel == 0) return;
if(BPP <= 8){
pixPos = y * BytesPerLine + x;
lptemp = (char*)paaPixel + pixPos;
*lptemp = Index;
}
else{
// 2005-8-27 13:04:52
lptemp = (char*)pFix + 0x40;
curLUT = *((int*)lptemp);
if(curLUT != preLUT){//改变了调色板时,一定要重新根据索引转颜色...
preLUT = curLUT;
preIndex = -1;
}
if(preIndex != Index){
//转索引为RGB颜色值..
curColor = Convert_Index16IntoIndex32(Index);
}
pixPos = y * BytesPerLine + x * 4;
lptemp = (char*)paaPixel + pixPos;
*(int*)lptemp = curColor;
preIndex = Index;
}
//表明LCD显存数据已有变化,在LCD窗口中的定时器会检测此值以决定是
//否重画窗口,即更新显示LCD屏幕...
ModifyCnt++;
if(pFix != 0){
lptemp = (char*)((char*)pFix + 0x3c);
*((int*)lptemp) = ModifyCnt;
}
}
这个函数是最基本的画点函数,是LCD模拟显示器的最基本与最核心的函数,其余画线、画矩形、画位图等等功能均由以此为基础,画点时指定颜色时是以索引指定的,具体这个索引到RGB颜色的转换分两种情况,前面已经反复的讲过索引到RGB颜色的转换,这里再重复一下Convert_Index16IntoIndex32这个8位以上象素点的索引转颜色,其实在UCGUI中有一个专门的文件中存放的文件都是用于各种情况下的索引转颜色,无论一个象素点占几位,只要用到颜色索引的都必须提供一个索引到颜色之间互转的两个函数,调色板模式下在画点函数中不用再转换,直接从根据索引从调色板中取,但是在实始化调色板时还是用到了索引到RGB的转换来初始化调色板;UCGUI提供的颜色索引到RGB颜色的互转算法只是一种比较适合的算法但并非必须如此,只是一种转换约定方法而已。
这个函数当中有一个小的优化,那就是当与上一次颜色相同时,则不必再进行索引到颜色之间的转换,这一点对于区域性画图很有优势,不过要记住调色板改变后就必须重新进行索引与颜色间的转换。
[paaPixel]-------------LCD显存起始地址。
[BytesPerLine]---------LCD显示屏一行象素之字节数。
[ModifyCnt]------------表示LCD显示是否有更新,每画一个点加1。
[2]、LCDSIM_FillRect--------矩形填充.
//填充一个矩形...
//#define FASTTING 1 //定义是否加速,使用串传送,注意移值性问题...
void LCDSIM_FillRect(int x0, int y0, int x1, int y1, int Index)
{
int step = 1, color = 0;
int line = 0, comlum = 0;
char* lptemp = 0;
if(BPP == 0) return;
if(x1 > XSize) x1 = XSize - 1;
if(y1 > YSize) y1 = YSize - 1;
if(x0 < 0) x1 = 1;
if(y1 < 0) y1 = 1;
if(paaPixel == 0) return;
if(BPP > 8){
step = 4;
color = Convert_Index16IntoIndex32(Index);
}
else{
color = Index;
}
comlum = (x1 - x0);
lptemp = (char*)paaPixel + BytesPerLine * y0 + x0 * step;
//矩形宽度是否为整行...
#ifdef FASTTING
if(comlum == XSize){
comlum = comlum*(y1-y0);
_asm{
mov eax, color
mov ecx, comlum
mov edi, lptemp
}
if(step == 1) __asm rep stosb
else __asm rep stosd
return;
}
#endif
// 2005-8-27 14:35:38 使用串传送指令提高速度...
for(line = y0; line < (y1 - y0); line++){
#ifdef FASTTING
_asm{
mov eax, color
mov ecx, comlum
mov edi, lptemp
}
if(step == 1) __asm rep stosb
else __asm rep stosd
lptemp += BytesPerLine;
#else
// 2005-8-27 14:54:02
for(comlum = x0; comlum < (x1 - x0); comlum++){
lptemp = (char*)paaPixel + BytesPerLine * line + comlum * step;
if(step == 4) *((int*)lptemp) = color;
else *lptemp = color;
}
#endif
}
}
这个函数进行了优化处理,在画矩形时因为是块填充,把以只须进行一次索引到颜色的转换,而且在行填充当中,也是采用串传送,一次填充四字节,如果是全屏填充,那么可以采用一串传送完成任务,传送数据时是直接传送到显存中,矩形填充的效率很重要,调用频率比较高,一定要注意优化,而且在真正的硬件应用当中,也必须进行类似于此的专门的优化处理,这样会大大提高效率。
[3]、Convert_Index16IntoIndex32----转换象素位数大于8位时的颜色索引为RGB颜色值。
当单个象素位数 LCD_BITSPERPIXEL 不大于8位时,直接写索引值到显存中(因为后面采用的位图显示函数会根据指定调色板来显示这些象素点,内部会包含索引到RGB颜色的转换);大于8位时,必须将索引值转换为RGB颜色值后再写入。 其实在我们这个模拟LCD显示屏当中,我们往其中画点的过程就是构造位图数据的过程,但没有BMP文件头、位图信息头,在BMP位图文件中,这些都辅助用于解析显示位图用的,我们这里的LCD调色板是放在另外一块内存中,并已经初始化; 当构造好位图数据后,我们调用WIN的API SetDIBitsToDevice或StretchDIBits来将位图画到LCD窗口中,下面说明一下这两个API:
[SetDIBitsToDevice]----是将设备无关位图中的颜色数据画到与指定的设备场景相关的设备上的指定矩形中,这个函数不会进行缩放,即原矩形与目标矩形大小相同,它有一个参数为位图数据,还有一个参数为位图信息结构及调色板(BITMAPINFO),8位及其以下位图必须指定调色板,其余为指定源矩形与目标矩形及设备场景句柄。
[StretchDIBits]--------除完成以上功能还会对位图进行缩放显示,此种情况即为模拟器中LCD_YMAG、LCD_XMAG不为1的情况,即放大显示。
调用这两个函数之前必须先调用SetStretchBltMode位图映射模式。
[4]、LCDSIM_GetPixelColor----取点RGB颜色值
int LCDSIM_GetPixelColor(int x, int y)
{
int Color = 0;
if(paaPixel == 0) return Color;
if(BPP > 8){
//直接在显示内存中取,其值即为该点RGB值.
Color = *((int*)paaPixel + y * BytesPerLine + x * 4);
}
else if(BPP <= 8){
Color = *((char*)paaPixel + BytesPerLine * y + x * 4);
Color = LCDSIM_Index2Color(LCDSIM_GetPixelIndex(x,y));
}
return Color;
}
获取指定象素点的RGB颜色值,当LCD_BITSPERPIXEL大于8时,此点RGB直接在LCD显存中取即是;当不大于8时,则须根据从LCD显存中取回的索引值再取调色板中RGB值,即下面的函数:转换索引为颜色值。
[5]、LCDSIM_Index2Color------根据索引转换成RGB值。
int LCDSIM_Index2Color(int Index)
{
int getColor = 0;
if(BPP == 0) return getColor;
else if(BPP > 8){
getColor = Convert_Index16IntoIndex32(Index);
}
else if(BPP <= 8){
getColor = *((char*)pBitmapInfo + 0x28 + Index*4) << 16 | *((char*)pBitmapInfo + 0x29 + Index*4) << 8 | *((char*)pBitmapInfo + 0x2a + Index*4);
}
return getColor;
}
[pBitmapInfo]-----------位图信息起始地址,其中位图结构大小0x28.
[pBitmapInfo+0x28]---调色板起始址址。
[6]、Convert_Index16IntoIndex32----转换LCD_BITSPERPIXEL大于8时的颜色索引值为RGB颜色值
LCD_COLOR Convert_Index16IntoIndex32(int Index)
{
// 2005-6-4 12:33:06
//此一句可以底上下面的switch N句,编译器将由FixedPalette预定义值来选定
//具体的RGB颜色与颜色索引间的转换函数。
return INDEX2COLOR(Index);
/* LCD_COLOR convertColor = 0;
switch(FixedPalette){
case 444:
convertColor = LCD_Index2Color_444(Index);
break;
case 555:
convertColor = LCD_Index2Color_555(Index);
break;
case -555:
convertColor = LCD_Index2Color_M555(Index);
break;
case 565:
convertColor = LCD_Index2Color_565(Index);
break;
case -565:
convertColor = LCD_Index2Color_M555(Index);
break;
case -444: //无此项转换...
break;
}
return convertColor;
*/
}
在这个函数中,具体的转换的功能是由GUI\ConvertColor目录下的几个文件中提供的,具体参看UCGUI源码。
[7]、LCDSIM_SetLUTEntry------设置调色板信息。
void LCDSIM_SetLUTEntry(U8 Pos, LCD_COLOR color)
{
char* lptemp = 0;
if(BPP == 0) return;
color=FilterColor(color,LCDSIM_aLCDColorBlack[0],LCDSIM_aLCDColorWhite[0]);
lptemp = (char*)pBitmapInfo + 0x28 + Pos * 4;
*(char*)lptemp++ = (color & 0xff0000) >> 16;
*(char*)lptemp++ = (color & 0xff00) >> 8;
*(char*)lptemp = color & 0xff;
ModifyCnt++;
LUT_ModifyCnt++;
if(pFix != 0){
lptemp = (char*)pFix + 0x3c;
*lptemp = ModifyCnt;
lptemp = (char*)pFix + 0x40;
*((int*)lptemp) = LUT_ModifyCnt;
}
}
[LUT_ModifyCnt]----记录调色信息的更改次数。
[pBitmapInfo]---------位图信息起始地址。
[FilterColor]-----------此函数负责将指定颜色值转换为在背景色与前景色之间的值。转换的方法是(colorWhite & 0xff - colorBlack &0xff) * (color & 0xff) / 0xff; colorWhite为前景色,colorBlack为背景色,取相应的R/G/B位,用前景色减去背景色之值再乘以color的对应R/G/B的值, 所得值再除以255得商即为调整后颜色值,这样做目的是将指定颜色值调整为前景色与背景色之间。
-----模拟器的输入模拟在UCGUI当中有专门的接收输入的接口,有MOUSE、KEY、TOUCH的,分别为GUI_MOUSE_StoreState、GUI_StoreKeyMsg、GUI_TOUCH_StoreState这三个函数,三UCGUI中关于这些消息的处理都比较简易,并没有什么队列,都是接收一个处理一个,这对于简单的嵌入式应用来说已经足够,可以减少内存占用,但有可能造成消息的丢失,MOUSE及KEY消息均来自于LCD窗口的窗口消息处理函数中。
[MOUSE]
[1]、LCDSIM_SetMouseState--------传送MOUSE及TOUCH(触摸屏)消息到UCGUI。
void NotifyMouseState(LCD_tMouseState mouseState)
{
if(mouseState.KeyStat == 0){
GUI_TOUCH_StoreState(-1, -1);
}
else{
GUI_TOUCH_StoreState(mouseState.x, mouseState.y);
}
GUI_MOUSE_StoreState((const GUI_PID_STATE*)&mouseState);
}
MOUSE消息处理中,包括WM_MOUSEMOVE、WM_LBUTTONDOWN、WM_LBUTTONUP这三种,NotifyMouseState的功能就是内存消息至ucgui.
[2]、LCDSIM_GetMouseState--------获取MOUSE消息。
[KEY]
[1]、HandleKeyEvents-------------传送KEY消息到UCGUI。
void HandleKeyEvents(UINT message, WPARAM wParam)
{
int key = 0, keyCount = 0;
switch(message){
case WM_KEYDOWN:
key = VirtKey2Key(wParam);
if(key == 0) key = Keydown2ASCII(wParam);
keyCount = 1;
break;
case WM_KEYUP:
key = VirtKey2Key(wParam);
keyCount = 0;
break;
}
if(key != 0) GUI_StoreKeyMsg(key, keyCount);
}
[VirtKey2Key]----主要处理一些特殊键,如SHIFT、DELTE、BACKSPACE、INSERT、CRTL、ENTER、方向键等。
[Keydown2ASCII]--将键盘虚拟码转换为ASCII码,大小写区别的,在键盘处理当中,如上所说的特殊键是有特别用途的,UCGUI中已重定义这些特殊键所对应的KEY值,可以在GUI.h当中查找GUI_KEY_UP来找到这些特殊键在UCGUI中的KEY值。在传送键盘消息时,这里只在WM_KEYDOWN时处理普通键,WM_KEYUP中并未处理,WM_KEYUP只处理了特殊键。其它人可以根据自己人须求要改写这个传送键盘消息到UCGUI的函数。
HandleKeyEvents在LCD的窗口消息函数中,当LCD窗口有键盘消息时,即传送至UCGUI内部,以驱动UCGUI的键盘处理。
三、拔去见日-----UCGUISim模拟器的模块划分。
在上面介如了模拟器的基本原理,差不多将核心的东西都说出来了,这时简要的说明一下模拟器的几个构成模块。
emWin.c----------创建UCGUISim模器主窗口及LCD显示窗口,处理KEY、MOUSE消息传送,开启、暂停UCGUI程序,输出LOG等。
LCDSIM.c--------实现模拟LCD显示屏。
Branding.c--------显示版权。
GUI_X_SIM.c---实现UCGUI的临界代码锁及实际的LOG输出及延时功能。
具体可以能见源码,这里就不就代码作详细解说了。