开始
- Hello,World
main()
{
printf("Hello,World");
}
- 程序入口
嵌入式操作系统的应用程序通常是与整个系统固定在一起。MMI可以看成一个大的程序,我们写的小程序就是大程序的分支。将自己写的程序在大程序中添加新的入口。目前先借用已有的程序入口goto_main_menu,主菜单的入口函数。
void mmi_myapp_entry(void)
{
//我们的程序由此开始
}
void goto_main_menu(void)
{
//将主菜单切换为我们的程序
mmi_myapp_entry();
return;
......
}
- 打印文本
显示文本串的函数原型如下:
void(*gui_print_text)(UI_string_type_text);
所以我们的程序显示字符串代码如下:
void mmi_myapp_entry(void)
{
//先清屏,去掉之前显示的内容
clear_screen();
//设置文本输出起始位置(文本属性的设置是对于整个系统,因此每次输出文本时都需要重新设置)
gui_move_text_cursor(50,100);
//设置文本颜色
gui_set_text_color(UI_COLOR_RED);
//L表示强制将字符串以Uinicode编码输入
gui_print_text(L"Hello,World");
//需要重新刷新屏幕才能显示,参数表示刷新整个屏幕
gui_BLT_double_buffer(0,0,UI_device_width-1,UI_device_height-1);
}
屏幕
手机的屏幕类似于windows中的窗口概念,即一个应用程序在某个状态时的显示模式及交互方式,但我们的屏幕是独占整个显示系统及交互系统,任何时候只能由一个屏幕来控制。一个程序可能由多个屏幕组成,类似于windows中一个程序可能有多个窗口。当前我们的写的程序一直只有一个屏幕显示。
- 新的屏幕
前面我们已经将Hello,World在屏幕上输出了,但是只要稍微等上一会,就会发现屏幕上多出了一些主页面上的东西显示出来了,这是因为没有退出上一个程序。
void mmi_myapp_entry(void)
{
//退出上一个程序,避免局部可能显示在新的页面中
EntryNewScreen(MAIN_MENU_SCREENID,NULL,NULL,NULL);
//去掉状态栏
entry_full_screen();
clear_screen();
gui_move_text_cursor(50,100);
gui_set_text_color(UI_COLOR_RED);
gui_print_text(L"Hello,World");
gui_BLT_double_buffer(0,0,UI_device_width-1,UI_device_height-1);
}
- 屏幕历史
当我们的程序在运行时,如果有新的程序出来(如插上充电器时的提示框),会强制退出我们自己的程序,但新的程序结束后会发现直接返回了Idle。为了避免这个问题,系统建立了一套屏幕历史管理机制。我们只需要在调用EntryNewScreen时传入我们新的屏幕的ID以及入口函数,那么系统下次调用EntryNewScreen是会我们的屏幕加入历史记录,当新的屏幕退出后,系统会将我们的屏幕从历史中弹出并显示。
EntryNewScreen的函数原型
U8 EntryNewScreen(U16 newscrnID,FuncPtr newExitHandler,FuncPtr newEntryHandler,void *peerBuf)
参数1:新显示屏幕的序号
参数2:屏幕退出时会调用的函数
参数3:新屏幕的入口函数
参数4:暂不使用
void mmi_myapp_entry(void)
{
EntryNewScreen(MAIN_MENU_SCREENID,NULL,mmi_myapp_entry,NULL);
entry_full_screen();
clear_screen();
gui_move_text_cursor(50,100);
gui_set_text_color(UI_COLOR_RED);
gui_print_text(L"Hello,World");
gui_BLT_double_buffer(0,0,UI_device_width-1,UI_device_height-1);
}
- 手动加入历史
当EntryNewScreen的第三个参数为空时,系统就不会自动加入历史中,我们也可以在mmi_myapp_exit中手动添加进历史
void mmi_myapp_exit(void)
{
history currHistory;
S16 nHistory=0;
currHistory.scrnID = MAIN_MENU_SCREENID;
currHistory.entryFuncPtr = mmi_myapp_entry;
pfnUnicodeStrcpy((S8*)currHistory.inputBuffer,(S8*)&nHistory);
AddHistory(currHistory);
}
void mmi_myapp_entry(void)
{
EntryNewScreen(MAIN_MENU_SCREENID,mmi_myapp_exit,NULL,NULL);
entry_full_screen();
clear_screen();
gui_move_text_cursor(50,100);
gui_set_text_color(UI_COLOR_RED);
gui_print_text(L"Hello,World");
gui_BLT_double_buffer(0,0,UI_device_width-1,UI_device_height-1);
}
- 返回最近的屏幕
有进入就有退出,退出屏幕也需要手动执行。我们通常用GoBackHistory通知系统将历史中最后一次显示的屏幕弹出来。
void mmi_myapp_entry(void)
{
EntryNewScreen(MAIN_MENU_SCREENID,NULL,mmi_myapp_entry,NULL);
entry_full_screen();
clear_screen();
gui_move_text_cursor(50,100);
gui_set_text_color(UI_COLOR_RED);
gui_print_text(L"Hello,World");
gui_BLT_double_buffer(0,0,UI_device_width-1,UI_device_height-1);
//我们通常将右软键设为返回最近显示的屏幕
SetKeyHandler(GoBackHistory,KEY_RSK,KEY_EVENT_UP);
}
程序
- 新程序
为了将自己的程序规范化,我们需要将自己的程序独立出来。
1. 代码独立:就是将程序代码放到单独的文件中。
2. 数据独立:就是资源独立(下一部分介绍)。
在修改之前,我们先将自己的程序命名为”MyApp”。
- 添加程序文件
一般新加的MMI程序都放到plutommi\MMI下面,创建如下目录:
程序总目录 plutommi\MMI\MyApp
源文件目录 plutommi\MMI\MyApp\MyAppSrc
MyAppSrc.c是本程序的主源文件,需要将主函数mmi_myapp_entry和mmi_myapp_exit从MainMenu.c中转移到此处。
头文件目录 plutommi\MMI\MyApp\MyAppInc
MyAppProt.h---放本程序所有函数声明,但此头文件只被本程序的源文件所加载
#ifndef _MYAPPPORT_H;
#define _MYAPPPORT_H;
#include "MyAppGprot.h"
extern void mmi_myapp_exit(void);
extern void mmi_myapp_entry(void);
#endif/*_MYAPPPORT_H*/
MyAppTypes.h---用来放本程序所需的类型,结构,常量定义。
MyAppGprot.h---也是用来放函数声明,但此头文件是被别的程序加载的,此文件所声明的都是对外的接口。
#ifndef _MYAPPGPORT_H;
#define _MYAPPGPORT_H;
#define "PixtelDataTypes.h"
#include "MyAppTypes.h"
extern void mmi_myapp_entry(void);
#endif/*_MYAPPPORT_H*/
MyAppDefs.h---用来放本程序的资源ID定义。
typedef enmu
{
SCR_MYAPP_MAIN=MYAPP_BASE+1,
}SCREENID_LIST_MYAPP;
- 将文件加入项目
文件需要使用ARM编译器,为了将文件加入项目,必须手动将新文件路径添加到以下几个表文件中:
修改make\plutommi\下的三个文件夹:
1. plutommi.li:此文件用来指明MMI所要编译的所有源文件。在文件中添加
plutommi\MMI\MyApp\MyAppSrc\MyAppSrc.c
2. plutommi.inc:此文件用来指明MMI所有头文件所在目录(因为源文件中加载头文件时都没有路径,所以需要在此申明)
plutommi\MMI\MyApp\MyAppInc
3. plutommi.pth:此文件用来指明MMI所有源文件所在目录。
plutommi\MMI\MyApp\MyAppSrc
- 程序开关
为了尽量精简最终生成的烧录程序,我们一般都会给每个小程序加上自己的编译开关,并将自己程序所有的代码都包含进编译开关。
MMI的编译开关一般都放到文件plutommi\Customer\CustResource\PLUTO_MMI_featuresPLUTO.h中,按照如下方式添加:
/**************************
[Application]:MyApp
**************************/
#define _MMI_MYAPP_
如下所示,我们一般也会将入口加入编译开关:
#include "MyAppGprot.h"
void goto_main_menu(void)
{
#ifdef _MMI_MYAPP_
//将主菜单切换成我们的程序:
mmi_myapp_entry(void)();
return;
#endif/*_MMI_MYAPP_*/
}
资源
- 资源介绍
通常将程序使用的数据分为动态数据与静态数据两种,动态数据即程序运行时才能知道的数据,一般是由程序动态生成。而静态数据是固定的,在编译时即可将其转换成其他二进制数据,保存到最终烧录的文件中,静态数据也称为资源。
常见资源类型:字串,图像,菜单,字库,主题,声音,以及某些程序单独使用的资源。
添加新的程序一般只会修改其中三种:字串,图像,菜单。
添加一项资源通常分为三步:原料,ID,装载。
原料:原材料,如图像就是准备一张新图,字串就是各种语言的Unicode编码。
ID:资源项的别名,程序只能通过ID来获取资源(ID一般定义在XXDefs.h中).
装载:装载在编译目标烧录文件之前就会被执行,其目的有两个:一是将原材料转换成二进制数据,二是生成将ID与二进制数据联系起来的映射表。
资源装载预编译程序是plutommi\Custommer\ResGenerator\mtk_resgenerator.exe,这个程序在每次编译目标烧录文件之前临时编译生成的。下面的修改基本与这个程序有关。
- 添加文件
在plutommi\Custommer\CustResource\PLUTO_MMI\Res_MMI下创建一个新文件:
plutommi\Custommer\CustResource\PLUTO_MMI\Res_MMI\Res_Myapp.c
并在文件中添加一个函数PopulateMyAppRes:
#include "StdC.h"
#ifdef DEVELOPER_BUILD_FIRST_PASS
...
void PopulateMyAppRes(void)
{
}
#endif/*DEVELOPER_BUILD_FIRST_PASS*/
此文件用在预编译时装载资源,每个程序都有自己的资源装载文件,这些文件与plutommi\Custommer\ResGenerator\mtk_resgenerator.exe一起生成mtk_resgenerator.exe并在Windows下被执行。
- 修改Makefile,PopulateRes.c,readexcel.c
修改Makefile:
在文件plutommi\Custommer\ResGenerator\Makefile中添加如下两行:
-I"../../MMI/MainMenu/MainMenuInc" \
-I"../../MMI/MyApp/MyAppInc" \
此文件是资源装载预编译程序的Makefile。
修改PopulateRes.c:
在plutommi\MMI\Resource\PopulateRes.c
...
extern void PopulateMainDemoRes(void);
extern void PopulateMyAppRes(void);
...
void PopulateResData(void)
{
...
PRINT_INFORMATION(("Populating Main Menu Resource\n"));
PopulateMainMenuRes();
PRINT_INFORMATION(("Populating MyApp Resource\n"));
PopulateMyAppRes();
...
}
mtk_resgenerator.exe在执行时会呼叫到这里面的PopulateResData。
修改readexcel.c
在plutommi\Customer\ResGenerator\readexcel.c(找不到此文件,可省略该步骤)
...
#include "SettingDefs.h"
#ifdef _MMI_MYAPP_
#include "MyAppDefs.h"
#endif /*_MMI_MYAPP_*/
...
字符串资源有自己单独的装载预编译程序readexcel.exe.此程序在mtk_resgenerator.exe呼叫完后会被接着生成并执行。
- 资源ID
在加ID之前先为本程序添加一个基础ID,因所有的程序资源ID都是各自为政个定义各的,但是这些ID又不能冲突(每种类型的资源ID都是在同一个取值空间),所以我们就用这些基础ID将每个程序ID取值隔离开来。
基础ID统一定义在plutommi\MMI\Inc\MMIDataType.h:
...
typef enmu
{
...
RESOURCE_BASE_RANGE(MAIN_MENU,600),
//新增基础ID
RESOURCE_BASE_RANGE(MY_APP,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)
/***************************************
*MyApp
***************************************/
#define MYAPP_BASE ((U16)RESOURCE_BASE_MYAPP)
#define MYAPP_BASE_MAX ((U16)RESOURCE_BASE_MYAPP_END)
RESOURCE_BASE_TABLEITEM(MYAPP)
...
重点是在RESOURCE_BASE_RANGE(MYAPP,100)这里的100表示我们的程序ID定义不会超过100个(是任何一种类型的资源ID数量都不会超过100,不是所有加起来)。
还有一种资源是跟屏幕历史控制有关,即前面所讲的屏幕的序号,前面没有定义就用的MainMenu的屏幕序号,下面我们就给自己的程序加上屏幕序号(也定义在MyAppDefs.h中):
typedef enmu
{
SCR_MYAPP_MAIN=MYAPP_BASE+1,
}SCREENID_LIST_MYAPP;
下面将我们主程序改过来:
void mmi_myapp_entry(void)
{
EntryNewScreen(SCR_MYAPP_MAIN,NULL,mmmi_myapp_entry,NULL);
...
}
字串资源
将字串”Hello,World”转移到资源中去,并为其添加多国语言版本。
- 字串ID
先在MyAppDefs.h中添加字串ID:
typedef enum
{
STR_MYAPP_HELLO=MYAPP_BASE+1,
}STRINGID_LIST_MYAPP;
- 字串资源
在plutommi\Customer\CustResource\PLUTO_MMI\ref_list.txt中添加一行
(注意添加字符串资源文件时参考已有的字串再进行添加)
此文件中将字串资源与ID对应起来
- 字串装载
在函数PopulateMyAppRes中添加一行:
void PopulateMyAppRes(void)
{
//字串ID,,默认显示,字串描述
ADD_APPLICATION_STRING2(STR_MYAPP_HELLO,"Hello,world","MyApp.");
}
宏ADD_APPLICATION_STRING2用来装载字串
- 字串读取
使用函数GetString可将字串资源读取出来:
void mmi_myapp_entry(void)
{
.....
gui_print_text((UI_string_type)GetString(STR_MYAPP_HELLO));
.....
}
菜单资源
添加新的菜单项,新菜单放在[MainMenu]->[Organizer]->[Hello,World]
- 菜单项ID
//所有菜单的ID都放在头文件plutommi\MMI\Inc\Global\MenuItems.h
enum GLOBALMENUITEMSID
{
IDLE SCREEN_MENU_ID = 1;
.....
.....
//加入自己的菜单ID
MENU_ID_MYAPP_HELLO,
MENU_ID_DEVAPP_START,
MENNU_ID_DEVAPP_END = MENU_ID_DEVAPP_START + 100,
MAX_MENU_ITEMS_VALUE,
MENU_ITEM_END
};
//新菜单ID必须放在MAX_MENU_ITEMS_VALUE之前
- 菜单加载
首先,我们需要将MENU_ID_MYAPP_HELLO加入到Organizer的下级列表中,按照如下方式修改Res_MainMenu.c(main menu及main menu下一级子菜单都在此文件中加载):
typedef enum
{
#if defined(_MMI_CALENDAR_)
ORG_ENUM_CALRNDAR,
#endif
#if defined(_MMI_TODOLIST)
ORG_ENUM_TODOLIST,
#endif
ORG_ENUM_ALRAM,
#ifdefined(_MMI_WORLD_CLOCK_)
ORG_ENUM_WORLDCLOCK,
#endif
#ifdef _MMI_MESSAGES_CLUB_
ORG_ENUM_SERVICE,
#endif
//添加如下代码
#ifdef _MMI_MYAPP_
MENU_ENUM_MYAPP_HELLO,
#endif/*_MMI_MYAPP_*/
ORG_ENUM_TOTAL
}OrganizerMenu;
.....
.....
#if defined(_MMI_VERSION_2_)
void PopulateMainMenuRes(void)
{
.....
/*organizer*/
//用来装载菜单资源
//参数1:新加菜单ID,参数2:新菜单上一级菜单ID,参数3:子菜单总个数,参数4到参数N:每个子菜单项ID
//参数N+1:隐藏属性,一般设为SHOW,参数N+2:菜单项转移属性,参数N+3:下级菜单的显示风格,参数N+4:此菜单项显示文本串ID,参数N+5:此菜单的小图标ID
ADD_APPLICATION_MENUITEM((MAIN_MENU_ORGANIZER_MENUID,IDLE_SCREEN_MENU_ID,ORG_ENUM_TOTAL,
#if defined(_MMI_CALENDAR_)
ORGANIZER_CALENDAR_MENU,
#endif
#if defined(_MMI_TODOLIST_)
ORGANIZER_TODOLIST_MENU,
#endif
ORGANIZER_ALARM_MENU,
#if defined(_MMI_WORLD_CLOCK_)
ORGANIZER_WORLDCLOCK_MENU,
#endif
#ifdef _MMI_MESSAGES_CLUB_
EXTRA_SHORTCUTS_MENUID,
#endif
//添加如下代码
#ifdef _MMI_MYAPP_
MENU_ID_MYAPP_HELLO,
#endif
SHOW,
MOVEABLEWITHINPARENT|INSERTABLE,
DISP_LIST,
MAIN_MENU_ORGANIZER_TEXT,
MAIN_MENU_ORGANIZER_ICON
));
.....
}
然后加载MENU_ID_MYAPP_HELLO本身:
void PopulateMyAPpRes(void)
{
ADD_APPLICATION_STRING2(STR_MYAPP_HELLO,"Hello,World","MyApp.");
ADD_APPLICATION_MENUITEM((MENU_ID_MYAPP_HELLO,MAIN_MENU_ORGANIZER_MENUID,0,SHOW,SHORTCUTABLE,DISP_LIST,STR_MYAPP_HELLO,0))
}
//MENU_ID_MYAPP_HELLO没有下级菜单,所以第三个参数设为0
由于每个菜单项的行为都由菜单项自己控制,系统只能进行高亮显示时发通知过来。每个菜单项都要在开机时告知系统将由哪个函数来接受通知,SetHiliteHandler就是用来做此事的(参数1:菜单ID,参数2:接受通知的函数指针)。我们通常会为每个程序建一个初始化函数,此函数只在开机时运行一次,如下面代码所示的mmi_myapp_init,在此函数中我们会将本程序所有菜单都注册一遍。
在菜单项接受通知函数中,我们通常所做的只有一件事,更改左右软键的响应函数。左软键必须修改,否则菜单项无法进入下一级菜单,右软键可有可无(一般程序都是GOBackHistory)。
修改见MyAppSrc.c
....
void mmi_myapp_hilite_hello(void)
{
SetLeftSoftkeyFunction(mmi_myapp_entry,KEY_EVENT_UP);
}
void mmi_myapp_init(void)
{
SetHiliteHandler(MENU_ID_MYAPP_HELLO,mmi_myapp_hilite_hello);
}
//同时这些函数要在头文件中声明
MyAppProt.h
......
extern void mmi_myapp_hilite_hello(void);
......
MyAppGprot.h
.....
extern void mmi_myapp_init(void);
.....
//需要开机初始化函数mmi_myapp_init,在MMITask.c进行如下修改
.....
#ifdef _MMI_MYAPP_
#include "MyAppGprot.h"
#endif
.....
void InitAllApplications(void)
{
.....
#ifdef _MMI_MYAPP_
mmi_myapp_init();
#endif
...
}
图像资源
- 图像ID
先在MyAppDefs.h中添加字串ID:
typedef enum
{
IMG_MYAPP_HELLO = MYAPP_BASE+1,
}IMAGEID_LIST_MYAPP;
- 新加目录并加入图片
通常在plutommi\Customer\Images\目录下
找到对应屏幕尺寸(例如176X220),找到主屏文件夹MainLCD,创建子文件夹MyApp
plutommi\Customer\Images\PLUTO176X220\MainLCD\MyApp
在该文件夹下添加新图片IMG_MYAPP.bmp
- 装载图片
宏ADD_APPLICATION_IMAGE2用来加载图像资源
void PopulateMyAppRes(void)
{
.....
//参数1:图像ID,参数2:图像路径(路径前加宏CUSTIMG_PATH在运行时自动转换为相应的图像根目录plutommi\Customer\Images\PLUTO176X220\,参数3:对图像的描述)
ADD_APPLICATION_IMAGE2(IMG_MYAPP_HELLO,CUSTIMG_PATH"\\\\MainLCD\\\\MyApp\\\\IMG_MyApp.bmp","Hello World");
.....
}
- 将图片作为菜单ICON
void PopulateMyAppRes(void)
{
.....
//将最后一个参数改为图像资源ID
ADD_APPLICATION_MENUITEM((MENU_IDMY_APP_HELLO,MAIN_MENU_ORGANIZER_MENUID,0,SHOW,SHORTCUTABLE,DISP_LIST,STR_MYAPP_HELLO,IMG_MYAPP_HELLO));
}