BetaFlight模块设计之二十:CMS菜单模块分析
基于BetaFlight开源代码框架简介的框架设计,逐步分析内部模块功能设计。
CMS菜单模块
描述:将BetaFlight所有配置内容在不同的设备上进行CMS(Custom Menu System)菜单显示。
├──> 初始化
│ ├──> [x]硬件初始化
│ └──> [v]业务初始化cmsInit
├──> 任务
│ ├──> [x]实时任务
│ ├──> [x]事件任务
│ └──> [v]时间任务[TASK_CMS] = DEFINE_TASK("CMS", NULL, NULL, cmsHandler, TASK_PERIOD_HZ(20), TASK_PRIORITY_LOW),
├──> 驱动
│ ├──> [x]查询
│ └──> [x]中断
└──> 接口
├──> bool cmsDisplayPortRegister(displayPort_t *pDisplay); //注册显示CMS菜单的设备,可以注册多个。
├──> void cmsMenuOpen(void); //打开菜单
├──> const void *cmsMenuChange(displayPort_t *pPort, const void *ptr); //进入子菜单
├──> const void *cmsMenuExit(displayPort_t *pPort, const void *ptr); //退出菜单
├──> void cmsAddMenuEntry(OSD_Entry *menuEntry, char *text, uint16_t flags, CMSEntryFuncPtr func, void *data);
└──> void cmsSetExternKey(cms_key_e extKey);
注:多菜单显示设备支持,详见支持的设备类型。
typedef enum {
DISPLAYPORT_DEVICE_TYPE_MAX7456 = 0,
DISPLAYPORT_DEVICE_TYPE_OLED,
DISPLAYPORT_DEVICE_TYPE_MSP,
DISPLAYPORT_DEVICE_TYPE_FRSKYOSD,
DISPLAYPORT_DEVICE_TYPE_CRSF,
DISPLAYPORT_DEVICE_TYPE_HOTT,
DISPLAYPORT_DEVICE_TYPE_SRXL,
} displayPortDeviceType_e;
CMS菜单按键控制
- 打开菜单:IS_MID(THROTTLE) && IS_LO(YAW) && IS_HI(PITCH)
- 进入菜单:IS_MID(THROTTLE) && IS_LO(YAW) && IS_HI(PITCH) //菜单打开的情况下
- 向上翻页: IS_HI(PITCH)
- 向下翻页:IS_LO(PITCH)
- 向左翻页:IS_LO(ROLL)
- 向右翻页:IS_HI(ROLL)
- 退出选项:IS_LO(YAW)
- 保存菜单:IS_HI(YAW)
CMS菜单Elements
整个菜单从组成结构上由elements构成,分为两大类:CMS_Menu、OSD_Etnry。
CMS_Menu
typedef const void *(*CMSMenuFuncPtr)(displayPort_t *pDisp);
typedef const void *(*CMSMenuOnExitPtr)(displayPort_t *pDisp, const OSD_Entry *self);
typedef const void *(*CMSMenuOnDisplayUpdatePtr)(displayPort_t *pDisp, const OSD_Entry *selected);
typedef struct
{
#ifdef CMS_MENU_DEBUG
// These two are debug aids for menu content creators.
const char *GUARD_text;
const OSD_MenuElement GUARD_type;
#endif
const CMSMenuFuncPtr onEnter;
const CMSMenuOnExitPtr onExit;
const CMSMenuOnDisplayUpdatePtr onDisplayUpdate;
const OSD_Entry *entries;
} CMS_Menu;
OSD_Etnry
typedef const void *(*CMSEntryFuncPtr)(displayPort_t *displayPort, const void *ptr);
typedef struct
{
const char * text;
// Logical OR of OSD_MenuElement and flags below
uint16_t flags;
CMSEntryFuncPtr func;
void *data;
} __attribute__((packed)) OSD_Entry;
Element类型
各种类型对应的菜单操作详见cmsHandleKey函数。
typedef enum
{
OME_Label, //标记字符串
OME_Back, //返回前级菜单
OME_OSD_Exit,
OME_Submenu, //子菜单类型
OME_Funcall, //执行函数
OME_Bool,
OME_INT8, //OSD_INT8_t
OME_UINT8, //OSD_UINT8_t
OME_UINT16, //OSD_UINT16_t
OME_INT16, //OSD_INT16_t
OME_UINT32, //OSD_UINT32_t
OME_INT32, //OSD_INT32_t
OME_String, //OSD_String_t
OME_FLOAT, //OSD_FLOAT_t
#ifdef USE_OSD
OME_VISIBLE,
#endif
OME_TAB, //OSD_TAB_t, 列表
OME_END,
// Debug aid
OME_MENU,
OME_MAX = OME_MENU
} OSD_MenuElement;
可调Element类型
typedef struct
{
uint8_t *val;
uint8_t min;
uint8_t max;
uint8_t step;
} OSD_UINT8_t;
typedef struct
{
int8_t *val;
int8_t min;
int8_t max;
int8_t step;
} OSD_INT8_t;
typedef struct
{
int16_t *val;
int16_t min;
int16_t max;
int16_t step;
} OSD_INT16_t;
typedef struct
{
uint16_t *val;
uint16_t min;
uint16_t max;
uint16_t step;
} OSD_UINT16_t;
typedef struct
{
int32_t *val;
int32_t min;
int32_t max;
int32_t step;
} OSD_INT32_t;
typedef struct
{
uint32_t *val;
uint32_t min;
uint32_t max;
uint32_t step;
} OSD_UINT32_t;
typedef struct
{
uint8_t *val;
uint8_t min;
uint8_t max;
uint8_t step;
uint16_t multipler;
} OSD_FLOAT_t;
typedef struct
{
uint8_t *val;
uint8_t max;
const char * const *names;
} OSD_TAB_t;
CMS菜单结构
第一层菜单(Top Menu)
CMS_Menu cmsx_menuMain = {
#ifdef CMS_MENU_DEBUG
.GUARD_text = "MENUMAIN",
.GUARD_type = OME_MENU,
#endif
.onEnter = mainMenuOnEnter,
.onExit = NULL,
.onDisplayUpdate = NULL,
.entries = menuMainEntries,
};
static const OSD_Entry menuMainEntries[] =
{
{"-- MAIN --", OME_Label, NULL, NULL},
{"PROFILE", OME_Submenu, cmsMenuChange, &cmsx_menuImu},
{"FEATURES", OME_Submenu, cmsMenuChange, &cmsx_menuFeatures},
#ifdef USE_OSD
{"OSD", OME_Submenu, cmsMenuChange, &cmsx_menuOsd},
#endif
{"FC&FIRMWARE", OME_Submenu, cmsMenuChange, &cmsx_menuFirmware},
{"MISC", OME_Submenu, cmsMenuChange, &cmsx_menuMisc},
{"SAVE/EXIT", OME_Funcall, cmsx_SaveExitMenu, NULL},
{NULL, OME_END, NULL, NULL},
};
第二层菜单(SubMenu)
IMU菜单
CMS_Menu cmsx_menuImu = {
#ifdef CMS_MENU_DEBUG
.GUARD_text = "XIMU",
.GUARD_type = OME_MENU,
#endif
.onEnter = cmsx_menuImu_onEnter,
.onExit = cmsx_menuImu_onExit,
.onDisplayUpdate = NULL,
.entries = cmsx_menuImuEntries,
};
static const OSD_Entry cmsx_menuImuEntries[] =
{
{ "-- PROFILE --", OME_Label, NULL, NULL},
{"PID PROF", OME_UINT8, cmsx_profileIndexOnChange, &(OSD_UINT8_t){ &tmpPidProfileIndex, 1, PID_PROFILE_COUNT, 1}},
{"PID", OME_Submenu, cmsMenuChange, &cmsx_menuPid},
#ifdef USE_SIMPLIFIED_TUNING
{"SIMPLIFIED TUNING", OME_Submenu, cmsMenuChange, &cmsx_menuSimplifiedTuning},
#endif
{"MISC PP", OME_Submenu, cmsMenuChange, &cmsx_menuProfileOther},
{"FILT PP", OME_Submenu, cmsMenuChange, &cmsx_menuFilterPerProfile},
{"RATE PROF", OME_UINT8, cmsx_rateProfileIndexOnChange, &(OSD_UINT8_t){ &tmpRateProfileIndex, 1, CONTROL_RATE_PROFILE_COUNT, 1}},
{"RATE", OME_Submenu, cmsMenuChange, &cmsx_menuRateProfile},
{"FILT GLB", OME_Submenu, cmsMenuChange, &cmsx_menuFilterGlobal},
#if (defined(USE_DYN_NOTCH_FILTER) || defined(USE_DYN_LPF)) && defined(USE_EXTENDED_CMS_MENUS)
{"DYN FILT", OME_Submenu, cmsMenuChange, &cmsx_menuDynFilt},
#endif
#ifdef USE_EXTENDED_CMS_MENUS
{"COPY PROF", OME_Submenu, cmsMenuChange, &cmsx_menuCopyProfile},
#endif /* USE_EXTENDED_CMS_MENUS */
{"BACK", OME_Back, NULL, NULL},
{NULL, OME_END, NULL, NULL}
};
FEATURES菜单
static CMS_Menu cmsx_menuFeatures = {
#ifdef CMS_MENU_DEBUG
.GUARD_text = "MENUFEATURES",
.GUARD_type = OME_MENU,
#endif
.onEnter = NULL,
.onExit = NULL,
.onDisplayUpdate = NULL,
.entries = menuFeaturesEntries,
};
static const OSD_Entry menuFeaturesEntries[] =
{
{"--- FEATURES ---", OME_Label, NULL, NULL},
#if defined(USE_BLACKBOX)
{"BLACKBOX", OME_Submenu, cmsMenuChange, &cmsx_menuBlackbox},
#endif
#if defined(USE_VTX_CONTROL)
#if defined(USE_VTX_RTC6705) || defined(USE_VTX_SMARTAUDIO) || defined(USE_VTX_TRAMP)
{"VTX", OME_Funcall, cmsSelectVtx, NULL},
#endif
#endif // VTX_CONTROL
#ifdef USE_LED_STRIP
{"LED STRIP", OME_Submenu, cmsMenuChange, &cmsx_menuLedstrip},
#endif // LED_STRIP
{"POWER", OME_Submenu, cmsMenuChange, &cmsx_menuPower},
#ifdef USE_CMS_FAILSAFE_MENU
{"FAILSAFE", OME_Submenu, cmsMenuChange, &cmsx_menuFailsafe},
#endif
#ifdef USE_PERSISTENT_STATS
{"PERSISTENT STATS", OME_Submenu, cmsMenuChange, &cmsx_menuPersistentStats},
#endif
{"BACK", OME_Back, NULL, NULL},
{NULL, OME_END, NULL, NULL}
};
CMS菜单
CMS_Menu cmsx_menuOsd = {
#ifdef CMS_MENU_DEBUG
.GUARD_text = "MENUOSD",
.GUARD_type = OME_MENU,
#endif
.onEnter = cmsx_menuOsdOnEnter,
.onExit = cmsx_menuOsdOnExit,
.onDisplayUpdate = NULL,
.entries = cmsx_menuOsdEntries
};
const OSD_Entry cmsx_menuOsdEntries[] =
{
{"---OSD---", OME_Label, NULL, NULL},
#ifdef USE_OSD_PROFILES
{"OSD PROFILE", OME_UINT8, NULL, &(OSD_UINT8_t){&osdConfig_osdProfileIndex, 1, 3, 1}},
#endif
#ifdef USE_EXTENDED_CMS_MENUS
{"ACTIVE ELEM", OME_Submenu, cmsMenuChange, &menuOsdActiveElems},
{"TIMERS", OME_Submenu, cmsMenuChange, &menuTimers},
{"ALARMS", OME_Submenu, cmsMenuChange, &menuAlarms},
#endif
#ifdef USE_MAX7456
{"INVERT", OME_Bool, cmsx_max7456Update, &displayPortProfileMax7456_invert},
{"BRT BLACK", OME_UINT8, cmsx_max7456Update, &(OSD_UINT8_t){&displayPortProfileMax7456_blackBrightness, 0, 3, 1}},
{"BRT WHITE", OME_UINT8, cmsx_max7456Update, &(OSD_UINT8_t){&displayPortProfileMax7456_whiteBrightness, 0, 3, 1}},
#endif
{"BACKGROUND",OME_TAB, cmsx_osdBackgroundUpdate, &(OSD_TAB_t){&osdMenuBackgroundType, DISPLAY_BACKGROUND_COUNT - 1, lookupTableCMSMenuBackgroundType}},
{"BACK", OME_Back, NULL, NULL},
{NULL, OME_END, NULL, NULL}
};
FIRMWARE菜单
CMS_Menu cmsx_menuFirmware = {
#ifdef CMS_MENU_DEBUG
.GUARD_text = "MENUFIRMWARE",
.GUARD_type = OME_MENU,
#endif
#if defined(USE_BOARD_INFO)
.onEnter = cmsx_FirmwareInit,
#else
.onEnter = NULL,
#endif
.onExit = NULL,
.onDisplayUpdate = NULL,
.entries = menuFirmwareEntries
};
static const OSD_Entry menuFirmwareEntries[] = {
{ "--- INFO ---", OME_Label, NULL, NULL },
{ "FWID", OME_String, NULL, FC_FIRMWARE_IDENTIFIER },
{ "FWVER", OME_String, NULL, FC_VERSION_STRING },
{ "GITREV", OME_String, NULL, __REVISION__ },
{ "TARGET", OME_String, NULL, __TARGET__ },
#if defined(USE_BOARD_INFO)
{ "MFR", OME_String, NULL, manufacturerId },
{ "BOARD", OME_String, NULL, boardName },
#endif
{ "--- SETUP ---", OME_Label, NULL, NULL },
{ "CALIBRATE", OME_Submenu, cmsMenuChange, &cmsx_menuCalibration},
{ "BACK", OME_Back, NULL, NULL },
{ NULL, OME_END, NULL, NULL}
};
MISC菜单
CMS_Menu cmsx_menuMisc = {
#ifdef CMS_MENU_DEBUG
.GUARD_text = "XMISC",
.GUARD_type = OME_MENU,
#endif
.onEnter = cmsx_menuMiscOnEnter,
.onExit = cmsx_menuMiscOnExit,
.onDisplayUpdate = NULL,
.entries = menuMiscEntries
};
static const OSD_Entry menuMiscEntries[]=
{
{ "-- MISC --", OME_Label, NULL, NULL },
{ "MIN THR", OME_UINT16 | REBOOT_REQUIRED, NULL, &(OSD_UINT16_t){ &motorConfig_minthrottle, 1000, 2000, 1 } },
{ "DIGITAL IDLE", OME_UINT8 | REBOOT_REQUIRED, NULL, &(OSD_UINT8_t) { &motorConfig_digitalIdleOffsetValue, 0, 200, 1 } },
{ "FPV CAM ANGLE", OME_UINT8, NULL, &(OSD_UINT8_t) { &rxConfig_fpvCamAngleDegrees, 0, 90, 1 } },
{ "RC PREV", OME_Submenu, cmsMenuChange, &cmsx_menuRcPreview},
{ "BACK", OME_Back, NULL, NULL},
{ NULL, OME_END, NULL, NULL}
};
注:只要是OME_Submenu类型的OSD_Entry就一直能通过cmsMenuChange进入下级菜单。
CMS菜单代码
所有菜单相关代码在src/main/cms/路径下。
src/main/cms/
├── cms_types.h //CMS菜单相关类型定义
├── cms.c //CMS菜单控制操作
├── cms.h //CMS菜单模块对外接口定义
├── cms_menu_blackbox.c
├── cms_menu_blackbox.h
├── cms_menu_failsafe.c
├── cms_menu_failsafe.h
├── cms_menu_firmware.c
├── cms_menu_firmware.h
├── cms_menu_gps_rescue.c
├── cms_menu_gps_rescue.h
├── cms_menu_imu.c
├── cms_menu_imu.h
├── cms_menu_ledstrip.c
├── cms_menu_ledstrip.h
├── cms_menu_main.c
├── cms_menu_main.h
├── cms_menu_misc.c
├── cms_menu_misc.h
├── cms_menu_osd.c
├── cms_menu_osd.h
├── cms_menu_persistent_stats.c
├── cms_menu_persistent_stats.h
├── cms_menu_power.c
├── cms_menu_power.h
├── cms_menu_saveexit.c
├── cms_menu_saveexit.h
├── cms_menu_vtx_common.c
├── cms_menu_vtx_common.h
├── cms_menu_vtx_rtc6705.c
├── cms_menu_vtx_rtc6705.h
├── cms_menu_vtx_smartaudio.c
├── cms_menu_vtx_smartaudio.h
├── cms_menu_vtx_tramp.c
└── cms_menu_vtx_tramp.h
0 directories, 35 files
主要函数分析
cmsHandler
排除没有CMS菜单显示设备的情况。
cmsHandler
└──> <cmsDeviceCount > 0>
└──> cmsUpdate(currentTimeUs);
cmsUpdate
排除FC瘫痪模式、RunCam设置模式、USB连上电脑场景;并根据cmsInMenu是否已经打开菜单进行逻辑判断。
cmsUpdate
├──> <IS_RC_MODE_ACTIVE(BOXPARALYZE)> //FC瘫痪模式
│ └──> return
├──> <USE_RCDEVICE><rcdeviceInMenu> //已进入RunCam设置菜单
│ └──> return
├──> <USE_USB_CDC_HID><cdcDeviceIsMayBeActive> //USB连上电脑(配置或者模拟器等使用方式无需CMS菜单)
│ └──> return
├──> <!cmsInMenu> //Detect menu invocation
│ └──> <IS_MID(THROTTLE) && IS_LO(YAW) && IS_HI(PITCH) && !ARMING_FLAG(ARMED) && !IS_RC_MODE_ACTIVE(BOXSTICKCOMMANDDISABLE)>
│ ├──> cmsMenuOpen
│ └──> rcDelayMs = BUTTON_PAUSE; // Tends to overshoot if BUTTON_TIME(500ms)
├──> <cmsInMenu>
│ ├──> displayBeginTransaction(pCurrentDisplay, DISPLAY_TRANSACTION_OPT_RESET_DRAWING);
│ ├──> rcDelayMs = cmsScanKeys(currentTimeMs, lastCalledMs, rcDelayMs);
│ ├──> cmsDrawMenu(pCurrentDisplay, currentTimeUs);
│ ├──> <currentTimeMs > lastCmsHeartBeatMs + 500>
│ │ ├──> displayHeartbeat(pCurrentDisplay);
│ │ └──> lastCmsHeartBeatMs = currentTimeMs;
│ └──> displayCommitTransaction(pCurrentDisplay);
└──> lastCalledMs = millis();
cmsScanKeys
在已经打开菜单场景下,对按键进行扫描并做简单按键延时、消抖处理。
注:对于Graupner HoTT protocol 特殊处理,采用externKey来做判断。
cmsScanKeys
├──> <externKey != CMS_KEY_NONE>
│ ├──> rcDelayMs = cmsHandleKey(pCurrentDisplay, externKey);
│ └──> externKey = CMS_KEY_NONE;
├──> <externKey == CMS_KEY_NONE>
│ ├──> <IS_MID(THROTTLE) && IS_LO(YAW) && IS_HI(PITCH) && !ARMING_FLAG(ARMED)>
│ │ └──> key = CMS_KEY_MENU;
│ ├──> <IS_HI(PITCH)>
│ │ └──> key = CMS_KEY_UP;
│ ├──> <IS_LO(PITCH)>
│ │ └──> key = CMS_KEY_DOWN;
│ ├──> <IS_LO(ROLL)>
│ │ └──> key = CMS_KEY_LEFT;
│ ├──> <IS_HI(ROLL)>
│ │ └──> key = CMS_KEY_RIGHT;
│ ├──> <IS_LO(YAW)>
│ │ └──> key = CMS_KEY_ESC;
│ ├──> <IS_HI(YAW)>
│ │ └──> key = CMS_KEY_SAVEMENU;
│ ├──> <key == CMS_KEY_NONE> // No 'key' pressed, reset repeat control
│ │ └──> holdCount = 1;repeatCount = 1;repeatBase = 0;
│ ├──> <key != CMS_KEY_NONE> // The 'key' is being pressed; keep counting
│ │ └──> ++holdCount;
│ ├──> <rcDelayMs > 0>
│ │ └──> rcDelayMs -= (currentTimeMs - lastCalledMs); //处理按键时长
│ └──> <rcDelayMs <= 0 && key> //按键时长满足条件,并确实有按键按下(以最后时间检测到的按键为准)
│ ├──> rcDelayMs = cmsHandleKeyWithRepeat(pCurrentDisplay, key, repeatCount);
│ └──> [repeatCount] ajustments //根据CMS_KEY_LEFT、CMS_KEY_RIGHT键hold时长来提供repeatCount值,最多调整不超过5
└──> return rcDelayMs;
cmsHandleKeyWithRepeat
主要处理CMS_KEY_LEFT、CMS_KEY_RIGHT键hold时长来提供多次连续按键事件。
cmsHandleKeyWithRepeat
└──> <for (int i = 0 ; i < repeatCount ; i++)>
└──> ret = cmsHandleKey(pDisplay, key); // return ret
cmsHandleKey
按键对CMS Elements的实际操作。
cmsHandleKey
├──> <!currentCtx.menu>
│ └──> return BUTTON_TIME
├──> <key == CMS_KEY_MENU>
│ ├──> cmsMenuOpen
│ └──> return BUTTON_PAUSE;
├──> <key == CMS_KEY_ESC>
│ ├──> <osdElementEditing>
│ │ └──> osdElementEditing = false;
│ ├──> <!osdElementEditing>
│ │ └──> cmsMenuBack
│ └──> return BUTTON_PAUSE;
├──> <key == CMS_KEY_SAVEMENU && !saveMenuInhibited>
│ ├──> osdElementEditing = false;
│ ├──> cmsMenuChange
│ └──> return BUTTON_PAUSE;
├──> <(key == CMS_KEY_DOWN) && (!osdElementEditing)>
│ ├──> <currentCtx.cursorRow < pageMaxRow>
│ │ └──> currentCtx.cursorRow++;
│ └──> <currentCtx.cursorRow >= pageMaxRow>
│ ├──> cmsPageNext
│ └──> currentCtx.cursorRow = 0; // Goto top in any case
├──> <(key == CMS_KEY_UP) && (!osdElementEditing)>
│ ├──> currentCtx.cursorRow--;
│ ├──> <(rowIsSkippable(pageTop + currentCtx.cursorRow)) && currentCtx.cursorRow > 0> // Skip non-title labels, strings and dynamic read-only entries
│ │ └──> currentCtx.cursorRow--;
│ └──> <currentCtx.cursorRow == -1 || ((pageTop + currentCtx.cursorRow)->flags & OSD_MENU_ELEMENT_MASK) == OME_Label>
│ ├──> cmsPagePrev(pDisplay);
│ └──> currentCtx.cursorRow = pageMaxRow;
├──> <(key == CMS_KEY_DOWN || key == CMS_KEY_UP) && (!osdElementEditing)>
│ └──> return BUTTON_TIME
├──> p = pageTop + currentCtx.cursorRow;
├──> <p->flags & OSD_MENU_ELEMENT_MASK>
│ ├──> <OME_Submenu>
│ │ └──> ... //Element类型操作, cmsMenuChange
│ ├──> <OME_Funcall>
│ │ └──> ... //Element类型操作, cmsMenuBack/setRebootRequired
│ ├──> <OME_OSD_Exit>
│ │ └──> ... //Element类型操作
│ ├──> <OME_Back>
│ │ └──> ... //Element类型操作, cmsMenuBack
│ ├──> <OME_Bool>
│ │ └──> ... //Element类型操作, setRebootRequired
│ ├──> <OME_VISIBLE><USE_OSD>
│ │ └──> ... //Element类型操作, setRebootRequired
│ ├──> <OME_UINT8>
│ │ └──> ... //Element类型操作, setRebootRequired
│ ├──> <OME_TAB>
│ │ └──> ... //Element类型操作, setRebootRequired
│ ├──> <OME_INT8>
│ │ └──> ... //Element类型操作, setRebootRequired
│ ├──> <OME_UINT16>
│ │ └──> ... //Element类型操作, setRebootRequired
│ ├──> <OME_INT16>
│ │ └──> ... //Element类型操作, setRebootRequired
│ ├──> <OME_UINT32>
│ │ └──> ... //Element类型操作, setRebootRequired
│ ├──> <OME_INT32>
│ │ └──> ... //Element类型操作, setRebootRequired
│ ├──> <OME_String>
│ │ └──> ... //Element类型操作
│ ├──> <OME_Label>
│ │ └──> ... //Element类型操作
│ ├──> <OME_END>
│ │ └──> ... //Element类型操作
│ └──> <OME_MENU> // Shouldn't happen
└──> return res
其他函数
关于细节函数的分析,有兴趣可以深入挖掘,如以下函数:
1. display相关函数
- displayBeginTransaction
- displayHeartbeat
- displayCommitTransaction
2. cmsMenu相关函数
- cmsMenuOpen
- cmsMenuExit
3. cmsDrawMenu相关函数
- cmsDrawMenuEntry
- cmsDrawMenu
- cmsDrawMenuItemValue
4. cmsPage相关函数
- cmsPageSelect
- cmsPageNext
- cmsPagePrev