 Hello, in this video we'll focus on basic operations on tasks and the delay functions. Let's have a closer look what is the difference between OSDelae and OSDelayUntil functions. OSDelae is calling vTaskDelay and requires include vTaskDelay within 3-year-to-sconfig.h file. It sends the calling task from round to block state for given number of milliseconds counting from this function call. OSDelaeUntil is calling vTaskDelayUntil and requires inclusion of vTaskDelayUntil within 3-year-to-sconfig.h file. It checks the current text value, xtask.getTickCount() function and the substruct this value from the specifying delay. Then it is sending the calling task from round to block state for remaining milliseconds period. Let's have a closer look on the delay functions available within 3-year-to-s. So we've got two of them, OSDelae, which is calling the 3-year-to-s API vTaskDelay function. And the second one is OSDelaeUntil, which is calling vTaskDelayUntil function. Let's focus on OSDelae. OSDelae is calling vTaskDelay from 3-year-to-s API. It is defined within the tasks.c file. And to use this function we need to enable include vTaskDelay within the configuration of 3-year-to-s. The vTaskDelay function is performing the following list of operations. It is first calling the vTaskSuspendAll() function, which is in fact freezing all the tasks, freezing the scheduler without disabling the interrupts of the hardware. So the RTOS tick will be held, will be stopped for a while and there would be no operation on the 3-year-to-s, on other components. Then it is removing the task, which is calling this function, from the running list, from the run list. And it's moving it to the delayed state by sending the task name to the delayed list with given delay value. And it is done by using function prvadd current state, current task to the delayed list. Then after this operation, which is in fact sending the task from run to the blocked state, the scheduler is resuming its operations by function exTaskResumeAll() function execution. And at the end, there is a trigger for the context switch by triggering pentSvInterrupt using the macro port yield within API. And this macro is switching the context to the other task, which is in a ready state. Let's do some practice. To save some time needed for the new project creation, we will reuse previous exercise done at the beginning of this session. In case you would like to store each exercise, please use file, export option and use an example in dedicated zip file. In case you would like to make an exercise from scratch for your STM32 platform, please create a new project with an STM32QBemix or STM32QPIDE for your selected MCU and then please change timebase for HAL from default SysTick to Timer 6. It can be done within SysPeripherialGroup. Enable free R2S in version CMCOS version 2. Additionally, after code degeneration, please implement the function task underscore action. In our examples, we are using it to accept one HAR argument, one letter and return nothing. This letter is passed over SWO interface to displace small comments from the tasks or interrupts. But you can implement your own task underscore action function, of course. Let's have a look how does it look like in a time domain. Both tasks have the same priority. So task 1 and task 2 have the same priority as priority normal and idle task has lower priority. So it will be executed only if both tasks would be blocked. So what I am doing within the code, I'm invoking some task action of task 1 and then I'm calling the OSDelay. OSDelay function is sending task 1 from running state to the blocked state for a given time, in our case one second. So OSDelay is sending this task 1 and it is done by triggering the PentSV software interrupt to switch the context. PentSV interrupt is selecting task 2 as a second one to be executed. Task 2 is performing his operation, sending this task action with argument 2 and again is calling OSDelay with argument 1000 millisecond. And again, it is done like this, that is triggered PentSV software interrupt which is selecting the only ready task which is idle task. Idle task is executed for a bit less than one second and then the delay ends and the task 1 is ready to be executed. It's coming back from the blocked state into the ready state. So PentSV is called because task 1 has higher priority than idle. So it is immediately exchanging with idle. So PentSV is changing, switching the context from idle to task 1 and again task 1 is sending task action with argument 1 and it is going into the blocked state by calling OSDelay. So it is done by calling PentSV software interrupt which is switching the context from task 1 to task 2 and the story continues like at the beginning. Below we can see how it looks like with the states. So at the beginning both of the tasks are in a ready state then task 1 is running, task 2 is ready. Then task 2 is running and the task 1 is blocked. Then both tasks are blocked and then after let's say this almost one second elapsed task 1 is back and task 2 is still blocked. And then in the meantime task 2 is unblocked, it's in ready state and there is the last switch from task 1 to task 2. And it is going like this all around. Within the configuration, so pinout and configuration tab at stm32cubemix or stm32cubede, press 3R2S button and select task and queues. We will see on the screen the configuration window of 3R2S with displayed the list of the current tasks. At the beginning we can see that only one task is defined, it is called default task. So what we need to do is to rename it to task 1. We can keep the priority OS priority normal, the stack size kept to 128 words and refunction. Let's name it start task 1, call the generation. It will be the default without any parameters, so null and allocation dynamic. Once we'll do it, we will add another task. So we need to press add button with below and we can specify the next task. So the name it would be task 2, priority the same OS priority normal, the same stack size 128 words, entry function it will be start task 2 and the rest of the parameters we keep as default. So call generation default, parameter null and allocation dynamic. As we discussed at the beginning of this session, 3R2S allows us to scale its size, its usage of the flash and RAM. We can enable and disable some functionalities within the config parameters and include parameters of 3R2S configuration. Those components are stored within 3R2S config.h file and one of those components which are not necessary for the preparations is a delay function. So in case we would like to have a delay function which would send our task from run state to the block state for a given time, we need to enable vtask delay function. To do this, we should go into the include parameters tab in 3R2S configuration and change for vtask delay position option from disabled to enabled. Last step within the cubemix configuration is to configure the project and generate the code. To do this, please go into the project manager tab then within the project tab, please select the project name, project location and type of the toolchain we will use in cubemix. We can select different toolchains, IRKL AC61 or CUBE IDE. Within CUBE IDE environment, we will have only one option. It will be STM32 CUBE IDE. To generate the code, we need to click either generate code button on the upper right corner which is the case of STM32 CUBE MX or press generate code within STM32 CUBE IDE. Let's have a closer look on a code generated by our STM32 CUBE MX or CUBE IDE. Let's open main.c file and coming from the top. Within the private variables, we can see the declaration of two handlers. Of our tasks, we just specified within the configurator. Then within the private function prototypes, we can see the declaration of two functions which has been assigned to those tasks. Please have a look that both functions are returning nothing and accepting the argument as a pointer to any type. So pointer to void. We will not use any argument, passing any argument to the functions within this exercise, but it is worth to remember that there is such an option. Then within the main function, we can see the classical configuration of the hardware. So there is a how in it. There is a clock configuration and there is the initialization of each peripheral we just selected within the configurator CUBE MX or CUBE IDE. In our case, it will be only GPIO in this exercise. And after this, there is a space for our configuration before the main code will run. We will not use it. And after this user code end to section, there is a call to OS kernel initialize, which is in fact allocating the memory of the stack dedicated for our operating system. So this is important that this function should be called before any operating system component is created. So this is done in the initialization part. The next step after the initialization of this memory for the operating system is the creation of the components of the operating system we specified within the configuration in CUBE MX or CUBE IDE. Please remember that we can add or remove tasks or semaphores within the working operating system. So from, for example, from other tasks, but there is an option of course to specify them as well before we will start the operating system. This is the case of this example. Within the configurators, we have specified two tasks, task one and task two. Both of them have the same priority, the same stack size is 128 words. And both of them have different functions assigned to them, which would be executed during the time assigned to the task. So within task one, we'll use start task one function and the task two will use start task two function. And the creation of the task is divided into two steps. The first one is the definition of task parameters. For this, we are using the structure OSThread attribute underscore T, which we can see in the beginning, where we are specifying the name, priority and the stack size of assigned to the task. Of course, this field is prefilled by the CUBE generator. And then on the second step, we are creating the task using the function OSThread new. The first argument is the function name which is assigned to the task. The second one is the argument we would like to pass to this function. And the third one is an address of the attribute structure we just filled in before. After this, as a result of this function, we've got in return the handler to the new task, which can be used later on to perform the operation on a given task. So for example, increase or decrease the priority, delete the task or put it on different mode like suspend mode. The same operations we are doing for the second task using exactly the same flow. So first filling the structure with the parameters and then creating the task using OSThread new function. After we create all of the components we just selected from within the configurator, it is a time to start the scheduler. The scheduler is started by executing the function OSCarnalStart and this function is called just before the main while1 loop. So in fact, in our FreeRtoys based application we never reach the while1 loop. Our code execution is finishing on this OSCarnalStart function. And then within the job of FreeRtoys we are just traveling from one task function to the other and we are executing the interrupt procedures of course. So once we start the operating system the scheduler is selecting the tasks for execution. From this moment we can observe that we will be switched from start task one to start task two. One by one. What we need to do is to fill the body of the functions start task one and start task two. As we discussed at the beginning we are not using any arguments. We are not passing any arguments to those functions and we will do the most basic task body function. Please have a look that this function is in fact built from two components. The first one is at the beginning, the place where we can put some values which should be used only at the first run of the task. So the initialization value some local variables which would be used only by this task. And then we've got the infinite loop. This infinite loop is very important while we are creating the task body functions because we should never exit from the task. This is, it should be treated like a mini main function in C, in Classical C. So what we need to do is to fill this endless loop. As I would say first operation just to check whether everything is working we will use here some task action. I name it as task underscore action with argument one and we can assign to this any kind of function like it was proposed at the beginning of this training just to be more agnostic. So after this action I propose to send the task from run mode to the blocked mode for one second. To do this I will use the function osdelay with argument 1000. Osdelay is accepting the number of the milliseconds we would like to spend in blocked state. In this case I'm expecting that my function my task will execute task action with argument one and then immediately it will go to the blocked state for one second giving the chance to do some action for task two. In this case what we need to do with the task two within the task two we will do similar operation that the only difference would be that instead of task underscore action one we will use task underscore action two and again osdelay 1000. What would be the effect of this function we will see bit later on on the screen. After we process the code we can compile it using the hammer button and after a while one is done we can start the debug session of course after connecting the board. In my case I have programmed the task action to send the data over the ITM interface so this argument one and two I'm just passing via ITM and I'm observing it within a single wire viewer ITM data console in STM32QPIDE and as you can see as both tasks have the same priority and we've got let's say the preemptive model I can see one by one task two task one task two task one so those are let's say working one by one as expected. Let's analyze the situation if we will not use this osdelay function. In such a case both of the tasks will be either in running state or in ready state. There will be no blocked state and there will be no space for idle task to be executed because it has lower priority than both of them. So what would be the flow in this case? Task one will start its execution for a given period of time defined by the time slice which is usually one millisecond it is configurable with a free artist config.h file and it will be moved from run state to the ready state by the SysTick and SysTick will trigger PentSV and PentSV will switch context switch from task one to task two and task two will start the execution till the SysTick will again trigger the PentSV to switch the context from task two to again task one and it will be done continuously like this. What would be the execution of task one during this one millisecond of time slice? It will send continuously this task action one so we can see it will be sent as fast as possible because during its time dedicated for this particular task it is trying to execute as much code as possible from its endless loop. So it can be few iterations of this loop or in case of really big task it can be only part of it. What is important is that during the switching of the context the current state of the code executed by the task is saved on the task space task stack so after it will return for execution it will come back to the state when it finished the job before. So this is how it will work without OS delay. And now let's think what would happen if we will use instead of OS delay we will use HAL delay function. So HAL delay function it is again using some delay but in this case it is using the delay of HAL library which is using the time base which should be different from the sys-tick. In our case it is timer six and it is not sending our task to any other state it will still continue within the running mode but doing nothing. So it will be a pure waste. The effect of this would be the following we will have the HAL action sending one for example for task one and then HAL delay 1000 millisecond would mean that we will stay within this loop not doing anything for one second till the sys-tick will switch us to the other task. So as an effect on the terminal window we will see like before task the effect of task one so one, two, one, two but there would be no idle task execution in between. So it is highly not recommended to use HAL delay function within the task because it is really a pure waste and the operating system is doing nothing except of processing the interrupts in fact. Let's check what would be the difference if we change the priority of task one. We will increase it so it will be more important than task two. So let's come back to either STM32-QBmx or to the QBmx perspective within STM32-QB IDE and please select R3 or TLS configuration and within this configuration please come back to task and QSTUB. Then let's double click on task one name. There should be the edit window displayed and increase the priority from OS Priority Normal to for example OS Priority Normal 2. It can be any different but the important is to have higher priority than previous one. Then press OK. After this please regenerate the project and please come back to the tool chain to main.cfile and compile complete project again. We will not do any software modification but up by our own. Once done let's see what is the difference within the execution of the code. For these purposes let's replace OS delay with HAL delay and argument 15ms. It will be used only to not spam the terminal too much. So for task two which has lower priority we are expecting to have visibility of number two and then it will be blocked for 50ms. For task one as it has higher priority we will do some modifications. We will try to send five times our let's say action so task action with argument one and then we will put the task to the block state for one second using OS delay function. Just not to spam terminal too much I will use again the HAL delay with 50ms argument otherwise I would see continuously one one one one one in the terminal which is not nice view. So let's use it for a debug purposes only and let's try to build the code. And as expected the task one will be executed five times and then it will go to the block state due to the call of OS delay function for one second and then there will be a chance for task task two. Task two will be triggered as many times as possible within this remaining one almost one second before task two will be woken up from the block state to the ready state. This is why we use these HAL delay functions just to limit number of the occurrences of the loop of the task two. We could use instead for example idle task which would be executed after but just for the illustration we demonstrate this HAL delay. We can do a slight modification within start task one body function and we can comment out the OS delay line which are let's say sending our task from around to the block state for one second. Let's see what would be the difference. So let's do this change compile the code and run in debug session and as expected if we remove the OS delay within the task body function which has higher priority this task could be executed continuously because it is the only one on the ready list on the highest priority so the scheduler will select it all the time and there would be no chance that task two could be executed within these conditions. Thank you for watching this video.