BetaFlight飞控启动&运行过程简介

  • 1. 源由
  • 2. 启动过程
    • 2.1 main(主程序)
    • 2.2 init (初始化)
    • 2.3 run
  • 3. 任务调度
    • 3.1 任务定义
    • 3.2 scheduler (调度器)
  • 4. 总结
  • 5. 参考资料
  • 6. 附录 -- 问题汇总
    • 6.1 Why desiredPeriodCycles is so important to Betaflight task?
    • 6.2 What root cause has made gyro task to been overrun, so scheduler has to skip a gyro cycle?
    • 6.3 What if serial task is running quite long, which corrupt gyro cycle, is it a problem?
    • 6.4 What does the condition of "scheduleCount & SCHED_TASK_DEFER_MASK == 0" stand for in scheduler?

1. 源由

BetaFlight经过工程结构、代码框架、传感模块框架、统一硬件配置文件这一路的研读,让我们对BetaFlight飞控系统更多的了解。

  • BetaFlight开源工程结构简明介绍
  • BetaFlight开源代码框架简介
  • BetaFlight深入传感设计:传感模块设计框架
  • BetaFlight统一硬件配置文件研读

逐步领悟到结构化设计的优势的同时,为了更好的理解整体系统如何融合在一起,我们将会进入本章BetaFlight飞控启动和运行过程,这也是SOC系统初始化的主轴。

2. 启动过程

作为C语言级别启动过程的入口,相信大家都很容易就想到了Linux应用程序之Helloworld入门。

因此,我们这里就是从main入口开始分析的,详见下面代码走读。

2.1 main(主程序)

不错,C的入口就是main程序,而BetaFlight是用C语言开发的一款飞控项目,其入口当然亦是如此,详见:betaflight\src\main\main.c。

整体上分为两部分:初始化和运行。

main├──> init└──> run

注:飞控基本上电池拔掉就好了,没有复杂的去初始化过程。这也体现了航模的专属特性 _

2.2 init (初始化)

整个初始化流程是比较长的,所以看这个初始化流程真的比较辛苦,但是从流程的角度来说,注意以下几点:

  1. 初始化步骤逐一自上而下执行;
  2. 初始化可能存在硬件初始化和应用初始化;
  3. 应用对应的硬件初始化应早于应用执行初始化;
  4. 应用之前有耦合的需要根据依赖关系初始化;

笔者阅读代码时,整个函数长度 1000 - 258 = 742 (代码行,含空行),非常可怕的一个函数。

注:如果能够进行归纳,整理,进行应用功能块区分就好了,可惜目前代码尚没有做到这点。

