前言
做一个小项目时需要实现GUI及相关操作(响应按键)。用的SoC的优点是功耗低,但是受限于硬件能力,之前的SDK里并没有对GUI有很好的支持。后面对GUI的界面外观还有一定的要求,就在网上搜了一下开源GUI的相关资料。最终使用ucGUI实现了GUI操作,这里把相关的学习过程做一个简单小结,所有相关资料上传到了百度云盘(链接:http://pan.baidu.com/s/1qYvv84G 密码:1o4l)
基本流程是选择开源GUI——移植ucGUI——实现GUI元素的显示——显示单页GUI界面——显示含有嵌套关系的GUI系统——优化显示流程。
1.选择开源GUI
用的SoC不支持linux,也不是Cortex-M系列的,最后只找到了两个合适的开源GUI——zlggui和ucGUI。zlggui代码量非常小,实现的gui也十分简单,适合我这种新手入门。把代码简单过了一遍并移植了下,算是加深了LCD上显示汉字、图片的代码实现(移植过程我主要参考了http://www.openedv.com/posts/list/32830.htm内容,zlggui源码见网盘)。
2.移植ucGUI
接着就是自己最终使用的ucGUI了,我用了3.90版本,参考了STM32上移植ucGUI的流程实现了在自己项目中的移植(正点原子上有很多教学贴,我参考的是附件中《ZK_UCGUI移植解析.pdf》文件)。移植过程不算复杂,主要是提供(1)LCD初始化函数(2)LCD画点函数(3)LCD的一些参数配置(分辨率,RGB位数等)。之后使用ucGUI的函数成功显示出一串字符,移植大体完成。
3.实现GUI元素的显示
之后就是熟悉如何使用ucGUI的函数,我是一边看《ucGUI中文手册.pdf》一边用硬件来做实验验证。项目中没有要求实现动画,我就主要学习了文本显示、位图显示、按钮、窗口、抗锯齿的内容,后面也满足了开发要求。
一个GUI操作界面,一般也就包含按钮、文本、背景图等元素,项目里主要就是实现显示图片和显示字符。显示图片就使用emwin提供的图片转换工具,将图片转换为c文件,调用ucGUI接口函数即可(工具见文件夹中emwin.zip)。试验后发现在使用工具转换位图时,选中压缩选项时生成的c文件大小要小很多,不过GUI显示图片花费的时间相对长一些(需要先解压缩得到像素点数据再显示)。
显示字符方面包括汉字和非汉字,一种常用的方法是使用网友提供的小软件导入选择的字体生成对应c文件,之后调用ucGUI的显示字符函数显示即可,相关资料见《定制UCGUI使用的汉字库》文件夹。这种方法在显示字号较大的字体时锯齿明显,不太美观。之后在网上找到另一种方法,利用ucGUI的抗锯齿功能绘制字符,字符文件占用空间相对大些但是显示效果要好很多。我主要借鉴的是http://blog.sina.com.cn/s/blog_74bd70030102v8od.html,用到的工具见《FontCvtST》目录。
4.显示单页GUI界面
显示元素实现后,就是尝试显示单页GUI界面,ucGUI教程里有提供过一个“对话框”控件,控件中可以按需求摆放一些元素(包括按钮、文本、图片等)并实现对这些元素操作(按钮按下、文本切换等)的响应。不过其响应函数相对复杂。自己偷懒采用的是使用窗口管理器方式,自己构造一个针对该界面的响应函数,伪代码如下:
static void _cbWindow1(WM_MESSAGE *pMSG)
{
/*窗口1的响应函数*/
}
static void _cbWindow2(WM_MESSAGE *pMSG)
{
/*窗口2的响应函数*/
}
void gui_demo(MENU_KEY *key)
{
创建各个窗口及其界面元素(按钮、文本等),只执行一次;
switch(*key)
{
/*让窗口响应按键值实现文本切换等效果*/
WM_Invalidate(&window1);
/*根据按键值更新界面指针等*/
}
}
之后在循环任务中循环调用该函数即可实现该界面对于不同按键的响应。
5.显示含有嵌套关系的GUI系统
GUI操作界面一般不止一页,而是多页且多级的。为了实现不同界面的切换,自己使用链表将不同的GUI界面串联起来;同时为了统一循环任务中对界面GUI响应函数的调用形式,使用一个全局指针指向当前工作的界面。给出部分代码如下:
(1)定义一个结构体表示一个GUI页面
struct func_node
{
void (*p_func)(MENU_KEY *key);//上面描述的该界面的响应函数,项目中界面响应按键值来更新
void (*p_clear)(void);//销毁该界面的函数
struct func_node *pre_node;//同一级的该界面的上一个界面
struct func_node *next_node;//同一级的该界面的下一个界面
struct func_node **last_level;//上一级的界面
struct func_node **next_level;//下一级界面
}
(2)将各个界面串联起来
假如有界面A、B、C,B和C为二级界面,且都是A的下一级界面。代码实现思想如下:
struct func_node node_A, node_B, node_C;
struct func_node *pnode, *pnode_level_1, *pnode_level_2;
其中pnode始终指向要显示的界面;pnode_level_1始终指向要显示的一级界面;pnode_level_2始终指向要显示的二级界面。之后初始化各个func_node变量,并利用链表串联起来
pnode_level_1 = &node_A;
pnode_level_2 = &node_B;
pnode_level_1->next_level = &pnode_level_2;
node_B.next_node = &node_C;
node_C.pre_node = &node_B;
p_node = pnode_level_1;
(3)界面的响应函数中更新指向界面的指针
函数void (*p_func)(MENU_KEY *key)就是上一小节中定义的界面响应函数,函数中需要包含根据按键值实时修改p_node、p_node_level_1和p_node_level_2部分。目的在于使它们分别始终指向要显示的界面、要显示的一级界面和要显示的二级界面。当需要更换界面时,如下更新p_node
void (*p_func)(MENU_KEY *key)
{
switch(*key)
{
/*更新指向界面的指针p_node、p_node_level_1和p_node_level_2*/
如果需要切换到下一级菜单
p_node = (struct func_node *)*(pnode->next_level);
如果切换到上一级菜单
p_node = (struct func_node *)*(pnode->last_level);
如果切换同一级的下一页菜单
p_node = p_node->next_node;
如果切换同一级的上一页菜单
p_node = p_node->pre_node;
}
}
(4)界面响应函数的调用
在循环任务中如下执行
while(1)
{
获得按键值key;
p_node->p_func(key);
}
这样处理,每一个界面对应一个单独的界面响应函数,将复杂的响应处理封装在p_func()中;同时在循环任务中统一了调用界面响应函数的接口。
6.优化显示流程
后面感觉GUI刷新速率不够快,在网上搜到一些有关优化显示流程的资料。优化主要包括以下几个方面:
(1)优化ucGUI的画点函数,该函数是被调用的最频率的函数,因此优化效果十分显著。
(2)利用lcd控制芯片的特性优化ucGUI的划线函数
(3)利用ucGUI的窗口缓存,分配较大的内存用于缓存窗口绘制
具体我参考了文件中《ucgui液晶显示深度优化篇.pdf》文档,也上传到了云盘中。
7.其他
http://www.eepw.com.cn/article/272288.htm中介绍了ucGUI绘制GIF动画
http://bbs.armfly.com/read.php?tid=377中有很多关于ucGUI的资料,十分给力
整体来说比较偷懒地使用了ucGUI提供的功能,也满足了项目需求。