BetaFlight模块设计之十六:OSD更新任务分析

Posted lida2003

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了BetaFlight模块设计之十六:OSD更新任务分析相关的知识,希望对你有一定的参考价值。

BetaFlight模块设计之十六:OSD更新任务分析

基于BetaFlight开源代码框架简介的框架设计,逐步分析内部模块功能设计。

OSD更新任务

描述:主要用于在摄像头视频上增加一层飞控数据展示层,也通常叫做OSD Layer上显示一些FC参数,比如:高度,速度等。

 ├──> 初始化
 │   ├──> [v]硬件初始化max7456DisplayPortInit/max7456PreInit/osdInit
 │   └──> [v]业务初始化pgResetFn_osdConfig
 ├──> 任务
 │   ├──> [x]实时任务
 │   ├──> [v]事件任务[TASK_OSD] = DEFINE_TASK("OSD", NULL, osdUpdateCheck, osdUpdate, TASK_PERIOD_HZ(OSD_FRAMERATE_DEFAULT_HZ), TASK_PRIORITY_LOW),
 │   └──> [x]时间任务
 ├──> 驱动
 │   ├──> [x]查询
 │   └──> [x]中断
 └──> 接口
     └──> 支持[FRSKYOSD](https://www.frsky-rc.com/wp-content/uploads/Downloads/Manual/OSD/OSD%20OSD%20Mini-Manual.pdf)[MAX7456](https://datasheets.maximintegrated.com/en/ds/MAX7456.pdf)MSP三种OSD显示方式

机型配置

在机型上宏定义使用MAX7456芯片,作为在视频信号上叠加OSD的处理芯片。

\\src\\main\\target\\KAKUTEF7\\target.h
#define USE_MAX7456
#define MAX7456_SPI_INSTANCE    SPI2
#define MAX7456_SPI_CS_PIN      SPI2_NSS_PIN

驱动上对默认的SPI做了默认配置。

\\src\\main\\pg\\max7456.c
PG_REGISTER_WITH_RESET_FN(max7456Config_t, max7456Config, PG_MAX7456_CONFIG, 0);

void pgResetFn_max7456Config(max7456Config_t *config)

    config->clockConfig = MAX7456_CLOCK_CONFIG_NOMINAL;
    config->csTag = IO_TAG(MAX7456_SPI_CS_PIN);
    config->spiDevice = SPI_DEV_TO_CFG(spiDeviceByInstance(MAX7456_SPI_INSTANCE));
    config->preInitOPU = false;

在驱动和业务之间增加了类似OSD适配层,对驱动进行了封装。

\\src\\main\\io\\displayport_max7456.c
static const displayPortVTable_t max7456VTable = 
    .grab = grab,
    .release = release,
    .clearScreen = clearScreen,
    .drawScreen = drawScreen,
    .screenSize = screenSize,
    .writeString = writeString,
    .writeChar = writeChar,
    .isTransferInProgress = isTransferInProgress,
    .heartbeat = heartbeat,
    .redraw = redraw,
    .isSynced = isSynced,
    .txBytesFree = txBytesFree,
    .layerSupported = layerSupported,
    .layerSelect = layerSelect,
    .layerCopy = layerCopy,
    .writeFontCharacter = writeFontCharacter,
    .checkReady = checkReady,
    .setBackgroundType = setBackgroundType,
;

osdUpdateCheck函数分析

这里是稳态OSD_STATE_IDLE,周期进入OSD_STATE_CHECK状态的触发地。

osdUpdateCheck
 ├──> static timeUs_t osdUpdateDueUs = 0;
 ├──> <osdState == OSD_STATE_IDLE>
 │   └──> <cmpTimeUs(currentTimeUs, osdUpdateDueUs)>
 │       ├──> osdState = OSD_STATE_CHECK;   //超过OSD_UPDATE_INTERVAL_US时间后,触发状态机进入check模式
 │       ├──> <osdUpdateDueUs>
 │       │   └──> osdUpdateDueUs += OSD_UPDATE_INTERVAL_US;
 │       └──> <!osdUpdateDueUs>
 │           └──>  osdUpdateDueUs = currentTimeUs + OSD_UPDATE_INTERVAL_US;
 └──> return (osdState != OSD_STATE_IDLE);

osdUpdate函数分析

不管是从OSD_STATE_INIT触发,还是从OSD_STATE_CHECK触发的业务,最终将归于稳态OSD_STATE_IDLE,详见下面状态机分析。

osdUpdate
 ├──> <osdState != OSD_STATE_UPDATE_CANVAS>
 │   └──> schedulerIgnoreTaskExecRate
 ├──> switch (osdState)
 │   ├──> <OSD_STATE_INIT>
 │   │   └──> 状态机业务,略。。。
 │   ├──> <OSD_STATE_CHECK>
 │   │   └──> 状态机业务,略。。。
 │   ├──> <OSD_STATE_UPDATE_HEARTBEAT>
 │   │   └──> 状态机业务,略。。。
 │   ├──> <OSD_STATE_PROCESS_STATS1>
 │   │   └──> 状态机业务,略。。。
 │   ├──> <OSD_STATE_REFRESH_STATS>
 │   │   └──> 状态机业务,略。。。
 │   ├──> <OSD_STATE_PROCESS_STATS2>
 │   │   └──> 状态机业务,略。。。
 │   ├──> <OSD_STATE_PROCESS_STATS3>
 │   │   └──> 状态机业务,略。。。
 │   ├──> <OSD_STATE_UPDATE_ALARMS>
 │   │   └──> 状态机业务,略。。。
 │   ├──> <OSD_STATE_UPDATE_CANVAS>
 │   │   └──> 状态机业务,略。。。
 │   ├──> <OSD_STATE_GROUP_ELEMENTS>
 │   │   └──> 状态机业务,略。。。
 │   ├──> <OSD_STATE_UPDATE_ELEMENTS>
 │   │   └──> 状态机业务,略。。。
 │   ├──> <OSD_STATE_COMMIT>
 │   │   └──> 状态机业务,略。。。
 │   ├──> <OSD_STATE_TRANSFER>
 │   │   └──> 状态机业务,略。。。
 │   └──> <OSD_STATE_IDLE>
 │       └──> 状态机业务,略。。。
 ├──> <!schedulerGetIgnoreTaskExecTime()>
 │   ├──> executeTimeUs = micros() - currentTimeUs;
 │   └──> <!firstPass>
 │       ├──> <osdCurrentState == OSD_STATE_UPDATE_ELEMENTS>
 │       │   ├──> <executeTimeUs > (osdElementGroupDurationFractionUs[osdCurrentElementGroup] >> OSD_EXEC_TIME_SHIFT)>
 │       │   │   └──> osdElementGroupDurationFractionUs[osdCurrentElementGroup] = executeTimeUs << OSD_EXEC_TIME_SHIFT;
 │       │   └──> <osdElementGroupDurationFractionUs[osdCurrentElementGroup] > 0>
 │       │       └──> osdElementGroupDurationFractionUs[osdCurrentElementGroup]--;
 │       ├──> <executeTimeUs > (osdStateDurationFractionUs[osdCurrentState] >> OSD_EXEC_TIME_SHIFT)>
 │       │   └──> osdStateDurationFractionUs[osdCurrentState] = executeTimeUs << OSD_EXEC_TIME_SHIFT;
 │       └──> <osdStateDurationFractionUs[osdCurrentState] > 0>
 │           └──> osdStateDurationFractionUs[osdCurrentState]--;
 ├──> <osdState == OSD_STATE_UPDATE_ELEMENTS>
 │   └──> schedulerSetNextStateTime((osdElementGroupDurationFractionUs[osdElementGroup] >> OSD_EXEC_TIME_SHIFT) + OSD_ELEMENT_RENDER_GROUP_MARGIN);
 └──> <!(osdState == OSD_STATE_UPDATE_ELEMENTS)>
     ├──> <osdState == OSD_STATE_IDLE>
     │   └──> schedulerSetNextStateTime((osdStateDurationFractionUs[OSD_STATE_CHECK] >> OSD_EXEC_TIME_SHIFT) + OSD_TASK_MARGIN);
     └──> <!osdState == OSD_STATE_IDLE>
         └──> schedulerSetNextStateTime((osdStateDurationFractionUs[osdState] >> OSD_EXEC_TIME_SHIFT) + OSD_TASK_MARGIN);

OSD Display分析回顾

根据上面的分析,貌似代码历史比较悠久,有几个抽象的概念:

  1. 机型硬件配置USE_MAX7456(\\src\\main\\target\\KAKUTEF7\\target.h)
  2. 框架代码Init初始化,使用了OSD_DISPLAYPORT_DEVICE概念的初始化函数max7456DisplayPortInit(\\src\\main\\io\\displayport_max7456.c)
  3. OSD_DISPLAYPORT_DEVICE抽象了OSD接口static const displayPortVTable_t max7456VTable(\\src\\main\\io\\displayport_max7456.c)
  4. OSD_DISPLAYPORT_DEVICE根据OSD接口封装硬件驱动(\\src\\main\\io\\displayport_max7456.c)
  5. 驱动代码(\\src\\main\\drivers\\max7456.c)

如果有机会,可以尝试一下以下尝试(目的:解耦和简化业务)

  1. 【重构】OSD_DISPLAYPORT状态机业务抽象
  2. 【优化】OSD_DISPLAYPORT标准API抽象
  3. 【重构】OSD_DISPLAYPORT_DEVICE可以考虑适配层,机型硬件配置在板级启动过程进行适配层注册
  4. 【优化】硬件驱动代码
  5. 【优化】框架代码调整

以上是关于BetaFlight模块设计之十六:OSD更新任务分析的主要内容,如果未能解决你的问题,请参考以下文章

BetaFlight模块设计之十二:电传任务分析

BetaFlight模块设计之十三:Gyro过滤任务分析

BetaFlight模块设计之十四:高度计算任务分析

BetaFlight模块设计之十七:pinioBox任务分析

BetaFlight模块设计之十:磁力计任务分析

BetaFlight模块设计之十八:图传模块同步任务分析