init├──> <SERIAL_PORT_COUNT> printfSerialInit├──> systemInit├──> tasksInitData├──> IOInitGlobal├──> <USE_HARDWARE_REVISION_DETECTION> detectHardwareRevision├──> <USE_TARGET_CONFIG> targetConfiguration├──> pgResetAll├──> <USE_SDCARD_SPI> configureSPIBusses();  initFlags |= SPI_BUSSES_INIT_ATTEMPTED;├──> sdCardAndFSInit; initFlags |= SD_INIT_ATTEMPTED;├──> <!sdcard_isInserted()> failureMode(FAILURE_SDCARD_REQUIRED)├──> [SD Card FS check] //while (afatfs_getFilesystemState() != AFATFS_FILESYSTEM_STATE_READY)│   ├──> afatfs_poll()│   └──> <afatfs_getFilesystemState() == AFATFS_FILESYSTEM_STATE_FATAL> failureMode(FAILURE_SDCARD_INITIALISATION_FAILED)├──> <CONFIG_IN_EXTERNAL_FLASH> || <CONFIG_IN_MEMORY_MAPPED_FLASH)>│   ├──> pgResetAll()│   ├──> <CONFIG_IN_EXTERNAL_FLASH> configureSPIBusses(); initFlags |= SPI_BUSSES_INIT_ATTEMPTED;│   ├──> configureQuadSPIBusses();configureOctoSPIBusses();initFlags |= QUAD_OCTO_SPI_BUSSES_INIT_ATTEMPTED;│   ├──> bool haveFlash = flashInit(flashConfig());│   ├──> <!haveFlash>failureMode(FAILURE_EXTERNAL_FLASH_INIT_FAILED)│   └──> initFlags |= FLASH_INIT_ATTEMPTED├──> initEEPROM├──> ensureEEPROMStructureIsValid├──> bool readSuccess = readEEPROM()├──> <USE_BOARD_INFO> initBoardInformation├──> <!readSuccess || !isEEPROMVersionValid() || strncasecmp(systemConfig()->boardIdentifier, TARGET_BOARD_IDENTIFIER, sizeof(TARGET_BOARD_IDENTIFIER))> resetEEPROM()├──> systemState |= SYSTEM_STATE_CONFIG_LOADED├──> <USE_DEBUG_PIN> dbgPinInit├──> debugMode = systemConfig()->debug_mode├──> <TARGET_PREINIT> targetPreInit├──> <!defined(USE_VIRTUAL_LED)> ledInit(statusLedConfig())├──> <!defined(SIMULATOR_BUILD)> EXTIInit├──> <USE_BUTTONS>│   ├──> buttonsInit│   └──> <EEPROM_RESET_PRECONDITION && defined(BUTTON_A_PIN) && defined(BUTTON_B_PIN)>  //#define EEPROM_RESET_PRECONDITION (!isMPUSoftReset())│       └──> resetEEPROM/systemReset├──> <defined(STM32F4) || defined(STM32G4)> // F4 has non-8MHz boards, G4 for Betaflight allow 24 or 27MHz oscillator│   └──> systemClockSetHSEValue(systemConfig()->hseMhz * 1000000U)├──> <USE_OVERCLOCK> OverclockRebootIfNecessary(systemConfig()->cpu_overclock)├──> <USE_MCO>│   ├──> <defined(STM32F4) || defined(STM32F7)>│   │   └──> mcoConfigure(MCODEV_2, mcoConfig(MCODEV_2));│   └──> <defined(STM32G4)>│       └──> mcoConfigure(MCODEV_1, mcoConfig(MCODEV_1));├──> <USE_TIMER> timerInit├──> <BUS_SWITCH_PIN> busSwitchInit├──> <defined(USE_UART) && !defined(SIMULATOR_BUILD)> uartPinConfigure(serialPinConfig())├──> [serialInit]│   ├──> <AVOID_UART1_FOR_PWM_PPM> serialInit(featureIsEnabled(FEATURE_SOFTSERIAL), featureIsEnabled(FEATURE_RX_PPM) || featureIsEnabled(FEATURE_RX_PARALLEL_PWM) ? SERIAL_PORT_USART1 : SERIAL_PORT_NONE);│   ├──> <AVOID_UART2_FOR_PWM_PPM> serialInit(featureIsEnabled(FEATURE_SOFTSERIAL), featureIsEnabled(FEATURE_RX_PPM) || featureIsEnabled(FEATURE_RX_PARALLEL_PWM) ? SERIAL_PORT_USART2 : SERIAL_PORT_NONE);│   ├──> <AVOID_UART3_FOR_PWM_PPM> serialInit(featureIsEnabled(FEATURE_SOFTSERIAL), featureIsEnabled(FEATURE_RX_PPM) || featureIsEnabled(FEATURE_RX_PARALLEL_PWM) ? SERIAL_PORT_USART3 : SERIAL_PORT_NONE);│   └──> <else> serialInit(featureIsEnabled(FEATURE_SOFTSERIAL), SERIAL_PORT_NONE)├──> mixerInit(mixerConfig()->mixerMode)├──> uint16_t idlePulse = motorConfig()->mincommand├──> <featureIsEnabled(FEATURE_3D)> idlePulse = flight3DConfig()->neutral3d├──> <motorConfig()->dev.motorPwmProtocol == PWM_TYPE_BRUSHED> idlePulse = 0; // brushed motors├──> <USE_MOTOR> motorDevInit(&motorConfig()->dev, idlePulse, getMotorCount()); systemState |= SYSTEM_STATE_MOTORS_READY├──> <USE_RX_PPM> <featureIsEnabled(FEATURE_RX_PARALLEL_PWM)> pwmRxInit(pwmConfig())├──> <USE_BEEPER> beeperInit(beeperDevConfig())├──> <defined(USE_INVERTER) && !defined(SIMULATOR_BUILD)> initInverters(serialPinConfig())├──> [Hardware Bus Initialization]│   ├──> <TARGET_BUS_INIT> targetBusInit()│   └──> <else> │       ├──> <!(initFlags & SPI_BUSSES_INIT_ATTEMPTED)> configureSPIBusses();initFlags |= SPI_BUSSES_INIT_ATTEMPTED;│       ├──> <!(initFlags & QUAD_OCTO_SPI_BUSSES_INIT_ATTEMPTED)> configureQuadSPIBusses();configureOctoSPIBusses();initFlags |= QUAD_OCTO_SPI_BUSSES_INIT_ATTEMPTED;│       ├──> <defined(USE_SDCARD_SDIO) && !defined(CONFIG_IN_SDCARD) && defined(STM32H7)> sdioPinConfigure(); SDIO_GPIO_Init();│       ├──> <USE_USB_MSC>│       │   ├──> mscInit()│       │   └──> <USE_SDCARD> <blackboxConfig()->device == BLACKBOX_DEVICE_SDCARD> <sdcardConfig()->mode> <!(initFlags & SD_INIT_ATTEMPTED)> sdCardAndFSInit();initFlags |= SD_INIT_ATTEMPTED;│       ├──> <USE_FLASHFS> <blackboxConfig()->device == BLACKBOX_DEVICE_FLASH> emfat_init_files│       ├──> <USE_SPI> spiInitBusDMA│       ├──> <mscStart() == 0> mscWaitForButton();│       ├──> <mscStart() != 0> systemResetFromMsc()│       ├──> <USE_PERSISTENT_MSC_RTC>│       │   ├──> persistentObjectWrite(PERSISTENT_OBJECT_RTC_HIGH, 0);│       │   └──> persistentObjectWrite(PERSISTENT_OBJECT_RTC_LOW, 0);│       └──> <USE_I2C>│           ├──> i2cHardwareConfigure(i2cConfig(0));│           ├──> <USE_I2C_DEVICE_1> i2cInit(I2CDEV_1);│           ├──> <USE_I2C_DEVICE_2> i2cInit(I2CDEV_2);│           ├──> <USE_I2C_DEVICE_3> i2cInit(I2CDEV_3);│           └──> <USE_I2C_DEVICE_4> i2cInit(I2CDEV_4);├──> <USE_HARDWARE_REVISION_DETECTION> updateHardwareRevision     ├──> <USE_VTX_RTC6705> bool useRTC6705 = rtc6705IOInit(vtxIOConfig());├──> <USE_CAMERA_CONTROL> cameraControlInit();├──> <USE_ADC> adcInit(adcConfig());├──> initBoardAlignment(boardAlignment());├──> <!sensorsAutodetect()>│   ├──> <isSystemConfigured()>│   │   └──> indicateFailure(FAILURE_MISSING_ACC, 2);│   └──> setArmingDisabled(ARMING_DISABLED_NO_GYRO);├──> systemState |= SYSTEM_STATE_SENSORS_READY;├──> gyroSetTargetLooptime(pidConfig()->pid_process_denom); // Set the targetLooptime based on the detected gyro sampleRateHz and pid_process_denom├──> validateAndFixGyroConfig();├──> gyroSetTargetLooptime(pidConfig()->pid_process_denom); // Now reset the targetLooptime as it's possible for the validation to change the pid_process_denom├──> gyroInitFilters();├──> pidInit(currentPidProfile);├──> mixerInitProfile();├──> <USE_PID_AUDIO> pidAudioInit();├──> <USE_SERVOS>│   ├──> servosInit();│   ├──> <isMixerUsingServos()> servoDevInit(&servoConfig()->dev) //pwm_params.useChannelForwarding = featureIsEnabled(FEATURE_CHANNEL_FORWARDING);│   └──> servosFilterInit();├──> <USE_PINIO> pinioInit(pinioConfig());├──> <USE_PIN_PULL_UP_DOWN> pinPullupPulldownInit();├──> <USE_PINIOBOX> pinioBoxInit(pinioBoxConfig());├──> [LED Oprations]├──> imuInit();├──> failsafeInit();├──> rxInit();├──> <USE_GPS> <featureIsEnabled(FEATURE_GPS)>│   ├──> gpsInit();│   └──> <USE_GPS_RESCUE> gpsRescueInit();├──> <USE_LED_STRIP>│   ├──> ledStripInit();│   └──> <featureIsEnabled(FEATURE_LED_STRIP)> ledStripEnable();├──> <USE_ESC_SENSOR> <featureIsEnabled(FEATURE_ESC_SENSOR)> escSensorInit();├──> <USE_USB_DETECT> usbCableDetectInit();├──> <USE_TRANSPONDER> <featureIsEnabled(FEATURE_TRANSPONDER)>│   ├──> transponderInit();│   ├──> transponderStartRepeating();│   └──> systemState |= SYSTEM_STATE_TRANSPONDER_ENABLED;├──> <USE_FLASH_CHIP> <!(initFlags & FLASH_INIT_ATTEMPTED)>│   └──> flashInit(flashConfig());initFlags |= FLASH_INIT_ATTEMPTED;├──> <USE_FLASHFS> flashfsInit();├──> <USE_SDCARD> <dcardConfig()->mode> <!(initFlags & SD_INIT_ATTEMPTED)>│   └──> sdCardAndFSInit();initFlags |= SD_INIT_ATTEMPTED;├──> <USE_BLACKBOX> blackboxInit();├──> <USE_ACC> <mixerConfig()->mixerMode == MIXER_GIMBAL> accStartCalibration();├──> gyroStartCalibration(false);├──> <USE_BARO> baroStartCalibration();├──> positionInit();├──> <defined(USE_VTX_COMMON) || defined(USE_VTX_CONTROL)> vtxTableInit();├──> <USE_VTX_CONTROL> │   ├──> vtxControlInit();│   ├──> <USE_VTX_COMMON> vtxCommonInit();│   ├──> <USE_VTX_MSP> vtxMspInit();│   ├──> <USE_VTX_SMARTAUDIO> vtxSmartAudioInit();│   ├──> <USE_VTX_TRAMP> vtxTrampInit();│   └──> <USE_VTX_RTC6705> <!vtxCommonDevice() && useRTC6705> vtxRTC6705Init();├──> <USE_TIMER> timerStart();├──> batteryInit(); // always needs doing, regardless of features.├──> <USE_RCDEVICE> rcdeviceInit();├──> <USE_PERSISTENT_STATS> statsInit();├──> [Initialize MSP]│   ├──> mspInit();│   └──> mspSerialInit();├──> <USE_CMS> cmsInit();├──> <defined(USE_OSD) || (defined(USE_MSP_DISPLAYPORT) && defined(USE_CMS))> displayPort_t *osdDisplayPort = NULL;├──> <USE_OSD>│   ├──> osdDisplayPortDevice_e osdDisplayPortDevice = OSD_DISPLAYPORT_DEVICE_NONE;│   └──> <featureIsEnabled(FEATURE_OSD)>│       ├──> osdDisplayPortDevice_e device = osdConfig()->displayPortDevice;│       ├──> <case OSD_DISPLAYPORT_DEVICE_AUTO:> FALLTHROUGH;│       ├──> <case OSD_DISPLAYPORT_DEVICE_FRSKYOSD:>│       │   ├──> osdDisplayPort = frskyOsdDisplayPortInit(vcdProfile()->video_system);│       │   └──> <osdDisplayPort || device == OSD_DISPLAYPORT_DEVICE_FRSKYOSD>  osdDisplayPortDevice = OSD_DISPLAYPORT_DEVICE_FRSKYOSD; break;│       ├──> <case OSD_DISPLAYPORT_DEVICE_MAX7456:>│       │   └──> <max7456DisplayPortInit(vcdProfile(), &osdDisplayPort) || device == OSD_DISPLAYPORT_DEVICE_MAX7456> osdDisplayPortDevice = OSD_DISPLAYPORT_DEVICE_MAX7456;break;│       ├──> <case OSD_DISPLAYPORT_DEVICE_MSP:>│       │   ├──> osdDisplayPort = displayPortMspInit();│       │   └──> <osdDisplayPort || device == OSD_DISPLAYPORT_DEVICE_MSP> osdDisplayPortDevice = OSD_DISPLAYPORT_DEVICE_MSP; break;│       ├──> <case OSD_DISPLAYPORT_DEVICE_NONE:) default:│       ├──> osdInit(osdDisplayPort, osdDisplayPortDevice);│       └──> <osdDisplayPortDevice == OSD_DISPLAYPORT_DEVICE_NONE> featureDisableImmediate(FEATURE_OSD);├──> <defined(USE_CMS) && defined(USE_MSP_DISPLAYPORT)> <!osdDisplayPort> cmsDisplayPortRegister(displayPortMspInit());├──> <USE_DASHBOARD> <featureIsEnabled(FEATURE_DASHBOARD)>│   ├──> dashboardInit();│   ├──> <USE_OLED_GPS_DEBUG_PAGE_ONLY> dashboardShowFixedPage(PAGE_GPS);│   └──> <!USE_OLED_GPS_DEBUG_PAGE_ONLY> dashboardResetPageCycling();dashboardEnablePageCycling();├──> <USE_TELEMETRY> <featureIsEnabled(FEATURE_TELEMETRY)> telemetryInit();├──> setArmingDisabled(ARMING_DISABLED_BOOT_GRACE_TIME);├──> <defined(USE_SPI) && defined(USE_SPI_DMA_ENABLE_EARLY)> spiInitBusDMA();├──> <USE_MOTOR> │   ├──> motorPostInit();│   └──> motorEnable();├──> <defined(USE_SPI) && defined(USE_SPI_DMA_ENABLE_LATE) && !defined(USE_SPI_DMA_ENABLE_EARLY)>│   └──> spiInitBusDMA();├──> debugInit();├──> unusedPinsInit();├──> tasksInit();└──> systemState |= SYSTEM_STATE_READY;

