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_
29 #include "build/build_config.h"
30 #include "build/debug.h"
32 #include "scheduler/scheduler.h"
34 #include "config/config_unittest.h"
36 #include "common/maths.h"
37 #include "common/time.h"
38 #include "common/utils.h"
40 #include "drivers/time.h"
42 // DEBUG_SCHEDULER, timings for:
44 // 1 - pidController()
45 // 2 - time spent in scheduler
46 // 3 - time spent executing check function
48 static FAST_RAM cfTask_t
*currentTask
= NULL
;
50 static FAST_RAM
uint32_t totalWaitingTasks
;
51 static FAST_RAM
uint32_t totalWaitingTasksSamples
;
53 static FAST_RAM
bool calculateTaskStatistics
;
54 FAST_RAM
uint16_t averageSystemLoadPercent
= 0;
57 static FAST_RAM
int taskQueuePos
= 0;
58 STATIC_UNIT_TESTED FAST_RAM
int taskQueueSize
= 0;
60 // No need for a linked list for the queue, since items are only inserted at startup
62 STATIC_UNIT_TESTED FAST_RAM cfTask_t
* taskQueueArray
[TASK_COUNT
+ 1]; // extra item for NULL pointer at end of queue
66 memset(taskQueueArray
, 0, sizeof(taskQueueArray
));
71 bool queueContains(cfTask_t
*task
)
73 for (int ii
= 0; ii
< taskQueueSize
; ++ii
) {
74 if (taskQueueArray
[ii
] == task
) {
81 bool queueAdd(cfTask_t
*task
)
83 if ((taskQueueSize
>= TASK_COUNT
) || queueContains(task
)) {
86 for (int ii
= 0; ii
<= taskQueueSize
; ++ii
) {
87 if (taskQueueArray
[ii
] == NULL
|| taskQueueArray
[ii
]->staticPriority
< task
->staticPriority
) {
88 memmove(&taskQueueArray
[ii
+1], &taskQueueArray
[ii
], sizeof(task
) * (taskQueueSize
- ii
));
89 taskQueueArray
[ii
] = task
;
97 bool queueRemove(cfTask_t
*task
)
99 for (int ii
= 0; ii
< taskQueueSize
; ++ii
) {
100 if (taskQueueArray
[ii
] == task
) {
101 memmove(&taskQueueArray
[ii
], &taskQueueArray
[ii
+1], sizeof(task
) * (taskQueueSize
- ii
));
110 * Returns first item queue or NULL if queue empty
112 FAST_CODE cfTask_t
*queueFirst(void)
115 return taskQueueArray
[0]; // guaranteed to be NULL if queue is empty
119 * Returns next item in queue or NULL if at end of queue
121 FAST_CODE cfTask_t
*queueNext(void)
123 return taskQueueArray
[++taskQueuePos
]; // guaranteed to be NULL at end of queue
126 void taskSystem(timeUs_t currentTimeUs
)
128 UNUSED(currentTimeUs
);
130 // Calculate system load
131 if (totalWaitingTasksSamples
> 0) {
132 averageSystemLoadPercent
= 100 * totalWaitingTasks
/ totalWaitingTasksSamples
;
133 totalWaitingTasksSamples
= 0;
134 totalWaitingTasks
= 0;
136 #if defined(SIMULATOR_BUILD)
137 averageSystemLoadPercent
= 0;
141 #ifndef SKIP_TASK_STATISTICS
142 #define MOVING_SUM_COUNT 32
143 timeUs_t checkFuncMaxExecutionTime
;
144 timeUs_t checkFuncTotalExecutionTime
;
145 timeUs_t checkFuncMovingSumExecutionTime
;
147 void getCheckFuncInfo(cfCheckFuncInfo_t
*checkFuncInfo
)
149 checkFuncInfo
->maxExecutionTime
= checkFuncMaxExecutionTime
;
150 checkFuncInfo
->totalExecutionTime
= checkFuncTotalExecutionTime
;
151 checkFuncInfo
->averageExecutionTime
= checkFuncMovingSumExecutionTime
/ MOVING_SUM_COUNT
;
154 void getTaskInfo(cfTaskId_e taskId
, cfTaskInfo_t
* taskInfo
)
156 taskInfo
->taskName
= cfTasks
[taskId
].taskName
;
157 taskInfo
->subTaskName
= cfTasks
[taskId
].subTaskName
;
158 taskInfo
->isEnabled
= queueContains(&cfTasks
[taskId
]);
159 taskInfo
->desiredPeriod
= cfTasks
[taskId
].desiredPeriod
;
160 taskInfo
->staticPriority
= cfTasks
[taskId
].staticPriority
;
161 taskInfo
->maxExecutionTime
= cfTasks
[taskId
].maxExecutionTime
;
162 taskInfo
->totalExecutionTime
= cfTasks
[taskId
].totalExecutionTime
;
163 taskInfo
->averageExecutionTime
= cfTasks
[taskId
].movingSumExecutionTime
/ MOVING_SUM_COUNT
;
164 taskInfo
->latestDeltaTime
= cfTasks
[taskId
].taskLatestDeltaTime
;
168 void rescheduleTask(cfTaskId_e taskId
, uint32_t newPeriodMicros
)
170 if (taskId
== TASK_SELF
) {
171 cfTask_t
*task
= currentTask
;
172 task
->desiredPeriod
= MAX(SCHEDULER_DELAY_LIMIT
, (timeDelta_t
)newPeriodMicros
); // Limit delay to 100us (10 kHz) to prevent scheduler clogging
173 } else if (taskId
< TASK_COUNT
) {
174 cfTask_t
*task
= &cfTasks
[taskId
];
175 task
->desiredPeriod
= MAX(SCHEDULER_DELAY_LIMIT
, (timeDelta_t
)newPeriodMicros
); // Limit delay to 100us (10 kHz) to prevent scheduler clogging
179 void setTaskEnabled(cfTaskId_e taskId
, bool enabled
)
181 if (taskId
== TASK_SELF
|| taskId
< TASK_COUNT
) {
182 cfTask_t
*task
= taskId
== TASK_SELF
? currentTask
: &cfTasks
[taskId
];
183 if (enabled
&& task
->taskFunc
) {
191 timeDelta_t
getTaskDeltaTime(cfTaskId_e taskId
)
193 if (taskId
== TASK_SELF
) {
194 return currentTask
->taskLatestDeltaTime
;
195 } else if (taskId
< TASK_COUNT
) {
196 return cfTasks
[taskId
].taskLatestDeltaTime
;
202 void schedulerSetCalulateTaskStatistics(bool calculateTaskStatisticsToUse
)
204 calculateTaskStatistics
= calculateTaskStatisticsToUse
;
207 void schedulerResetTaskStatistics(cfTaskId_e taskId
)
209 #ifdef SKIP_TASK_STATISTICS
212 if (taskId
== TASK_SELF
) {
213 currentTask
->movingSumExecutionTime
= 0;
214 currentTask
->totalExecutionTime
= 0;
215 currentTask
->maxExecutionTime
= 0;
216 } else if (taskId
< TASK_COUNT
) {
217 cfTasks
[taskId
].movingSumExecutionTime
= 0;
218 cfTasks
[taskId
].totalExecutionTime
= 0;
219 cfTasks
[taskId
].maxExecutionTime
= 0;
224 void schedulerInit(void)
226 calculateTaskStatistics
= true;
228 queueAdd(&cfTasks
[TASK_SYSTEM
]);
231 FAST_CODE
void scheduler(void)
234 const timeUs_t currentTimeUs
= micros();
236 // Check for realtime tasks
237 bool outsideRealtimeGuardInterval
= true;
238 for (const cfTask_t
*task
= queueFirst(); task
!= NULL
&& task
->staticPriority
>= TASK_PRIORITY_REALTIME
; task
= queueNext()) {
239 const timeUs_t nextExecuteAt
= task
->lastExecutedAt
+ task
->desiredPeriod
;
240 if ((timeDelta_t
)(currentTimeUs
- nextExecuteAt
) >= 0) {
241 outsideRealtimeGuardInterval
= false;
246 // The task to be invoked
247 cfTask_t
*selectedTask
= NULL
;
248 uint16_t selectedTaskDynamicPriority
= 0;
250 // Update task dynamic priorities
251 uint16_t waitingTasks
= 0;
252 for (cfTask_t
*task
= queueFirst(); task
!= NULL
; task
= queueNext()) {
253 // Task has checkFunc - event driven
254 if (task
->checkFunc
) {
255 #if defined(SCHEDULER_DEBUG)
256 const timeUs_t currentTimeBeforeCheckFuncCall
= micros();
258 const timeUs_t currentTimeBeforeCheckFuncCall
= currentTimeUs
;
260 // Increase priority for event driven tasks
261 if (task
->dynamicPriority
> 0) {
262 task
->taskAgeCycles
= 1 + ((currentTimeUs
- task
->lastSignaledAt
) / task
->desiredPeriod
);
263 task
->dynamicPriority
= 1 + task
->staticPriority
* task
->taskAgeCycles
;
265 } else if (task
->checkFunc(currentTimeBeforeCheckFuncCall
, currentTimeBeforeCheckFuncCall
- task
->lastExecutedAt
)) {
266 #if defined(SCHEDULER_DEBUG)
267 DEBUG_SET(DEBUG_SCHEDULER
, 3, micros() - currentTimeBeforeCheckFuncCall
);
269 #ifndef SKIP_TASK_STATISTICS
270 if (calculateTaskStatistics
) {
271 const uint32_t checkFuncExecutionTime
= micros() - currentTimeBeforeCheckFuncCall
;
272 checkFuncMovingSumExecutionTime
+= checkFuncExecutionTime
- checkFuncMovingSumExecutionTime
/ MOVING_SUM_COUNT
;
273 checkFuncTotalExecutionTime
+= checkFuncExecutionTime
; // time consumed by scheduler + task
274 checkFuncMaxExecutionTime
= MAX(checkFuncMaxExecutionTime
, checkFuncExecutionTime
);
277 task
->lastSignaledAt
= currentTimeBeforeCheckFuncCall
;
278 task
->taskAgeCycles
= 1;
279 task
->dynamicPriority
= 1 + task
->staticPriority
;
282 task
->taskAgeCycles
= 0;
285 // Task is time-driven, dynamicPriority is last execution age (measured in desiredPeriods)
286 // Task age is calculated from last execution
287 task
->taskAgeCycles
= ((currentTimeUs
- task
->lastExecutedAt
) / task
->desiredPeriod
);
288 if (task
->taskAgeCycles
> 0) {
289 task
->dynamicPriority
= 1 + task
->staticPriority
* task
->taskAgeCycles
;
294 if (task
->dynamicPriority
> selectedTaskDynamicPriority
) {
295 const bool taskCanBeChosenForScheduling
=
296 (outsideRealtimeGuardInterval
) ||
297 (task
->taskAgeCycles
> 1) ||
298 (task
->staticPriority
== TASK_PRIORITY_REALTIME
);
299 if (taskCanBeChosenForScheduling
) {
300 selectedTaskDynamicPriority
= task
->dynamicPriority
;
306 totalWaitingTasksSamples
++;
307 totalWaitingTasks
+= waitingTasks
;
309 currentTask
= selectedTask
;
312 // Found a task that should be run
313 selectedTask
->taskLatestDeltaTime
= currentTimeUs
- selectedTask
->lastExecutedAt
;
314 selectedTask
->lastExecutedAt
= currentTimeUs
;
315 selectedTask
->dynamicPriority
= 0;
318 #ifdef SKIP_TASK_STATISTICS
319 selectedTask
->taskFunc(currentTimeUs
);
321 if (calculateTaskStatistics
) {
322 const timeUs_t currentTimeBeforeTaskCall
= micros();
323 selectedTask
->taskFunc(currentTimeBeforeTaskCall
);
324 const timeUs_t taskExecutionTime
= micros() - currentTimeBeforeTaskCall
;
325 selectedTask
->movingSumExecutionTime
+= taskExecutionTime
- selectedTask
->movingSumExecutionTime
/ MOVING_SUM_COUNT
;
326 selectedTask
->totalExecutionTime
+= taskExecutionTime
; // time consumed by scheduler + task
327 selectedTask
->maxExecutionTime
= MAX(selectedTask
->maxExecutionTime
, taskExecutionTime
);
329 selectedTask
->taskFunc(currentTimeUs
);
333 #if defined(SCHEDULER_DEBUG)
334 DEBUG_SET(DEBUG_SCHEDULER
, 2, micros() - currentTimeUs
- taskExecutionTime
); // time spent in scheduler
336 DEBUG_SET(DEBUG_SCHEDULER
, 2, micros() - currentTimeUs
);
340 GET_SCHEDULER_LOCALS();