1、开发主机要求及所需工具:
操作系统:Windows 2000, WinXP. 推荐Windows 2000 with SP2 or later.
编 译 器:ADS (Arm Developer Suite) v1.2 [Build 842]
Perl解析:ActivePerl, 推荐使用ActivePerl 5.6.1版本
仿真工具:MTK PC Simulator
跟踪工具:Catcher_L1 v3.10.01,从串口输出调试信息,只做普通UI应用的话,大致不会用到,用PC仿真工具就好了
烧录工具:FlashTool_UI v3.1.05 或 FlashTool_v3.0844.00
其 他:PL2303_Driver_XP2K v204102,这是下载线用到的USB串口驱动,以前机器上装过PL2303驱动的,建议改用这个,否则可能会有蓝屏的问题。
2、开发环境建立过程:
新建一个目录,英文的好了,否则有些工具不认识中文路径,这里以E:\MTK_P1300为例说明,下面我用文字和图片简单说明下安装过程,更详细的安装过程可以看压缩文件“手机开发板C语言开发视频.rar”中的Flash文件“P1300_Build_Env.swf”。
解压文件huayu109_ads1.2.rar到某个目录,比如说解压到“E:\MTK_P1300”,解压完后运行“E:\MTK_P1300\ADS1.2\Setup.exe”,一路按下一步,最后安装License时选定文件“E:\MTK_P1300\ADS1.2\CRACK\ license.dat”,然后接着一路下一步直到安装完。安装完后删掉目录“E:\MTK_P1300\ADS1.2\”,节省磁盘空间。
运行huayu102_ADS12_Patch_Windows.rar压缩文件中的ADS_Patch_Window.exe,这是个自解压的压缩文件,选Unzip解压到上一步ADS的安装目录(缺省是C:\Program Files\ARM\ADSv1_2),解压过程中如果问到是否覆盖,全部选覆盖好了。
直接运行压缩包huayu106_perlzip.rar里面扩展名为msi的安装文件,缺省安装就好,一路Next,呵呵。
上述几步完成后,编译环境基本建立,此时可以开一个dos窗(开始菜单->运行->输入cmd->确定)看一下,安装正常应该能顺利执行以下命令,如下图:
注意看版本号,应该是ADS1.2 [Build 842],不是的话可能没打补丁,或补丁打的位置不对,请看第2步ADS补丁部分的说明并重新打一次,直到版本号对为止,哈哈。
这是perl解释器的运行画面。
注1:上述安装过程会自动创建编译环境所需的环境变量,如果发现编译不正常了,可能是安装了其他编译工具导致冲突,这时建议查看下系统环境变量,把Perl和ADS的安装目录调到最前面,同时从path中去掉可能会产生冲突的编译工具链的路径(例如winavr),如下图所示(我是安装到D盘滴,缺省是C盘,不过我C盘几乎被我塞满了):
如果嫌改path麻烦(因为要用到其他编译工具链的时候还得改回去),还有一种办法,就是改make.bat批处理,具体见注4中相关说明。
注2: 如果安装ADS时,改变了默认安装路径,需要修改源码中的设置,源码目录树解压过程见模拟器编译环节相关介绍。
make\Option.mak
----------------------------------
ifeq ($(strip $(COMPILER)),ADS)
DIR_ARM = c:\progra~1\arm\adsv1_2 # 修改这里
DIR_ARM := $(strip $(DIR_ARM))
DIR_TOOL = $(DIR_ARM)\bin
DIR_ARMLIB = $(DIR_ARM)\lib
DIR_ARMINC = $(DIR_ARM)\include
endif
------------------------------------------------
比如改装到D盘了,这里把红色部分“c”改成“d”就好了
运行压缩文件huayu108_Source Insight3.5.rar中的安装文件安装即可。至于编辑器,这个看个人喜好了,不过Source Inside看代码蛮方便的。
模拟器MTK PC Simulator是用来在PC上仿真调试用的,要安装VC6 SP6(同时要安装Uuicode 静态和动态库,没有装Unicode库的可以用“huayu103_MTK模拟器DLL补丁.zip”中的库,拷到系统目录“%windir%\system32”下即可)。我机器上的VC是有装Unicode库的,没装过Unicode库的如果在模拟器编译或运行中出现问题,建议重装下VC6,安装时勾选Unicode库,并打上相关补丁到SP6。
模拟器能模拟真机的大部分行为,这样能给调试带来很大便利,不需要每次改动都要烧录。
模拟器要从源码编译,解压压缩包“huayu201_P1300_V1.7_Release.rar”里面的文件“HUAYU_P1300_V1.7_Release.rar”到目录“E:\MTK_P1300”,此时会出现目录“E:\MTK_P1300\P1300_V1.7_Release”,这就是P1300的代码目录树了;接着打上最新的补丁,解压压缩包“huayu203_P1300_V1.7_Release_patch.rar”里面的压缩文件“HUAYU_P1300_V1.7_Release_patch.rar”到目录
“E:\MTK_P1300\P1300_V1.7_Release”,解压时选择全覆盖以替换被修改过的文件,打补丁之前对目录树里面的文件进行修改过的,需要手动再加上去。
编译PC仿真器之前需要先把刚才上面得到的目录树build一次,开一个dos窗,盘符和目录转到代码目录树对应的盘符和目录,例如:
E:
cd E:\MTK_P1300\P1300_V1.7_Release\
make new
注3:第一次make的时候要用“make new”,make new的时间比较长(慢的机器搞不好要2小时以上),编译过程中间会出现一些文件找不到的信息,只要编译过程没意外终止,那些信息可以忽略。编译日志文件位置: build\NEOTEL25_06B\log\,要查看编译过程有啥问题就看这个目录下的文件了;生成的bin文件位置: build\NEOTEL25_06B\*.bin,下载烧录就是烧bin文件。
这里顺带介绍下build的命令格式:
make new 清除后重新编译整个项目
make resgen 重新生成资源
make remake 重新编译链接项目
make viewlog xxx 查看模块xxx的编译日志
make 查看可以用make选项帮助信息
注4:如果机器上装了其他gcc编译工具链(比如winavr之类)的话可能要手动改下Path,否则编译会出错,修改Path的方法见注1,也可以修改批处理文件“E:\MTK_P1300\P1300_V1.7_Release\make.bat”,在“perl make2.pl %*”之前加上下面两句:
set PERL5LIB=D:\perl\lib
set PATH=D:\perl\bin;d:\progra~1\arm\adsv1_2\bin;E:\MTK_P1300\P1300_V1.7_Release\tools;c:\windows\system32;c:\windows;c:\windows\system
注意,上面的路径是我机器上的,不是缺省安装路径,要根据自己的实际安装路径做修改。
下面开始编译PC仿真器了,用VC6打开工程文件“E:\MTK_P1300\P1300_V1.7_Release\plutommi\mmi\ PC_Simulator.dsw”,然后开始编译,编译时间比较长,具体看机器配置了,这时又可以干点别的啥了,呵呵。
因为仿真器工程涉及文件较多,编译费时,建议编译过程中电脑上少开窗口,特别是网络类的,如QQ,有时会发现开QQ后,编译过程中VC会挂死,呵呵。有可能是开QQ后防火墙过滤网络数据占用较多系统资源,如果发现VC编译特别慢或干脆挂死,可以重启动一下电脑,只开必要的窗口,然后开始编译。
如果编译过程中发现怪异的问题,如:
incomingstringiddef.h(120) : error C2059: syntax error : 'constant' 或者
error C2065: 'STR_CM_REDIAL' : undeclared identifier 之类
这个时侯你可能需要看一下你VC相关路径的设置顺序,如下图:
把VC原本的头文件路径调整到最前面,我刚开始编译的时候SDK的头文件在前面,编译总是通不过,改一下就好了,库也一样都调整下比较保险,呵呵。
解压文件P1300_Build_Guide.rar到E:\MTK_P1300,然后在文件管理器进入目录“E:\MTK_P1300\P1300_Build_Guide”,接着解压Catcher_L1_v3.10.01.zip到当前目录下的Catcher_L1_v3.10.01子目录,进入子目录Catcher_L1_v3.10.01,发送一个Catcher.exe的快捷方式到桌面。
在需要用到TRACE的时候,在代码中使用函数
void kal_prompt_trace(module_type mod_id, const kal_char *fmt,...); 打印需要查看的信息,使用Catcher跟踪查看。
这个有点类似Linux的Kernel Debug工具,具体使用方法见文档《cather使用手册.doc》,这个文档可在压缩包“huayu101_p1300软件使用工具及开发指南.rar”里面找到。
解压文件“E:\MTK_P1300\P1300_Build_Guide\ FlashTool_UI_exe_v3.1.05.zip”到当前目录,然后进入目录“E:\MTK_P1300\P1300_Build_Guide\FlashTool_v3.1.05”,发送一个Flash_tool.exe的快捷方式到桌面好了。
还有个下载工具FlashTool_v3.0844.00.rar,也是不用安装,解压后直接用的,据说烧录速度比上面那个快。
具体使用方法见文档《flash_tool_MT平台使用教程.doc》,这个文档可在压缩包“huayu101_p1300软件使用工具及开发指南.rar”里面找到。
运行压缩文件
“E:\MTK_P1300\P1300_Build_Guide\ PL2303_Driver_XP2K_v204102.zip”里面的可执行文件进行安装,一路下一步,这里不再赘述。
上面一节我们建立好了开发环境,现在是时候实战一把了,在进行实质性的项目开发之前,我们先来了解下P1300上写程序的一般性做法。从“Hello World”开始吧,有句笑话讲:“会编程就是会Hello World,编程高手就是会写很多个Hello World”,虽然是句笑话,但是细想也是有道理的,麻雀虽小五脏俱全嘛,掌握了框架,剩下的就无非是些编程技巧的问题了。这和建房子一个道理,地基打的深不深,框架建的是否合理,决定房屋总体质量好不好,其他就是装修的功夫了,当然还有水电线路的铺设,这涉及到习惯和技巧了,设计不好会漏水漏电,哈哈。扯远了,下面开始Hello World。
下面为描述上的方便,我们将Hello World称为一个模块。首先建立新的模块目录,之后把模块相关文件都放到这个目录下统一进行管理,新的模块一般放到plutommi\MMI下面,这里我们新建一个目录“HelloWorld”,然后在模块目录“HelloWorld”下再建三个子目录:“Inc”、“Src”和“Res”,分别用来存放模块的头文件、源文件和资源文件,目录结构如下图所示:
接着我们在Inc下创建几个头文件:HelloWorldGprot.h,HelloWorldProt.h, HelloWorldTypes.h, HelloWorldDefs.h, 再在Src下创建一个源文件:HelloWorld.c,关于文件名和函数名等的命名标准每个公司及个人各有不同,统一就好,否则在进行大的项目开发时彼此协同会遇到问题,有时候弄不好光这些问题就能折腾你半天甚至数日或数月,呵呵。
上面几个文件的作用我大致讲下:
HelloWorldGprot.h 模块对外接口,供模块外部调用的函数原型在此申明,模块内部接口就不要放这里了
HelloWorldProt.h 模块内部接口,供模块内部调用的函数原型在此申明
HelloWorldTypes.h 本模块用到的一些常量、自定义数据类型、结构的定义
HelloWorldDefs.h 本模块用到的资源ID定义
HelloWorld.c 模块功能函数的实现部分
头文件HelloWorldGprot.h的内容大致如下:
/*************************************************************************/
#ifndef __HELLOWORLD_GPROT_H__
#define __HELLOWORLD_GPROT_H__
#include "PixtelDataTypes.h"
#include "HelloWorldTypes.h"
extern void mmi_HelloWorld_entry(void);/* 模块入口,理解成dos程序的main好了 */
#endif /* __HELLOWORLD_GPROT_H__ */
/*************************************************************************/
头文件HelloWorldProt.h的内容的大致如下:
/*************************************************************************/
#ifndef __HELLOWORLD_PROT_H__
#define __HELLOWORLD_PROT_H__
#include "HelloWorldGprot.h"
extern void mmi_HelloWorld_entry(void); /* 本模块主界面入口例程 */
extern void mmi_HelloWorld_exit(void); /* 本模块主界面退出例程 */
#endif /* __HELLOWORLD_PROT_H__ */
/*************************************************************************/
其他几个文件的内容因模块功能而异,这里先略过,后面再讲。
紧接着,我们要修改的相关系统文件,使这个模块成为整个项目的一部分,需要修改的系统文件如下:
make\plutommi\plutommi.inc 所有mmi部分的头文件所在目录的相对路径列表
make\plutommi\plutommi.pth 所有mmi部分的源文件所在目录的相对路径列表
make\plutommi\plutommi.lis 所有mmi部分的源文件(相对路径)列表
在上述3个文件里面分别加上我们模块的对应内容,就现在讲的HelloWorld而言,所加内容如下:
make\plutommi\plutommi.inc文件追加一行“plutommi\mmi\HelloWorld\Inc”
make\plutommi\plutommi.pth文件追加一行“plutommi\mmi\HelloWorld\Src”
make\plutommi\plutommi.lis文件追加一行“plutommi\mmi\HelloWorld\Src\HelloWorld.c”
再接着,我们需要增加一个开关,以决定这个模块是否成为最终发布版本的一部分,这实际上是一个编译开关,需要修改系统文件及相关的模块源文件,需要修改的系统文件包括:
plutommi\Customer\CustResource\PLUTO_MMI\MMI_featuresPLUTO.h,这里我们用 名字为“__MMI_HELLOWORLD_ENABLED__”这个开关好了,在这个文件里面追加一行:
#define __MMI_HELLOWORLD_ENABLED__,同时在相关源文件里面用:
#ifdef __MMI_HELLOWORLD_ENABLED__
#endif
来框住相关代码行就OK了,当不打算将这个模块发布的时候,注释掉“#define __MMI_HELLOWORLD_ENABLED__”这一句重新make一次就好了。
下面讲具体实现的部分,为了简化描述,我们暂时先将我们的模块入口挂接到主菜单入口处(后面讲到资源的部分可将入口移到某个新建的子菜单下),修改下文件“plutommi\mmi\mainmenu\mainmenusrc\MainMenu.c”中的goto_main_menu函数,如下所示红色字体部分:
void goto_main_menu(void)
{
#ifdef __MMI_HELLOWORLD_ENABLED__
mmi_HelloWorld_entry();
return;
#else
… // 此处为之前goto_main_menu的代码
#endif
}
同时我们需要在文件MainMenu.c中包含头文件HelloWorldGprot.h,所以再在这个文件的头文件包含代码块(通常是文件的顶部区域)追加这一句:#include "HelloWorldGprot.h"。
下面是HelloWorld.c的内容:
/*************************************************************************/
#include "stdC.h"
#include "MMI_Features.h" /* 编译开关会出现在这个由make update生成的文件里面 */
#include "L4Dr.h"
#include "L4Dr1.h"
#include "AllAppGprot.h"
#include "FrameworkStruct.h"
#include "GlobalConstants.h"
#include "EventsGprot.h"
#include "mmiappfnptrs.h"
#include "HistoryGprot.h"
#include "HelloWorldProt.h"
#include "HelloWorldTypes.h"
#include "HelloWorldDefs.h"
#include "MainMenuDef.h"
#include "wgui_categories.h"
#include "Unicodexdcl.h"
/* 模块入口 */
void mmi_HelloWorld_entry(void)
{
#ifdef __MMI_HELLOWORLD_ENABLED__
/* 强制退出当前屏幕,之后进入到我们的模块了 */
/* 上电缺省是idle屏幕,现进入MAIN_MENU_SCREENID屏 */
/* 注意看第二个参数,这个是当我们模块被强制退出时执行的一些操作 */
EntryNewScreen(MAIN_MENU_SCREENID, mmi_HelloWorld_exit, NULL, NULL);
/* 关掉屏幕顶部的状态条,我们要用整个屏幕 */
entry_full_screen();
/* 擦除当前背景 */
clear_screen();
/* 移动文本输出光标 */
gui_move_text_cursor(50, 100);
/* 设置字体颜色 */
gui_set_text_color(UI_COLOR_RED);
/* 输出文本到显示缓冲, 注意是Unicode编码 */
gui_print_text(L"Hello, World");
/* 刷新屏幕显示,MMI用的是双缓冲绘图方式,而且需要显式刷新 */
gui_BLT_double_buffer(0, 0, UI_device_width - 1, UI_device_height - 1);
/* 注册一个按键处理,右软键弹起时返回到之前被我们强制退出的模块 */
SetKeyHandler(GoBackHistory, KEY_RSK, KEY_EVENT_UP);
#endif
}
/* 模块出口
* 当我们的模块被其他模块强制退出时会执行这个函数,
* 这个函数的常见写法,包括:
* 1、模块已申请资源的释放(如果需要的话),这一步可选
* 2、手动把自己压栈到窗口(实际是整个屏)堆栈里面,
* 便于强制我们退出的模块执行完后重新把我们叫出来
* 不像Window的窗口管理是自动压栈的,Pluto MMI需要手动压栈
* 3、其他一些清理动作
*/
void mmi_HelloWorld_exit(void)
{
#ifdef __MMI_HELLOWORLD_ENABLED__
history currHistory;
S16 nHistory = 0;
currHistory.scrnID = MAIN_MENU_SCREENID;
currHistory.entryFuncPtr = mmi_HelloWorld_entry;
pfnUnicodeStrcpy( (S8*)currHistory.inputBuffer, (S8*)&nHistory);
AddHistory(currHistory);
#endif
}
/*************************************************************************/
编码的部分基本上就是这些了,然后我们开始make,这次要用“make update”,基于上述对相关系统文件的改动,make update会自动将刚才的模块加入到整个项目中去,同时也会更新仿真器VC工程文件,如下图所示,HelloWorld模块的相关文件已经被自动加入到MMI库里面了:
后续的make,如果是不涉及到系统相关文件的修改,用“make remake”就可以了,以节省编译时间,因为make一次太耗时间了。
注意:对于仿真程序的编译,“make update”似乎缺少一些处理,导致可能还需要手工在文件“plutommi\mmi\GlobalSimulatorPathDef”中追加一行,以便模块的头文件能被顺利找到,如下所示:
/I ".\HelloWorld\Inc"
好,现在编译仿真程序开始看下效果,编译 (make update会清除掉仿真程序之前编译的中间文件,这样仿真程序会整个重新编译) 完后运行仿真器程序,如下图所示,点选File->Network Simulator启动仿真,启动后手机模拟器进入Idle画面,点选手机模拟画面上的“菜单”,就进入到我们的Hello World了,而不是之前的主菜单,然后点右按钮,画面回到Idle画面。
至此,Hello World基本完成,这是一个相对完整的模块框架,在此基础之上我们可以逐步开始进入真正有意义的项目开发了。
我们知道,很多时候我们需要应付来自不同国家和地区客户的需求,他们使用的语言编码不一样,但实际上对产品功能的要求是基本相同的;此外即便是同一种语言,客户的个性化需求(例如外观换皮肤)也要求我们有相应手段来处理和产品业务逻辑关系不是很密切的产品表现手段,大多数时候,这些东西涉及到:字符串、图标、图片、菜单、字库、主题、声音等,也就是我们常说的资源,做过多语言版本软件的朋友应该对此深有体会。一般来讲VC使用资源动态库来解决这个问题,那么在MTK平台上一般是怎么解决的呢,我们一起来看下。
1、资源ID
在进入实际编码前,让我们先来看看MTK平台是如何管理资源的。MTK平台大致将资源分为以下几类:字符串、菜单、图片、字库、主题、声音、屏幕,所有资源统一编码管理,对于同一类型的资源,每个资源有全系统唯一的编号,也就是资源ID,字符串资源和其他资源稍有不同,同一个语义的不同语言版本字符串共用同一个字符串资源ID,系统自动根据当前语言设定提取对应语言版本的字串。此外,屏幕是一种平常我们Windows开发中不多见的一种资源,这里我们可以简单的理解成Windows的窗口句柄好了。为了不让系统各个模块的资源ID彼此重叠从而导致混乱,系统提供专门的宏来处理,并将相关的定义统一放到同一个文件里面便于管理。
具体来讲,这个文件是“plutommi\mmi\Inc\MMIDataType.h”,在开始添加实际资源之前,我们需要大致估计一个安全值,这个值代表我们可能用到的资源最大数,注意这个最大数并不是我们用到的所有资源的总数,而是某一类资源,因为不同类型的资源其资源ID是允许重复的,比如说我们总共要用到20个字符串,10张图片,1个菜单项,那么这个最大数是20,一般会再留一些可扩展的空间,就HelloWorld模块而言,最大数取100足够了(当然了,这个数后面还可以再改的)。最大值取好后,我们就可以着手向系统申报我们的资源ID空间了,修改文件“plutommi\mmi\Inc\MMIDataType.h”如下所示红色字体部分,这样系统就预留了100个全系统唯一的资源ID给我们了,注意,是每一种资源都有100个可用的资源ID。
typedef enum
{
……
RESOURCE_BASE_RANGE(MAIN_MENU, 600),
RESOURCE_BASE_RANGE(HELLOWORLD, 100),
……
} RESOURCE_BASE_ENUM;
……
/*************************************************************************
* Main Menu
**************************************************************************/
#define MAIN_MENU_BASE ((U16) RESOURCE_BASE_MAIN_MENU)
#define MAIN_MENU_BASE_MAX((U16) RESOURCE_BASE_MAIN_MENU_END)
RESOURCE_BASE_TABLE_ITEM(MAIN_MENU)
/*************************************************************************
* HelloWorld
**************************************************************************/
#define HELLOWORLD_BASE ((U16) RESOURCE_BASE_HELLOWORLD)
#define HELLOWORLD_BASE_MAX ((U16) RESOURCE_BASE_HELLOWORLD_END)
RESOURCE_BASE_TABLE_ITEM(HELLOWORLD)
……
好,现在我们有自己的资源ID空间了,之前我们有暂借MAIN_MENU的屏幕ID资源,现在用回我们自己的,增加相关定义到HelloWorldDefs.h,现在HelloWorldDefs.h看起是这样:
/************************************************************************/
#ifndef __HELLOWORLD_DEFS_H__
#define __HELLOWORLD_DEFS_H__
typedef enum
{
SCR_HELLOWORLD = HELLOWORLD_BASE + 1,
}SCREENID_LIST_HELLOWORLD;
#endif /* __HELLOWORLD_DEFS_H__ */
/************************************************************************/
接着修改模块入口函数mmi_HelloWorld_entry,见红色字体部分,如下所示:
EntryNewScreen(SCR_HELLOWORLD, mmi_HelloWorld_exit, NULL, NULL);
屏幕ID资源是一种最简单的资源,就是一个数值,定义完就可以使用了,其他资源相对复杂,需要有一个生成的过程,下面我们接着看资源的生成。
2、资源的生成
限于篇幅,这里我们主要讲常用的3种资源:字符串、图片和菜单。首先,我们来看这些资源是如何被生成的,后面我们再讲如何使用这些资源。MTK平台的资源是通过一个叫mtk_resgenerator.exe的程序生成的,这个程序位于目录plutommi\Customer\ResGenerator下,是个临时生成的可执行文件,每次项目相关资源被修改,该程序都要重新编译,实际上这个程序是由一系列需要自有资源的模块根据一定的资源生成规则将各自的资源生成代码组合而成的。资源生成相关的代码统一放在同一个目录下面,因此我们要给自己的模块添加资源,只需要在这个目录下建立自己的资源生成.c文件,并按照指定的生成规则编写代码即可。
具体来讲,这个目录就是plutommi\Customer\CustResource\PLUTO_MMI\Res_MMI。注意,这个目录下不要放其他不相关的.c或.cpp源文件,因为编译脚本会自动将这个目录下的所有源文件作为mtk_resgenerator.exe的一部分来处理。
好,下面开始资源生成相关的编码。我们继续接着在HelloWorld模块上讲好了,首先在上面说的目录下面创建一个文件:Res_HelloWorld.c,这个文件的内容大致框架如下:
/*************************************************************************/
#include "StdC.h"
#ifdef DEVELOPER_BUILD_FIRST_PASS // 注意,后面资源生成相关代码要用这个编译开关框住
#include "PopulateRes.h"
#include "MMI_features.h"
#include "GlobalMenuItems.h"
#include "HelloWorldDefs.h"
void PopulateHelloWorldRes(void)
{
#ifdef __MMI_HELLOWORLD_ENABLED__
…… // 这里是资源生成部分的代码,基本上都是些宏,后面讲到具体资源的时候我们再讲
#endif
}
#endif /* DEVELOPER_BUILD_FIRST_PASS */
/*************************************************************************/
接着,我们还需要修改系统文件plutommi\Customer\ResGenerator\Makefile,以便编译系统能顺利找到我们定义资源ID的头文件"HelloWorldDefs.h",在PLUTO_INC =这一项里面追加一行:-I "../../MMI/HelloWorld/Inc",注意,追加新行的时候别忘了在上一行尾部添上续行符号“\”。
再接着,我们要把我们的资源生成函数“PopulateHelloWorldRes”放到合适的地方调用,这样才能生成我们要的资源,这里我们需要修改另外一个系统.c源文件:“plutommi\mmi\Resource\PopulateRes.c”,需要修改的见红色字体部分,如下所示:
……
extern void PopulateMmiapiRes(void);
extern void PopulateHelloWorldRes(void);
……
void PopulateResData(void)
{
……
#ifdef DOWNLOAD_MCU /* Added, 20071218 */
PRINT_INFORMATION(("Populating MCU Download Resource\n"));
Populate_MCUDownload();
#endif
#ifdef __MMI_HELLOWORLD_ENABLED__
PRINT_INFORMATION(("Populating HelloWorld Resource\n"));
PopulateHelloWorldRes();
#endif
……
}
对于字符串资源的生成,我们还需要修改另外一个系统.c源文件,这个文件是“plutommi\Customer\ResGenerator\readexcel.c”,这个文件是专门用来处理字符串资源的,mtk_resgenerator.exe会调用这个部分来单独处理字符串相关的部分。需要修改的见红色字体部分,如下所示:
……
#include "SettingDefs.h"
#ifdef __MMI_HELLOWORLD_ENABLED__
#include "HelloWorldDefs.h"
#endif
……
只加上面3句就好,主要是让字串资源生成程序能根据我们的资源ID定义头文件“HelloWorldDefs.h”辨识到我们的字符串资源。
首先在我们的资源ID定义头文件"HelloWorldDefs.h"里面加入字符串ID的定义,如下所示红色字体部分:
typedef enum
{
STR_HELLOWORLD_HELLO = HELLOWORLD_BASE + 1,
}STRINGID_LIST_HELLOWORLD;
好,现在我们有一个全系统唯一的字符串资源ID了,让我们给这个字符串资源ID设定具体的文本内容,这需要修改到一个系统文本文件:
“plutommi\Customer\CustResource\PLUTO_MMI\ref_list.txt” ,注意,这个文本文件是Unicode编码的,可以用UltraEdit打开,不过最好用Excel打开,这样便于编辑修改,这个文件的格式如下:
第一列:字符串资源ID,这里我们只加了一个,也就是我们上面定义的“STR_HELLOWORLD_HELLO”;
第二列:字符串资源所属模块,这个是便于设计者辨识资源归属的,资源生成程序并没用到,最好和当前的模块号同名,便于识别和管理;
第三列:该字串的最大长度,取所有语言中文本长度最长的一个
第四列:字串描述,这一列可以随意
第五列及以后各列分别对应各个不用语言的字串
按照上述格式,我们增加追加一条,如下红色字体所示:
STR_HELLOWORLD_HELLO HelloWorld 20 Hello World "Hello,World" 你好,世界 你好,世界
接着,我们要在我们的资源生成函数PopulateHelloWorldRes中增加一行,这样才能将上面的资源真正变成最终系统资源的一部分,一条宏指令就搞定了,如下红色字体所示:
ADD_APPLICATION_STRING2(STR_HELLOWORLD_HELLO, “Hello, World”, “HelloWorld”);
这个宏的第一个参数是字符串ID,第二个参数是当前系统语种对应的字串内容为空的时候的替代值,第三个参数是串的描述,可以随意。好,至此我们就成功的向系统中添加了一个字符串资源,而且是包含多语种支持的。
同样,第一步我们向资源ID定义头文件"HelloWorldDefs.h"里面加入图片ID的定义,如下所示红色字体部分:
typedef enum
{
IMG_HELLOWORLD = HELLOWORLD_BASE + 1,
}IMAGEID_LIST_HELLOWORLD;
接着我们把设计好的图片放到系统约定的目录,一般来讲系统是按照屏幕分辨率来分类存放图片资源的,对于P1300 320*240的屏幕分辨率来讲,这个目录是“plutommi\Customer\Images\PLUTO240X320\MainLCD”,在这个目录下我们建一个名字为“HelloWorld”的子目录,之后把我们模块要用到的图片通通都放到这个子目录下以便于管理。我们增加一个小图片HelloWorld.bmp到这个子目录下,后面我们将这个小图片作为菜单项的图标,如下所示:
再接着,同样我们需要在资源生成函数PopulateHelloWorldRes中增加一行,这也是一个宏,如下所示红色字体部分:
ADD_APPLICATION_IMAGE2(IMG_HELLOWORLD,
CUST_IMG_PATH"\\\\MainLCD\\\\HelloWorld\\\\HelloWorld.bmp",
"HelloWorld");
这个宏第一个参数是图片资源ID,第二个参数是图片存储路径(CUST_IMG_PATH对应“plutommi\Customer\Images\PLUTO240X320”),第三个参数是资源描述,这样一个图片资源就加好了,下面接着看菜单资源的添加,这一部分稍稍繁琐一点(主要是相对应的宏参数比较多)。
注意:HelloWorld子目录以及相关文件实际上是要加入到压缩文件“plutommi\Customer\Images\PLUTO240X320\image.zip”中(放到MainLCD目录下),“plutommi\Customer\Images\PLUTO240X320\MainLCD”这个目录是由image.zip生成的临时目录。
我们都知道,菜单是一种树形的结构,一个菜单项一般有以下几个要素:
第5个涉及到菜单资源的使用,我们放到后面再讲,下面就前面几项讲讲如何向添加一个新的菜单项。
首先确定我们新的菜单项的位置,我们放到工具菜单下好了,就是主菜单进去后那个小推车对应的菜单。
第一步:我们要定义一个全系统唯一的菜单ID,这需要在系统文件“plutommi\mmi\Inc\GlobalMenuItems.h”中名字为GLOBALMENUITEMSID的枚举型定义里面增加一行,如下红色字体所示:
enum GLOBALMENUITEMSID
{
IDLE_SCREEN_MENU_ID = 1,
……
MENU_ID_HELLOWORLD,
/*************************************************************
* Add new menuitems definitions before here
*************************************************************/
/* Add All Menus defines above MAX_MENU_ITEMS_VALUE Only */
……
}
从上面我们可以看出,菜单ID并没有用到我们之前提到的HELLOWORLD_BASE到HELLOWORLD_BASE_MAX之间的数值,而是重新启用另外一套数值空间,没关系,只要能保证同一种资源每一个都有全系统唯一编号就好了。
第二步:找到工具菜单的定义部分,将MENU_ID_HELLOWORLD作为其中的一部分,这实际上是要修改主菜单的相关资源,主菜单的资源定义文件是“plutommi\Customer\CustResource\PLUTO_MMI\Res_MMI\Res_MainMenu.c”,打开这个文件,找到OrganizerMenu的定义部分,增加我们的相关定义,如下所示红色部分:
typedef enum
{
……
#if defined(__MMI_EBOOK_READER__)
ENUM_EBOOK,
#endif
#if defined(__MMI_HELLOWORLD_ENABLED__)
ENUM_ID_HELLOWORLD,
#endif
ORG_ENUM_TOTAL
} OrganizerMenu;
对于OrganizerMenu这个定义的修改,主要意义在于ORG_ENUM_TOTAL,这个值实际上是对应菜单的子菜单数,系统会引用到这个值,我们往枚举定义OrganizerMenu里面增加一项,这个值自动加1,也就是对应菜单的子菜单数比以前大1。
第三步:把我们的菜单ID作为工具箱菜单的一个子菜单,在对应位置插入一行,如下红色字体所示:
#if defined(__MMI_VERSION_2__) /* Max 0224 */
void PopulateMainMenuRes(void)
{
……
/* oganizer */
ADD_APPLICATION_MENUITEM((MAIN_MENU_ORGANIZER_MENUID,IDLE_SCREEN_MENU_ID,
ORG_ENUM_TOTAL + 1, // fun and games menu
……
#if defined(__MMI_EBOOK_READER__)
MAIN_MENU_EBOOK_MENUID,
#endif
#if defined(__MMI_HELLOWORLD_ENABLED__)
MENU_ID_HELLOWORLD,
#endif
……
}
通过上面三步我们已经将新的菜单项成功插入到工具箱菜单,也就是说我们解决了菜单几个元素中的“父菜单项”的问题,接着我们来看看其他几项元素。这里我们介绍下ADD_APPLICATION_MENUITEM这个宏,刚才往工具箱菜单插入我们的菜单项时已经接触到这个宏了,生成菜单资源用的就是这个宏,接着我们就会用到。这个宏的参数个数是可变的,其参数具体含义如下:
参数1:菜单项本身的ID,就我们的例子而言是MENU_ID_HELLOWORLD
参数2:菜单项的父菜单ID,这里是MAIN_MENU_ORGANIZER_MENUID
参数3:本菜单子菜单个数,假定为N好了,这个参数之后是变参部分,随后的N个参数为各个子菜单的ID
参数4+N:菜单是否显示,一般为SHOW
参数5+N:菜单其他属性,可以是这些值的组合:NONMOVEABLE, MOVEABLEWITHOARENT, MOVEABLECROSSPARENT, INSERTABLE, SHORTCUTABLE,一般我们就用SHORTCUTABLE就好了。
参数6+N: 下级菜单的显示风格,以下风格任选其一就好:
DISP_LIST—列表显示,常见风格
DISP_MATRIX—矩阵显示,如九格宫、十二格宫,主菜单一般用这种风格
DISP_CIRCULAR_3D—循环3D显示,只有主菜单用到
DISP_PAGE—翻页格式,每个菜单项一页,一般也是主菜单才会用到
DISP_FIXED_GRID—很少用到,可忽略
参数7+N:菜单项显示文本对应的字串资源ID
参数8+N:菜单项对应的小图标的资源ID
好,根据上述参数说明,我们在函数PopulateHelloWorldRes中加入菜单生成宏,如下红色字体所示:
ADD_APPLICATION_MENUITEM(
(MENU_ID_HELLOWORLD, /* 要加左括号 */
MAIN_MENU_ORGANIZER_MENUID,
0, /* 我们没有子菜单 */
SHOW,
SHORTCUTABLE,
DISP_LIST,
STR_HELLOWORLD_HELLO,
IMG_HELLOWORLD)); /* 要加右括号 */
至此,我们完成菜单项资源的生成部分
注:ADD_APPLICATION_MENUITEM这个宏的参数实际上是一个,上面那些参数要用括号“()”括起来。
3、资源的使用
上一小节我们分别完成了字符串资源、图片资源以及菜单资源的生成部分,这一小节我们来看如何的使用这些资源:
字符串资源的使用比较简单,用函数GetString就可以了,如下红色所示,我们修改下之前输出字符的操作:
gui_print_text((UI_string_type)GetString(STR_HELLOWORLD_HELLO));
注意,GetString前面有个类型转换,这个数据类型定义在“gui_data_types.h”里面,因此需要在HelloWorld.c中包含此头文件。
实际上我们在用宏ADD_APPLICATION_MENUITEM生成菜单的使用已经用过了,更多的使用实例这里不再赘述,读者可自行寻找相关代码查看使用方法,基本上都是将图片资源ID作为一个参数传入某个函数或者宏。
实际上就是给菜单项设定一个响应动作,使之转到我们模块的入口,设置菜单项响应动作的函数是SetHiliteHandler,设置的动作只需要在系统上电后初始化的部分执行一次就可以了,这里我们为模块新增一个函数,这个函数专门处理我们模块上电初始化的部分,如下红色字体所示我们在HelloWorld.c中增加一下部分:
void mmi_HelloWorld_hilite(void)
{
#ifdef __MMI_HELLOWORLD_ENABLED__
SetLeftSoftkeyFunction(mmi_HelloWorld_entry, KEY_EVENT_UP);
#endif
}
void mmi_HelloWorld_init(void)
{
#ifdef __MMI_HELLOWORLD_ENABLED__
SetHiliteHandler(MENU_ID_HELLOWORLD, mmi_HelloWorld_hilite);
#endif
}
这实际上要用两个函数,由这两个函数共同完成菜单点击通知的接收和转入我们模块主入口的动作。
mmi_HelloWorld_init这个函数是一个外部接口,我们把申明放到HelloWorldGprot.h中;mmi_HelloWorld_hilite这个函数是内部接口,我们把申明放到HelloWorldProt.h中。然后我们把mmi_HelloWorld_init放入到函数InitAllApplications中调用已完成我们模块的初始化设定,修改文件“plutommi\mmi\Framework\Tasks\TasksSrc\MMITask.c”,如下红色字体部分所示:
……
#ifdef __MMI_HELLOWORLD_ENABLED__
#include “HelloWorldGprot.h”
#endif
……
void InitAllApplications(void)
{
……
#ifdef __MMI_HELLOWORLD_ENABLED__
mmi_HelloWorld_init();
#endif
……
}
现在我们的菜单就成了整个的一部分了,因为之前我们是暂借主菜单的入口来挂接模块的,现在把之前的挂接去掉,去掉如下红色字体部分:
void goto_main_menu(void)
{
#ifdef __MMI_HELLOWORLD_ENABLED__
mmi_HelloWorld_entry();
return;
#else
……
#endif
}
好,现在来编译一把看看效果,还是用“make update”吧,下面是效果图:
下载测试:
我用的FlashTool_v3.0844.00,据说下载速度要快些,运行这个软件,设定“Download Agent”文件(用FlashTool_v3.0844.00目录下的“MTK_AllInOne_DA.bin”)和“Scatter-loading File”文件(build\NEOTEL25_06B\ scatNEOTEL25_06B.txt),设定“Scatter-loading File”文件后,下载到ROM的bin文件会自动选上刚刚编译出来的那个,如果要改成下载其他bin文件,点选ROM(注意是点选到ROM这3个字母上,不是勾选前面的框框,框框必须是打钩的,否则bin文件不会下下去),“Authentication File”貌似可以不理,如下图所示:
将USB下载线连上电脑(注意是下载线,不是普通USB线,USB下载线在接电脑的那一头封装了一个PL2303的转换芯片,这部分的个头比普通USB线大),此时电脑上会出现一个新的串口,可以用设备管理器看一下新的串口是串口几,我的电脑是COM3,如下图所示:
接着,如下图所示,设置下载工具的相关选项:
好,准备下载了,下载前先关手机(比较彻底的做法是将电池拿下),将手机接上下载线,然后点下载,点完之后软件下边出现下载进度条,装上电池,一直按住手机开机键直到下载进度条开始动为止。
下载完屏幕上会弹出一个OK的图,表示已经成功完成了软件的下载,如下图:
开机试下看,看到HelloWorld了,运行结果和仿真器一样(如果不一样,你可能需要make remake一下),不过开机时会提示Midlet找不到,这是因为系统默认开机时执行指定的Java程序,但是这个Java程序系统里没找见。在设置->话机设置->Java运行时设置,手动关闭“自动运行Java程序”就行了(或者你有做好的Java程序,选定一个),更多的关于Java方面的东西,请参考华禹相关资料。
下载的Baudrate设置如下所示,最高可以到921600,建议设置到这个速率,下载快:
关于MMI的更多细节,大家可以看压缩包“huayu501_MTK界面开发说明.rar”里面的8.htm~20.htm,里面讲的很详细,我这里就不再重复了,呵呵。后面我将重点放到与设备硬件的操作上。
通常我们用P1300这类的模块做产品,大致都会在上面再扩展一些自己的硬件,通过串口扩展是一种不错的想法,这是一种非常常见的机对机通讯方式,几乎所有的芯片都有串口通讯能力,下面我们来看看P1300上如何进行Rs232串口通讯的。
为了简化描述和突出主题,我们这里将以P1300的USB串口(就是接USB下载线的那个口,实际上是串口1)为例进行说明,通讯的对象是电脑主机(具体地说是电脑上的一个串口工具,这样可确保通讯的另外一端是正常工作的,便于调试)。
1、串口的初始化
在MTK平台上,串口的操作和常见的操作系统不太一样,串口句柄并不是以文件形式的出现,而是有点类似单片机的做法,即每个串口有固定的编号,串口的初始化不需要有类似Windows/Unix等打开设备文件的操作(实际上是有UART_Open这个函数的,不过我们没有用到,大概是底层硬件初始化会用到,这里我们简单的理解成不需要Open就好了),下面我们对照实际的初始化代码逐句讲解初始化所需要做的事情,如下所示:
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// 我们要用到的串口, uart_port1是枚举型数值,usb串口的编号
#define HELLO_WORLD_UART_PORT uart_port1 // 手机接USB下载线的地方
// 定义我们的应用所属系统的哪一个模块
// 这里是MMI模块,实际上我更愿意称MMI为一个子系统,将我们的应用称为模块
#define MOD_HELLO_WORLD MOD_MMI
// 数据接收Buffer大小
#define MAX_ECHO_PACKET_LEN 128
// 睡眠模式句柄
static kal_uint8 ghSleepMode = 0xff;
// 我们要用到的串口之前的占用者
static module_type gnOrigUartOwner;
static kal_bool gbUartInitialized = KAL_FALSE;
static void init_uart(void)
{
#ifdef __MMI_HELLOWORLD_ENABLED__
if(gbUartInitialized)
{
return;
}
// 确保只调用一次, 网友澄滢提出修正 09.07.28
if(0xFF == ghSleepMode)
{
ghSleepMode = L1SM_GetHandle();
}
// 禁止休眠,休眠后串口收发会有问题
// 问题:如何做到串口唤醒? FIXME
L1SM_SleepDisable(ghSleepMode);
// 记录我们要用的串口的当前占有者
gnOrigUartOwner = UART_GetOwnerID(HELLO_WORLD_UART_PORT);
// 下面我们申明要占用这个串口了
UART_SetOwner(HELLO_WORLD_UART_PORT, MOD_HELLO_WORLD);
// 以上对串口占用的申明,我理解更多是一种编程者之间的约定
// 当发现串口的占用者不是本程序所属模块时,说明我们申明占用之后
// 又有其他模块申明占用同一个串口,此时如果收到数据应不予处理
// 当然,也有可能同一模块的其他程序申明占用同一串口
// 这需要在设计应用时统一协调,避免数据发生混乱
// 设置波特率,缺省的起停位和校验为:8,n,1,即8个数据位,1个停止位,无校验
UART_SetBaudRate(HELLO_WORLD_UART_PORT, UART_BAUD_115200, MOD_HELLO_WORLD);
// 其他串口设定(如起停位、校验等)使用函数 UART_ReadDCBConfig 和 UART_SetDCBConfig
// 详细参数见结构体 UARTDCBStruct
// 注册一个事件钩子函数,当串口(任何)有数据到达时,我们的钩子函数将被调用
// 注意,同一种事件同时只能注册一个钩子函数,因此:
// 如果在我们的程序处理串口的同时还有其他程序要读取和处理(任何)串口数据,
// 就必须由当前的钩子函数代为处理
// 实际上我觉得系统底层可以改一下,改成Windows钩子的方式,可以挂多个,能够依次调用或跳过
SetProtocolEventHandler(mmi_HelloWorld_uart_readyToRead_ind_handler, MSG_ID_UART_READY_TO_READ_IND);
gbUartInitialized = KAL_TRUE;
#endif
}
2、从串口读数据
这里有两个相关的函数,第一个是上面初始化部分提到的钩子函数mmi_HelloWorld_uart_readyToRead_ind_handler,另外一个是读函数,函数体如下所示:
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
static void mmi_HelloWorld_uart_readyToRead_ind_handler(void *msg)
{
#ifdef __MMI_HELLOWORLD_ENABLED__
uart_ready_to_read_ind_struct* uart_rtr_ind = (uart_ready_to_read_ind_struct*)msg;
// 检查一下端口以及所有者信息,不匹配的数据略过
// 注意,作为例子程序,我们只是查看和处理我们关心的数据
// 其他与此同时需要监听和处理串口数据的程序,须由相关的代码完成代理的工作
if( KAL_FALSE == gbUartEchoStarted ||
HELLO_WORLD_UART_PORT != uart_rtr_ind->port ||
MOD_HELLO_WORLD != UART_GetOwnerID(uart_rtr_ind->port) )
{
// 如果底层能做成钩子链,这里就可以调用钩子链的下一个钩子了
return;
}
gwLenUartBuffer = read_from_uart(gabyUartBuffer, sizeof(gabyUartBuffer), HELLO_WORLD_UART_PORT, MOD_HELLO_WORLD);
// 呼叫数据处理部分
uart_echo_process();
#endif
}
static U16 read_from_uart(U8 *pbyBuf, U16 wLenMax, UART_PORT hPort, module_type hOwner)
{
U16 wLenAvail;
U16 wLenRead;
U16 wLenRet = 0;
U8 byStatus = 0;
#ifdef __MMI_HELLOWORLD_ENABLED__
// 收取数据,超出最大包长的数据将简单丢弃,这一层需要具体的应用协议做相应处理
while( (wLenAvail = UART_GetBytesAvail(hPort) > 0 && wLenRet < wLenMax) )
{
if (wLenAvail + wLenRet > wLenMax)
{
wLenAvail = wLenMax - wLenRet;
}
wLenRead = UART_GetBytes(hPort, (kal_uint8 *)(pbyBuf + wLenRet), (kal_uint16)wLenAvail, &byStatus, hOwner);
wLenRet += wLenRead;
}
// 读完之后,清除接收Buffer
UART_ClrRxBuffer(hPort, hOwner);
#endif
return wLenRet;
}
3、向串口写数据
static U8 write_to_uart(U8 *pbyBuf, U16 wLenBuf, UART_PORT hPort, module_type hOwner)
{
U16 wSent= 0;
U8 bRet = FALSE;
#ifdef __MMI_HELLOWORLD_ENABLED__
// 发送前清FIFO和Buffer,注意:这一步必须做,否则收发会有问题
UART_Purge(hPort, RX_BUF, hOwner); // 清除设备输入FIFO
UART_Purge(hPort, TX_BUF, hOwner); // 清除设备输出FIFO
UART_ClrTxBuffer(hPort, hOwner); // 清除发送Buffer
UART_ClrRxBuffer(hPort, hOwner); // 清除接收Buffer
wSent = UART_PutBytes(hPort, (kal_uint8 *)pbyBuf, (kal_uint16)wLenBuf, hOwner);
#endif
if (wSent == wLenBuf)
{
bRet = TRUE;
}
return bRet ;
}
4、关闭串口
关闭串口实际上是将相关的设置改动恢复到之前的状态,不需要有close的动作,如下所示:
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
static void exit_uart()
{
#ifdef __MMI_HELLOWORLD_ENABLED__
if(gbUartInitialized)
{
// 清除事件挂钩,网友澄滢提出修正 09.07.28
ClearProtocolEventHandler(MSG_ID_UART_READY_TO_READ_IND);
// 恢复成原有的端口占用者
UART_SetOwner(HELLO_WORLD_UART_PORT, (kal_uint8) gnOrigUartOwner);
// 允许休眠
L1SM_SleepEnable(ghSleepMode);
gbUartInitialized = KAL_FALSE;
}
#endif
}
5、下载测试
Make remake一下->下载程序到手机->关闭下载工具->运行串口工具,打开USB下载线对应的串口,设定为115200,8N1->手机开机(此时下载线不要拔下,我们用的就是这个下载线测试的)->手机工具菜单->Hello World->手机左键启动串口Echo(启动后再按左键停止串口Echo)。
注1:串口的部分在仿真器上模拟不了,链接的时候也会提示找不到相关函数符号。
注2:在HelloWorld的串口Echo停止工作时我们也能偶尔看到串口会原样回显,这应该是其他程序在处理,不是我们的HelloWorld,我们的HelloWorld会将输入转成全大写再回显。
注3:使用手机的USB下载口当作串口时,要用USB下载线进行连接,同时要注意相关的工程设置为以下:
--------------
TST Config
< 无 >
< 115200 >
PS Config
< UART 1 >
< 115200 >
--------------
设置方法:
待机界面下(拨号)输入 *#3646633# (进入 工程模式)->设备->"Set UART"->"UART Setting"
程序运行后的电脑端截图如下:
我采用1ms定时发送“Hello, World!\r\n”, 每毫秒15字节的速度,接收比发送多,这是因为启动Echo时,我有送一个串“Hello World Uart Echo Example Started! \r\n”(40字节)过来,不过接收多出的部分应该不止10个字节(1928380 – 1928370),应该是40个字节,数据似乎有部分丢失(刚好2个包, 15 * 2 = 30 = 40 - 10),应该是电脑端发送太快的原因(115200 / (8 + 1) / 1000 = 12.8字节/毫秒,小于15,而且还有数据处理和echo的时间),这样的响应速度算相当不错了。
6、完整的C程序
下面贴出比较完整的HelloWorld.c程序,供大家参考,由于时间的关系,开发心得暂时到此为止,感谢华禹公司及其兄弟公司的支持,感谢各位网友!
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#include "stdC.h"
#include "MMI_Features.h" /* 编译开关会出现在这个由make update生成的文件里面 */
#include "L4Dr.h"
#include "L4Dr1.h"
#include "AllAppGprot.h"
#include "FrameworkStruct.h"
#include "GlobalConstants.h"
#include "EventsGprot.h"
#include "mmiappfnptrs.h"
#include "HistoryGprot.h"
#include "HelloWorldProt.h"
#include "HelloWorldTypes.h"
#include "HelloWorldDefs.h"
#include "MainMenuDef.h"
#include "wgui_categories.h"
#include "Unicodexdcl.h"
#include "gui_data_types.h"
#include "Uart_sw.h"
// 我们要用到的串口, uart_port1是枚举型数值,usb串口的编号
#define HELLO_WORLD_UART_PORT uart_port1 // 手机接USB下载线的地方
// 定义我们的应用所属系统的哪一个模块
// 这里是MMI模块,实际上我更愿意称MMI为一个子系统,将我们的应用称为模块
#define MOD_HELLO_WORLD MOD_MMI
// 数据接收Buffer大小
#define MAX_ECHO_PACKET_LEN 128
// 外部函数申明,没有对应头文件,手动加吧
extern module_type UART_GetOwnerID(UART_PORT port);
extern void UART_ClrTxBuffer(UART_PORT port, module_type ownerid);
extern void UART_ClrRxBuffer(UART_PORT port, module_type ownerid);
// 本程序内部使用的函数申明
static void init_uart(void);
static void mmi_HelloWorld_uart_readyToRead_ind_handler(void *msg);
static U16 read_from_uart(U8 *pbyBuf, U16 wLenMax, UART_PORT hPort, module_type hOwner);
static U8 write_to_uart(U8 *pbyBuf, U16 wLenBuf, UART_PORT hPort, module_type hOwner);
static void exit_uart();
static void start_uart_echo(void);
static void uart_echo_process(void);
static void stop_uart_echo(void);
// 睡眠模式句柄
static kal_uint8 ghSleepMode = 0xFF;
// 我们要用到的串口之前的占用者
static module_type gnOrigUartOwner;
static kal_bool gbUartInitialized = KAL_FALSE;
static kal_bool gbUartEchoStarted = KAL_FALSE;
static U16 gwLenUartBuffer = 0;
static U8 gabyUartBuffer[MAX_ECHO_PACKET_LEN];
static void init_uart(void)
{
#ifdef __MMI_HELLOWORLD_ENABLED__
if(gbUartInitialized)
{
return;
}
// 确保只调用一次, 网友澄滢提出修正09.07.28
if(0xFF == ghSleepMode)
{
ghSleepMode = L1SM_GetHandle();
}
// 禁止休眠,休眠后串口收发会有问题
// 问题:如何做到串口唤醒? FIXME
L1SM_SleepDisable(ghSleepMode);
// 记录我们要用的串口的当前占有者
gnOrigUartOwner = UART_GetOwnerID(HELLO_WORLD_UART_PORT);
// 下面我们申明要占用这个串口了
UART_SetOwner(HELLO_WORLD_UART_PORT, MOD_HELLO_WORLD);
// 以上对串口占用的申明,我理解更多是一种编程者之间的约定
// 当发现串口的占用者不是本程序所属模块时,说明我们申明占用之后
// 又有其他模块申明占用同一个串口,此时如果收到数据应不予处理
// 当然,也有可能同一模块的其他程序申明占用同一串口
// 这需要在设计应用时统一协调,避免数据发生混乱
// 设置波特率,缺省的起停位和校验为:8,n,1,即8个数据位,1个停止位,无校验
UART_SetBaudRate(HELLO_WORLD_UART_PORT, UART_BAUD_115200, MOD_HELLO_WORLD);
// 其他串口设定(如起停位、校验等)使用函数 UART_ReadDCBConfig 和 UART_SetDCBConfig
// 详细参数见结构体 UARTDCBStruct
// 注册一个事件钩子函数,当串口(任何)有数据到达时,我们的钩子函数将被调用
// 注意,同一种事件同时只能注册一个钩子函数,因此:
// 如果在我们的程序处理串口的同时还有其他程序要读取和处理(任何)串口数据,
// 就必须由当前的钩子函数代为处理
// 实际上我觉得系统底层可以改一下,改成Windows钩子的方式,可以挂多个,能够依次调用或跳过
SetProtocolEventHandler(mmi_HelloWorld_uart_readyToRead_ind_handler, MSG_ID_UART_READY_TO_READ_IND);
gbUartInitialized = KAL_TRUE;
#endif
}
static U16 read_from_uart(U8 *pbyBuf, U16 wLenMax, UART_PORT hPort, module_type hOwner)
{
U16 wLenAvail;
U16 wLenRead;
U16 wLenRet = 0;
U8 byStatus = 0;
#ifdef __MMI_HELLOWORLD_ENABLED__
// 收取数据,超出最大包长的数据将简单丢弃,这一层需要具体的应用协议做相应处理
while( (wLenAvail = UART_GetBytesAvail(hPort) > 0 && wLenRet < wLenMax) )
{
if (wLenAvail + wLenRet > wLenMax)
{
wLenAvail = wLenMax - wLenRet;
}
wLenRead = UART_GetBytes(hPort, (kal_uint8 *)(pbyBuf + wLenRet), (kal_uint16)wLenAvail, &byStatus, hOwner);
wLenRet += wLenRead;
}
// 读完之后,清除接收Buffer
UART_ClrRxBuffer(hPort, hOwner);
#endif
return wLenRet;
}
static U8 write_to_uart(U8 *pbyBuf, U16 wLenBuf, UART_PORT hPort, module_type hOwner)
{
U16 wSent= 0;
U8 bRet = FALSE;
#ifdef __MMI_HELLOWORLD_ENABLED__
// 发送前清FIFO和Buffer,注意:这一步必须做,否则收发会有问题
UART_Purge(hPort, RX_BUF, hOwner); // 清除设备输入FIFO
UART_Purge(hPort, TX_BUF, hOwner); // 清除设备输出FIFO
UART_ClrTxBuffer(hPort, hOwner); // 清除发送Buffer
UART_ClrRxBuffer(hPort, hOwner); // 清除接收Buffer
wSent = UART_PutBytes(hPort, (kal_uint8 *)pbyBuf, (kal_uint16)wLenBuf, hOwner);
#endif
if (wSent == wLenBuf)
{
bRet = TRUE;
}
return bRet ;
}
static void exit_uart()
{
#ifdef __MMI_HELLOWORLD_ENABLED__
if(gbUartInitialized)
{
// 清除事件挂钩,网友澄滢提出修正 09.07.28
ClearProtocolEventHandler(MSG_ID_UART_READY_TO_READ_IND);
// 恢复成原有的端口占用者
UART_SetOwner(HELLO_WORLD_UART_PORT, (kal_uint8) gnOrigUartOwner);
// 允许休眠
L1SM_SleepEnable(ghSleepMode);
gbUartInitialized = KAL_FALSE;
}
#endif
}
static void mmi_HelloWorld_uart_readyToRead_ind_handler(void *msg)
{
#ifdef __MMI_HELLOWORLD_ENABLED__
uart_ready_to_read_ind_struct* uart_rtr_ind = (uart_ready_to_read_ind_struct*)msg;
// 检查一下端口以及所有者信息,不匹配的数据略过
// 注意,作为例子程序,我们只是查看和处理我们关心的数据
// 其他与此同时需要监听和处理串口数据的程序,须由相关的代码完成代理的工作
if( KAL_FALSE == gbUartEchoStarted ||
HELLO_WORLD_UART_PORT != uart_rtr_ind->port ||
MOD_HELLO_WORLD != UART_GetOwnerID(uart_rtr_ind->port) )
{
// 如果底层能做成钩子链,这里就可以调用钩子链的下一个钩子了
return;
}
gwLenUartBuffer = read_from_uart(gabyUartBuffer, sizeof(gabyUartBuffer), HELLO_WORLD_UART_PORT, MOD_HELLO_WORLD);
// 呼叫数据处理部分
uart_echo_process();
#endif
}
static void start_uart_echo(void)
{
#ifdef __MMI_HELLOWORLD_ENABLED__
S8 strHello[] = "Hello World Uart Echo Example Started!\r\n";
if(gbUartEchoStarted)
{
return;
}
init_uart();
write_to_uart((kal_uint8*)strHello, (kal_uint16)strlen(strHello), HELLO_WORLD_UART_PORT, MOD_HELLO_WORLD);
gbUartEchoStarted = KAL_TRUE;
SetKeyHandler(stop_uart_echo, KEY_LSK, KEY_EVENT_UP);
#endif
}
static void uart_echo_process(void)
{
#ifdef __MMI_HELLOWORLD_ENABLED__
U8 i;
// 稍稍处理一下,这样就能看出是否确实是我们的程序在处理数据
for(i = 0; i < gwLenUartBuffer; i++)
{
if(gabyUartBuffer[i] >= 'a' && gabyUartBuffer[i] <= 'z')
{
gabyUartBuffer[i] -= 0x20;
}
}
// 回显
write_to_uart(gabyUartBuffer, gwLenUartBuffer, HELLO_WORLD_UART_PORT, MOD_HELLO_WORLD);
#endif
}
static void stop_uart_echo(void)
{
#ifdef __MMI_HELLOWORLD_ENABLED__
S8 strBye[] = "Hello World Uart Echo Example Stop!\r\n";
if(gbUartEchoStarted)
{
write_to_uart((kal_uint8*)strBye, (kal_uint16)strlen(strBye), HELLO_WORLD_UART_PORT, MOD_HELLO_WORLD);
gbUartEchoStarted = KAL_FALSE;
SetKeyHandler(start_uart_echo, KEY_LSK, KEY_EVENT_UP);
}
exit_uart();
#endif
}
gdi_handle hAnimation;
void stop_play_anim(void)
{
gdi_anim_stop(hAnimation);
}
/* 模块入口 */
void mmi_HelloWorld_entry(void)
{
#ifdef __MMI_HELLOWORLD_ENABLED__
S32 x, y, w, h;
color colorText = {255, 255, 128, 100};
color colorFill = {207, 252, 109, 100};
color colorShadow = {166, 201, 81, 100};
stFontAttribute tFont = {0};
U8 dotted_line_bitvalues[] = {1, 0, 1, 0, 1, 0, 1};
UI_filled_area tFiller = {0};
static color g_colors[3] = {{255, 0, 0}, {0, 255, 0}, {0, 0, 255}};
static U8 perc[2] = {50, 50};
gradient_color gc = {g_colors, perc, 3};
tFont.size = LARGE_FONT;
tFont.italic = 1;
/* 强制退出当前屏幕,之后进入到我们的模块了 */
/* 上电缺省是idle屏幕,现进入MAIN_MENU_SCREENID屏 */
/* 注意看第二个参数,这个是当我们模块被强制退出时执行的一些操作 */
EntryNewScreen(SCR_HELLOWORLD, mmi_HelloWorld_exit, NULL, NULL);
gui_lock_double_buffer();
/* 关掉屏幕顶部的状态条,我们要用整个屏幕 */
entry_full_screen();
/* 擦除当前背景 */
clear_screen();
tFiller.flags = UI_FILLED_AREA_TYPE_GRADIENT_COLOR | UI_FILLED_AREA_VERTICAL_FILL | UI_FILLED_AREA_DOUBLE_BORDER;
tFiller.border_color = UI_COLOR_GREEN;
tFiller.gc = &gc;
gui_draw_filled_area(0, 0, UI_device_width - 1, UI_device_height - 1, &tFiller);
/* 设置字体颜色 */
gui_set_text_color(colorText/*UI_COLOR_RED*/);
gui_set_font(&tFont);
gui_set_text_border_color(UI_COLOR_GREEN);
gui_measure_string((UI_string_type)GetString(STR_HELLOWORLD_HELLO), &w, &h);
x = (UI_device_width - w) >> 1;
y = UI_device_height - ((UI_device_height - h) >> 2);
gui_draw_rectangle(x - 7, y - 7, x + w + 7, y + h + 7, UI_COLOR_RED);
gui_fill_rectangle(x - 6, y - 6, x + w + 6, y + h + 6, colorFill);
gui_line(x - 4, y + h + 4, x + w + 4, y + h + 4, colorShadow);
gui_line(x - 5, y + h + 5, x + w + 5, y + h + 5, colorShadow);
gui_line(x - 6, y + h + 6, x + w + 6, y + h + 6, colorShadow);
gui_line(x + w + 4, y - 4, x + w + 4, y + h + 4, colorShadow);
gui_line(x + w + 5, y - 5, x + w + 5, y + h + 5, colorShadow);
gui_line(x + w + 6, y - 6, x + w + 6, y + h + 6, colorShadow);
/* 移动文本输出光标 */
gui_move_text_cursor(x, y);
/* 输出文本到显示缓冲, 注意是Unicode编码 */
// gui_print_text((UI_string_type)GetString(STR_HELLOWORLD_HELLO));
gui_print_bordered_text((UI_string_type)GetString(STR_HELLOWORLD_HELLO));
gdi_draw_line_style(x, y + h + 2, x + w + 2, y + h + 2,
gdi_act_color_from_rgb(100, 255, 0, 0),
sizeof(dotted_line_bitvalues),
dotted_line_bitvalues);
// 显示图片
gdi_image_get_dimension_id(MAIN_MENU_MATRIX_ORGANIZER_ICON, &w, &h);
x = (UI_device_width - w) >> 1;
y = (UI_device_height - h) >> 1;
gdi_image_draw_id(x, 10, MAIN_MENU_MATRIX_ORGANIZER_ICON);
// 显示动画
gdi_image_get_dimension_id(MAIN_MENU_MATRIX_ORGANIZER_ANIMATION, &w, &h);
x = (UI_device_width - w) >> 1;
y = (UI_device_height - h) >> 1;
gdi_anim_draw_id(x, 10 + h, MAIN_MENU_MATRIX_ORGANIZER_ANIMATION, &hAnimation);
gui_unlock_double_buffer();
/* 刷新屏幕显示,MMI用的是双缓冲绘图方式,而且需要显式刷新 */
gui_BLT_double_buffer(0, 0, UI_device_width - 1, UI_device_height - 1);
/* 注册一个按键处理,右软键弹起时返回到之前被我们强制退出的模块 */
SetKeyHandler(GoBackHistory, KEY_RSK, KEY_EVENT_UP);
// SetKeyHandler(stop_play_anim, KEY_LSK, KEY_EVENT_UP);
SetKeyHandler(start_uart_echo, KEY_LSK, KEY_EVENT_UP);
#endif
}
/* 模块出口
* 当我们的模块被其他模块强制退出时会执行这个函数,
* 这个函数的常见写法,包括:
* 1、模块已申请资源的释放(如果需要的话),这一步可选
* 2、手动把自己压栈到窗口(实际是整个屏)堆栈里面,
* 便于强制我们退出的模块执行完后重新把我们叫出来
* 不像Window的窗口管理是自动压栈的,Pluto MMI需要手动压栈
* 3、其他一些清理动作
*/
void mmi_HelloWorld_exit(void)
{
#ifdef __MMI_HELLOWORLD_ENABLED__
history currHistory;
S16 nHistory = 0;
currHistory.scrnID = MAIN_MENU_SCREENID;
currHistory.entryFuncPtr = mmi_HelloWorld_entry;
pfnUnicodeStrcpy( (S8*)currHistory.inputBuffer, (S8*)&nHistory);
AddHistory(currHistory);
stop_uart_echo();
#endif
}
void mmi_HelloWorld_hilite(void)
{
#ifdef __MMI_HELLOWORLD_ENABLED__
SetLeftSoftkeyFunction(mmi_HelloWorld_entry, KEY_EVENT_UP);
#endif
}
void mmi_HelloWorld_init(void)
{
#ifdef __MMI_HELLOWORLD_ENABLED__
SetHiliteHandler(MENU_ID_HELLOWORLD, mmi_HelloWorld_hilite);
#endif
}
test