2.3 run

初始化完成以后,我们来理解run运行的过程,它主要是一个BetaFlight自研的一个任务调度。

注:这个任务调度不会再任务运行过程中途被打断,与通常OS系统所描述的任务是不一样的。

run└──> loop: scheduler()

通俗点的理解:

可以认为BetaFlight是在一个bare-metal loop的基础上增加了对业务过程耗时统计+基于过程时间优先级调度的一个算法逻辑scheduler。

注:后面我们会重点针对BetaFlight自研的调度器进行研读和介绍。

3. 任务调度

系统启动后,接下来最为核心的就是任务调度。作为飞控自研的调度器,其BetaFlight设计有以下特点:

  1. 历史遗留问题:从性价比,开发历程等方面来思考,也许不难想象,为什么现在是一个基于bare-metal的一个自研任务调度器。
  2. 业务强耦合:鉴于IMU和PID业务对于飞行器飞行来说至关重要,因此该调度器在Gyro/Acc/PID的任务调用采用了精准时刻调度(+/-0.1us)。
  3. 任务优先级:MCU性能和业务逻辑复杂度,在至关重要任务调度之后剩余的时间片可能存在资源不够导致任务“饿死”得不到调度的情况。
  4. 边缘弹性任务时间优化:有限资源 + 非充分测试 + 资源可体感设计(OSD/CPU占用率等)

