Task Scheduling in FreeRTOS – Context Switching

The previous article on FreeRTOS task scheduling – startup mentioned that the first task runs in an infinite loop mode. Clearly, having only one task running is not sufficient. So how does the running task yield control of the MCU to allow other tasks to run? One way is for the task to voluntarily yield control, for example by calling osDelay (vTaskDelay) or osDelayUntil (vTaskDelayUntil). The other way is through time-slice scheduling, where the task yields control passively when its time slice expires.First, let’s take a look at how vTaskDelay operates:Task Scheduling in FreeRTOS - Context SwitchingAs shown in the figure, the osDelay function converts milliseconds to system ticks, then adds the current task to the DelayedList. After that, a PendSV (Pendable Service Call) is generated. In the PendSV handler, the context of the current task is saved, and then vTaskSwitchContext is called. As the name suggests, this function completes the task switching by finding a task from the highest priority task in the ReadyList, updating pCurrentTCB, and then restoring the task from pCurrentTCB. At this point, pCurrentTCB points to the new task, and finally, the program returns to the new task using bx r14, continuing execution from where it was interrupted when added to the ReadyList, completing a task switch. The search for ready tasks is also very fast, implemented in just a few statements, designed very elegantly.Next, let’s see how task switching is done through time-slicing.Task Scheduling in FreeRTOS - Context Switching

First, when the SysTick timeout occurs, it enters the interrupt handler. In the handler, xTickCount is incremented. It also needs to handle overflow situations; for an unsigned int, if overflow occurs, the count resets to 0. If it is found that the time for a task in the DelayedTaskList has arrived, the corresponding task needs to be removed from the DelayedTaskList and added to the ReadyList. It also needs to check if it is preemptive; if it is, it must determine whether the current task’s priority is lower than that in the ReadyList. Only if it is lower will a task switch occur. Therefore, for preemptive switching, a high-priority task may continuously occupy the processor. If a task switch is needed, a PendSV exception is generated, which returns to the previously analyzed exception handling function to complete the task switch.

Normally, we configure the SysTick period to 1 millisecond, or 1 kHz. How many instructions can the STM32 execute in one millisecond? Here is the answer provided by AI:

Number of Instructions Executed

  • Clock Frequency of STM32F407: Typically, the main frequency is 168 MHz (maximum supported frequency).

  • Cycles per Instruction (CPI):

    • The Cortex-M4 requires an average of 1-2 cycles to execute a Thumb-2 instruction (varies with instruction complexity).

  • Theoretical Calculation:

    • 1 ms = 168,000 clock cycles.

    • Executable instructions ≈ 84,000-168,000 instructions (assuming CPI is 1-2).

  • Mapping C Code to Instructions:

    • A simple line of C code (e.g., <span>a = b + c;</span>) may compile to 5-10 instructions.

    • Complex operations (e.g., floating-point calculations, function calls) may generate more instructions.

  • Estimation:

    • If each line of C code corresponds to an average of 10 instructions, then approximately 8,400-16,800 lines of C code can be executed in 1 ms.

    • The actual value may be lower (due to overhead from branching, memory access, etc.)

As shown, a lot can still be accomplished within a task’s execution cycle.

Leave a Comment