 All right, let me continue with a closer description of the scheduler and the two modes of operation. The first one I'd like to touch is the cooperative multitasking. The cooperative multitasking is a little bit special in the fact that it requires a cooperation between different tasks or between all tasks. So each task has to give up to be able to allow other tasks to run. This requires that the task gets, for example, blocked by calling a function that blocks it by waiting for something, being it a timeout or some other object. Or it can get to a ready state by calling, for example, OS thread yield and it can be as well put to suspend mode by the system, typically other tasks, or it can as well suspend itself. One advantage is that the tasks are not preemptive by tasks with a higher priority, so you get all the time you need to finish your own job. Then there is not implemented a time slicing, so the tasks don't switch periodically based on a time base. And to allow it, you have to disable the preemption in the freeRTOS config. When we choose a preemptive multitasking, the situation changes a little bit. So the preemptive multitasking works with the tasks, especially if they have the same priority. So the freeRTOS always chooses the task with the highest priority, but if there are more tasks with the same highest priority, it will switch between them with the round robin algorithm. So in this case, the context, the tasks, which happens when there is a time slice that passes, which can be a millisecond or longer time defined by the global system frequency. Then when the task with the higher priority has become ready, when the task gets blocked or when the task gets ready by yielding. The preemption is enabled by a use preemption macro set to one. So this way the behavior of the operating system becomes a little bit more human friendly because all the tasks with the same priority get scheduled after a time slice. So there is a feeling that all of them run faster, even if this may not be exactly the same thing. Why I don't use preemptive multitasking too much? You have to think about the fact that the time slice happens in an interrupt so the tasks which can happen anytime during your application run time. Which means if you use non recursive functions and a lot of CRTL functions are not reentrant, you may have a trouble. For example, printf, the typical case, the typical function is not a reentrant function because it uses some internal variables of the CRTL library. And if you interrupt it in the middle by an interrupt and you give a job to another task that as well uses printf, then it starts overwriting the internal variables and it becomes non consistent and it can as well crush your application. So be careful about using non reentrant functions and this you will typically learn in the documentation for the compiler and its libraries. So if it's described whether it's reentrant or not, it helps you whether to use it or not. If you don't have any information about it, then I discourage you from using preemptive. There is as well a possibility to use the cooperative with preemption by IRQ, which means that still the task switch normally happens when you give up your CPU time. But the task switch can happen as well when you get an external interrupt and at the end of the interrupt you wake up some higher priority task and then this causes the task switch. So at that moment the interrupts are used to trigger the context switch and it can force a higher priority task to execute instead of the original one. So it's a preemptive system without time slicing. The scheduler is a way how to switch the tasks and to define under which priorities the tasks will switch. So it's typically triggered by the Pandable SV and it scans all the tasks that are in the ready state and chooses the one with the highest priority. If the priority is equal among other tasks, then it goes in a round robin. In freeRTOS round robin is implemented for the tasks with the same priority. Now let's talk a little bit more about the interrupts and connection to the hardware. So when we look in the interrupts, you can see how the interrupts cooperate together. The system service interrupt is raised by an SWI instruction and it's used only once to start the scheduler at a high priority. So you can see that the SVC occurs only at the beginning of the freeRTOS execution and as soon as it's finished, it provides the task switch and the first tasks execute. Then we have got a SISTIC interrupt that has lowest priority of all the interrupts used by the freeRTOS and it is used for defining a task switch on the defined tick rate related or expressed in hertz. The SISTIC, if the preemptive operation is defined, calls the task switch which means it sets the flag for Pandable system interrupt if the context switch is necessary. And finally the Pandable SV interrupt is finally used for a task switch and it's typically called from the SISTIC and it is as well set in functions of the RTOS API to guarantee the context switch whenever we change a state of the task. So if for example we enter a blocking state, if we enter a ready state, in all these situations the context switch is generated. Now let's look at the priority of the interrupts. The interrupts are selected or differentiated by the fact that you either call the RTOS functions or not. If we look at the RTOS related interrupts, they are defined on the lowest priority. Then the interrupts within the RTOS space, so those where for example you get a character from the UART and you put it into the freeRTOS queue, they have to be or they are limited in the priority below the SISTIC call interrupt priority. So all interrupts with the relation to the freeRTOS have to be run below the maximum SISTIC call interrupt priority. This is to guarantee that the freeRTOS will always have the option to take over using system routines. If you look in the CubaMix interrupt definition and you enable the freeRTOS, it will give you the option to checkmark if the interrupt calls the RTOS functions or not. If yes, it is limited to the RTOS interrupt priorities. If you declare that it doesn't use the RTOS functions, you are free to choose higher priorities, which means you can define a very fast peripheral response time if you don't use the RTOS functions. Speaking about the IRQ, when we operate with interrupts, we need to use specific functions. These functions typically have suffix from ISR. They are different a little bit from the standard functions used among the tasks, because they can as well pass a context or a calling task. So this differentiation has to be done if you use native API by a programmer. If, however, you use a CMSIS API, this differentiation is done by the CMSIS function. For example, in the case of OSMF for release, you don't need to care which function you call, there is just one. And it internally differentiates whether it calls from the interrupt or from a standard task. You can as well see the API functions used in the native API from interrupts and the CMSIS OS API. So all the functions for the native API have the suffix from ISR. The CMSIS OS APIs don't care. Now let's look at the boot time of an application running on RTOS. There are things that are dependent on the hardware and they allow you to tweak the startup. So what is hardware dependent? The CPU clocks. Cubemix sets the CPU clocks in a main routine, but if you need a very fast application startup time, what can you do? Where you can tweak your CPU clock if you need a very fast startup time. Do you know function system in it? It is the very first function called after the reset. In our library, it's defined to reset the oscillators and to run from the standard internal RC oscillator. So it guarantees a safe and reliable startup. However, if you have a crystal connected and you have a very big memory to initialize, it's probably better to start with boosting your clocks, turning on the PLL and starting on the PLL. Because then the memory initialization, copying of the blocks, initializing the init values of variables and so on, will be much faster. So the first way is to move the clock initialization from the main to the system in it. Then the RTL library will initialize the variables. So it will take the init values for the static and global variables. It will as well clear the static and global variables without initialization value. So it's lot of memset and memcopy. And then if there is any other setup needed, for example for C++, then it will as well call the static constructors of the classes. And it can as well initialize stack, heap and other things. And only then you get into your main routine. And in the main routine you set up your main task and any others if needed. Then you set up the queues, mutexes, timers and so on. And you start the scheduler. From that point you can see how much time it needs. So per one object it takes approximately 500 CPU cycles. The application task needs more than 1000 cycles to create. And the initial scheduler start takes approximately 1200 cycles. So if you sum all this you get a startup time when you get first context switch and you launch your application, your first task. So sometimes it's beneficial if you start the scheduler very quickly. You just create your init task, launch the scheduler and then while you define other tasks you can initialize the peripherals and other tasks later. So the primary tasks that are needed to execute very quickly can be defined for example in preemptive mode and they can already run while you initialize further peripherals. So you have got a possibility to create tasks that wait for a peripheral initialization and only when a peripheral is initialized, especially if it relies on some external things like connecting a ternet cable or detecting the ternet cable. Such tasks can be created very quickly at the beginning but then it can be launched when the cable is physically connected. So you then speed up the response. Further, the RTOS defines an idle task. This is a special one. It's implemented for the sake of having at least one active task that will run if nothing else is available. It's created automatically by the kernel. The idle task has one interesting thing. You can create an idle hook which then gets called whenever idle task starts. This way you can implement low power mode because if the microcontroller has no other task to be scheduled, which means it doesn't need to do anything. Then this idle call or idle hook allows you to put the microcontroller in a low power in sleep or stop mode. If you use the tickless idle, you have to do a couple of other things like stopping the timebase, like defining the wakeup interrupts and so on. But it allows you to disable the periodic wakeup of the microcontroller from a timebase interrupt. Additionally, implementing the entry and exit macros or hooks in the idle allow you to calculate the CPU idle time. So it can tell you how much the CPU is loaded. This is all the counters. Now I am running at 30% free time. Now I am running at 0% free time or 99% free time. This is because we can measure at which time we entered the idle and when we exited the idle. Friartos is started by calling the OS kernel start in typically main.c file. So when you initialize at least one task, you call the OS kernel start and as soon as you enter it, it starts scheduling the tasks. Normally, it calls the vtask start scheduler from the native API and then it should never return. So when this is called, it calls the task create for idle task. It as well sets up the timer task if you opted for it. And then it calls the export start scheduler which generates a first context switch and chooses the first active task to be running. The port start first task then locates the stack, sets the MSP and PSP and it triggers the software interrupt which calls the SVC handler and this one restores the context, loads the tcb from the first task that you implemented and starts executing this task. So you can see that it's a series of different jobs to do before you start your first one but it all makes very clear sense. I would say that the all behavior of the Friartos is based on lists. If you create a queue, it's a list of items. If you create a semaphore or a mutex, it's a list with a one element. So most of the functionality in the Friartos is different lists. So there exists a possibility to instantiate a list and a lot of lists are as well used internally in the kernel. When we look at them, you normally don't get access to these lists but in case you want to debug, you can still put them in a watch window and you can traverse them through. So we can see that there is a ready task list that lists all the tasks or pointers to the tcb for the tasks that can be instantiated and put into the running state. So task list is an array with a size of maximum priorities because it lists the tasks depending on their priority. If you define a specific priority, it will be put in the appropriate element of the task list array. Probably one interesting information, you can change the priority of the task in runtime and that means it will be moved between the ready lists. Further, if you terminate a task, it will be put into the list tasks waiting termination until they are removed. Further, there is a suspended task list, pending ready task list where it is put after you, for example, send a message to the task. So it's put from the blocked state to the pending ready and then the scheduler will put it into the appropriate priority ready list. You have got a delayed task list where the tasks wait for a specific timeout. So with each timebase, the waiting time for these tasks is decremented and when it reaches zero, it gets into the pending ready. So that the task that's waiting for something has elapsed its waiting time so we can use it again. And we have got as well overflow delayed task list that overflows the current tick time. That's how the Friartos organizes different tasks in different states. Speaking about the kernel start, we already discussed that on the previous slides, but the API offered the functionality to end the scheduler. It's not implemented on STM32 so we cannot finish the Friartos, which means we never exit from the OS kernel start. Further, we can check if the kernel is running or not. This is a very important thing, especially if you, for example, implement lock functions. So if you print something on a UART, like error messages, status messages and so on, and this function formulates the output and sends it. When you, for example, use this functionality before you start the scheduler, for example, during peripheral initialization and different stacks initialization, such function, if it uses, for example, a queue for the messages, can use the operating system functions if the kernel is running. But if you run the lock function before you start the kernel, it must use a different method of synchronizing the messages and sending of the data. So, for example, if the kernel is not running, you will use a blocking function for sending the data over UART. But if the kernel is running, you can use DMA and some task that takes care about the messages from the queue. You can create dynamically the space for the text. You can send it through the queue to the sending task, and then it's more efficient because you don't need to wait for the DMA transaction to finish. So, the function OS kernel running is a very important one when you need to create a cooperation between non-RTOS and RTOS functionality. And you can as well read the value of the kernel SysTick where it's implemented as a 32-bit counter internally, and it's used for timing, but you can as well use it to monitor how long your application is alive. Be careful when you implement support for tickless mode. In low power modes, the kernel SysTick is not incremented while the microcontroller sleeps. So, when you wake up, if you need this time to be rather precise, you have to maintain the time separately and you have to update the internal time.