void Task1(void *argument)
{
int i = 0;
for(;;)
{
HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_0);
for(i=0; i<10000000; i++);
}
}
void Task2(void *argument)
{
int i = 0;
for(;;)
{
HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_14);
for(i=0; i<10000000; i++);
}
}
Why does the blinking order of the LEDs start to change over time?
For example: first the PB0 LED lights up first, then the PB14. After a few periods, the PB14 LED starts to light up first.
The first observation is behavior by definition. The two tasks run concurrently and independent of each other. The preemption of the respective tasks is not synced up with their periodic computation, so there is no reason why the blinking pattern should be predictable.
The difference between the two implementations is most likely due to compiler optimization related code differences.
Another lesson is or will be that those for(int i=0; i<10000000; i++); loops will get optimized out because they do nothing (useful) from the compiler point of view. Better use real delay functions such as vTaskDelay(Until).
If you insist to use your own (clock dependent !) delay loops you’ve to e.g. make ‘i’ a volatile variable to avoid that it’s optimized out.
The point you need to understand is that the preemptive scheduler can interrupt a running task at any (almost any, to be more precise) point of time, regardless of where in their respective computations the tasks are. So if you want your tasks to expose a predictable pattern, you need some Form of intertask communication between them.
Using vTaskDelay() - which is the preferred strategy - instead of CPU cycle burning does not address your requirement, even if it may look that way; it is an indirect effect of your tasks yielding the CPU at timer boundary points and only two tasks passing back and forth the baton. As soon as you have a third task with an unpredictable CPU usage pattern or differing flash frequencies, you will see the same behavior.
In your semaphore example, your tasks enforce an order by synchronizing their execution via a sync object (in this case a sempahore). That is perfectly ok, except that if the execution of your tasks is so tightly coupled that you would not need two tasks in the first place, but I am certain that you experiment with this only to learn about multitasking.
In the vTaskDelay() implementation, your delay is implicitly synchronized with the scheduler tick as the parameter to vTaskDelay() is a number of system ticks, so the scheduler (unknowingly) helps in lining up your LED flash periods and scheduling intervals.