Betaflight 嵌入式操作系统架构解析:与 FreeRTOS 的深度对比
- 摘要
- 一、Betaflight的调度系统架构
- 二、FreeRTOS的核心调度机制
- 三、任务调度机制对比
- 四、内存管理机制对比
- 五、中断处理与实时性保障
- 六、系统架构与适用性分析
- 七、代码实现差异分析
- 八、性能与资源消耗对比
- 九、飞控系统中的实际应用效果
- 十、总结与适用场景建议
摘要
本文深入探讨了开源飞控固件 Betaflight 所采用的轻量级操作系统架构,并将其与广泛应用的嵌入式实时操作系统 FreeRTOS 进行多维度对比。
Betaflight 4.5.2飞控系统不使用FreeRTOS ,而是采用了一种轻量级自研非抢占式调度系统 。这种调度机制针对飞控应用进行了高度优化,能够更好地满足无人机控制所需的确定性和低延迟要求。与FreeRTOS相比,Betaflight调度器在实时性保障、资源占用和任务执行确定性方面具有明显优势,但也牺牲了部分灵活性和扩展性。两者在任务调度方式、内存管理机制和中断处理策略上存在根本性差异,这些差异直接影响了飞控系统的性能表现和适用场景。
一、Betaflight的调度系统架构
Betaflight的调度系统是一种基于时间片的非抢占式调度机制 ,其核心实现位于src/main/scheduler.c文件中。调度器的主循环(通常在 src/main/main.c 的 main 函数中)会不断地检查当前时间,并执行那些到达了预定周期的任务。这个过程是非抢占式的,也就是说,一个任务必须主动执行完毕,下一个任务才能开始。
c
/******************************************************************************
*
* 函数名 : main
*
* 函数功能 : Cleanflight/Betaflight的入口函数
*
* 参数 : 无
*
* 返回值 : 无
*
* 其他 :
*
******************************************************************************/
int main(int argc, char * argv[])
{
#ifdef USE_MAIN_ARGS
targetParseArgs(argc, argv);
#else
UNUSED(argc);
UNUSED(argv);
#endif
init();
run();
return 0;
}
/******************************************************************************
*
* 函数名 : run
*
* 函数功能 : 运行Cleanflight/Betaflight
*
* 参数 : 无
*
* 返回值 : 无
*
* 其他 :
*
******************************************************************************/
void FAST_CODE run(void)
{
while (true) {
scheduler();
#if defined(RUN_LOOP_DELAY_US) && RUN_LOOP_DELAY_US > 0
/**< 这里的目的是在每次调度任务之后,让系统短暂休眠一段时间,防止CPU过载,确保系统稳定运行。 */
delayMicroseconds_real(RUN_LOOP_DELAY_US); /**< 延时RUN;RUN_LOOP_DELAY_US = 50us */
#endif
}
}
系统通过scheduler()函数按顺序执行各个任务,每个任务都有预设的执行频率和优先级 。这种调度方式确保了关键控制任务(如传感器读取和电机输出)能够获得确定性的执行时间,避免了因任务切换导致的不可预测延迟。
1、任务属性结构体
Betaflight的任务定义通过taskDescriptor_t结构体实现,包含任务函数指针、执行频率、优先级等关键参数 :
c
typedef struct
{
const char *taskName; /**< 任务名称 */
const char *subTaskName; /**< 子任务名称 */
bool(*checkFunc) (timeUs_t currentTimeUs, timeDelta_t currentDeltaTimeUs); /**< 检查函数指针,用于决定是否执行任务 */
void(*taskFunc) (timeUs_t currentTimeUs); /**< 任务函数指针,实际执行的任务逻辑 */
timeDelta_t desiredPeriodUs; /**< 期望的执行周期 */
const int8_t staticPriority; /**< 静态优先级,动态优先级以此为基准增长 */
} task_attribute_t; /**< 任务属性结构体 */
c
#define DEFINE_TASK(taskNameParam, subTaskNameParam, checkFuncParam, \
taskFuncParam, desiredPeriodParam, staticPriorityParam) { \
.taskName = taskNameParam, \
.subTaskName = subTaskNameParam, \
.checkFunc = checkFuncParam, \
.taskFunc = taskFuncParam, \
.desiredPeriodUs = desiredPeriodParam, \
.staticPriority = staticPriorityParam \
} /**< 定义任务的宏,用于初始化任务结构体 */
2、系统任务列表所有的任务
c
typedef enum
{
/* Actual tasks */
TASK_SYSTEM = 0, /**< 系统任务 */
TASK_MAIN, /**< 主任务 */
TASK_GYRO, /**< 陀螺仪任务 */
TASK_FILTER, /**< 滤波器任务 */
TASK_PID, /**< PID 控制任务 */
TASK_ACCEL, /**< 加速度计任务 */
TASK_ATTITUDE, /**< 姿态任务 */
TASK_RX, /**< 接收任务 */
TASK_SERIAL, /**< 串口任务 */
TASK_DISPATCH, /**< 分发任务 */
TASK_BATTERY_VOLTAGE, /**< 电池电压任务 */
TASK_BATTERY_CURRENT, /**< 电池电流任务 */
TASK_BATTERY_ALERTS, /**< 电池警告任务 */
#ifdef USE_BEEPER
TASK_BEEPER, /**< 蜂鸣器任务 */
#endif
#ifdef USE_GPS
TASK_GPS, /**< GPS 任务 */
#endif
#ifdef USE_GPS_RESCUE
TASK_GPS_RESCUE, /**< GPS 救援任务 */
#endif
#ifdef USE_ALTITUDE_HOLD
TASK_ALTHOLD, /**< 高度保持任务 */
#endif
#ifdef USE_POSITION_HOLD
TASK_POSHOLD, /**< 位置保持任务 */
#endif
#ifdef USE_MAG
TASK_COMPASS, /**< 磁力计任务 */
#endif
#ifdef USE_BARO
TASK_BARO, /**< 气压计任务 */
#endif
#ifdef USE_RANGEFINDER
TASK_RANGEFINDER, /**< 测距仪任务 */
#endif
#ifdef USE_OPTICALFLOW
TASK_OPTICALFLOW, /**< 光流任务 */
#endif
#if defined(USE_BARO) || defined(USE_GPS)
TASK_ALTITUDE, /**< 高度任务 */
#endif
#ifdef USE_DASHBOARD
TASK_DASHBOARD, /**< 仪表盘任务 */
#endif
#ifdef USE_TELEMETRY
TASK_TELEMETRY, /**< 遥测任务 */
#endif
#ifdef USE_LED_STRIP
TASK_LEDSTRIP, /**< LED 灯带任务 */
#endif
#ifdef USE_TRANSPONDER
TASK_TRANSPONDER, /**< 无线电应答器任务 */
#endif
#ifdef USE_STACK_CHECK
TASK_STACK_CHECK, /**< 堆栈检查任务 */
#endif
#ifdef USE_OSD
TASK_OSD, /**< OSD 显示任务 */
#endif
#ifdef USE_BST
TASK_BST_MASTER_PROCESS, /**< BST 主进程任务 */
#endif
#ifdef USE_ESC_SENSOR
TASK_ESC_SENSOR, /**< 电机传感器任务 */
#endif
#ifdef USE_CMS
TASK_CMS, /**< CMS 任务 */
#endif
#ifdef USE_VTX_CONTROL
TASK_VTXCTRL, /**< 无线视频传输控制任务 */
#endif
#ifdef USE_CAMERA_CONTROL
TASK_CAMCTRL, /**< 相机控制任务 */
#endif
#ifdef USE_RCDEVICE
TASK_RCDEVICE, /**< 遥控设备任务 */
#endif
#ifdef USE_ADC_INTERNAL
TASK_ADC_INTERNAL, /**< 内部 ADC 任务 */
#endif
#ifdef USE_PINIOBOX
TASK_PINIOBOX, /**< PinIO 框任务 */
#endif
#ifdef USE_CRSF_V3
TASK_SPEED_NEGOTIATION, /**< CRSF 速度协商任务 */
#endif
#ifdef USE_RC_STATS
TASK_RC_STATS, /**< 遥控统计任务 */
#endif
#ifdef USE_GIMBAL
TASK_GIMBAL, /**< 云台任务 */
#endif
/* Count of real tasks */
TASK_COUNT, /**< 实际任务计数 */
/* Service task IDs */
TASK_NONE = TASK_COUNT, /**< 无任务 */
TASK_SELF /**< 自我任务 */
} taskId_e;
3、任务优先级列表
c
typedef enum
{
TASK_PRIORITY_REALTIME = -1, // Task will be run outside the scheduler logic
TASK_PRIORITY_LOWEST = 1,
TASK_PRIORITY_LOW = 2,
TASK_PRIORITY_MEDIUM = 3,
TASK_PRIORITY_MEDIUM_HIGH = 4,
TASK_PRIORITY_HIGH = 5,
TASK_PRIORITY_MAX = 255 /**< 最高优先级 */
} taskPriority_e;
4、任务初始化列表
系统任务列表存储在src/main/fc/task.c task_attribute_t task_attributes[TASK_COUNT]数组中,按优先级排序 :
c
/// Task ID data in .data (initialised data)
task_attribute_t task_attributes[TASK_COUNT] = {
[TASK_SYSTEM] = DEFINE_TASK("SYSTEM", "LOAD", NULL, taskSystemLoad, TASK_PERIOD_HZ(10), TASK_PRIORITY_MEDIUM_HIGH), /**< 系统加载任务,中高优先级,1000000 / 10Hz */
[TASK_MAIN] = DEFINE_TASK("SYSTEM", "UPDATE", NULL, taskMain, TASK_PERIOD_HZ(1000), TASK_PRIORITY_MEDIUM_HIGH), /**< 主更新任务,中高优先级,1000000 / 1000Hz */
[TASK_SERIAL] = DEFINE_TASK("SERIAL", NULL, NULL, taskHandleSerial, TASK_PERIOD_HZ(100), TASK_PRIORITY_LOW), /**< 串口处理任务,低优先级,1000000 / 100Hz */
[TASK_BATTERY_ALERTS] = DEFINE_TASK("BATTERY_ALERTS", NULL, NULL, taskBatteryAlerts, TASK_PERIOD_HZ(5), TASK_PRIORITY_MEDIUM), /**< 电池警告任务,中优先级,1000000 / 5Hz */
[TASK_BATTERY_VOLTAGE] = DEFINE_TASK("BATTERY_VOLTAGE", NULL, NULL, batteryUpdateVoltage, TASK_PERIOD_HZ(SLOW_VOLTAGE_TASK_FREQ_HZ), TASK_PRIORITY_MEDIUM), /**< 电池电压更新任务,中优先级,频率可更新 */
[TASK_BATTERY_CURRENT] = DEFINE_TASK("BATTERY_CURRENT", NULL, NULL, batteryUpdateCurrentMeter, TASK_PERIOD_HZ(50), TASK_PRIORITY_MEDIUM), /**< 电池电流更新任务,中优先级,1000000 / 50Hz */
#ifdef USE_TRANSPONDER
[TASK_TRANSPONDER] = DEFINE_TASK("TRANSPONDER", NULL, NULL, transponderUpdate, TASK_PERIOD_HZ(250), TASK_PRIORITY_LOW), /**< 传输器更新任务,低优先级,1000000 / 250Hz */
#endif
#ifdef USE_STACK_CHECK
[TASK_STACK_CHECK] = DEFINE_TASK("STACKCHECK", NULL, NULL, taskStackCheck, TASK_PERIOD_HZ(10), TASK_PRIORITY_LOWEST), /**< 堆栈检查任务,最低优先级,10Hz */
#endif
[TASK_GYRO] = DEFINE_TASK("GYRO", NULL, NULL, taskGyroSample, TASK_GYROPID_DESIRED_PERIOD, TASK_PRIORITY_REALTIME), /**< 陀螺仪采样任务,实时优先级 1000000 / 100Hz */
[TASK_FILTER] = DEFINE_TASK("FILTER", NULL, NULL, taskFiltering, TASK_GYROPID_DESIRED_PERIOD, TASK_PRIORITY_REALTIME), /**< 过滤任务,实时优先级 */
[TASK_PID] = DEFINE_TASK("PID", NULL, NULL, taskMainPidLoop, TASK_GYROPID_DESIRED_PERIOD, TASK_PRIORITY_REALTIME), /**< PID主循环任务,实时优先级 */
#ifdef USE_ACC
[TASK_ACCEL] = DEFINE_TASK("ACC", NULL, NULL, taskUpdateAccelerometer, TASK_PERIOD_HZ(1000), TASK_PRIORITY_MEDIUM), /**< 加速计更新任务,中优先级,1000000 / 1000Hz */
[TASK_ATTITUDE] = DEFINE_TASK("ATTITUDE", NULL, NULL, imuUpdateAttitude, TASK_PERIOD_HZ(100), TASK_PRIORITY_MEDIUM), /**< 姿态更新任务,中优先级,1000000 / 100Hz */
#endif
[TASK_RX] = DEFINE_TASK("RX", NULL, rxUpdateCheck, taskUpdateRxMain, TASK_PERIOD_HZ(33), TASK_PRIORITY_HIGH), /**< 接收机更新任务,高优先级,1000000 / 33Hz */
[TASK_DISPATCH] = DEFINE_TASK("DISPATCH", NULL, NULL, dispatchProcess, TASK_PERIOD_HZ(1000), TASK_PRIORITY_HIGH), /**< 分发任务,高优先级,1000000 / 1000Hz */
#ifdef USE_BEEPER
[TASK_BEEPER] = DEFINE_TASK("BEEPER", NULL, NULL, beeperUpdate, TASK_PERIOD_HZ(100), TASK_PRIORITY_LOW), /**< 蜂鸣器更新任务,低优先级,1000000 / 100Hz */
#endif
#ifdef USE_GPS
[TASK_GPS] = DEFINE_TASK("GPS", NULL, NULL, gpsUpdate, TASK_PERIOD_HZ(TASK_GPS_RATE), TASK_PRIORITY_MEDIUM), /**< GPS更新任务,中优先级,防止缓冲区溢出, 1000000 / 100Hz */
#endif
#ifdef USE_GPS_RESCUE
[TASK_GPS_RESCUE] = DEFINE_TASK("GPS_RESCUE", NULL, NULL, taskGpsRescue, TASK_PERIOD_HZ(TASK_GPS_RESCUE_RATE_HZ), TASK_PRIORITY_MEDIUM), /**< GPS救援任务,中优先级 */
#endif
#ifdef USE_ALTITUDE_HOLD
[TASK_ALTHOLD] = DEFINE_TASK("ALTHOLD", NULL, NULL, updateAltHold, TASK_PERIOD_HZ(ALTHOLD_TASK_RATE_HZ), TASK_PRIORITY_LOW), /**< 海拔保持更新任务,低优先级 */
#endif
#ifdef USE_POSITION_HOLD
[TASK_POSHOLD] = DEFINE_TASK("POSHOLD", NULL, NULL, updatePosHold, TASK_PERIOD_HZ(POSHOLD_TASK_RATE_HZ), TASK_PRIORITY_LOW), /**< 位置保持更新任务,低优先级 */
#endif
#ifdef USE_MAG
[TASK_COMPASS] = DEFINE_TASK("COMPASS", NULL, NULL, taskUpdateMag, TASK_PERIOD_HZ(TASK_COMPASS_RATE_HZ), TASK_PRIORITY_LOW), /**< 磁力计更新任务,低优先级 */
#endif
#ifdef USE_BARO
[TASK_BARO] = DEFINE_TASK("BARO", NULL, NULL, taskUpdateBaro, TASK_PERIOD_HZ(TASK_BARO_RATE_HZ), TASK_PRIORITY_LOW), /**< 气压计更新任务,低优先级 */
#endif
#if defined(USE_BARO) || defined(USE_GPS)
[TASK_ALTITUDE] = DEFINE_TASK("ALTITUDE", NULL, NULL, taskCalculateAltitude, TASK_PERIOD_HZ(TASK_ALTITUDE_RATE_HZ), TASK_PRIORITY_LOW), /**< 海拔计算任务,低优先级 */
#endif
#ifdef USE_DASHBOARD
[TASK_DASHBOARD] = DEFINE_TASK("DASHBOARD", NULL, NULL, dashboardUpdate, TASK_PERIOD_HZ(10), TASK_PRIORITY_LOW), /**< 仪表盘更新任务,低优先级,10Hz */
#endif
#ifdef USE_OSD
[TASK_OSD] = DEFINE_TASK("OSD", NULL, osdUpdateCheck, osdUpdate, TASK_PERIOD_HZ(OSD_FRAMERATE_DEFAULT_HZ), TASK_PRIORITY_LOW), /**< OSD更新任务,低优先级 */
#endif
#ifdef USE_TELEMETRY
[TASK_TELEMETRY] = DEFINE_TASK("TELEMETRY", NULL, NULL, taskTelemetry, TASK_PERIOD_HZ(250), TASK_PRIORITY_LOW), /**< 遥测更新任务,低优先级,250Hz */
#endif
#ifdef USE_LED_STRIP
[TASK_LEDSTRIP] = DEFINE_TASK("LEDSTRIP", NULL, NULL, ledStripUpdate, TASK_PERIOD_HZ(TASK_LEDSTRIP_RATE_HZ), TASK_PRIORITY_LOW), /**< LED条带更新任务,低优先级 */
#endif
#ifdef USE_BST
[TASK_BST_MASTER_PROCESS] = DEFINE_TASK("BST_MASTER_PROCESS", NULL, NULL, taskBstMasterProcess, TASK_PERIOD_HZ(50), TASK_PRIORITY_LOWEST), /**< BST主进程任务,最低优先级,50Hz */
#endif
#ifdef USE_ESC_SENSOR
[TASK_ESC_SENSOR] = DEFINE_TASK("ESC_SENSOR", NULL, NULL, escSensorProcess, TASK_PERIOD_HZ(100), TASK_PRIORITY_LOW), /**< ESC传感器处理任务,低优先级,100Hz */
#endif
#ifdef USE_CMS
[TASK_CMS] = DEFINE_TASK("CMS", NULL, NULL, cmsHandler, TASK_PERIOD_HZ(20), TASK_PRIORITY_LOW), /**< CMS处理任务,低优先级,20Hz */
#endif
#ifdef USE_VTX_CONTROL
[TASK_VTXCTRL] = DEFINE_TASK("VTXCTRL", NULL, NULL, vtxUpdate, TASK_PERIOD_HZ(5), TASK_PRIORITY_LOWEST), /**< VTX控制更新任务,最低优先级,5Hz */
#endif
#ifdef USE_RCDEVICE
[TASK_RCDEVICE] = DEFINE_TASK("RCDEVICE", NULL, NULL, rcdeviceUpdate, TASK_PERIOD_HZ(20), TASK_PRIORITY_MEDIUM), /**< RC设备更新任务,中优先级,20Hz */
#endif
#ifdef USE_CAMERA_CONTROL
[TASK_CAMCTRL] = DEFINE_TASK("CAMCTRL", NULL, NULL, taskCameraControl, TASK_PERIOD_HZ(5), TASK_PRIORITY_LOW), /**< 摄像头控制任务,低优先级,5Hz */
#endif
#ifdef USE_ADC_INTERNAL
[TASK_ADC_INTERNAL] = DEFINE_TASK("ADCINTERNAL", NULL, NULL, adcInternalProcess, TASK_PERIOD_HZ(1), TASK_PRIORITY_LOWEST), /**< 内部ADC处理任务,最低优先级,1Hz */
#endif
#ifdef USE_PINIOBOX
[TASK_PINIOBOX] = DEFINE_TASK("PINIOBOX", NULL, NULL, pinioBoxUpdate, TASK_PERIOD_HZ(20), TASK_PRIORITY_LOWEST), /**< PinioBox更新任务,最低优先级,20Hz */
#endif
#ifdef USE_RANGEFINDER
[TASK_RANGEFINDER] = DEFINE_TASK("RANGEFINDER", NULL, NULL, taskUpdateRangefinder, TASK_PERIOD_HZ(10), TASK_PRIORITY_LOWEST), /**< 距离传感器更新任务,最低优先级,10Hz */
#endif
#ifdef USE_OPTICALFLOW
[TASK_OPTICALFLOW] = DEFINE_TASK("OPTICALFLOW", NULL, NULL, taskUpdateOpticalflow, TASK_PERIOD_HZ(10), TASK_PRIORITY_LOWEST), /**< 光流传感器更新任务,最低优先级,10Hz */
#endif
#ifdef USE_CRSF_V3
[TASK_SPEED_NEGOTIATION] = DEFINE_TASK("SPEED_NEGOTIATION", NULL, NULL, speedNegotiationProcess, TASK_PERIOD_HZ(100), TASK_PRIORITY_LOW), /**< 速度协商任务,低优先级,100Hz */
#endif
#ifdef USE_RC_STATS
[TASK_RC_STATS] = DEFINE_TASK("RC_STATS", NULL, NULL, rcStatsUpdate, TASK_PERIOD_HZ(100), TASK_PRIORITY_LOW), /**< RC统计信息更新任务,低优先级,100Hz */
#endif
#ifdef USE_GIMBAL
[TASK_GIMBAL] = DEFINE_TASK("GIMBAL", NULL, NULL, gimbalUpdate, TASK_PERIOD_HZ(100), TASK_PRIORITY_MEDIUM), /**< 云台更新任务,中优先级,100Hz */
#endif
};
5、任务初始化
初始化飞行控制器中的各种任务,并根据具体的硬件配置和特性来启用或禁用这些任务。它还根据需要调整任务的执行频率,以确保飞行控制器能够根据不同的需求和环境正确地运行和管理任务。通过这种方式,代码实现了对飞行控制器任务的灵活配置和管理。
c
/******************************************************************************
*
* 函数名 : tasksInit
*
* 函数功能 : 任务初始化
*
* 参数 : 无
*
* 返回值 : 无
*
* 其他 :
*
******************************************************************************/
void tasksInit(void)
{
schedulerInit(); /**< 初始化调度器 */
setTaskEnabled(TASK_MAIN, true); /**< 启用主任务 */
setTaskEnabled(TASK_SERIAL, true); /**< 启用串行任务 */
rescheduleTask(TASK_SERIAL, TASK_PERIOD_HZ(serialConfig()->serial_update_rate_hz)); /**< 根据配置重新安排串行任务的频率 */
const bool useBatteryVoltage = batteryConfig()->voltageMeterSource != VOLTAGE_METER_NONE;
setTaskEnabled(TASK_BATTERY_VOLTAGE, useBatteryVoltage); /**< 根据配置启用电池电压监测任务 */
#if defined(USE_BATTERY_VOLTAGE_SAG_COMPENSATION)
if (isSagCompensationConfigured()) {
rescheduleTask(TASK_BATTERY_VOLTAGE, TASK_PERIOD_HZ(FAST_VOLTAGE_TASK_FREQ_HZ)); /**< 如果启用了电压下降补偿,重新安排电池电压监测任务为快速采样 */
}
#endif
const bool useBatteryCurrent = batteryConfig()->currentMeterSource != CURRENT_METER_NONE;
setTaskEnabled(TASK_BATTERY_CURRENT, useBatteryCurrent); /**< 根据配置启用电池电流监测任务 */
const bool useBatteryAlerts = batteryConfig()->useVBatAlerts || batteryConfig()->useConsumptionAlerts || featureIsEnabled(FEATURE_OSD);
setTaskEnabled(TASK_BATTERY_ALERTS, (useBatteryVoltage || useBatteryCurrent) && useBatteryAlerts); /**< 根据配置启用电池警告任务 */
#ifdef USE_STACK_CHECK
setTaskEnabled(TASK_STACK_CHECK, true); /**< 启用堆栈检查任务 */
#endif
if (sensors(SENSOR_GYRO)) {
rescheduleTask(TASK_GYRO, gyro.sampleLooptime); /**< 根据配置重新安排陀螺仪采样任务频率 */
rescheduleTask(TASK_FILTER, gyro.targetLooptime); /**< 根据配置重新安排滤波任务频率 */
rescheduleTask(TASK_PID, gyro.targetLooptime); /**< 根据配置重新安排PID任务频率 */
setTaskEnabled(TASK_GYRO, true); /**< 启用陀螺仪任务 */
setTaskEnabled(TASK_FILTER, true); /**< 启用滤波任务 */
setTaskEnabled(TASK_PID, true); /**< 启用PID任务 */
schedulerEnableGyro(); /**< 在调度器中启用陀螺仪 */
}
#if defined(USE_ACC)
if (sensors(SENSOR_ACC) && acc.sampleRateHz) {
setTaskEnabled(TASK_ACCEL, true); /**< 启用加速度计任务 */
rescheduleTask(TASK_ACCEL, TASK_PERIOD_HZ(acc.sampleRateHz)); /**< 根据配置重新安排加速度计任务频率 */
setTaskEnabled(TASK_ATTITUDE, true); /**< 启用姿态任务 */
}
#endif
#ifdef USE_RANGEFINDER
if (sensors(SENSOR_RANGEFINDER)) {
setTaskEnabled(TASK_RANGEFINDER, featureIsEnabled(FEATURE_RANGEFINDER)); /**< 根据配置启用测距仪任务 */
}
#endif
#ifdef USE_OPTICALFLOW
if (sensors(SENSOR_OPTICALFLOW)) {
setTaskEnabled(TASK_OPTICALFLOW, featureIsEnabled(FEATURE_OPTICALFLOW)); /**< 根据配置启用光流任务 */
}
#endif
setTaskEnabled(TASK_RX, true); /**< 启用接收机任务 */
setTaskEnabled(TASK_DISPATCH, dispatchIsEnabled()); /**< 根据配置启用分派任务 */
#ifdef USE_BEEPER
setTaskEnabled(TASK_BEEPER, true); /**< 启用蜂鸣器任务 */
#endif
#ifdef USE_GPS
setTaskEnabled(TASK_GPS, featureIsEnabled(FEATURE_GPS)); /**< 根据配置启用GPS任务 */
#endif
#ifdef USE_GPS_RESCUE
setTaskEnabled(TASK_GPS_RESCUE, featureIsEnabled(FEATURE_GPS)); /**< 根据配置启用GPS救援任务 */
#endif
#ifdef USE_ALTITUDE_HOLD
setTaskEnabled(TASK_ALTHOLD, sensors(SENSOR_BARO) || featureIsEnabled(FEATURE_GPS)); /**< 根据配置启用高度保持任务 */
#endif
#ifdef USE_POSITION_HOLD
setTaskEnabled(TASK_POSHOLD, featureIsEnabled(FEATURE_GPS)); /**< 根据配置启用位置保持任务 */
#endif
#ifdef USE_MAG
setTaskEnabled(TASK_COMPASS, sensors(SENSOR_MAG)); /**< 根据配置启用磁罗盘任务 */
#endif
#ifdef USE_BARO
setTaskEnabled(TASK_BARO, sensors(SENSOR_BARO)); /**< 根据配置启用气压计任务 */
#endif
#if defined(USE_BARO) || defined(USE_GPS)
setTaskEnabled(TASK_ALTITUDE, sensors(SENSOR_BARO) || featureIsEnabled(FEATURE_GPS)); /**< 根据配置启用高度任务 */
#endif
#ifdef USE_DASHBOARD
setTaskEnabled(TASK_DASHBOARD, featureIsEnabled(FEATURE_DASHBOARD)); /**< 根据配置启用仪表盘任务 */
#endif
#ifdef USE_TELEMETRY
if (featureIsEnabled(FEATURE_TELEMETRY)) {
setTaskEnabled(TASK_TELEMETRY, true); /**< 启用遥测任务 */
if (rxRuntimeState.serialrxProvider == SERIALRX_JETIEXBUS) {
rescheduleTask(TASK_TELEMETRY, TASK_PERIOD_HZ(500)); /**< 对于Jeti Exbus,重新安排遥测任务为500Hz */
} else if (rxRuntimeState.serialrxProvider == SERIALRX_CRSF) {
rescheduleTask(TASK_TELEMETRY, TASK_PERIOD_HZ(500)); /**< 对于CRSF,重新安排遥测任务为500Hz */
}
}
#endif
#ifdef USE_LED_STRIP
setTaskEnabled(TASK_LEDSTRIP, featureIsEnabled(FEATURE_LED_STRIP)); /**< 根据配置启用LED灯带任务 */
#endif
#ifdef USE_TRANSPONDER
setTaskEnabled(TASK_TRANSPONDER, featureIsEnabled(FEATURE_TRANSPONDER)); /**< 根据配置启用转发器任务 */
#endif
#ifdef USE_OSD
rescheduleTask(TASK_OSD, TASK_PERIOD_HZ(osdConfig()->framerate_hz)); /**< 根据配置重新安排OSD任务频率 */
setTaskEnabled(TASK_OSD, featureIsEnabled(FEATURE_OSD) && osdGetDisplayPort(NULL)); /**< 根据配置启用OSD任务 */
#endif
#ifdef USE_BST
setTaskEnabled(TASK_BST_MASTER_PROCESS, true); /**< 启用BST主处理任务 */
#endif
#ifdef USE_ESC_SENSOR
setTaskEnabled(TASK_ESC_SENSOR, featureIsEnabled(FEATURE_ESC_SENSOR)); /**< 根据配置启用ESC传感器任务 */
#endif
#ifdef USE_ADC_INTERNAL
setTaskEnabled(TASK_ADC_INTERNAL, true); /**< 启用内部ADC任务 */
#endif
#ifdef USE_PINIOBOX
pinioBoxTaskControl(); /**< 控制PinioBox任务 */
#endif
#ifdef USE_CMS
#ifdef USE_MSP_DISPLAYPORT
setTaskEnabled(TASK_CMS, true); /**< 启用CMS任务 */
#else
setTaskEnabled(TASK_CMS, featureIsEnabled(FEATURE_OSD) || featureIsEnabled(FEATURE_DASHBOARD)); /**< 根据配置启用CMS任务 */
#endif
#endif
#ifdef USE_VTX_CONTROL
#if defined(USE_VTX_RTC6705) || defined(USE_VTX_SMARTAUDIO) || defined(USE_VTX_TRAMP) || defined(USE_VTX_MSP)
setTaskEnabled(TASK_VTXCTRL, true); /**< 启用VTX控制任务 */
#endif
#endif
#ifdef USE_CAMERA_CONTROL
setTaskEnabled(TASK_CAMCTRL, true); /**< 启用相机控制任务 */
#endif
#ifdef USE_RCDEVICE
setTaskEnabled(TASK_RCDEVICE, rcdeviceIsEnabled()); /**< 根据配置启用RC设备任务 */
#endif
#ifdef USE_CRSF_V3
const bool useCRSF = rxRuntimeState.serialrxProvider == SERIALRX_CRSF;
setTaskEnabled(TASK_SPEED_NEGOTIATION, useCRSF); /**< 如果使用CRSF,启用速度协商任务 */
#endif
#ifdef SIMULATOR_MULTITHREAD
rescheduleTask(TASK_RX, 1); /**< 在模拟器中重新安排接收机任务频率 */
#endif
#ifdef USE_RC_STATS
setTaskEnabled(TASK_RC_STATS, true); /**< 启用RC统计任务 */
#endif
#ifdef USE_GIMBAL
setTaskEnabled(TASK_GIMBAL, true); /**< 启用云台任务 */
#endif
}
6、调度器
系统调度器在src/main/scheduler/scheduler.c 在scheduler.c中实现,采用非抢占式循环调度算法 :
c
FAST_CODE void scheduler(void)
{
static uint32_t checkCycles = 0;
static uint32_t scheduleCount = 0;
#if defined(USE_LATE_TASK_STATISTICS)
static uint32_t gyroCyclesMean = 0;
static uint32_t gyroCyclesCount = 0;
static uint64_t gyroCyclesTotal = 0;
static float devSquared = 0.0f;
#endif
#if !defined(UNIT_TEST)
const timeUs_t schedulerStartTimeUs = micros();
#endif
timeUs_t currentTimeUs;
uint32_t nowCycles;
timeUs_t taskExecutionTimeUs = 0;
task_t *selectedTask = NULL;
uint16_t selectedTaskDynamicPriority = 0;
uint32_t nextTargetCycles = 0;
int32_t schedLoopRemainingCycles;
bool firstSchedulingOpportunity = false;
#if defined(UNIT_TEST)
if (nextTargetCycles == 0)
{
lastTargetCycles = getCycleCounter();
nextTargetCycles = lastTargetCycles + desiredPeriodCycles;
}
#endif
if (gyroEnabled)
{
// Realtime gyro/filtering/PID tasks get complete priority
task_t *gyroTask = getTask(TASK_GYRO);
nowCycles = getCycleCounter();
#if defined(UNIT_TEST)
lastTargetCycles = clockMicrosToCycles(gyroTask->lastExecutedAtUs);
#endif
nextTargetCycles = lastTargetCycles + desiredPeriodCycles;
schedLoopRemainingCycles = cmpTimeCycles(nextTargetCycles, nowCycles);
if (schedLoopRemainingCycles < -desiredPeriodCycles)
{
/* A task has so grossly overrun that at entire gyro cycle has been skipped
* This is most likely to occur when connected to the configurator via USB as the serial
* task is non-deterministic
* Recover as best we can, advancing scheduling by a whole number of cycles
*/
nextTargetCycles += desiredPeriodCycles * (1 + (schedLoopRemainingCycles / -desiredPeriodCycles));
schedLoopRemainingCycles = cmpTimeCycles(nextTargetCycles, nowCycles);
}
// Tune out the time lost between completing the last task execution and re-entering the scheduler
if ((schedLoopRemainingCycles < schedLoopStartMinCycles) &&
(schedLoopStartCycles < schedLoopStartMaxCycles))
{
schedLoopStartCycles += schedLoopStartDeltaUpCycles;
}
// Once close to the timing boundary, poll for it's arrival
if (schedLoopRemainingCycles < schedLoopStartCycles)
{
if (schedLoopStartCycles > schedLoopStartMinCycles)
{
schedLoopStartCycles -= schedLoopStartDeltaDownCycles;
}
#if !defined(UNIT_TEST)
while (schedLoopRemainingCycles > 0)
{
nowCycles = getCycleCounter();
schedLoopRemainingCycles = cmpTimeCycles(nextTargetCycles, nowCycles);
}
#endif
currentTimeUs = micros();
taskExecutionTimeUs += schedulerExecuteTask(gyroTask, currentTimeUs);
if (gyroFilterReady())
{
taskExecutionTimeUs += schedulerExecuteTask(getTask(TASK_FILTER), currentTimeUs);
}
if (pidLoopReady())
{
taskExecutionTimeUs += schedulerExecuteTask(getTask(TASK_PID), currentTimeUs);
}
// Check for incoming RX data. Don't do this in the checker as that is called repeatedly within
// a given gyro loop, and ELRS takes a long time to process this and so can only be safely processed
// before the checkers
rxFrameCheck(currentTimeUs, cmpTimeUs(currentTimeUs, getTask(TASK_RX)->lastExecutedAtUs));
// Check for failsafe conditions without reliance on the RX task being well behaved
if (cmp32(millis(), lastFailsafeCheckMs) > PERIOD_RXDATA_FAILURE)
{
// This is very low cost taking less that 4us every 10ms
failsafeCheckDataFailurePeriod();
failsafeUpdateState();
lastFailsafeCheckMs = millis();
}
// This is the first scheduling opportunity after the realtime tasks have run
firstSchedulingOpportunity = true;
#if defined(USE_LATE_TASK_STATISTICS)
gyroCyclesNow = cmpTimeCycles(nowCycles, lastTargetCycles);
gyroCyclesTotal += gyroCyclesNow;
gyroCyclesCount++;
DEBUG_SET(DEBUG_SCHEDULER_DETERMINISM, 0, clockCyclesTo10thMicros(gyroCyclesNow));
int32_t deviationCycles = gyroCyclesNow - gyroCyclesMean;
devSquared += deviationCycles * deviationCycles;
// % CPU busy
DEBUG_SET(DEBUG_TIMING_ACCURACY, 0, getAverageSystemLoadPercent());
if (cmpTimeCycles(nextTimingCycles, nowCycles) < 0)
{
nextTimingCycles += clockMicrosToCycles(1000000);
// Tasks late in last second
DEBUG_SET(DEBUG_TIMING_ACCURACY, 1, lateTaskCount);
// Total lateness in last second in us
DEBUG_SET(DEBUG_TIMING_ACCURACY, 2, clockCyclesTo10thMicros(lateTaskTotal));
// Total tasks run in last second
DEBUG_SET(DEBUG_TIMING_ACCURACY, 3, taskCount);
lateTaskPercentage = 1000 * (uint32_t)lateTaskCount / taskCount;
// 10ths % of tasks late in last second
DEBUG_SET(DEBUG_TIMING_ACCURACY, 4, lateTaskPercentage);
float gyroCyclesStdDev = sqrtf(devSquared / gyroCyclesCount);
int32_t gyroCyclesStdDev100thus = clockCyclesTo100thMicros((int32_t)gyroCyclesStdDev);
DEBUG_SET(DEBUG_TIMING_ACCURACY, 7, gyroCyclesStdDev100thus);
DEBUG_SET(DEBUG_SCHEDULER_DETERMINISM, 7, gyroCyclesStdDev100thus);
gyroCyclesMean = gyroCyclesTotal / gyroCyclesCount;
devSquared = 0.0f;
gyroCyclesTotal = 0;
gyroCyclesCount = 0;
lateTaskCount = 0;
lateTaskTotal = 0;
taskCount = 0;
}
#endif
lastTargetCycles = nextTargetCycles;
gyroDev_t *gyro = gyroActiveDev();
// Bring the scheduler into lock with the gyro
if (gyro->gyroModeSPI != GYRO_EXTI_NO_INT)
{
// Track the actual gyro rate over given number of cycle times and set the expected timebase
static uint32_t terminalGyroRateCount = 0;
static int32_t sampleRateStartCycles;
if (terminalGyroRateCount == 0)
{
terminalGyroRateCount = gyro->detectedEXTI + GYRO_RATE_COUNT;
sampleRateStartCycles = nowCycles;
}
if (gyro->detectedEXTI >= terminalGyroRateCount)
{
// Calculate the number of clock cycles on average between gyro interrupts
uint32_t sampleCycles = nowCycles - sampleRateStartCycles;
desiredPeriodCycles = sampleCycles / GYRO_RATE_COUNT;
sampleRateStartCycles = nowCycles;
terminalGyroRateCount += GYRO_RATE_COUNT;
}
// Track the actual gyro rate over given number of cycle times and remove skew
static uint32_t terminalGyroLockCount = 0;
static int32_t accGyroSkew = 0;
int32_t gyroSkew = cmpTimeCycles(nextTargetCycles, gyro->gyroSyncEXTI) % desiredPeriodCycles;
if (gyroSkew > (desiredPeriodCycles / 2))
{
gyroSkew -= desiredPeriodCycles;
}
accGyroSkew += gyroSkew;
#if defined(USE_LATE_TASK_STATISTICS)
static int32_t minGyroPeriod = (int32_t)INT_MAX;
static int32_t maxGyroPeriod = (int32_t)INT_MIN;
static uint32_t lastGyroSyncEXTI;
gyroCyclesNow = cmpTimeCycles(gyro->gyroSyncEXTI, lastGyroSyncEXTI);
if (gyroCyclesNow)
{
lastGyroSyncEXTI = gyro->gyroSyncEXTI;
// If we're syncing to a short cycle, divide by eight
if (gyro->gyroShortPeriod != 0)
{
gyroCyclesNow /= 8;
}
if (gyroCyclesNow < minGyroPeriod)
{
minGyroPeriod = gyroCyclesNow;
}
// Crude detection of missed cycles caused by configurator traffic
if ((gyroCyclesNow > maxGyroPeriod) && (gyroCyclesNow < minGyroPeriod + minGyroPeriod / 2)) // 1.5 * minGyroPeriod
{
maxGyroPeriod = gyroCyclesNow;
}
}
#endif
if (terminalGyroLockCount == 0)
{
terminalGyroLockCount = gyro->detectedEXTI + GYRO_LOCK_COUNT;
}
if (gyro->detectedEXTI >= terminalGyroLockCount)
{
terminalGyroLockCount += GYRO_LOCK_COUNT;
// Move the desired start time of the gyroTask
lastTargetCycles -= (accGyroSkew / GYRO_LOCK_COUNT);
#if defined(USE_LATE_TASK_STATISTICS)
DEBUG_SET(DEBUG_SCHEDULER_DETERMINISM, 3, clockCyclesTo10thMicros(accGyroSkew / GYRO_LOCK_COUNT));
DEBUG_SET(DEBUG_SCHEDULER_DETERMINISM, 4, clockCyclesTo100thMicros(minGyroPeriod));
DEBUG_SET(DEBUG_SCHEDULER_DETERMINISM, 5, clockCyclesTo100thMicros(maxGyroPeriod));
DEBUG_SET(DEBUG_SCHEDULER_DETERMINISM, 6, clockCyclesTo100thMicros(maxGyroPeriod - minGyroPeriod));
minGyroPeriod = INT_MAX;
maxGyroPeriod = INT_MIN;
#endif
accGyroSkew = 0;
}
}
}
}
nowCycles = getCycleCounter();
schedLoopRemainingCycles = cmpTimeCycles(nextTargetCycles, nowCycles);
if (!gyroEnabled || (schedLoopRemainingCycles > (int32_t)clockMicrosToCycles(CHECK_GUARD_MARGIN_US)))
{
currentTimeUs = micros();
// Update task dynamic priorities
for (task_t *task = queueFirst(); task != NULL; task = queueNext())
{
if (task->attribute->staticPriority != TASK_PRIORITY_REALTIME)
{
// Task has checkFunc - event driven
if (task->attribute->checkFunc)
{
// Increase priority for event driven tasks
if (task->dynamicPriority > 0)
{
task->taskAgePeriods = 1 + (cmpTimeUs(currentTimeUs, task->lastSignaledAtUs) / task->attribute->desiredPeriodUs);
task->dynamicPriority = 1 + task->attribute->staticPriority * task->taskAgePeriods;
}
else if (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;
}
}
else
{
// Task is time-driven, dynamicPriority is last execution age (measured in desiredPeriods)
// Task age is calculated from last execution
task->taskAgePeriods = (cmpTimeUs(currentTimeUs, task->lastExecutedAtUs) / task->attribute->desiredPeriodUs);
if (task->taskAgePeriods > 0)
{
task->dynamicPriority = 1 + task->attribute->staticPriority * task->taskAgePeriods;
}
}
if (task->dynamicPriority > selectedTaskDynamicPriority)
{
timeDelta_t taskRequiredTimeUs = task->anticipatedExecutionTime >> TASK_EXEC_TIME_SHIFT;
int32_t taskRequiredTimeCycles = (int32_t)clockMicrosToCycles((uint32_t)taskRequiredTimeUs);
// Allow a little extra time
taskRequiredTimeCycles += checkCycles + taskGuardCycles;
// If there's no time to run the task, discount it from prioritisation unless aged sufficiently
// Don't block the SERIAL task.
if ((taskRequiredTimeCycles < schedLoopRemainingCycles) ||
((scheduleCount & SCHED_TASK_DEFER_MASK) == 0) ||
((task - tasks) == TASK_SERIAL))
{
selectedTaskDynamicPriority = task->dynamicPriority;
selectedTask = task;
}
}
}
}
// The number of cycles taken to run the checkers is quite consistent with some higher spikes, but
// that doesn't defeat its use
checkCycles = cmpTimeCycles(getCycleCounter(), nowCycles);
if (selectedTask)
{
// Recheck the available time as checkCycles is only approximate
timeDelta_t taskRequiredTimeUs = selectedTask->anticipatedExecutionTime >> TASK_EXEC_TIME_SHIFT;
#if defined(USE_LATE_TASK_STATISTICS)
selectedTask->execTime = taskRequiredTimeUs;
#endif
int32_t taskRequiredTimeCycles = (int32_t)clockMicrosToCycles((uint32_t)taskRequiredTimeUs);
nowCycles = getCycleCounter();
schedLoopRemainingCycles = cmpTimeCycles(nextTargetCycles, nowCycles);
// Allow a little extra time
taskRequiredTimeCycles += taskGuardCycles;
if (!gyroEnabled || firstSchedulingOpportunity || (taskRequiredTimeCycles < schedLoopRemainingCycles))
{
uint32_t antipatedEndCycles = nowCycles + taskRequiredTimeCycles;
taskExecutionTimeUs += schedulerExecuteTask(selectedTask, currentTimeUs);
nowCycles = getCycleCounter();
int32_t cyclesOverdue = cmpTimeCycles(nowCycles, antipatedEndCycles);
#if defined(USE_LATE_TASK_STATISTICS)
if (cyclesOverdue > 0)
{
if ((currentTask - tasks) != TASK_SERIAL)
{
DEBUG_SET(DEBUG_SCHEDULER_DETERMINISM, 1, currentTask - tasks);
DEBUG_SET(DEBUG_SCHEDULER_DETERMINISM, 2, clockCyclesTo10thMicros(cyclesOverdue));
currentTask->lateCount++;
lateTaskCount++;
lateTaskTotal += cyclesOverdue;
}
}
#endif // USE_LATE_TASK_STATISTICS
if ((currentTask - tasks) == TASK_RX)
{
skippedRxAttempts = 0;
}
#ifdef USE_OSD
else if ((currentTask - tasks) == TASK_OSD)
{
skippedOSDAttempts = 0;
}
#endif
if ((cyclesOverdue > 0) || (-cyclesOverdue < taskGuardMinCycles))
{
if (taskGuardCycles < taskGuardMaxCycles)
{
taskGuardCycles += taskGuardDeltaUpCycles;
}
}
else if (taskGuardCycles > taskGuardMinCycles)
{
taskGuardCycles -= taskGuardDeltaDownCycles;
}
#if defined(USE_LATE_TASK_STATISTICS)
taskCount++;
#endif // USE_LATE_TASK_STATISTICS
}
else if ((selectedTask->taskAgePeriods > TASK_AGE_EXPEDITE_COUNT) ||
#ifdef USE_OSD
(((selectedTask - tasks) == TASK_OSD) && (TASK_AGE_EXPEDITE_OSD != 0) && (++skippedOSDAttempts > TASK_AGE_EXPEDITE_OSD)) ||
#endif
(((selectedTask - tasks) == TASK_RX) && (TASK_AGE_EXPEDITE_RX != 0) && (++skippedRxAttempts > TASK_AGE_EXPEDITE_RX)))
{
// If a task has been unable to run, then reduce it's recorded estimated run time to ensure
// it's ultimate scheduling
selectedTask->anticipatedExecutionTime *= TASK_AGE_EXPEDITE_SCALE;
}
}
}
#if defined(UNIT_TEST)
readSchedulerLocals(selectedTask, selectedTaskDynamicPriority);
UNUSED(taskExecutionTimeUs);
#else
DEBUG_SET(DEBUG_SCHEDULER, 2, micros() - schedulerStartTimeUs - taskExecutionTimeUs); // time spent in scheduler
#endif
scheduleCount++;
} /**< 调度器主函数,用于管理任务的优先级和执行时机 */
这段代码的核心功能是Betaflight固件中的任务调度器。它确保了系统中各个任务能够按照预定的时间间隔和优先级顺序执行。特别是对于需要实时性的任务(如陀螺仪任务、过滤任务和PID任务),调度器会优先处理这些任务。
6.1、详细解析这个Betaflight调度器函数
我来详细解析这个Betaflight调度器函数的实现,这是Betaflight实时任务调度的核心代码。
6.1.1、函数整体结构分析
这个调度器函数采用了多级优先级调度策略,主要分为三个层次:
- 实时任务层 - 陀螺仪、滤波器、PID控制(最高优先级)
- 动态优先级任务层 - 根据任务年龄动态调整优先级
- 时间保护机制 - 防止任务超时影响实时性
基于Betaflight调度器的代码分析,绘制了详细的调度任务逻辑框图:

