emWin GUI的RFID手持终端界面设计*
2018-05-16,
,
(上海海事大学 信息工程学院,上海 201306)
引 言
伴随着嵌入式技术的成熟,人性化图形用户界面在嵌入式系统中的应用愈发广泛[1]。图形用户界面(Graphical User Interface,简称GUI),是指采用图形方式显示的计算机操作用户界面。与命令行界面相比,图形界面对于大部分用户来说更易于接受和操作。
RFID手持终端致力于解决当前物流行业取货、运输、配送等场合出现的问题。同时为了降低设备成本且方便一线员工使用,本文采用Cortex-M4内核的STM32F407ZGT6作为处理器,辅以FreeRTOS操作系统和emWin GUI设计出简单易操作的GUI界面用于人机交互,能够很好地满足实际需要。
1 RFID手持终端简介
随着物联网普及和物流行业的快速发展,RFID标签代替条码标签的趋势愈发明显,为了方便物流运输环节运输员对RFID标签的操作,包括取货环节输入运单号和送货环节读取商品信息,并与物流数据中心保持信息交互,本文设计的RFID终端包括硬件和软件部分。
1.1 硬件组成
考虑到RFID手持终端需要对标签进行读写,与物流数据中心保持信息交互,并有很好的可操作性,故设计一款采用ARM 32位Cortex-M4内核的STM32F407ZGT6作为主控芯片,SIM800C作为数据传输模块,MD3650A-HA 作为射频读写模块,且使用ATK-7’TFT LCD电容触摸屏作为人机交互的RFID手持终端。另外,为了显示快递实时定位信息,给RFID手持终端增加GPS定位功能,以满足快递取货送货最后一公里的真实需求。
写运单号面设计图如图1所示。
图1 写运单号面设计图
1.2 软件组成
综合对RFID终端的使用场景、功能、成本、开发难度等方面考虑,采用轻量级FreeRTOS作为终端操作系统,其具有最小的ROM、RAM和处理开销且能提供任务管理、时间管理等一系列常用功能,其完全免费,源码公开,同时具有可移植、可裁减、调度策略灵活的特点。二次开发简单,可以方便地移植到各种单片机上运行[2-3],能够很好满足终端的使用需求。
2 嵌入式GUI
目前在嵌入式平台上可用的GUI产品比较丰富,但大体上可分为如下几类。
① 低端嵌入式GUI:包括emWin(μCGUI)、RT-Thread/GUI,此类GUI适用于单片机、ARM7、ARM Cortex-M等平台,多与简单的RTOS系统(比如FreeRTOS、μC/OS、RT-Thread)配合使用,开发语言为C语言。
② 中端嵌入式GUI:包括miniGUI、OpenGUI、Microwindows等等,主要适用于ATM7、ARM9、ARM11、ARM Cortex-R等平台,多与嵌入式Linux、μCLinux操作系统配合使用,开发语言为C语言。
③ 高端嵌入式GUI:包括Qt/Embedded、Android、MFC等,主要适用于ARM9、ARM11、ARM Cortex-A等平台,多与嵌入式Linux、Win CE/Win Phone操作系统配合使用,开发语言为C++和JAVA语言。
介于本文设计的物联网终端主核芯片为ARM Cortex-M4架构的STM32F407,且采用的操作系统为FreeRTOS,结合手持终端要求的功耗低、成本低、性价比高等要求,选定SEGGER公司授权给ST(意法半导体)的STemwin,使用ST芯片的用户均可以免费试用。emWin软件架构和功能成熟,通过调用emWin提供的函数接口,可为图形LCD操作任何应用程序提供方便有效且与处理器和LCD控制器无关的图形用户界面(GUI)。而且emWin对ROM和RAM的消耗并不高,这使得其在嵌入式人机交互场合广受欢迎。
2.1 emWin简介
emWin是德国SEGGER公司的一个嵌入式GUI图形库,GUI图形库的概念就好像它是一个平台,我们只需要在这个平台上通过其提供的方法写自己的用户界面应用程序,非常简单、便捷。
emWin设计用于提供高效且独立于处理器和显示控制器的图形用户界面,用于任何使用图形显示进行操作的应用。它与单任务和多任务环境、专用操作系统或任何具有商业RTOS系统均兼容,同时由于其源代码开放,使得开发难度大大降低。
2.2 emWin组成
在emWin界面应用程序中,每个页面都是由三部分组成:
① 控件结构体数组:控件结构体数组包括此页面用到的所有控件。
② 回调函数:每个页面都对应这着一个回调函数,对界面进行的任何操作都会跳转到回调函数里面对对应的消息函数进行处理。
③ 页面创建函数:所有的页面和控件都对应一个句柄,通过句柄和ID可以找到并管理任意的页面和控件。
另外,控件句柄、控件ID 、消息也是非常重要的三个元素。其具体实现如下:
static const GUI_WIDGET_CREATE_INFO _aDialogCreate[] =//控件结构体数组{
控件(ID_0)
控件(ID_1)
……
控件(ID_x)
};
static void _cbDialog(WM_MESSAGE* pMsg){ //回调函数
case WM_PAINT:
//窗口重绘消息,要在Framewin或Window窗口之中显示文字
//或绘制直线、矩形、圆等在这里实现
……
case WM_INIT_DIALOG:
//初始化消息,创建窗口/控件时对其初始化参数进行设置,即
//显示的是未操作时的界面
……
case WM_NOTIFY_PARENT:
//操作触发消息处理(操作屏幕程序会跑到这里),一般功能函
//数放在此消息下,比如点击按键、点击编辑框(任何的操作)所
//实现的功能
……
}
hWin= GUI_CreateDialogBox(控件结构体数组, 回调函数, 父窗口句柄); //页面创建函数,返回该页面句柄,即hWin
3 emWin移植
移植包括对emWin进行参数配置、LCD驱动程序、触摸屏驱动程序编写以及emWin与FreeRTOS操作系统接口修改等。
3.1 emWin配置文件参数
主要是针对实际情况进行相应的参数调整,本文采用LCD规格为800×480,控制器为GT811。
3.1.1 修改GUICong.c
在GUICong.c为emWin分配内存,这里使用外部RAM。代码如下:
//设置EMWIN内存大小
#define GUI_NUMBYTES (500×1024)
#define GUI_BLOCKSIZE 0X80 //块大小
void GUI_X_Config(void){
U32*aMemory=mymalloc(SRAMEX,GUI_NUMBYTES); //从外部RAM中分配内存
GUI_ALLOC_AssignMemory((void*)aMemory, GUI_NUMBYTES); //为存储管理系统分配存储块
GUI_ALLOC_SetAvBlockSize(GUI_BLOCKSIZE); //设置块大小
GUI_SetDefaultFont(GUI_FONT_6X8); //设置字体
}
3.1.2 修改GUICong.h
在GUICong.h中进行功能配置修改,设置如下:
#define GUI_NUM_LAYERS 10 //显示的最大层数
#define GUI_OS (1) //使用操作系统
#define GUI_MAXTASK(5)
//最大可调用Emwin任务数
#define GUI_SUPPORT_TOUCH (1) //支持触摸
#define GUI_DEFAULT_FONT &GUI_Font6x8 //默认字体
#define GUI_SUPPORT_MOUSE (1) //支持鼠标
#define GUI_WINSUPPORT (1) //窗口管理
#define GUI_SUPPORT_MEMDEV (1) //存储设备
#define GUI_SUPPORT_DEVICES (1) //使用设备指针
3.1.3 配置LCDConf.h
根据触摸屏的具体规格来修改LCDConf.h 文件里面的参数,设置如下:
#define XSIZE_PHYS 800 //LCD 每一行的像素数
#define YSIZE_PHYS 480 ///LCD 的行数
#define COLOR_CONVERSION GUICC_M565
//选择RGB565色彩模式
#define TOUCH_AD_TOP160
//按下触摸屏的顶部,写下Y轴模拟输入值
#define TOUCH_AD_BOTTOM3990
//按下触摸屏的底部,写下Y轴模拟输入值
#define TOUCH_AD_LEFT160
//按下触摸屏的左侧,写下X轴模拟输入值
#define TOUCH_AD_RIGHT3990
//按下触摸屏的右侧,写下X轴模拟输入值
#define CT_EXCHG_XY 1 //调转 XY 坐标
#define CT_MAX_TOUCH 5 //电容触摸屏最大支持的点数
3.2 LCD画点程序
液晶驱动最核心的函数是画点函数,可以认为几乎所有的emWin显示功能都是通过最终调用画点函数来实现的[4],本设计画点函数设计如下:
//画点,x,y:坐标,POINT_COLOR:此点颜色值
void LCD_DrawPoint(u16 x,u16 y){
LCD_SetCursor(x,y); //设置光标位置
LCD_WriteRAM_Prepare(); //开始写入GRAM
TFTLCD->LCD_RAM=POINT_COLOR;
}
3.3 LCD触摸屏驱动程序
STM32F407通过FSMC总线与触摸屏模块相连,首先调用GT811_Init()函数用于初始化电容触摸屏驱动芯片GT811,并在其中开启外部中断1,使用GT811_Send_Cfg发送GT811的配置参数。初始化完成后,每当GT811有数据可供读取的时候,CPU就可以在CT_INT信号上接收到一个脉冲信号(100us左右的低电平脉冲),CPU只需要在接收到CT_INT中断后,调用GT811_Scan()函数依次读取GT811的输出信息寄存器,将读取到的数据组织起来,就可以获得最多5个点的触摸数据。
3.4 操作系统相关接口部分的修改
FreeRTOS移植成功后,便可以将emWin与CPU的相关部分交给FreeRTOS处理,即需要提供一些内核接口函数来实现emWin在FreeRTOS上的运行。任务调度相关接口函数实现如下:
//GUI延时函数
void GUI_X_Delay(int period) {
vTaskDelay(period);}
void GUI_X_InitOS(void){
osMutex=xSemaphoreCreateMutex();//创建互斥信号量
vSemaphoreCreateBinary(osSemaphore);//创建二值信号量
}
//等待信号量
void GUI_X_Lock(void){
xSemaphoreTake(osMutex,portMAX_DELAY);
//请求信号量
}
//发送信号量
void GUI_X_Unlock(void){
xSemaphoreGive(osMutex);//释放信号量
}
//返回任务ID
U32 GUI_X_GetTaskId(void){
return((uint32_t)xTaskGetCurrentTaskHandle());
//获取任务ID
}
至此,emWin GUI的移植基本上完成,需要编写测试用例,如果显示效果不理想,再对移植的GUI进行底层配置优化和改进。
4 用户界面设计与功能实现
4.1 设计流程
本项目利用emWin的配套工具GUIBuilder,允许不使用C语言的情况下,编程图形创建对话框。可以对窗口小部件进行创建、拖曳、删除和调整大小等操作,以实现组态、拖放式人机用户界面设计[5]。GUIBuilder操作界面非常简单、一目了然,生成的界面文件是标准C文件,将生成的C文件加到emWin工程中,并在emWin这个图形库平台上运行,即可显示所设计的界面。
初步界面设计完成后,用CodeBlocks进行模拟显示,CodeBlocks是emWin脱机模拟仿真开发环境软件,将emWin GUIBuilder保存的.c文件复制到Codeblocks示例文件的Application文件中,在LCDConf.c里面设置屏幕尺寸,GUIDEMO_Start.c里面创建主窗体,加入中文显示库和代码并在对应按钮消息下添加界面转换函数,便可模拟出GUIBuilder所设计的界面。
通过C语言和API函数对照emWin使用手册可对模拟界面进行完善,以设计出更友善的人机交互界面。
最后将CodeBlocks中的C文件加入μVision工程中,并添加相应的功能实现代码,烧录进单片机之后便可进行实际测试。
物联网终端拟设计主界面和4个子界面,子界面分别为读标签信息、写入运单号、设置、GPRS数据传送。下面就主界面和RFID标签读写等三个界面进行具体分析。
4.2 RFID终端界面设计
GUIBuilder设计界面如图2所示。模拟图如图3所示。
图2 主界面设计图
图3 主界面模拟图
控件结构体数组为:
static const GUI_WIDGET_CREATE_INFO _aDialogCreate[] = {
{FRAMEWIN_CreateIndirect, "TFT LCD", ID_FRAMEWIN_0, 0, 0, 800, 480, 0, 0x64, 0},
{ BUTTON_CreateIndirect, "read data", ID_BUTTON_0, 189, 126, 107, 33, 0, 0x0, 0 },
{ BUTTON_CreateIndirect, "write RFID", ID_BUTTON_1, 187, 240, 110, 35, 0, 0x0, 0 },
{ BUTTON_CreateIndirect, "send data", ID_BUTTON_2, 385, 241, 110, 37, 0, 0x0, 0 },
{ IMAGE_CreateIndirect, "Image", ID_IMAGE_0, 619, 318, 97, 99, 0, 0, 0 },
{ BUTTON_CreateIndirect, "Button", ID_BUTTON_3, 385, 123, 109, 33, 0, 0x0, 0 },
};
主要成员方法见表1。
表1 终端界面成员方法表
4.3 写运单号界面
GUIBuilder设计界面如图4所示。模拟图如图5所示。
图4 写运单号面设计图
图5 写运单号面模拟图
用12个按钮来分别模拟数字0~9、空格和删除键,其中数字和空格的按钮操作触发消息处理函数为:
void Button_trans(char k){
int i;
for(i=0;i<20;i++){
if(trans_number[i] == ' '){
trans_number[i] = k;
break;
}
}
}
删除的按钮操作触发消息处理函数为:
void Button_delete(){
int i;
for(i=0;i<20;i++){
if(trans_number[i] == ' '){
trans_number[i-1] = ' ';
break;
}
}
}
标签注册的按钮操作触发消息处理程序为:
case ID_BUTTON_1: //通过“标签注册”发送的通知
switch(NCode) {
case WM_NOTIFICATION_CLICKED:
//点击按钮
break;
case WM_NOTIFICATION_RELEASED:
//松开按钮
memcpy(Operating_state,Operation_failed,20);
//操作提示信息初始化
hItem = WM_GetDialogItem(pMsg->hWin, ID_EDIT_0);
//当前句柄指向标签订单号编辑框
EDIT_GetText(hItem, trans_number,trans_num_len);
//获取有效长度的编辑框数据
EDIT_SetText(hItem, trans_number);
//将有效长度的数据重新写入编辑框
WRITE_RFID_trans_number(trans_number);
//将订单号写入RFID标签的第一个扇区第1数据块
hItem = WM_GetDialogItem(pMsg->hWin, ID_TEXT_1);
//当前句柄指向操作提示消息框
TEXT_SetText(hItem, Operating_state);
//写入操作提示消息
break;
}
break;
由程序可知,点击标签注册按钮并松开后,系统首先初始化操作提示消息数组,然后将句柄指向标签订单号编辑框,再获取用户输入到编辑框的指定长度的订单号并重新输出到编辑框中,然后将订单号写入RFID标签,如标签写入成功,就在操作提示框显示操作成功提示符。
其中写订单号到RFID标签函数如下:
void WRITE_RFID_trans_number(uint8_tt trans_number[]){
unsigned char buffWrite[50] = {0x01, 0x17, 0xA4, 0x20, 0x01, 0x01, 0x00};
//写块数据指令
buffWrite[4] = 1; //指定数据块
for(i = 0; i < 16 && trans_number[i] !='/0'; i++){
buffWrite[i+6] = trans_number[i];
}
arr_end = buffWrite[1] - 1;
buffWrite[arr_end] = Checksum(buffWrite, buffWrite[1]); //计算校验和
cortexSendStringUart2(buffWrite,buffWrite[1]);
//将指令输出到RFID模块
buffRead = (uint8_tt *)malloc(32);
//申请接收数据空间并初始化
memset(buffRead, 0, 1);
data2_len = USART2_RX_STA & 0X3FFF;
//计算接收到的数据长度
memcpy(buffRead,USART2_RX_BUF,data2_len);
//接收RFID模块返回数据
USART2_RX_STA=0;
//判断是否接收成功,并将操作状态信息写入操作状态数组
...
free(buffRead); //释放申请的空间
4.4 读标签信息界面
GUIBuilder设计界面如图6所示。模拟图如图7所示。
图6 读标签信息界面设计图
图7 读标签信息界面模拟图
读数据按钮操作触发消息处理程序为:
case ID_BUTTON_0: //通过“读数据”按钮发送的通知
switch(NCode) {
case WM_NOTIFICATION_CLICKED: //点击按钮
break;
case WM_NOTIFICATION_RELEASED: //松开按钮
memcpy(Operating_state,
Operation_failed,20);
//操作提示信息初始化
READ_RFID_trans_number(); //读运单号函数
TEXT_SetText(hItem, data_usart1);
...
hItem = WM_GetDialogItem
(pMsg->hWin, ID_TEXT_4);
TEXT_SetText(hItem, Operating_state);
//写入操作提示信息
break;
}
break;
由程序可知,点击标签注册按钮并松开后,系统首先初始化操作提示消息数组,然后执行读运单号函数,将标签运单号读出并显示在屏幕上,然后依次读取标签中其他信息并对应显示,如标签写入成功,就在操作提示框显示操作成功提示符。
5 性能测试与实验分析
将程序烧录进RFID手持终端主核芯片后,对产品功能进行测试。首先,通过按钮操作,成功实现将订单号输入到RFID标签之中。其次,读取出来的运单号与之前写入标签的运单号一致,读出的其它信息则是由信息处理中心写入标签。订单号和定位信息上传功能:通过触控操作显示屏获取并显示GPS信息,并将GPS信息与写入标签的订单号信息打包成数据块通过GPRS发送给远程服务器。
结 语
参考文献
[1] Wang T, Chen H P.Application of XML to Development of GUI in Embedded System[J].Journal of Wuhan Yejin University of Science & Technology, 2005(1).
[2] 刘滨, 王琦, 刘丽丽.嵌入式操作系统FreeRTOS的原理与实现[J].单片机与嵌入式系统应用, 2005(7):8-11.
[3] 朱迪.FreeRTOS实时操作系统任务调度优化的研究与实现[D].南京:南京邮电大学, 2015.
[4] 邵青.基于STM32F4x9的LCD显示设计方案[J].单片机与嵌入式系统应用,2014,14(6):82-83.
[5] 罗富文,吴辉,康伟,等.STM32平台的μC/GUI移植与图形界面设计[J].单片机与嵌入式系统应用,2012,12(8):19-22.
[6] 张峰, 张团善, 吕双庆,等.emWin在LPC1788上的移植与应用[J].电子设计工程, 2015(6):156-160.