3.1 任务定义

这个结构体是一个优先级和任务配置的相关结构。当然因为BetaFlight自研调度器与飞控应用高度耦合(强耦合),这只是调度的一部分内容。

typedef struct {// Task static datatask_attribute_t *attribute;// Schedulinguint16_t dynamicPriority;           // measurement of how old task was last executed, used to avoid task starvationuint16_t taskAgePeriods;timeDelta_t taskLatestDeltaTimeUs;timeUs_t lastExecutedAtUs;          // last time of invocationtimeUs_t lastSignaledAtUs;          // time of invocation event for event-driven taskstimeUs_t lastDesiredAt;             // time of last desired execution// Statisticsfloat    movingAverageCycleTimeUs;timeUs_t anticipatedExecutionTime;  // Fixed point expectation of next execution timetimeUs_t movingSumDeltaTime10thUs;  // moving sum over 64 samplestimeUs_t movingSumExecutionTime10thUs;timeUs_t maxExecutionTimeUs;timeUs_t totalExecutionTimeUs;      // total time consumed by task since boottimeUs_t lastStatsAtUs;             // time of last stats gathering for rate calculation
#if defined(USE_LATE_TASK_STATISTICS)uint32_t runCount;uint32_t lateCount;timeUs_t execTime;
#endif
} task_t;

