2 * This file is part of Cleanflight and Betaflight.
4 * Cleanflight and Betaflight are free software. You can redistribute
5 * this software and/or modify this software under the terms of the
6 * GNU General Public License as published by the Free Software
7 * Foundation, either version 3 of the License, or (at your option)
10 * Cleanflight and Betaflight are distributed in the hope that they
11 * will be useful, but WITHOUT ANY WARRANTY; without even the implied
12 * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
13 * See the GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this software.
18 * If not, see <http://www.gnu.org/licenses/>.
21 #define SRC_MAIN_SCHEDULER_C_
31 #include "drivers/accgyro/accgyro.h"
33 #include "build/build_config.h"
34 #include "build/debug.h"
36 #include "common/maths.h"
37 #include "common/time.h"
38 #include "common/utils.h"
40 #include "drivers/time.h"
41 #include "drivers/accgyro/accgyro.h"
42 #include "drivers/system.h"
48 #include "flight/failsafe.h"
50 #include "scheduler.h"
52 #include "sensors/gyro_init.h"
54 // DEBUG_SCHEDULER, timings for:
55 // 0 - Average time spent executing check function
56 // 1 - Time spent priortising
57 // 2 - time spent in scheduler
59 // DEBUG_SCHEDULER_DETERMINISM, requires USE_LATE_TASK_STATISTICS to be defined
60 // 0 - Gyro task start cycle time in 10th of a us
61 // 1 - ID of late task
62 // 2 - Amount task is late in 10th of a us
63 // 3 - Gyro lock skew in 10th of a us
64 // 4 - Minimum Gyro period in 100th of a us
65 // 5 - Maximum Gyro period in 100th of a us
66 // 6 - Span of Gyro period in 100th of a us
67 // 7 - Standard deviation of gyro cycle time in 100th of a us
69 // DEBUG_TIMING_ACCURACY, requires USE_LATE_TASK_STATISTICS to be defined
71 // 1 - Tasks late in last second
72 // 2 - Total lateness in last second in 10ths us
73 // 3 - Total tasks run in last second
74 // 4 - 10ths % of tasks late in last second
75 // 7 - Standard deviation of gyro cycle time in 100th of a us
77 // DEBUG_TASK, requires USE_LATE_TASK_STATISTICS to be defined
78 // 0 - Value of scheduler_debug_task setting
82 // 4 - estimated execution time (us)
83 // 5 - actual execution time (us)
84 // 6 - difference between estimated and actual execution time
87 extern task_t tasks
[];
89 static FAST_DATA_ZERO_INIT task_t
*currentTask
= NULL
;
90 static FAST_DATA_ZERO_INIT
bool ignoreCurrentTaskExecRate
;
91 static FAST_DATA_ZERO_INIT
bool ignoreCurrentTaskExecTime
;
93 int32_t schedLoopStartCycles
;
94 static int32_t schedLoopStartMinCycles
;
95 static int32_t schedLoopStartMaxCycles
;
96 static uint32_t schedLoopStartDeltaDownCycles
;
97 static uint32_t schedLoopStartDeltaUpCycles
;
99 int32_t taskGuardCycles
;
100 static int32_t taskGuardMinCycles
;
101 static int32_t taskGuardMaxCycles
;
102 static uint32_t taskGuardDeltaDownCycles
;
103 static uint32_t taskGuardDeltaUpCycles
;
105 FAST_DATA_ZERO_INIT
uint16_t averageSystemLoadPercent
= 0;
107 static FAST_DATA_ZERO_INIT
int taskQueuePos
= 0;
108 STATIC_UNIT_TESTED FAST_DATA_ZERO_INIT
int taskQueueSize
= 0;
110 static FAST_DATA_ZERO_INIT
bool gyroEnabled
;
112 static int32_t desiredPeriodCycles
;
113 static uint32_t lastTargetCycles
;
115 static uint8_t skippedRxAttempts
= 0;
117 static uint8_t skippedOSDAttempts
= 0;
120 #if defined(USE_LATE_TASK_STATISTICS)
121 static int16_t lateTaskCount
= 0;
122 static uint32_t lateTaskTotal
= 0;
123 static int16_t taskCount
= 0;
124 static uint32_t lateTaskPercentage
= 0;
125 static uint32_t nextTimingCycles
;
126 static int32_t gyroCyclesNow
;
129 static timeMs_t lastFailsafeCheckMs
= 0;
131 // No need for a linked list for the queue, since items are only inserted at startup
133 #define TASK_QUEUE_RESERVE 1
135 #define TASK_QUEUE_RESERVE 0
137 STATIC_UNIT_TESTED FAST_DATA_ZERO_INIT task_t
* taskQueueArray
[TASK_COUNT
+ 1 + TASK_QUEUE_RESERVE
]; // extra item for NULL pointer at end of queue (+ overflow check in UNTT_TEST)
139 void queueClear(void)
141 memset(taskQueueArray
, 0, sizeof(taskQueueArray
));
146 bool queueContains(const task_t
*task
)
148 for (int ii
= 0; ii
< taskQueueSize
; ++ii
) {
149 if (taskQueueArray
[ii
] == task
) {
156 bool queueAdd(task_t
*task
)
158 if ((taskQueueSize
>= TASK_COUNT
) || queueContains(task
)) {
161 for (int ii
= 0; ii
<= taskQueueSize
; ++ii
) {
162 if (taskQueueArray
[ii
] == NULL
|| taskQueueArray
[ii
]->attribute
->staticPriority
< task
->attribute
->staticPriority
) {
163 memmove(&taskQueueArray
[ii
+1], &taskQueueArray
[ii
], sizeof(task
) * (taskQueueSize
- ii
));
164 taskQueueArray
[ii
] = task
;
172 bool queueRemove(task_t
*task
)
174 for (int ii
= 0; ii
< taskQueueSize
; ++ii
) {
175 if (taskQueueArray
[ii
] == task
) {
176 memmove(&taskQueueArray
[ii
], &taskQueueArray
[ii
+1], sizeof(task
) * (taskQueueSize
- ii
));
185 * Returns first item queue or NULL if queue empty
187 FAST_CODE task_t
*queueFirst(void)
190 return taskQueueArray
[0]; // guaranteed to be NULL if queue is empty
194 * Returns next item in queue or NULL if at end of queue
196 FAST_CODE task_t
*queueNext(void)
198 return taskQueueArray
[++taskQueuePos
]; // guaranteed to be NULL at end of queue
201 static timeUs_t taskTotalExecutionTime
= 0;
203 void taskSystemLoad(timeUs_t currentTimeUs
)
205 static timeUs_t lastExecutedAtUs
;
206 timeDelta_t deltaTime
= cmpTimeUs(currentTimeUs
, lastExecutedAtUs
);
208 // Calculate system load
210 averageSystemLoadPercent
= 100 * taskTotalExecutionTime
/ deltaTime
;
211 taskTotalExecutionTime
= 0;
212 lastExecutedAtUs
= currentTimeUs
;
214 schedulerIgnoreTaskExecTime();
217 #if defined(SIMULATOR_BUILD)
218 averageSystemLoadPercent
= 0;
222 uint32_t getCpuPercentageLate(void)
224 #if defined(USE_LATE_TASK_STATISTICS)
225 return lateTaskPercentage
;
231 timeUs_t checkFuncMaxExecutionTimeUs
;
232 timeUs_t checkFuncTotalExecutionTimeUs
;
233 timeUs_t checkFuncMovingSumExecutionTimeUs
;
234 timeUs_t checkFuncMovingSumDeltaTimeUs
;
236 void getCheckFuncInfo(cfCheckFuncInfo_t
*checkFuncInfo
)
238 checkFuncInfo
->maxExecutionTimeUs
= checkFuncMaxExecutionTimeUs
;
239 checkFuncInfo
->totalExecutionTimeUs
= checkFuncTotalExecutionTimeUs
;
240 checkFuncInfo
->averageExecutionTimeUs
= checkFuncMovingSumExecutionTimeUs
/ TASK_STATS_MOVING_SUM_COUNT
;
241 checkFuncInfo
->averageDeltaTimeUs
= checkFuncMovingSumDeltaTimeUs
/ TASK_STATS_MOVING_SUM_COUNT
;
244 void getTaskInfo(taskId_e taskId
, taskInfo_t
* taskInfo
)
246 taskInfo
->isEnabled
= queueContains(getTask(taskId
));
247 taskInfo
->desiredPeriodUs
= getTask(taskId
)->attribute
->desiredPeriodUs
;
248 taskInfo
->staticPriority
= getTask(taskId
)->attribute
->staticPriority
;
249 taskInfo
->taskName
= getTask(taskId
)->attribute
->taskName
;
250 taskInfo
->subTaskName
= getTask(taskId
)->attribute
->subTaskName
;
251 taskInfo
->maxExecutionTimeUs
= getTask(taskId
)->maxExecutionTimeUs
;
252 taskInfo
->totalExecutionTimeUs
= getTask(taskId
)->totalExecutionTimeUs
;
253 taskInfo
->averageExecutionTime10thUs
= getTask(taskId
)->movingSumExecutionTime10thUs
/ TASK_STATS_MOVING_SUM_COUNT
;
254 taskInfo
->averageDeltaTime10thUs
= getTask(taskId
)->movingSumDeltaTime10thUs
/ TASK_STATS_MOVING_SUM_COUNT
;
255 taskInfo
->latestDeltaTimeUs
= getTask(taskId
)->taskLatestDeltaTimeUs
;
256 taskInfo
->movingAverageCycleTimeUs
= getTask(taskId
)->movingAverageCycleTimeUs
;
257 #if defined(USE_LATE_TASK_STATISTICS)
258 taskInfo
->lateCount
= getTask(taskId
)->lateCount
;
259 taskInfo
->runCount
= getTask(taskId
)->runCount
;
260 taskInfo
->execTime
= getTask(taskId
)->execTime
;
264 void rescheduleTask(taskId_e taskId
, timeDelta_t newPeriodUs
)
268 if (taskId
== TASK_SELF
) {
270 } else if (taskId
< TASK_COUNT
) {
271 task
= getTask(taskId
);
275 task
->attribute
->desiredPeriodUs
= MAX(SCHEDULER_DELAY_LIMIT
, newPeriodUs
); // Limit delay to 100us (10 kHz) to prevent scheduler clogging
277 // Catch the case where the gyro loop is adjusted
278 if (taskId
== TASK_GYRO
) {
279 desiredPeriodCycles
= (int32_t)clockMicrosToCycles((uint32_t)getTask(TASK_GYRO
)->attribute
->desiredPeriodUs
);
283 void setTaskEnabled(taskId_e taskId
, bool enabled
)
285 if (taskId
== TASK_SELF
|| taskId
< TASK_COUNT
) {
286 task_t
*task
= taskId
== TASK_SELF
? currentTask
: getTask(taskId
);
287 if (enabled
&& task
->attribute
->taskFunc
) {
295 timeDelta_t
getTaskDeltaTimeUs(taskId_e taskId
)
297 if (taskId
== TASK_SELF
) {
298 return currentTask
->taskLatestDeltaTimeUs
;
299 } else if (taskId
< TASK_COUNT
) {
300 return getTask(taskId
)->taskLatestDeltaTimeUs
;
306 // Called by tasks executing what are known to be short states
307 void schedulerIgnoreTaskStateTime(void)
309 ignoreCurrentTaskExecRate
= true;
310 ignoreCurrentTaskExecTime
= true;
313 // Called by tasks with state machines to only count one state as determining rate
314 void schedulerIgnoreTaskExecRate(void)
316 ignoreCurrentTaskExecRate
= true;
319 // Called by tasks without state machines executing in what is known to be a shorter time than peak
320 void schedulerIgnoreTaskExecTime(void)
322 ignoreCurrentTaskExecTime
= true;
325 bool schedulerGetIgnoreTaskExecTime(void)
327 return ignoreCurrentTaskExecTime
;
330 void schedulerResetTaskStatistics(taskId_e taskId
)
332 if (taskId
== TASK_SELF
) {
333 currentTask
->anticipatedExecutionTime
= 0;
334 currentTask
->movingSumDeltaTime10thUs
= 0;
335 currentTask
->totalExecutionTimeUs
= 0;
336 currentTask
->maxExecutionTimeUs
= 0;
337 } else if (taskId
< TASK_COUNT
) {
338 getTask(taskId
)->anticipatedExecutionTime
= 0;
339 getTask(taskId
)->movingSumDeltaTime10thUs
= 0;
340 getTask(taskId
)->totalExecutionTimeUs
= 0;
341 getTask(taskId
)->maxExecutionTimeUs
= 0;
345 void schedulerResetTaskMaxExecutionTime(taskId_e taskId
)
347 if (taskId
== TASK_SELF
) {
348 currentTask
->maxExecutionTimeUs
= 0;
349 } else if (taskId
< TASK_COUNT
) {
350 task_t
*task
= getTask(taskId
);
351 task
->maxExecutionTimeUs
= 0;
352 #if defined(USE_LATE_TASK_STATISTICS)
359 void schedulerResetCheckFunctionMaxExecutionTime(void)
361 checkFuncMaxExecutionTimeUs
= 0;
364 void schedulerInit(void)
367 queueAdd(getTask(TASK_SYSTEM
));
369 schedLoopStartMinCycles
= clockMicrosToCycles(SCHED_START_LOOP_MIN_US
);
370 schedLoopStartMaxCycles
= clockMicrosToCycles(SCHED_START_LOOP_MAX_US
);
371 schedLoopStartCycles
= schedLoopStartMinCycles
;
372 schedLoopStartDeltaDownCycles
= clockMicrosToCycles(1) / SCHED_START_LOOP_DOWN_STEP
;
373 schedLoopStartDeltaUpCycles
= clockMicrosToCycles(1) / SCHED_START_LOOP_UP_STEP
;
375 taskGuardMinCycles
= clockMicrosToCycles(TASK_GUARD_MARGIN_MIN_US
);
376 taskGuardMaxCycles
= clockMicrosToCycles(TASK_GUARD_MARGIN_MAX_US
);
377 taskGuardCycles
= taskGuardMinCycles
;
378 taskGuardDeltaDownCycles
= clockMicrosToCycles(1) / TASK_GUARD_MARGIN_DOWN_STEP
;
379 taskGuardDeltaUpCycles
= clockMicrosToCycles(1) / TASK_GUARD_MARGIN_UP_STEP
;
381 desiredPeriodCycles
= (int32_t)clockMicrosToCycles((uint32_t)getTask(TASK_GYRO
)->attribute
->desiredPeriodUs
);
383 lastTargetCycles
= getCycleCounter();
385 #if defined(USE_LATE_TASK_STATISTICS)
386 nextTimingCycles
= lastTargetCycles
;
389 for (taskId_e taskId
= 0; taskId
< TASK_COUNT
; taskId
++) {
390 schedulerResetTaskStatistics(taskId
);
394 static timeDelta_t taskNextStateTime
;
396 FAST_CODE
void schedulerSetNextStateTime(timeDelta_t nextStateTime
)
398 taskNextStateTime
= nextStateTime
;
401 FAST_CODE timeDelta_t
schedulerGetNextStateTime(void)
403 return currentTask
->anticipatedExecutionTime
>> TASK_EXEC_TIME_SHIFT
;
406 FAST_CODE timeUs_t
schedulerExecuteTask(task_t
*selectedTask
, timeUs_t currentTimeUs
)
408 timeUs_t taskExecutionTimeUs
= 0;
411 currentTask
= selectedTask
;
412 ignoreCurrentTaskExecRate
= false;
413 ignoreCurrentTaskExecTime
= false;
414 taskNextStateTime
= -1;
415 float period
= currentTimeUs
- selectedTask
->lastExecutedAtUs
;
416 selectedTask
->lastExecutedAtUs
= currentTimeUs
;
417 selectedTask
->lastDesiredAt
+= selectedTask
->attribute
->desiredPeriodUs
;
418 selectedTask
->dynamicPriority
= 0;
421 const timeUs_t currentTimeBeforeTaskCallUs
= micros();
422 #if defined(USE_LATE_TASK_STATISTICS)
423 const timeUs_t estimatedExecutionUs
= selectedTask
->execTime
;
425 selectedTask
->attribute
->taskFunc(currentTimeBeforeTaskCallUs
);
426 taskExecutionTimeUs
= micros() - currentTimeBeforeTaskCallUs
;
427 taskTotalExecutionTime
+= taskExecutionTimeUs
;
428 selectedTask
->movingSumExecutionTime10thUs
+= (taskExecutionTimeUs
* 10) - selectedTask
->movingSumExecutionTime10thUs
/ TASK_STATS_MOVING_SUM_COUNT
;
429 if (!ignoreCurrentTaskExecRate
) {
430 // Record task execution rate and max execution time
431 selectedTask
->taskLatestDeltaTimeUs
= cmpTimeUs(currentTimeUs
, selectedTask
->lastStatsAtUs
);
432 selectedTask
->movingSumDeltaTime10thUs
+= (selectedTask
->taskLatestDeltaTimeUs
* 10) - selectedTask
->movingSumDeltaTime10thUs
/ TASK_STATS_MOVING_SUM_COUNT
;
433 selectedTask
->lastStatsAtUs
= currentTimeUs
;
436 // Update estimate of expected task duration
437 if (taskNextStateTime
!= -1) {
438 selectedTask
->anticipatedExecutionTime
= taskNextStateTime
<< TASK_EXEC_TIME_SHIFT
;
439 } else if (!ignoreCurrentTaskExecTime
) {
440 if (taskExecutionTimeUs
> (selectedTask
->anticipatedExecutionTime
>> TASK_EXEC_TIME_SHIFT
)) {
441 selectedTask
->anticipatedExecutionTime
= taskExecutionTimeUs
<< TASK_EXEC_TIME_SHIFT
;
442 } else if (selectedTask
->anticipatedExecutionTime
> 1) {
443 // Slowly decay the max time
444 selectedTask
->anticipatedExecutionTime
--;
448 if (!ignoreCurrentTaskExecTime
) {
449 selectedTask
->maxExecutionTimeUs
= MAX(selectedTask
->maxExecutionTimeUs
, taskExecutionTimeUs
);
452 selectedTask
->totalExecutionTimeUs
+= taskExecutionTimeUs
; // time consumed by scheduler + task
453 selectedTask
->movingAverageCycleTimeUs
+= 0.05f
* (period
- selectedTask
->movingAverageCycleTimeUs
);
454 #if defined(USE_LATE_TASK_STATISTICS)
455 selectedTask
->runCount
++;
457 if ((selectedTask
- tasks
) == schedulerConfig()->debugTask
) {
458 timeUs_t averageDeltaTime10thUs
= selectedTask
->movingSumDeltaTime10thUs
/ TASK_STATS_MOVING_SUM_COUNT
;
459 int16_t taskFrequency
= averageDeltaTime10thUs
== 0 ? 0 : lrintf(1e7f
/ averageDeltaTime10thUs
);
460 DEBUG_SET(DEBUG_TASK
, 0, schedulerConfig()->debugTask
);
461 DEBUG_SET(DEBUG_TASK
, 1, taskFrequency
);
462 DEBUG_SET(DEBUG_TASK
, 2, selectedTask
->maxExecutionTimeUs
);
463 DEBUG_SET(DEBUG_TASK
, 3, selectedTask
->movingSumExecutionTime10thUs
/ TASK_STATS_MOVING_SUM_COUNT
/ 10);
464 DEBUG_SET(DEBUG_TASK
, 4, estimatedExecutionUs
);
465 DEBUG_SET(DEBUG_TASK
, 5, taskExecutionTimeUs
);
466 DEBUG_SET(DEBUG_TASK
, 6, estimatedExecutionUs
- taskExecutionTimeUs
);
467 DEBUG_SET(DEBUG_TASK
, 7, selectedTask
->lateCount
);
472 return taskExecutionTimeUs
;
475 #if defined(UNIT_TEST)
476 STATIC_UNIT_TESTED task_t
*unittest_scheduler_selectedTask
;
477 STATIC_UNIT_TESTED
uint8_t unittest_scheduler_selectedTaskDynamicPriority
;
479 static void readSchedulerLocals(task_t
*selectedTask
, uint8_t selectedTaskDynamicPriority
)
481 unittest_scheduler_selectedTask
= selectedTask
;
482 unittest_scheduler_selectedTaskDynamicPriority
= selectedTaskDynamicPriority
;
486 FAST_CODE
void scheduler(void)
488 static uint32_t checkCycles
= 0;
489 static uint32_t scheduleCount
= 0;
490 #if defined(USE_LATE_TASK_STATISTICS)
491 static uint32_t gyroCyclesMean
= 0;
492 static uint32_t gyroCyclesCount
= 0;
493 static uint64_t gyroCyclesTotal
= 0;
494 static float devSquared
= 0.0f
;
497 #if !defined(UNIT_TEST)
498 const timeUs_t schedulerStartTimeUs
= micros();
500 timeUs_t currentTimeUs
;
502 timeUs_t taskExecutionTimeUs
= 0;
503 task_t
*selectedTask
= NULL
;
504 uint16_t selectedTaskDynamicPriority
= 0;
505 uint32_t nextTargetCycles
= 0;
506 int32_t schedLoopRemainingCycles
;
507 bool firstSchedulingOpportunity
= false;
509 #if defined(UNIT_TEST)
510 if (nextTargetCycles
== 0) {
511 lastTargetCycles
= getCycleCounter();
512 nextTargetCycles
= lastTargetCycles
+ desiredPeriodCycles
;
517 // Realtime gyro/filtering/PID tasks get complete priority
518 task_t
*gyroTask
= getTask(TASK_GYRO
);
519 nowCycles
= getCycleCounter();
520 #if defined(UNIT_TEST)
521 lastTargetCycles
= clockMicrosToCycles(gyroTask
->lastExecutedAtUs
);
523 nextTargetCycles
= lastTargetCycles
+ desiredPeriodCycles
;
524 schedLoopRemainingCycles
= cmpTimeCycles(nextTargetCycles
, nowCycles
);
526 if (schedLoopRemainingCycles
< -desiredPeriodCycles
) {
527 /* A task has so grossly overrun that at entire gyro cycle has been skipped
528 * This is most likely to occur when connected to the configurator via USB as the serial
529 * task is non-deterministic
530 * Recover as best we can, advancing scheduling by a whole number of cycles
532 nextTargetCycles
+= desiredPeriodCycles
* (1 + (schedLoopRemainingCycles
/ -desiredPeriodCycles
));
533 schedLoopRemainingCycles
= cmpTimeCycles(nextTargetCycles
, nowCycles
);
536 // Tune out the time lost between completing the last task execution and re-entering the scheduler
537 if ((schedLoopRemainingCycles
< schedLoopStartMinCycles
) &&
538 (schedLoopStartCycles
< schedLoopStartMaxCycles
)) {
539 schedLoopStartCycles
+= schedLoopStartDeltaUpCycles
;
542 // Once close to the timing boundary, poll for it's arrival
543 if (schedLoopRemainingCycles
< schedLoopStartCycles
) {
544 if (schedLoopStartCycles
> schedLoopStartMinCycles
) {
545 schedLoopStartCycles
-= schedLoopStartDeltaDownCycles
;
547 #if !defined(UNIT_TEST)
548 while (schedLoopRemainingCycles
> 0) {
549 nowCycles
= getCycleCounter();
550 schedLoopRemainingCycles
= cmpTimeCycles(nextTargetCycles
, nowCycles
);
553 currentTimeUs
= micros();
554 taskExecutionTimeUs
+= schedulerExecuteTask(gyroTask
, currentTimeUs
);
556 if (gyroFilterReady()) {
557 taskExecutionTimeUs
+= schedulerExecuteTask(getTask(TASK_FILTER
), currentTimeUs
);
559 if (pidLoopReady()) {
560 taskExecutionTimeUs
+= schedulerExecuteTask(getTask(TASK_PID
), currentTimeUs
);
563 // Check for incoming RX data. Don't do this in the checker as that is called repeatedly within
564 // a given gyro loop, and ELRS takes a long time to process this and so can only be safely processed
565 // before the checkers
566 rxFrameCheck(currentTimeUs
, cmpTimeUs(currentTimeUs
, getTask(TASK_RX
)->lastExecutedAtUs
));
568 // Check for failsafe conditions without reliance on the RX task being well behaved
569 if (cmp32(millis(), lastFailsafeCheckMs
) > PERIOD_RXDATA_FAILURE
) {
570 // This is very low cost taking less that 4us every 10ms
571 failsafeCheckDataFailurePeriod();
572 failsafeUpdateState();
573 lastFailsafeCheckMs
= millis();
576 // This is the first scheduling opportunity after the realtime tasks have run
577 firstSchedulingOpportunity
= true;
579 #if defined(USE_LATE_TASK_STATISTICS)
580 gyroCyclesNow
= cmpTimeCycles(nowCycles
, lastTargetCycles
);
581 gyroCyclesTotal
+= gyroCyclesNow
;
583 DEBUG_SET(DEBUG_SCHEDULER_DETERMINISM
, 0, clockCyclesTo10thMicros(gyroCyclesNow
));
584 int32_t deviationCycles
= gyroCyclesNow
- gyroCyclesMean
;
585 devSquared
+= deviationCycles
* deviationCycles
;
588 DEBUG_SET(DEBUG_TIMING_ACCURACY
, 0, getAverageSystemLoadPercent());
590 if (cmpTimeCycles(nextTimingCycles
, nowCycles
) < 0) {
591 nextTimingCycles
+= clockMicrosToCycles(1000000);
593 // Tasks late in last second
594 DEBUG_SET(DEBUG_TIMING_ACCURACY
, 1, lateTaskCount
);
595 // Total lateness in last second in us
596 DEBUG_SET(DEBUG_TIMING_ACCURACY
, 2, clockCyclesTo10thMicros(lateTaskTotal
));
597 // Total tasks run in last second
598 DEBUG_SET(DEBUG_TIMING_ACCURACY
, 3, taskCount
);
600 lateTaskPercentage
= 1000 * (uint32_t)lateTaskCount
/ taskCount
;
601 // 10ths % of tasks late in last second
602 DEBUG_SET(DEBUG_TIMING_ACCURACY
, 4, lateTaskPercentage
);
604 float gyroCyclesStdDev
= sqrt(devSquared
/gyroCyclesCount
);
605 int32_t gyroCyclesStdDev100thus
= clockCyclesTo100thMicros((int32_t)gyroCyclesStdDev
);
606 DEBUG_SET(DEBUG_TIMING_ACCURACY
, 7, gyroCyclesStdDev100thus
);
607 DEBUG_SET(DEBUG_SCHEDULER_DETERMINISM
, 7, gyroCyclesStdDev100thus
);
609 gyroCyclesMean
= gyroCyclesTotal
/gyroCyclesCount
;
619 lastTargetCycles
= nextTargetCycles
;
621 gyroDev_t
*gyro
= gyroActiveDev();
623 // Bring the scheduler into lock with the gyro
624 if (gyro
->gyroModeSPI
!= GYRO_EXTI_NO_INT
) {
625 // Track the actual gyro rate over given number of cycle times and set the expected timebase
626 static uint32_t terminalGyroRateCount
= 0;
627 static int32_t sampleRateStartCycles
;
629 if (terminalGyroRateCount
== 0) {
630 terminalGyroRateCount
= gyro
->detectedEXTI
+ GYRO_RATE_COUNT
;
631 sampleRateStartCycles
= nowCycles
;
634 if (gyro
->detectedEXTI
>= terminalGyroRateCount
) {
635 // Calculate the number of clock cycles on average between gyro interrupts
636 uint32_t sampleCycles
= nowCycles
- sampleRateStartCycles
;
637 desiredPeriodCycles
= sampleCycles
/ GYRO_RATE_COUNT
;
638 sampleRateStartCycles
= nowCycles
;
639 terminalGyroRateCount
+= GYRO_RATE_COUNT
;
642 // Track the actual gyro rate over given number of cycle times and remove skew
643 static uint32_t terminalGyroLockCount
= 0;
644 static int32_t accGyroSkew
= 0;
646 int32_t gyroSkew
= cmpTimeCycles(nextTargetCycles
, gyro
->gyroSyncEXTI
) % desiredPeriodCycles
;
647 if (gyroSkew
> (desiredPeriodCycles
/ 2)) {
648 gyroSkew
-= desiredPeriodCycles
;
651 accGyroSkew
+= gyroSkew
;
653 #if defined(USE_LATE_TASK_STATISTICS)
654 static int32_t minGyroPeriod
= (int32_t)INT_MAX
;
655 static int32_t maxGyroPeriod
= (int32_t)INT_MIN
;
656 static uint32_t lastGyroSyncEXTI
;
658 gyroCyclesNow
= cmpTimeCycles(gyro
->gyroSyncEXTI
, lastGyroSyncEXTI
);
661 lastGyroSyncEXTI
= gyro
->gyroSyncEXTI
;
662 // If we're syncing to a short cycle, divide by eight
663 if (gyro
->gyroShortPeriod
!= 0) {
666 if (gyroCyclesNow
< minGyroPeriod
) {
667 minGyroPeriod
= gyroCyclesNow
;
669 // Crude detection of missed cycles caused by configurator traffic
670 if ((gyroCyclesNow
> maxGyroPeriod
) && (gyroCyclesNow
< (1.5 * minGyroPeriod
))) {
671 maxGyroPeriod
= gyroCyclesNow
;
676 if (terminalGyroLockCount
== 0) {
677 terminalGyroLockCount
= gyro
->detectedEXTI
+ GYRO_LOCK_COUNT
;
680 if (gyro
->detectedEXTI
>= terminalGyroLockCount
) {
681 terminalGyroLockCount
+= GYRO_LOCK_COUNT
;
683 // Move the desired start time of the gyroTask
684 lastTargetCycles
-= (accGyroSkew
/GYRO_LOCK_COUNT
);
686 #if defined(USE_LATE_TASK_STATISTICS)
687 DEBUG_SET(DEBUG_SCHEDULER_DETERMINISM
, 3, clockCyclesTo10thMicros(accGyroSkew
/GYRO_LOCK_COUNT
));
688 DEBUG_SET(DEBUG_SCHEDULER_DETERMINISM
, 4, clockCyclesTo100thMicros(minGyroPeriod
));
689 DEBUG_SET(DEBUG_SCHEDULER_DETERMINISM
, 5, clockCyclesTo100thMicros(maxGyroPeriod
));
690 DEBUG_SET(DEBUG_SCHEDULER_DETERMINISM
, 6, clockCyclesTo100thMicros(maxGyroPeriod
- minGyroPeriod
));
691 minGyroPeriod
= INT_MAX
;
692 maxGyroPeriod
= INT_MIN
;
700 nowCycles
= getCycleCounter();
701 schedLoopRemainingCycles
= cmpTimeCycles(nextTargetCycles
, nowCycles
);
703 if (!gyroEnabled
|| (schedLoopRemainingCycles
> (int32_t)clockMicrosToCycles(CHECK_GUARD_MARGIN_US
))) {
704 currentTimeUs
= micros();
706 // Update task dynamic priorities
707 for (task_t
*task
= queueFirst(); task
!= NULL
; task
= queueNext()) {
708 if (task
->attribute
->staticPriority
!= TASK_PRIORITY_REALTIME
) {
709 // Task has checkFunc - event driven
710 if (task
->attribute
->checkFunc
) {
711 // Increase priority for event driven tasks
712 if (task
->dynamicPriority
> 0) {
713 task
->taskAgePeriods
= 1 + (cmpTimeUs(currentTimeUs
, task
->lastSignaledAtUs
) / task
->attribute
->desiredPeriodUs
);
714 task
->dynamicPriority
= 1 + task
->attribute
->staticPriority
* task
->taskAgePeriods
;
715 } else if (task
->attribute
->checkFunc(currentTimeUs
, cmpTimeUs(currentTimeUs
, task
->lastExecutedAtUs
))) {
716 const uint32_t checkFuncExecutionTimeUs
= cmpTimeUs(micros(), currentTimeUs
);
717 checkFuncMovingSumExecutionTimeUs
+= checkFuncExecutionTimeUs
- checkFuncMovingSumExecutionTimeUs
/ TASK_STATS_MOVING_SUM_COUNT
;
718 checkFuncMovingSumDeltaTimeUs
+= task
->taskLatestDeltaTimeUs
- checkFuncMovingSumDeltaTimeUs
/ TASK_STATS_MOVING_SUM_COUNT
;
719 checkFuncTotalExecutionTimeUs
+= checkFuncExecutionTimeUs
; // time consumed by scheduler + task
720 checkFuncMaxExecutionTimeUs
= MAX(checkFuncMaxExecutionTimeUs
, checkFuncExecutionTimeUs
);
721 task
->lastSignaledAtUs
= currentTimeUs
;
722 task
->taskAgePeriods
= 1;
723 task
->dynamicPriority
= 1 + task
->attribute
->staticPriority
;
725 task
->taskAgePeriods
= 0;
728 // Task is time-driven, dynamicPriority is last execution age (measured in desiredPeriods)
729 // Task age is calculated from last execution
730 task
->taskAgePeriods
= (cmpTimeUs(currentTimeUs
, task
->lastExecutedAtUs
) / task
->attribute
->desiredPeriodUs
);
731 if (task
->taskAgePeriods
> 0) {
732 task
->dynamicPriority
= 1 + task
->attribute
->staticPriority
* task
->taskAgePeriods
;
736 if (task
->dynamicPriority
> selectedTaskDynamicPriority
) {
737 timeDelta_t taskRequiredTimeUs
= task
->anticipatedExecutionTime
>> TASK_EXEC_TIME_SHIFT
;
738 int32_t taskRequiredTimeCycles
= (int32_t)clockMicrosToCycles((uint32_t)taskRequiredTimeUs
);
739 // Allow a little extra time
740 taskRequiredTimeCycles
+= checkCycles
+ taskGuardCycles
;
742 // If there's no time to run the task, discount it from prioritisation unless aged sufficiently
743 // Don't block the SERIAL task.
744 if ((taskRequiredTimeCycles
< schedLoopRemainingCycles
) ||
745 ((scheduleCount
& SCHED_TASK_DEFER_MASK
) == 0) ||
746 ((task
- tasks
) == TASK_SERIAL
)) {
747 selectedTaskDynamicPriority
= task
->dynamicPriority
;
755 // The number of cycles taken to run the checkers is quite consistent with some higher spikes, but
756 // that doesn't defeat its use
757 checkCycles
= cmpTimeCycles(getCycleCounter(), nowCycles
);
760 // Recheck the available time as checkCycles is only approximate
761 timeDelta_t taskRequiredTimeUs
= selectedTask
->anticipatedExecutionTime
>> TASK_EXEC_TIME_SHIFT
;
762 #if defined(USE_LATE_TASK_STATISTICS)
763 selectedTask
->execTime
= taskRequiredTimeUs
;
765 int32_t taskRequiredTimeCycles
= (int32_t)clockMicrosToCycles((uint32_t)taskRequiredTimeUs
);
767 nowCycles
= getCycleCounter();
768 schedLoopRemainingCycles
= cmpTimeCycles(nextTargetCycles
, nowCycles
);
770 // Allow a little extra time
771 taskRequiredTimeCycles
+= taskGuardCycles
;
773 if (!gyroEnabled
|| firstSchedulingOpportunity
|| (taskRequiredTimeCycles
< schedLoopRemainingCycles
)) {
774 uint32_t antipatedEndCycles
= nowCycles
+ taskRequiredTimeCycles
;
775 taskExecutionTimeUs
+= schedulerExecuteTask(selectedTask
, currentTimeUs
);
776 nowCycles
= getCycleCounter();
777 int32_t cyclesOverdue
= cmpTimeCycles(nowCycles
, antipatedEndCycles
);
779 #if defined(USE_LATE_TASK_STATISTICS)
780 if (cyclesOverdue
> 0) {
781 if ((currentTask
- tasks
) != TASK_SERIAL
) {
782 DEBUG_SET(DEBUG_SCHEDULER_DETERMINISM
, 1, currentTask
- tasks
);
783 DEBUG_SET(DEBUG_SCHEDULER_DETERMINISM
, 2, clockCyclesTo10thMicros(cyclesOverdue
));
784 currentTask
->lateCount
++;
786 lateTaskTotal
+= cyclesOverdue
;
789 #endif // USE_LATE_TASK_STATISTICS
791 if ((currentTask
- tasks
) == TASK_RX
) {
792 skippedRxAttempts
= 0;
795 else if ((currentTask
- tasks
) == TASK_OSD
) {
796 skippedOSDAttempts
= 0;
800 if ((cyclesOverdue
> 0) || (-cyclesOverdue
< taskGuardMinCycles
)) {
801 if (taskGuardCycles
< taskGuardMaxCycles
) {
802 taskGuardCycles
+= taskGuardDeltaUpCycles
;
804 } else if (taskGuardCycles
> taskGuardMinCycles
) {
805 taskGuardCycles
-= taskGuardDeltaDownCycles
;
807 #if defined(USE_LATE_TASK_STATISTICS)
809 #endif // USE_LATE_TASK_STATISTICS
810 } else if ((selectedTask
->taskAgePeriods
> TASK_AGE_EXPEDITE_COUNT
) ||
812 (((selectedTask
- tasks
) == TASK_OSD
) && (TASK_AGE_EXPEDITE_OSD
!= 0) && (++skippedOSDAttempts
> TASK_AGE_EXPEDITE_OSD
)) ||
814 (((selectedTask
- tasks
) == TASK_RX
) && (TASK_AGE_EXPEDITE_RX
!= 0) && (++skippedRxAttempts
> TASK_AGE_EXPEDITE_RX
))) {
815 // If a task has been unable to run, then reduce it's recorded estimated run time to ensure
816 // it's ultimate scheduling
817 selectedTask
->anticipatedExecutionTime
*= TASK_AGE_EXPEDITE_SCALE
;
822 #if defined(UNIT_TEST)
823 readSchedulerLocals(selectedTask
, selectedTaskDynamicPriority
);
824 UNUSED(taskExecutionTimeUs
);
826 DEBUG_SET(DEBUG_SCHEDULER
, 2, micros() - schedulerStartTimeUs
- taskExecutionTimeUs
); // time spent in scheduler
832 void schedulerEnableGyro(void)
837 uint16_t getAverageSystemLoadPercent(void)
839 return averageSystemLoadPercent
;
842 float schedulerGetCycleTimeMultiplier(void)
844 return (float)clockMicrosToCycles(getTask(TASK_GYRO
)->attribute
->desiredPeriodUs
) / desiredPeriodCycles
;