调度流程详细说明
实时任务调度路径(左侧)
- 时间精确控制 :通过忙等待(
while (schedLoopRemainingCycles > 0))确保实时任务在精确时间点执行 - 任务链式执行:陀螺仪 → 滤波器 → PID 的顺序保证数据处理流水线
- 硬件同步:与陀螺仪EXTI中断同步,消除累积误差
- 安全监控:独立的接收机和故障保护检查,不依赖任务系统
动态任务调度路径(右侧)
-
双重任务模型 :
- 事件驱动 :通过
checkFunc主动检查事件 - 时间驱动:基于固定周期调度
- 事件驱动 :通过
-
优先级计算 :
动态优先级 = 1 + 静态优先级 × 任务年龄周期数 -
智能选择:综合考虑优先级、剩余时间、任务关键性
-
防饿死机制:通过年龄阈值和加速机制确保长时间等待的任务最终被执行
这个调度器设计体现了嵌入式系统实时调度的精髓:在保证最高优先级任务确定性的前提下,通过智能算法为其他任务提供公平的服务机会。
c
┌───────────────────────────────────────────────────────────────┐
│ 调度器主函数 (scheduler) │
├───────────────────────────────────────────────────────────────┤
│ 1. 实时任务处理 (陀螺仪相关) │
│ ├─ 检查陀螺仪是否启用 │
│ ├─ 计算陀螺仪任务执行时机 │
│ ├─ 检查任务是否错过执行时间 │
│ │ ├─ 如果错过,计算需要跳过的周期数 │
│ ├─ 执行陀螺仪任务 │
│ ├─ 执行滤波器任务 (如果准备好) │
│ ├─ 执行PID任务 (如果准备好) │
│ └─ 更新陀螺仪同步信息 │
├───────────────────────────────────────────────────────────────┤
│ 2. 普通任务调度 │
│ ├─ 计算每个任务的动态优先级 │
│ │ ├─ 事件驱动任务:基于事件发生时间计算优先级 │
│ │ └─ 时间驱动任务:基于上次执行时间计算优先级 │
│ ├─ 选择优先级最高的任务 │
│ ├─ 检查是否有足够时间执行该任务 │
│ │ ├─ 如果有足够时间:执行任务 │
│ │ └─ 如果没有足够时间:考虑推迟或调整 │
│ └─ 更新任务统计信息 │
├───────────────────────────────────────────────────────────────┤
│ 3. 性能统计 │
│ ├─ 记录任务执行时间 │
│ ├─ 记录任务延迟 │
│ └─ 更新调度器状态 │
└───────────────────────────────────────────────────────────────┘
6.1.2、实时任务处理(最高优先级)
c
if (gyroEnabled) {
// 获取陀螺仪任务并计算时间
task_t *gyroTask = getTask(TASK_GYRO);
nowCycles = getCycleCounter();
nextTargetCycles = lastTargetCycles + desiredPeriodCycles;
schedLoopRemainingCycles = cmpTimeCycles(nextTargetCycles, nowCycles);
// 处理严重超时情况
if (schedLoopRemainingCycles < -desiredPeriodCycles) {
/* A task has so grossly overrun that at entire gyro cycle has been skipped
* This is most likely to occur when connected to the configurator via USB as the serial
* task is non-deterministic
* Recover as best we can, advancing scheduling by a whole number of cycles
*/
nextTargetCycles += desiredPeriodCycles * (1 + (schedLoopRemainingCycles / -desiredPeriodCycles));
schedLoopRemainingCycles = cmpTimeCycles(nextTargetCycles, nowCycles);
}
关键机制:
- 检测任务严重超时(超过整个陀螺仪周期)
- 主要发生在连接配置器时,串口任务不确定性导致
- 通过跳过完整周期数来恢复调度
6.1.3、实时任务精确触发
c
// 调整调度器启动时间
if ((schedLoopRemainingCycles < schedLoopStartMinCycles) &&
(schedLoopStartCycles < schedLoopStartMaxCycles)) {
schedLoopStartCycles += schedLoopStartDeltaUpCycles;
}
// 接近时间边界时轮询等待
if (schedLoopRemainingCycles < schedLoopStartCycles) {
if (schedLoopStartCycles > schedLoopStartMinCycles) {
schedLoopStartCycles -= schedLoopStartDeltaDownCycles;
}
// 忙等待直到到达精确执行时间
#if !defined(UNIT_TEST)
while (schedLoopRemainingCycles > 0) {
nowCycles = getCycleCounter();
schedLoopRemainingCycles = cmpTimeCycles(nextTargetCycles, nowCycles);
}
#endif
关键特点:
- 自适应时间调整:根据实际执行情况动态调整启动时机
- 忙等待机制:在接近执行时间时使用精确轮询
- 最小化抖动:确保实时任务在精确的时间点执行
6.1.4、实时任务执行流程
c
currentTimeUs = micros();
taskExecutionTimeUs += schedulerExecuteTask(gyroTask, currentTimeUs);
if (gyroFilterReady()) {
taskExecutionTimeUs += schedulerExecuteTask(getTask(TASK_FILTER), currentTimeUs);
}
if (pidLoopReady()) {
taskExecutionTimeUs += schedulerExecuteTask(getTask(TASK_PID), currentTimeUs);
}
// 接收机数据处理(不依赖RX任务)
rxFrameCheck(currentTimeUs, cmpTimeUs(currentTimeUs, getTask(TASK_RX)->lastExecutedAtUs));
// 故障保护检查
if (cmp32(millis(), lastFailsafeCheckMs) > PERIOD_RXDATA_FAILURE) {
failsafeCheckDataFailurePeriod();
failsafeUpdateState();
lastFailsafeCheckMs = millis();
}
firstSchedulingOpportunity = true;
执行顺序保证:
- 陀螺仪数据读取 (
TASK_GYRO) - 滤波器处理 (
TASK_FILTER) - 条件触发 - PID控制计算 (
TASK_PID) - 条件触发 - 接收机帧检查 - 独立于任务系统
- 故障保护检查 - 定时执行
6.1.5、性能监控与统计
c
#if defined(USE_LATE_TASK_STATISTICS)
gyroCyclesNow = cmpTimeCycles(nowCycles, lastTargetCycles);
gyroCyclesTotal += gyroCyclesNow;
gyroCyclesCount++;
DEBUG_SET(DEBUG_SCHEDULER_DETERMINISM, 0, clockCyclesTo10thMicros(gyroCyclesNow));
int32_t deviationCycles = gyroCyclesNow - gyroCyclesMean;
devSquared += deviationCycles * deviationCycles;
// % CPU 繁忙度
DEBUG_SET(DEBUG_TIMING_ACCURACY, 0, getAverageSystemLoadPercent());
if (cmpTimeCycles(nextTimingCycles, nowCycles) < 0) {
nextTimingCycles += clockMicrosToCycles(1000000);
// 上一秒中延迟的任务数量
DEBUG_SET(DEBUG_TIMING_ACCURACY, 1, lateTaskCount);
// 总延迟时间(微秒)
DEBUG_SET(DEBUG_TIMING_ACCURACY, 2, clockCyclesTo10thMicros(lateTaskTotal));
// 总任务执行次数
DEBUG_SET(DEBUG_TIMING_ACCURACY, 3, taskCount);
lateTaskPercentage = 1000 * (uint32_t)lateTaskCount / taskCount;
DEBUG_SET(DEBUG_TIMING_ACCURACY, 4, lateTaskPercentage);
// 计算标准差
float gyroCyclesStdDev = sqrtf(devSquared/gyroCyclesCount);
int32_t gyroCyclesStdDev100thus = clockCyclesTo100thMicros((int32_t)gyroCyclesStdDev);
DEBUG_SET(DEBUG_TIMING_ACCURACY, 7, gyroCyclesStdDev100thus);
// 重置统计
gyroCyclesMean = gyroCyclesTotal/gyroCyclesCount;
devSquared = 0.0f;
gyroCyclesTotal = 0;
gyroCyclesCount = 0;
lateTaskCount = 0;
lateTaskTotal = 0;
taskCount = 0;
}
#endif
监控指标:
- 任务延迟统计:延迟任务数量和总延迟时间
- CPU负载:系统繁忙程度百分比
- 时间抖动分析:标准差计算执行时间的稳定性
- 实时性能:陀螺仪任务执行的时间精度
6.1.6、陀螺仪同步机制
c
// 与陀螺仪硬件同步
if (gyro->gyroModeSPI != GYRO_EXTI_NO_INT) {
// 跟踪实际陀螺仪速率并设置预期时间基准
static uint32_t terminalGyroRateCount = 0;
static int32_t sampleRateStartCycles;
if (terminalGyroRateCount == 0) {
terminalGyroRateCount = gyro->detectedEXTI + GYRO_RATE_COUNT;
sampleRateStartCycles = nowCycles;
}
if (gyro->detectedEXTI >= terminalGyroRateCount) {
// 计算陀螺仪中断之间的平均时钟周期数
uint32_t sampleCycles = nowCycles - sampleRateStartCycles;
desiredPeriodCycles = sampleCycles / GYRO_RATE_COUNT;
sampleRateStartCycles = nowCycles;
terminalGyroRateCount += GYRO_RATE_COUNT;
}
// 消除陀螺仪时序偏差
static uint32_t terminalGyroLockCount = 0;
static int32_t accGyroSkew = 0;
int32_t gyroSkew = cmpTimeCycles(nextTargetCycles, gyro->gyroSyncEXTI) % desiredPeriodCycles;
if (gyroSkew > (desiredPeriodCycles / 2)) {
gyroSkew -= desiredPeriodCycles;
}
accGyroSkew += gyroSkew;
if (terminalGyroLockCount == 0) {
terminalGyroLockCount = gyro->detectedEXTI + GYRO_LOCK_COUNT;
}
if (gyro->detectedEXTI >= terminalGyroLockCount) {
terminalGyroLockCount += GYRO_LOCK_COUNT;
// 调整陀螺仪任务的期望开始时间
lastTargetCycles -= (accGyroSkew/GYRO_LOCK_COUNT);
accGyroSkew = 0;
}
}
同步机制:
- 硬件同步:利用陀螺仪EXTI中断进行精确同步
- 自适应频率:根据实际硬件频率调整期望周期
- 偏差消除:累计并补偿时序偏差,保持与硬件同步
6.1.7、动态优先级调度
c
// 更新任务动态优先级
for (task_t *task = queueFirst(); task != NULL; task = queueNext()) {
if (task->attribute->staticPriority != TASK_PRIORITY_REALTIME) {
// 任务有检查函数 - 事件驱动
if (task->attribute->checkFunc) {
// 增加事件驱动任务的优先级
if (task->dynamicPriority > 0) {
task->taskAgePeriods = 1 + (cmpTimeUs(currentTimeUs, task->lastSignaledAtUs) / task->attribute->desiredPeriodUs);
task->dynamicPriority = 1 + task->attribute->staticPriority * task->taskAgePeriods;
} else if (task->attribute->checkFunc(currentTimeUs, cmpTimeUs(currentTimeUs, task->lastExecutedAtUs))) {
// 检查函数返回true,触发任务
const uint32_t checkFuncExecutionTimeUs = cmpTimeUs(micros(), currentTimeUs);
// 更新执行时间统计
checkFuncMovingSumExecutionTimeUs += checkFuncExecutionTimeUs - checkFuncMovingSumExecutionTimeUs / TASK_STATS_MOVING_SUM_COUNT;
task->lastSignaledAtUs = currentTimeUs;
task->taskAgePeriods = 1;
task->dynamicPriority = 1 + task->attribute->staticPriority;
} else {
task->taskAgePeriods = 0;
}
} else {
// 时间驱动任务,动态优先级基于最后执行年龄
task->taskAgePeriods = (cmpTimeUs(currentTimeUs, task->lastExecutedAtUs) / task->attribute->desiredPeriodUs);
if (task->taskAgePeriods > 0) {
task->dynamicPriority = 1 + task->attribute->staticPriority * task->taskAgePeriods;
}
}
优先级计算策略:
- 事件驱动任务:当检查函数返回true时立即提升优先级
- 时间驱动任务:基于"任务年龄"(未执行的时间周期数)计算优先级
- 防饿死机制:年龄越大的任务优先级增长越快
6.1.8、任务选择与时间保护
c
if (task->dynamicPriority > selectedTaskDynamicPriority) {
timeDelta_t taskRequiredTimeUs = task->anticipatedExecutionTime >> TASK_EXEC_TIME_SHIFT;
int32_t taskRequiredTimeCycles = (int32_t)clockMicrosToCycles((uint32_t)taskRequiredTimeUs);
// 允许额外的时间裕量
taskRequiredTimeCycles += checkCycles + taskGuardCycles;
// 如果没有足够时间运行任务,除非年龄足够大,否则不选择
// 串口任务特殊处理,不阻塞
if ((taskRequiredTimeCycles < schedLoopRemainingCycles) ||
((scheduleCount & SCHED_TASK_DEFER_MASK) == 0) ||
((task - tasks) == TASK_SERIAL)) {
selectedTaskDynamicPriority = task->dynamicPriority;
selectedTask = task;
}
}
选择策略:
- 时间可行性检查:确保有足够时间完成任务
- 周期性强制调度:定期忽略时间约束防止饿死
- 关键任务特殊处理:串口任务不受时间限制
6.1.9、自适应时间保护机制
c
// 执行选中的任务
taskExecutionTimeUs += schedulerExecuteTask(selectedTask, currentTimeUs);
nowCycles = getCycleCounter();
int32_t cyclesOverdue = cmpTimeCycles(nowCycles, antipatedEndCycles);
// 处理超时情况
if (cyclesOverdue > 0) {
// 记录延迟统计(串口任务除外)
if ((currentTask - tasks) != TASK_SERIAL) {
currentTask->lateCount++;
lateTaskCount++;
lateTaskTotal += cyclesOverdue;
}
}
// 自适应调整保护时间
if ((cyclesOverdue > 0) || (-cyclesOverdue < taskGuardMinCycles)) {
if (taskGuardCycles < taskGuardMaxCycles) {
taskGuardCycles += taskGuardDeltaUpCycles;
}
} else if (taskGuardCycles > taskGuardMinCycles) {
taskGuardCycles -= taskGuardDeltaDownCycles;
}
保护机制:
- 动态保护时间:根据实际执行情况调整时间裕量
- 超时检测:监控任务是否超过预期执行时间
- 自适应调整:超时时增加保护时间,稳定时减少
6.1.10、任务加速机制
c
} else if ((selectedTask->taskAgePeriods > TASK_AGE_EXPEDITE_COUNT) ||
#ifdef USE_OSD
(((selectedTask - tasks) == TASK_OSD) && (TASK_AGE_EXPEDITE_OSD != 0) && (++skippedOSDAttempts > TASK_AGE_EXPEDITE_OSD)) ||
#endif
(((selectedTask - tasks) == TASK_RX) && (TASK_AGE_EXPEDITE_RX != 0) && (++skippedRxAttempts > TASK_AGE_EXPEDITE_RX))) {
// 如果任务长时间无法运行,减少其预估执行时间以确保最终被调度
selectedTask->anticipatedExecutionTime *= TASK_AGE_EXPEDITE_SCALE;
}
加速策略:
- 年龄阈值:超过一定年龄的任务获得加速
- 关键任务特殊处理:RX和OSD任务有独立的加速机制
- 预估时间调整:减少预估执行时间以提高被调度机会
6.2、总结
这个调度器展现了几个关键设计理念:
- 确定性优先:实时任务使用精确的时间控制和忙等待
- 自适应保护:根据系统负载动态调整时间保护机制
- 防饿死保障:通过年龄权重和强制调度确保所有任务都有机会执行
- 性能监控:全面的统计信息用于调试和优化
- 硬件同步:与陀螺仪硬件紧密同步减少时序抖动
这种设计在保证飞控实时性的同时,提供了良好的任务公平性和系统稳定性。
这种调度方式不支持任务抢占 ,任务按优先级顺序执行,高优先级任务不会被中断 。每个任务必须在指定的周期内完成执行,否则可能导致系统不稳定 。这种设计特别适合飞控系统的应用场景,因为飞控的关键控制任务(如传感器读取和电机输出)通常需要以固定频率执行,且执行时间相对确定。
二、FreeRTOS的核心调度机制
FreeRTOS是一款可抢占式实时操作系统 ,其核心调度实现位于tasks.c文件中。与Betaflight的非抢占式调度不同,FreeRTOS通过中断触发任务切换,允许高优先级任务抢占低优先级任务的CPU使用权 。
FreeRTOS任务调度的核心逻辑在vTaskSwitchContext()函数中实现 :
FreeRTOS的任务控制块(TCB)包含任务状态、优先级、堆栈指针等信息 :
c
typedef struct tCB {
StackType_t *pxTopOfStack; /* 堆栈顶部指针 */
UBaseType_t uxPriority; /* 任务优先级 */
char pcTaskName[ configMAX Task NAME LENGTH ]; /* 任务名称 */
List_t xStateList; /* 任务状态链表 */
uint32_t ulCounter; /* 计数器 */
uint32_t ulLastWakeTime; /* 最后唤醒时间 */
uint8_t ucTaskState; /* 任务状态 */
... /* 其他成员 */
} TCB_t;
FreeRTOS的调度器支持抢占式调度 ,通过系统滴答定时器中断或任务自身调用vTaskDelay()函数触发任务切换。这种机制能够更好地处理突发中断和异步事件,确保高优先级任务能够及时获得CPU资源 。
FreeRTOS还提供了丰富的任务间通信机制,如信号量、消息队列和事件标志组等 :
c
/* 创建二值信号量 */
xSemaphoreHandle xSemaphoreCreateBinary();
/* 获取信号量 */
xSemaphoreTake(xSemaphore, portMAX_DELAY);
/* 释放信号量 */
xSemaphoreGive(xSemaphore);
这些机制使得FreeRTOS能够在复杂系统中实现高效的任务协作,但同时也引入了额外的系统开销和潜在的延迟。
三、任务调度机制对比
Betaflight与FreeRTOS在任务调度机制上存在根本性差异,这些差异直接影响了飞控系统的性能表现。
| 对比项 | Betaflight | FreeRTOS |
|---|---|---|
| 调度方式 | 非抢占式循环调度 | 抢占式中断调度 |
| 任务切换 | 无中断切换,按优先级顺序执行 | 中断触发任务切换,高优先级可抢占低优先级 |
| 执行确定性 | 高,任务周期严格控制 | 中等,存在中断开销和任务切换延迟 |
| 资源占用 | 低,无额外RTOS内核开销 | 中等,需要维护任务控制块和就绪链表 |
| 适用场景 | 高实时性、确定性执行的飞控系统 | 需要处理异步事件的复杂嵌入式系统 |
Betaflight的非抢占式调度确保了关键控制任务(如传感器读取和电机输出)的确定性执行,不会因任务切换而中断。这种设计特别适合飞控系统的应用场景,因为飞控的关键控制任务需要以固定频率执行,且执行时间相对确定。例如,传感器读取任务以800Hz的频率执行(约1.25ms间隔),电机输出任务以400Hz的频率执行(约2.5ms间隔) 。
FreeRTOS的抢占式调度则更适合需要处理异步事件的复杂系统,如物联网设备或需要同时处理多种传感器数据的系统 。FreeRTOS通过中断触发任务切换,允许高优先级任务立即获得CPU资源,但这也引入了额外的中断开销和任务切换延迟。
在任务执行流程上,Betaflight采用顺序执行方式,每个任务必须在指定的周期内完成执行,否则可能导致系统不稳定 。这种设计简化了代码结构,减少了运行时开销,但也要求开发者对任务执行时间有精确控制。
FreeRTOS则采用中断驱动的调度方式,任务可以在任何时候被中断,高优先级任务可以立即抢占CPU资源 。这种机制提供了更好的实时响应能力,但也会增加系统复杂性和潜在的延迟。
四、内存管理机制对比
Betaflight和FreeRTOS在内存管理机制上也存在显著差异,这些差异影响了系统的稳定性和可扩展性。
Betaflight采用静态内存分配和固定大小的缓冲区设计,避免了动态内存分配的开销和潜在的内存碎片问题 。例如,PWM输出任务使用固定周期和占空比参数:
c
// PWM核心参数(严格2ms周期)
# define PWM_period 1599 // 周期计数:0~1599(共1600次)→ 2ms
# define PWM_PRESCALER 9 // 预分频:80MHz/100=800KHz(计数频率)
# define PWM_THROTTLE_min 800 // 50%占空比 → 1ms高电平(最慢)
# define PWM_throttle_max 1600 // 100%占空比 → 2ms高电平(最快)
这种设计减少了运行时开销 ,提高了系统的确定性和稳定性,但降低了内存使用的灵活性,增加了代码的复杂性。
FreeRTOS采用动态内存管理 机制,通过heap4.c中的链表结构管理空闲内存块 :
c
/* 定义内存块链表结构体 */
typedef struct A_BLOCK Link {
#ifdef MTK_SUPPORT_heap Debug
uint32_t magic_header;
#endif
struct A_BLOCK Link *pxNextFreeBlock; /*<< 下一空闲块指针 */
size_t xBlockSize; /*<< 内存块大小 */
#ifdef MTK_SUPPORT_heap Debug
uint32_t xLinkRegAddr;
#endif
} BlockLink_t;
/* 内存分配函数 */
void *pvPortmalloc( size_t xWantedSize ) {
vTaskSuspendAll(); // 挂起所有任务
{
// 遍历链表寻找空闲块
// 分配内存
}
(void)xTaskResumeAll(); // 恢复任务
return pvReturn;
}
FreeRTOS的内存管理提供了更好的灵活性 ,允许动态创建和删除任务及队列,但增加了内存使用的复杂性和潜在的碎片风险 。此外,动态内存分配也会引入额外的系统开销,可能影响实时性能。
在飞控系统的应用场景中,Betaflight的静态内存分配更适合资源受限的嵌入式平台,尤其是需要严格保证实时性的场景。而FreeRTOS的动态内存管理则更适合需要灵活创建和管理任务的复杂系统。
五、中断处理与实时性保障
在实时性要求极高的飞控系统中,中断处理机制是确保系统稳定运行的关键。
Betaflight的中断处理相对简单,主要使用外部中断触发传感器读取等关键任务 。例如,陀螺仪中断触发传感器读取任务:
c
//陀螺仪中断处理函数
void陀螺仪中断处理() {
//更新陀螺仪数据
陀螺仪更新();
//标记传感器数据已更新
sensor_data flag = true;
}
这种设计减少了中断嵌套和任务切换的开销 ,提高了系统的确定性和稳定性。但也限制了系统处理复杂中断的能力,无法同时处理多个高优先级中断。
FreeRTOS的中断处理则更加灵活,支持多级中断嵌套和任务切换 :
c
//FreeRTOS中断处理函数
void vPortSVCHandler() {
//保存当前任务上下文
portSAVE Context();
//执行操作系统服务
xTaskIncrementTick();
//恢复上下文
portRESTORE Context();
}
FreeRTOS通过中断服务程序(ISR)和任务之间的协作,可以更好地处理复杂中断场景。例如,在滴答定时器中断中,FreeRTOS会更新系统时钟并触发任务调度:
c
//滴答定时器中断处理
void xPortSysTickHandler() {
//更新系统时钟
xTaskIncrementTick();
//检查是否需要切换任务
if( xTaskIncrementTick() != pdFALSE ) {
portNVIC_INT_CTRL REG = portNVIC_PENDSVSET BIT; //触发任务切换
}
}
在实时性保障方面,Betaflight通过严格的时间片控制确保关键任务按时执行:
c
//检查任务是否需要执行
if (task->enabled && (currentTimeUs - task->last_run) >= task->interval ticks) {
//执行任务
}
这种机制确保了关键控制任务的周期性执行,不会因任务切换而中断,特别适合飞控系统的应用场景 。
FreeRTOS则通过优先级调度和抢占机制保证高优先级任务的及时执行:
c
//选择优先级最高的就绪任务
uxTopPriority = ( 31UL - ( uint32_t ) ucPortCountLeadingZeros( uxTopReadyPriority ) );
这种机制提供了更好的实时响应能力 ,但也增加了系统复杂性和潜在的延迟 。
六、系统架构与适用性分析
Betaflight和FreeRTOS在系统架构和适用性上存在明显差异,这些差异影响了它们在飞控系统中的应用效果。
Betaflight的系统架构 是一种轻量级、高度定制化的调度框架 ,主要关注飞控系统的实时性和确定性。其架构可以分为以下几个层次:
- 硬件抽象层(HAL):负责与MCU和各种外设交互,包括GPIO、SPI、I2C、UART等接口驱动 。
- 传感器处理层:负责IMU、气压计、磁力计等传感器数据的采集与处理 。
- 飞行控制层:包括姿态估计、PID控制、飞行模式管理等核心算法 。
- 用户接口层:负责与外部设备和用户的交互,包括CLI命令行界面、MSP协议通信、OSD显示等 。
- 调度层:负责协调各模块的运行,确保关键控制任务在毫秒级时间内完成 。
这种架构针对飞控应用进行了高度优化 ,减少了不必要的系统开销 ,提高了系统的确定性和稳定性。但也限制了系统的灵活性和可扩展性,增加了开发和维护的复杂性。
FreeRTOS的系统架构 则是一种通用、可扩展的实时操作系统 ,包含任务管理、时间管理、信号量、消息队列、内存管理等多个功能模块。其架构可以分为以下几个层次:
- 内核层:包括任务调度器、时间管理器等核心功能 。
- API层:提供任务创建、删除、延迟、信号量等API接口 。
- 移植层:实现与特定处理器架构的接口,如ARM Cortex-M系列 。
- 应用层:用户应用程序,使用FreeRTOS API实现功能 。
FreeRTOS的架构提供了更好的灵活性和可扩展性 ,允许开发者轻松添加新功能和任务 。但也引入了额外的系统开销和复杂性,可能影响实时性能。
在飞控系统的应用场景中,Betaflight的调度系统更适合资源受限的嵌入式平台,尤其是需要严格保证实时性的场景。例如,FPV竞速无人机需要以800Hz的频率读取传感器数据,以400Hz的频率输出电机控制信号,Betaflight的非抢占式调度能够更好地满足这一需求 。
FreeRTOS则更适合需要处理复杂中断和异步事件的系统,如需要同时处理多种传感器数据、通信协议和用户界面的系统。例如,某些需要同时处理GPS导航、避障传感器和用户输入的飞控系统可能更适合使用FreeRTOS 。
七、代码实现差异分析
Betaflight和FreeRTOS在代码实现上存在显著差异,这些差异反映了两种操作系统设计哲学的不同。
任务创建与管理 方面,Betaflight采用静态任务定义方式,所有任务都在编译时确定:
c
//Betaflight任务定义
static taskDescriptor_t task Descriptors[TASK_MAX] = {
[TASK_MOTORS_OUTPUT] = {
.function = motorsOutputTask,
.rate_hz = 400,
.interval ticks = 2500,
.priority = 1,
.enabled = true,
.name = "MOTORS"
},
//其他任务...
};
这种设计减少了运行时开销 ,提高了系统的确定性和稳定性,但降低了系统的灵活性,新增任务需要修改源码并重新编译。
FreeRTOS则采用动态任务创建方式,任务可以在运行时创建和删除:
c
//FreeRTOS任务创建
xTaskHandle xTaskCreate(
TaskFunction_t pxTaskCode, /* 任务函数 */
const char * const pcTaskName,/* 任务名称 */
uint16_t usStackDepth, /* 堆栈深度 */
void * const pvParam, /* 任务参数 */
UBaseType_t uxPriority, /* 任务优先级 */
xTaskHandle * const pxCreatedTask /* 任务句柄 */
);
这种机制提供了更好的灵活性和可扩展性 ,允许动态创建和管理任务 。但也增加了系统的复杂性和潜在的堆栈溢出风险,需要更谨慎的内存管理。
任务切换 方面,Betaflight采用顺序执行方式,任务按优先级顺序执行,不会被中断:
c
//Betaflight任务切换
for (int i = 0; i < TASK_MAX; i++) {
taskDescriptor_t *task = &task Descriptors[i];
if (需要执行) {
task->function(); //执行任务函数
}
}
这种机制确保了任务执行的确定性 ,不会因任务切换而中断关键控制任务 。但也限制了系统的灵活性,无法动态调整任务优先级和执行频率。
FreeRTOS则采用抢占式切换机制,通过中断触发任务切换:
c
//FreeRTOS任务切换
portNVIC_INT_CTRL REG = portNVIC_PENDSVSET BIT; //触发任务切换
这种机制提供了更好的实时响应能力 ,允许高优先级任务立即抢占CPU资源 。但也增加了系统复杂性和潜在的延迟,需要精确控制任务执行时间以避免系统不稳定。
中断处理 方面,Betaflight通常直接使用硬件中断处理关键任务:
c
//Betaflight中断处理
void陀螺仪中断处理() {
陀螺仪更新();
sensor_data_flag = true;
}
这种设计减少了中断嵌套和任务切换的开销 ,提高了系统的确定性和稳定性。但也限制了系统处理复杂中断的能力,无法同时处理多个高优先级中断。
FreeRTOS则通过中断服务程序(ISR)和任务之间的协作处理中断:
c
//FreeRTOS中断处理
void陀螺仪中断处理() {
xSemaphoreGiveFromISR(xSensorSemaphore, NULL); //释放信号量
}
这种机制提供了更好的灵活性和可扩展性 ,允许中断和任务之间的协作 。但也增加了系统的复杂性和潜在的延迟,需要精确控制中断处理时间和任务响应时间。
八、性能与资源消耗对比
在飞控系统这一对实时性和资源占用都有严格要求的应用场景中,Betaflight和FreeRTOS的性能表现和资源消耗存在明显差异。
实时性 方面,Betaflight的非抢占式调度能够更好地保证关键控制任务的确定性执行 。例如,传感器读取任务以800Hz的频率执行(约1.25ms间隔),电机输出任务以400Hz的频率执行(约2.5ms间隔) 。这种机制确保了关键控制任务不会因任务切换而中断,提供了更稳定的实时性能。
FreeRTOS的抢占式调度虽然能够更好地处理突发中断和异步事件 ,但任务切换引入的中断开销和上下文切换时间可能影响实时性能。特别是在需要以高频执行关键控制任务的飞控系统中,FreeRTOS的中断开销和任务切换延迟可能成为瓶颈。
资源占用 方面,Betaflight的调度系统占用资源极低 ,主要是一些任务描述结构体和计时变量。这种设计特别适合资源受限的嵌入式平台,如STM32F103系列MCU(256KB Flash,64KB RAM)。
FreeRTOS则需要更多的系统资源来维护任务控制块、就绪链表和中断服务程序等结构。虽然FreeRTOS内核通常只占用4-9KB的空间 ,但对于资源极度受限的飞控系统来说,这仍然是一笔不可忽视的开销。
代码复杂性 方面,Betaflight的调度系统代码简洁明了 ,易于理解和调试。这种设计降低了开发和维护的复杂性,适合飞控这一对确定性和稳定性要求极高的应用场景。
FreeRTOS的调度系统则代码复杂度较高 ,包含大量任务管理、时间管理和中断处理的逻辑。虽然这种复杂性提供了更好的灵活性和可扩展性,但也增加了开发和维护的难度,需要开发者对RTOS有深入理解。
九、飞控系统中的实际应用效果
在飞控系统的实际应用中,Betaflight的调度系统表现出了卓越的实时性和稳定性,特别适合FPV竞速等需要极高响应速度的场景 。例如,Betaflight能够以800Hz的频率读取传感器数据,以400Hz的频率输出电机控制信号,这种高频控制是实现精准飞行的关键 。
FreeRTOS则在需要处理复杂中断和异步事件的系统中表现更好 。例如,在需要同时处理GPS导航、避障传感器和用户输入的系统中,FreeRTOS的抢占式调度和丰富的任务间通信机制能够提供更好的支持。
Betaflight的调度系统在飞控应用中的优势主要体现在以下几个方面:
- 低延迟:关键控制任务(如传感器读取和电机输出)以高达800Hz的频率执行,确保飞控能够快速响应姿态变化 。
- 确定性执行:任务按顺序执行,不会因任务切换而中断,确保控制算法的稳定性和可预测性 。
- 轻量级设计 :调度器代码集中在
scheduler.c中,无额外内存管理或任务切换开销,适合资源受限的飞控平台 。 - 简单易用:代码结构清晰,易于理解和调试,降低了飞控开发的复杂性 。
FreeRTOS在飞控应用中的优势则体现在以下几个方面:
- 灵活的任务管理:支持动态创建和删除任务,便于模块化开发和功能扩展 。
- 丰富的任务间通信:提供信号量、消息队列等多种通信机制,便于复杂系统的实现 。
- 良好的可移植性:支持多种处理器架构和开发工具,便于系统移植和升级 。
- 活跃的社区支持:拥有庞大的开发者社区和丰富的学习资源,便于获取技术支持和解决方案 。
十、总结与适用场景建议
综合分析Betaflight和FreeRTOS的调度机制、内存管理和中断处理策略,可以得出以下结论:
Betaflight的自研调度系统 是一种针对飞控应用高度优化的轻量级调度框架 ,特别适合资源受限、实时性要求极高的飞控系统 。其非抢占式调度、静态内存分配和简单中断处理机制确保了关键控制任务的确定性和低延迟执行,为无人机提供了稳定的飞行控制。但也牺牲了部分灵活性和扩展性,增加了开发和维护的复杂性。
FreeRTOS 则是一种通用、可扩展的实时操作系统 ,适合需要处理复杂中断和异步事件的系统 。其抢占式调度、动态内存管理和丰富的任务间通信机制提供了更好的灵活性和可扩展性。但也引入了额外的系统开销和复杂性,可能影响实时性能。
对于飞控系统的开发者,选择哪种操作系统取决于具体应用需求:
- 对于高频控制、实时性要求极高的飞控系统(如FPV竞速无人机),Betaflight的自研调度系统可能是更好的选择,因为它能够提供更稳定的实时性能和更低的系统开销。
- 对于需要处理复杂中断和异步事件的飞控系统(如需要同时处理GPS导航、避障传感器和用户输入的系统),FreeRTOS的抢占式调度和丰富的任务间通信机制可能提供更好的支持。
- 对于资源极度受限的飞控平台(如使用STM32F103系列MCU),Betaflight的轻量级设计可能更适合,因为它能够更好地利用有限的系统资源。
- 对于需要灵活扩展和功能升级的飞控系统,FreeRTOS的动态任务管理和模块化设计可能提供更好的支持。
在飞控系统这一特殊应用场景中,Betaflight的自研调度系统通过针对飞控控制需求的深度优化,实现了比FreeRTOS更优异的实时性能和稳定性。这种设计哲学反映了飞控系统对确定性和低延迟的极致追求,也是Betaflight能够在无人机控制领域取得成功的关键因素。