3.2 scheduler (调度器)

这就是大名鼎鼎的BetaFlight任务调度器。这里对调度算法进行了认真的研读。

scheduler├──> <gyroEnabled>│   ├──> [Realtime gyro/filtering/PID tasks get complete priority]│   │   ├──> task_t *gyroTask = getTask(TASK_GYRO)│   │   ├──> nowCycles = getCycleCounter()│   │   ├──> nextTargetCycles = lastTargetCycles + desiredPeriodCycles│   │   └──> schedLoopRemainingCycles = cmpTimeCycles(nextTargetCycles, nowCycles)│   │ ################################################################################│   │ # Rootcause: USB VCP Data Transfer│   │ ################################################################################│   ├──> <schedLoopRemainingCycles < -desiredPeriodCycles>  //错过了一个desiredPeriodCycles周期,导致剩余时间负值(且大于一个运行周期)│   │   ├──> nextTargetCycles += desiredPeriodCycles * (1 + (schedLoopRemainingCycles / -desiredPeriodCycles))  //常见USB配置工具连接的时候,尽力挽救scheduler算法│   │   └──> schedLoopRemainingCycles = cmpTimeCycles(nextTargetCycles, nowCycles)│   │ ################################################################################│   │ # schedLoopStartMinCycles range rebound│   │ ################################################################################│   ├──> <(schedLoopRemainingCycles < schedLoopStartMinCycles) && (schedLoopStartCycles < schedLoopStartMaxCycles)>│   │   └──> schedLoopStartCycles += schedLoopStartDeltaUpCycles│   └──> <schedLoopRemainingCycles < schedLoopStartCycles>│       ├──> <schedLoopStartCycles > schedLoopStartMinCycles>│       │   └──> schedLoopStartCycles -= schedLoopStartDeltaDownCycles│       │ ################################################################################│       │ # Polling for gyro data│       │ ################################################################################│       ├──> <while (schedLoopRemainingCycles > 0)> │       │   ├──> nowCycles = getCycleCounter();│       │   └──> schedLoopRemainingCycles = cmpTimeCycles(nextTargetCycles, nowCycles);│       │ ################################################################################│       │ # Execute gyro/filter/pid tasks and check data avalibility in rc link│       │ ################################################################################│       ├──> currentTimeUs = micros()│       ├──> taskExecutionTimeUs += schedulerExecuteTask(gyroTask, currentTimeUs)│       ├──> <gyroFilterReady()> taskExecutionTimeUs += schedulerExecuteTask(getTask(TASK_FILTER), currentTimeUs)│       ├──> <pidLoopReady()> taskExecutionTimeUs += schedulerExecuteTask(getTask(TASK_PID), currentTimeUs)│       ├──> rxFrameCheck(currentTimeUs, cmpTimeUs(currentTimeUs, getTask(TASK_RX)->lastExecutedAtUs))  //检查是否有新的RC控制链路数据包收到│       ├──> <cmp32(millis(), lastFailsafeCheckMs) > PERIOD_RXDATA_FAILURE> //核查、更新failsafe状态│       ├──> lastTargetCycles = nextTargetCycles│       ├──> gyroDev_t *gyro = gyroActiveDev() //获取活跃的gyro设备│       │ ################################################################################│       │ # Exact gyro time sync, using GYRO_RATE_COUNT/GYRO_LOCK_COUNT│       │ ################################################################################│       └──> [Sync scheduler into lock with gyro] // gyro->gyroModeSPI != GYRO_EXTI_NO_INT, 这里指数级收敛,只要desiredPeriodCycles越精准,理论精度会越高│           ├──> [Calculate desiredPeriodCycles = sampleCycles / GYRO_RATE_COUNT and reset terminalGyroRateCount += GYRO_RATE_COUNT]│           └──> [Sync lastTargetCycles using exponential normalization by GYRO_RATE_COUNT/GYRO_LOCK_COUNT times iteration]├──> nowCycles = getCycleCounter();schedLoopRemainingCycles = cmpTimeCycles(nextTargetCycles, nowCycles);├──> <!gyroEnabled || (schedLoopRemainingCycles > (int32_t)clockMicrosToCycles(CHECK_GUARD_MARGIN_US))>│   │ ################################################################################│   │ # Find task need to be execute when there is time.│   │ ################################################################################│   ├──> currentTimeUs = micros()│   ├──> for (task_t *task = queueFirst(); task != NULL; task = queueNext()) <task->attribute->staticPriority != TASK_PRIORITY_REALTIME> //Update task dynamic priorities│   │   ├──> <task->attribute->checkFunc> //有属性检查函数│   │   │   ├──> <task->dynamicPriority > 0>│   │   │   │   ├──> task->taskAgePeriods = 1 + (cmpTimeUs(currentTimeUs, task->lastSignaledAtUs) / task->attribute->desiredPeriodUs);│   │   │   │   └──> task->dynamicPriority = 1 + task->attribute->staticPriority * task->taskAgePeriods;│   │   │   ├──> <task->attribute->checkFunc(currentTimeUs, cmpTimeUs(currentTimeUs, task->lastExecutedAtUs))>│   │   │   │   ├──> const uint32_t checkFuncExecutionTimeUs = cmpTimeUs(micros(), currentTimeUs);│   │   │   │   ├──> checkFuncMovingSumExecutionTimeUs += checkFuncExecutionTimeUs - checkFuncMovingSumExecutionTimeUs / TASK_STATS_MOVING_SUM_COUNT;│   │   │   │   ├──> checkFuncMovingSumDeltaTimeUs += task->taskLatestDeltaTimeUs - checkFuncMovingSumDeltaTimeUs / TASK_STATS_MOVING_SUM_COUNT;│   │   │   │   ├──> checkFuncTotalExecutionTimeUs += checkFuncExecutionTimeUs;   // time consumed by scheduler + task│   │   │   │   ├──> checkFuncMaxExecutionTimeUs = MAX(checkFuncMaxExecutionTimeUs, checkFuncExecutionTimeUs)│   │   │   │   ├──> task->lastSignaledAtUs = currentTimeUs│   │   │   │   ├──> task->taskAgePeriods = 1│   │   │   │   └──> task->dynamicPriority = 1 + task->attribute->staticPriority│   │   │   └──> <else>│   │   │       └──> task->taskAgePeriods = 0│   │   ├──> <!task->attribute->checkFunc> //无属性检查函数│   │   │   ├──> task->taskAgePeriods = (cmpTimeUs(currentTimeUs, task->lastExecutedAtUs) / task->attribute->desiredPeriodUs)│   │   │   └──> <task->taskAgePeriods > 0>│   │   │       └──> task->dynamicPriority = 1 + task->attribute->staticPriority * task->taskAgePeriods│   │   └──> <task->dynamicPriority > selectedTaskDynamicPriority>│   │       ├──> timeDelta_t taskRequiredTimeUs = task->anticipatedExecutionTime >> TASK_EXEC_TIME_SHIFT│   │       ├──> int32_t taskRequiredTimeCycles = (int32_t)clockMicrosToCycles((uint32_t)taskRequiredTimeUs)│   │       ├──> taskRequiredTimeCycles += checkCycles + taskGuardCycles //增加守护时间(预留一些)│   │       └──> <taskRequiredTimeCycles < schedLoopRemainingCycles> ||  //剩余时间足够执行任务│   │            <(scheduleCount & SCHED_TASK_DEFER_MASK) == 0> ||  //???? 这里还不太清楚什么情况?│   │            <(task - tasks) == TASK_SERIAL)>  //串行任务不阻塞│   │           ├──> selectedTaskDynamicPriority = task->dynamicPriority;│   │           └──> selectedTask = task;  //选中任务│   │ ################################################################################│   │ # Select task execution│   │ ################################################################################│   ├──> checkCycles = cmpTimeCycles(getCycleCounter(), nowCycles) //优先级调整,以及checkFunc运行需要计算耗时时间│   └──> <selectedTask>│       ├──> timeDelta_t taskRequiredTimeUs = selectedTask->anticipatedExecutionTime >> TASK_EXEC_TIME_SHIFT  // Recheck the available time as checkCycles is only approximate│       ├──> int32_t taskRequiredTimeCycles = (int32_t)clockMicrosToCycles((uint32_t)taskRequiredTimeUs)│       ├──> nowCycles = getCycleCounter()│       ├──> schedLoopRemainingCycles = cmpTimeCycles(nextTargetCycles, nowCycles)│       ├──> <!gyroEnabled || (taskRequiredTimeCycles < schedLoopRemainingCycles)>│       │   ├──> uint32_t antipatedEndCycles = nowCycles + taskRequiredTimeCycles;│       │   ├──> taskExecutionTimeUs += schedulerExecuteTask(selectedTask, currentTimeUs);│       │   ├──> nowCycles = getCycleCounter();│       │   ├──> int32_t cyclesOverdue = cmpTimeCycles(nowCycles, antipatedEndCycles);│       │   ├──> <(currentTask - tasks) == TASK_RX>│       │   │   └──> skippedRxAttempts = 0│       │   ├──> <(currentTask - tasks) == TASK_OSD>│       │   │   └──> skippedOSDAttempts = 0│       │   ├──> <(cyclesOverdue > 0) || (-cyclesOverdue < taskGuardMinCycles)> //超时,但可控在taskGuardMinCycles范围之内│       │   │   └──> <taskGuardCycles < taskGuardMaxCycles>│       │   │       └──> taskGuardCycles += taskGuardDeltaUpCycles //增加守护时间(预留更多一些)│       │   └──> <else if (taskGuardCycles > taskGuardMinCycles> //未超时│       │           └──> taskGuardCycles -= taskGuardDeltaDownCycles; //减少守护时间│       │ ################################################################################│       │ # Special task handling│       │ ################################################################################│       └──> <else <selectedTask->taskAgePeriods > TASK_AGE_EXPEDITE_COUNT>) ||│            <((selectedTask - tasks) == TASK_OSD) && (TASK_AGE_EXPEDITE_OSD != 0) && (++skippedOSDAttempts > TASK_AGE_EXPEDITE_OSD)> ||│            <((selectedTask - tasks) == TASK_RX) && (TASK_AGE_EXPEDITE_RX != 0) && (++skippedRxAttempts > TASK_AGE_EXPEDITE_RX)>│               └──> selectedTask->anticipatedExecutionTime *= TASK_AGE_EXPEDITE_SCALE└──> scheduleCount++;

