BetaFlight模块设计之二十:CMS菜单模块分析
BetaFlight模块设计之二十:CMS菜单模块分析
- CMS菜单模块
- CMS菜单按键控制
- CMS菜单Elements
- CMS_Menu
- OSD_Etnry
- Element类型
- 可调Element类型
- CMS菜单结构
- 第一层菜单(Top Menu)
- 第二层菜单(SubMenu)
- IMU菜单
- FEATURES菜单
- CMS菜单
- FIRMWARE菜单
- MISC菜单
- CMS菜单代码
- 主要函数分析
- cmsHandler
- cmsUpdate
- cmsScanKeys
- cmsHandleKeyWithRepeat
- cmsHandleKey
- 其他函数
- 1. display相关函数
- 2. cmsMenu相关函数
- 3. cmsDrawMenu相关函数
- 4. cmsPage相关函数
基于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;
#endifconst 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 belowuint16_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_tOME_UINT8, //OSD_UINT8_tOME_UINT16, //OSD_UINT16_tOME_INT16, //OSD_INT16_tOME_UINT32, //OSD_UINT32_tOME_INT32, //OSD_INT32_tOME_String, //OSD_String_tOME_FLOAT, //OSD_FLOAT_t
#ifdef USE_OSDOME_VISIBLE,
#endifOME_TAB, //OSD_TAB_t, 列表OME_END,// Debug aidOME_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.h0 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
BetaFlight模块设计之二十:CMS菜单模块分析相关推荐
- BetaFlight模块设计之二十六:接收机任务分析
BetaFlight模块设计之二十六:接收机任务分析 接收机任务 配置情况 硬件配置 软件配置 驱动配置 sbus驱动函数分析 sbusDataReceive函数 sbusFrameStatus函数 ...
- BetaFlight模块设计之二十九:滤波模块分析
BetaFlight模块设计之二十九:滤波模块分析 滤波模块 滤波类型 1. slewFilter 2. simpleLowpassFilter 3. laggedMovingAverage 4. p ...
- BetaFlight模块设计之二十四:transponder任务分析
BetaFlight模块设计之二十四:transponder任务分析 transponder任务 配置情况 硬件配置 驱动配置 业务配置 初始化 MSP协议 三种IR transponder type ...
- PX4模块设计之二十六:BatteryStatus模块
PX4模块设计之二十六:BatteryStatus模块 1. BatteryStatus模块简介 2. 模块入口函数 2.1 主入口battery_status_main 2.2 自定义子命令cust ...
- BetaFlight模块设计之二:SERIAL任务分析
BetaFlight模块设计之二:SERIAL任务分析 SERIAL任务 1.总体情况 2.主要函数分析 2.1 taskHandleSerial任务 2.2 命令行处理函数cliProcess 2. ...
- PX4模块设计之二:uORB消息代理
PX4模块设计之二:uORB消息代理 1. uORB模块接口 1.1. uORB服务接口 1.2. uORB消息注册/去注册接口 1.3. uORB消息发布接口 1.4. uORB消息订阅/去订阅接口 ...
- 【黑金原创教程】【FPGA那些事儿-驱动篇I 】实验二十:SDRAM模块③ — 页读写 α...
实验二十:SDRAM模块③ - 页读写 α 完成单字读写与多字读写以后,接下来我们要实验页读写.丑话当前,实验二十的页读写只是实验性质的东西,其中不存在任何实用价值,笔者希望读者可以把它当成页读写的热 ...
- AD(altium designer)15原理图与PCB设计教程(十)——信号完整性分析
目录 信号完整性简介 信号完整性模型 信号完整性分析的环境设定 信号完整性的设计规则 进行信号完整性的分析 信号完整性分析器 串扰分析 反射分析 声明:该文只适用于学习,其内容包含来自书本的摘抄和总结 ...
- 智能手环功能模块设计_智能手环、功能模块以及智能手表的制作方法
智能手环.功能模块以及智能手表的制作方法 [技术领域] [0001]本实用新型涉及通信领域,具体而言,涉及一种智能手环.功能模块以及智能手表. [背景技术] [0002]伴随着无线通信.传感器和处理器 ...
最新文章
- 15.3. REST
- 计算机程序设计考试题目,计算机程序设计员理论试题(题库)
- LVS NAT/DR
- access开发精要(11)-对象命名规定
- 分组中查询不符合条件的组
- UICollectionView 具体解说学习
- iptables简单应用
- 【WebRTC---进阶篇】(二)libevent实现高性能网络服务器
- Javascript基础 对象与方法的识别
- Qt4_深入信号和槽
- 想成为嵌入式程序员应知道的0x10个基本问题——转
- python学习多久可以就业_南昌多长时间能学会python(Python能去从事的工作)
- python语法简洁清晰、特色之一是强制用作为语句缩进_问道python之基础篇【一】 认识python...
- WordPress站点上传文件插件WordPress File Upload
- python 函数注释出现 :expected an indented block
- 李居明 饿火命(3)
- 白苹果了怎么办_iOS 13如何降级?iOS13降级失败怎么办?
- Android 注解处理器使用攻略
- 水瓶的全球与中国市场2022-2028年:技术、参与者、趋势、市场规模及占有率研究报告
- Paper写作查重需要注意哪些问题?