屏幕操作
现在要解决的问题是,嵌入式系统中经常要使用的并非是完整的汉字库,往往只是需要提供数量有限的汉字供必要的显示功能。例如,一个微波炉的LCD上没有必要提供显示"电子邮件"的功能;一个提供汉字显示功能的空调的LCD上不需要显示一条"短消息",诸如此类。但是一部手机、小灵通则通常需要包括较完整的汉字库。
如果包括的汉字库较完整,那么,由内码计算出汉字字模在库中的偏移是十分简单的:汉字库是按照区位的顺序排列的,前一个字节为该汉字的区号,后一个字节为该字的位号。每一个区记录94个汉字,位号则为该字在该区中的位置。因此,汉字在汉字库中的具体位置计算公式为:94*(区号-1)+位号-1。减1是因为数组是以0为开始而区号位号是以1为开始的。只需乘上一个汉字字模占用的字节数即可,即:(94*(区号-1)+位号-1)*一个汉字字模占用字节数,以16*16点阵字库为例,计算公式则为:(94*(区号-1)+(位号-1))*32。汉字库中从该位置起的32字节信息记录了该字的字模信息。
对于包含较完整汉字库的系统而言,我们可以以上述规则计算字模的位置。但是如果仅仅是提供少量汉字呢?譬如几十至几百个?最好的做法是:
定义宏:
# define EX_FONT_CHAR(value) # define EX_FONT_UNICODE_VAL(value) (value), # define EX_FONT_ANSI_VAL(value) (value), |
typedef struct _wide_unicode_font16x16 { WORD value; /* 内码 */ BYTE data[32]; /* 字模点阵 */ }Unicode; #define CHINESE_CHAR_NUM … /* 汉字数量 */ |
Unicode chinese[CHINESE_CHAR_NUM] = { { EX_FONT_CHAR("业") EX_FONT_UNICODE_VAL(0x4e1a) {0x04, 0x40, 0x04, 0x40, 0x04, 0x40, 0x04, 0x44, 0x44, 0x46, 0x24, 0x4c, 0x24, 0x48, 0x14, 0x50, 0x1c, 0x50, 0x14, 0x60, 0x04, 0x40, 0x04, 0x40, 0x04, 0x44, 0xff, 0xfe, 0x00, 0x00, 0x00, 0x00} }, { EX_FONT_CHAR("中") EX_FONT_UNICODE_VAL(0x4e2d) {0x01, 0x00, 0x01, 0x00, 0x21, 0x08, 0x3f, 0xfc, 0x21, 0x08, 0x21, 0x08, 0x21, 0x08, 0x21, 0x08, 0x21, 0x08, 0x3f, 0xf8, 0x21, 0x08, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00} }, { EX_FONT_CHAR("云") EX_FONT_UNICODE_VAL(0x4e91) {0x00, 0x00, 0x00, 0x30, 0x3f, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0xff, 0xfe, 0x03, 0x00, 0x07, 0x00, 0x06, 0x40, 0x0c, 0x20, 0x18, 0x10, 0x31, 0xf8, 0x7f, 0x0c, 0x20, 0x08, 0x00, 0x00} }, { EX_FONT_CHAR("件") EX_FONT_UNICODE_VAL(0x4ef6) {0x10, 0x40, 0x1a, 0x40, 0x13, 0x40, 0x32, 0x40, 0x23, 0xfc, 0x64, 0x40, 0xa4, 0x40, 0x28, 0x40, 0x2f, 0xfe, 0x20, 0x40, 0x20, 0x40, 0x20, 0x40, 0x20, 0x40, 0x20, 0x40, 0x20, 0x40, 0x20, 0x40} } } |
extern void DisplayTime(…) { static BYTE byHour,byMinute,bySecond; BYTE byNewHour, byNewMinute, byNewSecond; byNewHour = GetSysHour(); byNewMinute = GetSysMinute(); byNewSecond = GetSysSecond(); if(byNewHour!= byHour) { … /* 显示小时 */ byHour = byNewHour; } if(byNewMinute!= byMinute) { … /* 显示分钟 */ byMinute = byNewMinute; } if(byNewSecond!= bySecond) { … /* 显示秒钟 */ bySecond = byNewSecond; } } |
void ShowDot() { static BOOL bShowDot = TRUE; /* 再一次领略static关键字的威力 */ if(bShowDot) { showChar(’:’,xPos,yPos); } else { showChar(’ ’,xPos,yPos); } bShowDot = ! bShowDot; } |
/* 按下OK键 */ void onOkKey() { /* 判断在什么焦点菜单上按下Ok键,调用相应处理函数 */ Switch(currentFocus) { case MENU1: menu1OnOk(); break; case MENU2: menu2OnOk(); break; … } } /* 按下Cancel键 */ void onCancelKey() { /* 判断在什么焦点菜单上按下Cancel键,调用相应处理函数 */ Switch(currentFocus) { case MENU1: menu1OnCancel(); break; case MENU2: menu2OnCancel(); break; … } } |
/* 将菜单的属性和操作"封装"在一起 */ typedef struct tagSysMenu { char *text; /* 菜单的文本 */ BYTE xPos; /* 菜单在LCD上的x坐标 */ BYTE yPos; /* 菜单在LCD上的y坐标 */ void (*onOkFun)(); /* 在该菜单上按下ok键的处理函数指针 */ void (*onCancelFun)(); /* 在该菜单上按下cancel键的处理函数指针 */ }SysMenu, *LPSysMenu; |
static SysMenu menu[MENU_NUM] = { { "menu1", 0, 48, menu1OnOk, menu1OnCancel } , { " menu2", 7, 48, menu2OnOk, menu2OnCancel } , { " menu3", 7, 48, menu3OnOk, menu3OnCancel } , { " menu4", 7, 48, menu4OnOk, menu4OnCancel } … }; |
/* 按下OK键 */ void onOkKey() { menu[currentFocusMenu].onOkFun(); } /* 按下Cancel键 */ void onCancelKey() { menu[currentFocusMenu].onCancelFun(); } |
图2 经典的Hello,World! |
/****************************************** /* 函数名称: MessageBox /* 功能说明: 弹出式对话框,显示提醒用户的信息 /* 参数说明: lpStr --- 提醒用户的字符串输出信息 /* TYPE --- 输出格式(ID_OK = 0, ID_OKCANCEL = 1) /* 返回值: 返回对话框接收的键值,只有两种 KEY_OK, KEY_CANCEL /****************************************** typedef enum TYPE { ID_OK,ID_OKCANCEL }MSG_TYPE; extern BYTE MessageBox(LPBYTE lpStr, BYTE TYPE) { BYTE keyValue = -1; ClearScreen(); /* 清除屏幕 */ DisplayString(xPos,yPos,lpStr,TRUE); /* 显示字符串 */ /* 根据对话框类型决定是否显示确定、取消 */ switch (TYPE) { case ID_OK: DisplayString(13,yPos+High+1, " 确定 ", 0); break; case ID_OKCANCEL: DisplayString(8, yPos+High+1, " 确定 ", 0); DisplayString(17,yPos+High+1, " 取消 ", 0); break; default: break; } DrawRect(0, 0, 239, yPos+High+16+4); /* 绘制外框 */ /* MessageBox是模式对话框,阻塞运行,等待按键 */ while( (keyValue != KEY_OK) || (keyValue != KEY_CANCEL) ) { keyValue = getSysKey(); } /* 返回按键类型 */ if(keyValue== KEY_OK) { return ID_OK; } else { return ID_CANCEL; } } |
键盘操作
功能键的问题在于,用户界面并非固定的,用户功能键的选择将使屏幕画面处于不同的显示状态下。例如,主画面如图1:
/* 将窗口元素、消息处理函数封装在窗口中 */ struct windows { BYTE currentFocus; ELEMENT element[ELEMENT_NUM]; void (*messageFun) (BYTE keyValue); … }; /* 消息处理函数 */ void messageFunction(BYTE keyValue) { BYTE i = 0; /* 获得焦点元素 */ while ( (element .ID!= currentFocus)&& (i < ELEMENT_NUM) ) { i++; } /* "消息映射" */ if(i < ELEMENT_NUM) { switch(keyValue) { case OK: element.OnOk(); break; … } } } |
/* 用户数字输入结构体 */ typedef struct tagInputNum { BYTE byNum; /* 接收用户输入赋值 */ BYTE xPos; /* 数字输入在屏幕上的显示位置x坐标 */ BYTE yPos; /* 数字输入在屏幕上的显示位置y坐标 */ }InputNum, *LPInputNum; |
InputNum inputElement[NUM_LENGTH]; /* 接收用户数字输入的数组 */ /* 数字按键处理函数 */ extern void onNumKey(BYTE num) { if(num==0|| num==1) /* 只接收二进制输入 */ { /* 在屏幕上显示用户输入 */ DrawText(inputElement[currentElementInputPlace].xPos, inputElement[currentElementInputPlace].yPos, "%1d", num); /* 将输入赋值给数组元素 */ inputElement[currentElementInputPlace].byNum = num; /* 焦点及光标右移 */ moveToRight(); } } |
/* 从2进制数据位转化为有效数据:XXX */ void convertToXXX() { BYTE i; XXX = 0; for (i = 0; i < NUM_LENGTH; i++) { XXX += inputElement.byNum*power(2, NUM_LENGTH - i - 1); } } |
/* 从有效数据转化为2进制数据位:XXX */ void convertFromXXX() { BYTE i; XXX = 0; for (i = 0; i < NUM_LENGTH; i++) { inputElement.byNum = XXX / power(2, NUM_LENGTH - i - 1) % 2; } } |
性能优化
在C语言中,宏是产生内嵌代码的唯一方法。对于嵌入式系统而言,为了能达到性能要求,宏是一种很好的代替函数的方法。
写一个"标准"宏MIN ,这个宏输入两个参数并返回较小的一个:
错误做法:
#define MIN(A,B) ( A <= B ? A : B ) |
#define MIN(A,B) ((A)<= (B) ? (A) : (B) ) |
least = MIN(*p++, b); |
( (*p++) <= (b) ?(*p++)b) ) |
/* 求1+2+3+….+n的值 */ WORD Addition(BYTE n) { register i,s=0; for(i=1;i<=n;i++) { s=s+i; } return s; } |
/* 把两个输入参数的值相加,结果存放到另外一个全局变量中 */ int result; void Add(long a, long *b) { _asm { MOV AX, a MOV BX, b ADD AX, [BX] MOV result, AX } } |
/* 方法1 */ int i,j; i = 879 / 16; j = 562 % 32; /* 方法2 */ int i,j; i = 879 >> 4; j = 562 - (562 >> 5 << 5); |
#define INT_I2_MASK 0x0040 wTemp = inword(INT_MASK); outword(INT_MASK, wTemp &~INT_I2_MASK); |
#define INT_I2_MASK 0x0040 wTemp = inword(INT_MASK); outword(INT_MASK, wTemp | INT_I2_MASK); |
#define INT_I2_MASK 0x0040 wTemp = inword(INT_MASK); if(wTemp & INT_I2_MASK) { … /* 该位为1 */ } |
在嵌入式领域目标系统的应用系统日趋复杂,而由于竞争要求产品快速上市,开发技术日新月异,同时硬件发展的日益稳定,而软件故障却日益突出,软件的重要性逐渐引起人们的重视,越来越多的人认识到嵌入式系统的测试势在必行。提到嵌入式软件测试,首先要简单介绍一些软件工程的一些观点,现在,被普遍接受的软件的定义是:软件(software)是计算机系统中与硬件(hardware)相互依存的另一部分,它包括程序(program)、相关数据(data)及其说明文档(document)。其中程序是按照事先设计的功能和性能要求执行的指令序列;数据是是程序能正常操纵信息的数据结构;文档是与程序开发维护和使用有关的各种图文资料。
对于一般商用软件的测试,嵌入式软件测试有其自身的特点和测试困难。
由于嵌入式系统的自身特点,如实时性(Real-timing),内存不丰富,I / O通道少,开发工具昂贵,并且与硬件紧密相关CPU种类繁多,等等。嵌入式软件的开发和测试也就与一般商用软件的开发和测试策略有了很大的不同,可以说嵌入式软件是最难测试的一种软件。
嵌入式软件测试使用有效的测试策略是唯一的出路,它可以使开发的效率最大化,避免目标系统的瓶颈,使用在线仿真器节省昂贵的目标资源。自从出现高级语言,开发环境与最终运行环境通常都是存在差异的,嵌入式系统更是如此。开发环境被认为是主机平台,软件运行环境为目标平台。相应的测试为host-target测试或cross-testing。
讨论嵌入式软件测试首先就会遇到一个问题:为什么不把所有测试都放在目标上进行呢?因为若所有测试都放在目标平台上有很多不利的因素:
1)测试软件,可能会造成与开发者争夺时间的瓶颈,避免它只有提供更多的目标环境。
2)目标环境可能还不可行。
3)比起主机平台环境,目标环境通常是不精密的和不方便的。
4)提供给开发者的目标环境和联合开发环境通常是很昂贵的。
5)开发和测试工作可能会妨碍目标环境已存在持续的应用
从经济上和开发效率上考虑,软件开发周期中尽可能大的比例在主机系统环境中进行, 其中包括测试。
确定host-target测试环境后,开发测试人员又会遇到以下的问题:
1)多少开发人员会卷入测试工作(单元测试,软件集成,系统测试)?
2)多少软件应该测试,测试会花费多长时间?
3)在主机环境和目标环境有哪些软件工具,价格怎样,适合怎样?
4)多少目标环境可以提供给开发者,什么时候?
5)主机和目标机之间的连接怎样?
6)被测软件下载到目标机有多快?
7)使用主机与目标环境之间有什么限制(如软件安全标准)?
任何人或组织进行嵌入式软件的测试都应深入考虑以上问题,结合自身实际情况,选定合理测试策略和方案。
对于嵌入式软件测试或叫交叉测试(cross-test),在测试的各个阶段有着通用的策略:
1.单元测试:
所有单元级测试都可以在主机环境上进行,除非少数情况,特别具体指定了单元测试直接在目标环境进行。最大化在主机环境进行软件测试的比例,通过尽可能小的目标单元访问所有目标指定的界面。
在主机平台上运行测试速度比在目标平台上快的多,当在主机平台完成测试,可以在目标环境上重复作一简单的确认测试,确认测试结果在主机和目标机上没有被他们的不同影响。在目标环境上进行确认测试将确定一些未知的,未预料到的,未说明的主机与目标机的不同。例如,目标编译器可能有bug,但在主机编译器上没有。
2.集成测试:
软件集成也可在主机环境上完成,在主机平台上模拟目标环境运行,当然在目标环境上重复测试也是必须的,在此级别上的确认测试将确定一些环境上的问题,比如内存定位和分配上的一些错误。
在主机环境上的集成测试的使用,依赖于目标系统的具体功能有多少。有些嵌入式系统与目标环境耦合的非常紧密,若在主机环境做集成是不切实际的。一个大型软件的开发可以分几个级别的集成。低级别的软件集成在主机平台上完成有很大优势,越往后的集成越依赖于目标环境。
3.系统测试和确认测试
所有的系统测试和确认测试必须在目标环境下执行。当然在主机上开发和执行系统测试,然后移植到目标环境重复执行是很方便的。对目标系统的依赖性会妨碍将主机环境上的系统测试移植到目标系统上,况且只有少数开发者会卷入系统测试,所以有时放弃在主机环境上执行系统测试可能更方便。
确认测试最终的实施舞台必须在目标环境中,系统的确认必须在真实系统之下测试,而不能在主机环境下模拟。这关系到嵌入式软件的最终使用。
包括恢复测试、安全测试、强度测试、性能测试,已超出了软件测试的范畴,本文暂不讨论。
使用有效的cross-test测试策略可极大的提高嵌入式软件开发测试的水平和效率,当然正确的测试工具使用也是必不可少的:
总结一下,应用以上测试工具进行.Cross-test时的策略:
A) 使用测试工具的插装功能(主机环境)执行静态测试分析,并且为动态覆盖测试准备好一插装好的软件代码。
B) 使用源码在主机环境执行功能测试,修正软件的错误和测试脚本中的错误。
C) 使用插装后的软件代码执行覆盖率测试,添加测试用例或修正软件的错误,保证达到所要求的覆盖率目标。
D) 在目标环境下重复(B),确认软件在目标环境中执行测试的正确性。
E) 若测试需要达到极端的完整性,最好在目标系统上重复(C),确定软件的覆盖率没有改变。
通常在主机环境执行多数的测试,只是在最终确定测试结果和最后的系统测试才移植到目标环境,这样可以避免发生访问目标系统资源上的瓶颈,也可以减少在昂贵资源如在线仿真器上的费用。另外,若目标系统的硬件由于某种原因而不能使用时,最后的确认测试可以推迟直到目标硬件可用,这为嵌入式软件的开发测试提供了弹性。设计软件的可移植性是成功进行cross-test的先决条件,它通常可以提高软件的质量,并且度软件的维护大有益处。以上所提到的测试工具,都可以通过各自的方式提供测试在主机与目标之间的移植,从而使嵌入式软件的测试得以方便的执行。
使用有效的cross-test测试策略可极大的提高嵌入式软件开发测试的水平和效率,提高嵌入式软件的质量。
附录:
1)HOST-TARGET的连接方法简介:
直接连接
通过仿真器连接
使用介质进行间接连接
使用PROM等传递被测软件
测试的交互界面
无交互界面的连接
孙福朋 于 2006.6.18 收藏
文章评论(0条评论)
登录后参与讨论