注:这里讨论的是不包含UNIT_TEST和USE_LATE_TASK_STATISTICS的代码。

4. 总结

从当前最新4.4.x版本代码上看:

  1. BetaFlight整体代码可以分为两部分:初始化(init)和运行时(run)
  2. 应用功能代码已经通过宏定义,支持可剪裁编译;
  3. 整个调度器要了解存在一些背景知识需要澄清和讨论(已经在附录中整理,并正在与开发团队讨论中。。。。);

5. 参考资料

【1】BetaFlight开源工程结构简明介绍
【2】BetaFlight开源代码框架简介
【3】BetaFlight深入传感设计:传感模块设计框架
【4】BetaFlight统一硬件配置文件研读

6. 附录 – 问题汇总

预知问题后续细节,详见:BetaFlight飞控启动&运行过程简介疑问跟踪。

6.1 Why desiredPeriodCycles is so important to Betaflight task?

Let’s say default desiredPeriodCycles is about 8K, which 125us. And the code still check desiredPeriodCycles by GYRO_RATE_COUNT interrupts, what’s the story here?

If sample rate is NOT sync with gyro task, there might be dirty/invalid data, is there any bad thing happens if just missed 1 or 2 valid samples?

Or this code only tends to logically eliminate this effective data sampling bias?

6.2 What root cause has made gyro task to been overrun, so scheduler has to skip a gyro cycle?

As scheduler comments "A task has so grossly overrun that at entire gyro cycle has been skipped, especially via USB as the serial.

Is there too much serial communication through bf configurator and FC or just USB-VCP code on FC side take a lot of resources?

It’ll be the same issue when switching to TTL comunication with FC (suggest test bf-configurator <> usb_serial convertor <> FC(UART1 for example)?

6.3 What if serial task is running quite long, which corrupt gyro cycle, is it a problem?

As previous discussion on “Why desiredPeriodCycles is so important during Betaflight task?”

e.g. use MSP protocol as communication channel with companion computer
==> there are lots of packets, result in long time processing.
==> if this happens, it seems mess up with the gyro cycle, am i right? is there any solution or way to overcome this issue?

Code

6.4 What does the condition of “scheduleCount & SCHED_TASK_DEFER_MASK == 0” stand for in scheduler?

Code

There are three scenarios for normal(except gyro/acc/pid) tasks to execute:

  1. taskRequiredTimeCycles < schedLoopRemainingCycles // there is time for task to execute.
  2. scheduleCount & SCHED_TASK_DEFER_MASK == 0 //???
  3. (task - tasks) == TASK_SERIAL // ok, special case for serial

So what’s for case #2 “scheduleCount & SCHED_TASK_DEFER_MASK == 0”

BetaFlight飞控启动运行过程简介相关推荐

  1. 老李推荐: 第8章4节《MonkeyRunner源码剖析》MonkeyRunner启动运行过程-启动AndroidDebugBridge 1...

    老李推荐: 第8章4节<MonkeyRunner源码剖析>MonkeyRunner启动运行过程-启动AndroidDebugBridge 上一节我们看到在启动AndroidDebugBri ...

  2. 老李推荐:第8章2节《MonkeyRunner源码剖析》MonkeyRunner启动运行过程-解析处理命令行参数...

    老李推荐:第8章2节<MonkeyRunner源码剖析>MonkeyRunner启动运行过程-解析处理命令行参数 MonkeyRunnerStarter是MonkeyRunner启动时的入 ...

  3. 计算机启动突然断电,电脑启动运行过程主机突然断电怎么办

    有朋友跟小编说"我的电脑开机经常突然断电,断电以后就不能启动了,感觉就不通电了 ",那么电脑启动运行过程主机突然断电怎么办呢?小编为大家分享了解决电脑启动运行过程主机突然断电的方法 ...

  4. java 程序运行过程 简介

    这里的Java程序运行过程,是指我们编译好代码之后,在命令行开始执行java xxx命令,到java程序开始执行起来的这一过程,我们称其为运行时. 第一步,操作系统解析我们输入的java xxx命令, ...

  5. 老李推荐: 第8章4节《MonkeyRunner源码剖析》MonkeyRunner启动运行过程-启动AndroidDebugBridge 2...

    第81-86行,整个方法的主体就是创建一个"Device List Monitor"的线程.线程运行方法run直接调用DeviceMonitor的deviceMonitorLoop ...

  6. 第8章6节MonkeyRunner启动运行过程-启动Monkey 2

    有了以下的基本认知之后,我们就可以通过分析代码来阐述Monkey是怎么在用户调用MonkeyRunner.waitForConnection的方法引发的一系列调用过程中启动起来的了,我们先看下Monk ...

  7. 第8章6节MonkeyRunner启动运行过程-启动Monkey 4

    在第4节"启动设备监控线程DeviceMonitor"中我们已经学习了当DeviceMonitor在往ADB服务器发送监控命令"host:track-devices&qu ...

  8. 老李推荐:第8章2节《MonkeyRunner源码剖析》MonkeyRunner启动运行过程-解析处理命令行参数 2...

    我们这一节会先去分析下monkeyrunner是如何对参数进行处理的,我们跳转到MonkeyRunnerOptions这个类里面的processOptions这个方法: 93   public sta ...

  9. 第8章4节《MonkeyRunner源码剖析》MonkeyRunner启动运行过程-启动6

    这一部分的代码逻辑关系是这样的: 344行: 一个外部循环每次从上次保存下来的设备列表获得一个设备Device实例 350行: 再在一个内部循环从最新的设备列表中获得一个设备Device实例 353行 ...

最新文章

  1. Bioinfo:学习Python,做生信PartII 学习笔记
  2. Nginx实现负载均衡时常用的分配服务器策略
  3. php mysql explain_Mysql分析-explain的详细介绍
  4. .NET核心正则类详解
  5. (转) iPhone UI 开发的几点建议
  6. strtol,strtoll,strtoul, strtoull函数的使用
  7. 性能调优-硬盘方面,操作系统方面,文件系统方面
  8. 【转】WPF从我炫系统5---基本控件的用法
  9. 晶圆缺陷检测设备_KLA突破电子束晶圆缺陷检测瓶颈,将助EUV光刻机一臂之力
  10. 【浅墨著作】《逐梦旅程:Windows游戏编程之从零开始》勘误配套源代码下载
  11. python查找文字在图片中的位置_python实现简单图片文字识别翻译OCR
  12. 亚马逊云科技成为Meta关键长期战略云服务提供商;触宝科技延伸业务布局聚焦元宇宙 | 全球TMT...
  13. linux查看虚拟内存使用,Linux 使用 vmstat 查看虚拟内存状态
  14. 计算机二级实践网上教程答案,全国计算机等级二级教程课后习题+答案
  15. c语言中average的用法,average的用法辨析整理
  16. proteus仿真+keil——>制作流水灯
  17. Android中MaterialSearchView(搜索框)的简单实用
  18. 机器视觉光源的分类及各种光源的特点
  19. AutoSar DaVinci Developer工具的基本介绍
  20. 新书推荐--《Python程序设计入门与实践》

热门文章

  1. 【基于RFID的门禁系统】
  2. Photoshop制作一张精美的圣诞贺卡
  3. 初中计算机基础知识说课稿,初中信息技术说课稿
  4. 提取OutLook邮件里面的邮件头信息(发件人、收件人)
  5. 微信小程序商品详情html,微信小程序关于商品详情类的富文本解析器
  6. 系统架构设计师论文范文-论基于DSSA的软件架构设计与应用
  7. 在xshell等工具中使用命令的方式调用接口
  8. 计算机绘图实训日志通用篇,cad制图实习日记范文:
  9. 银杏烟花制造有限公司电子商务实例
  10. iOS开发 - UIImage加载内存性能比较