Can't understand how these tasks work

I use STM32 with HAL

Tasks:

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.

If you change the task code:

void Task1(void *argument)
{

  for(;;)
  {
    HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_0);
    for(int i=0; i<10000000; i++);
  }

}



void Task2(void *argument)
{

  for(;;)
  {
    HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_14);
    for(int i=0; i<10000000; i++);
  }

}

Then the blinking of the LEDs is strange.
Example: Sometimes the PB0 LED may turn on first, then turn off first.

Why does it work like this?
I thought the work of these tasks is the same and the LEDs will flash in turn.

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.

In the first lessons of working with FreeRTOS, they often show a picture of the work of tasks.
And the tasks run in sequence.

That’s why I thought the work of the tasks should look like this

How then should I proceed to get the expected result? (without using vDelay)

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.

Read a little about semaphores.

void Task1(void *argument)
{
	int i = 0;

  for(;;)
  {
	int err = xSemaphoreTake(sem1Handle,portMAX_DELAY);
	if (err == pdTRUE)
	{
		HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_0);
		for(i=0; i<10000000; i++);
		xSemaphoreGive(sem2Handle);
	}
  }

}



void Task2(void *argument)
{
	int i = 0;

  for(;;)
  {
	int err = xSemaphoreTake(sem2Handle,portMAX_DELAY);
	if (err == pdTRUE)
	{
		HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_14);
		for(i=0; i<10000000; i++);
		xSemaphoreGive(sem1Handle);
	}

  }

}

It seems to work.
If I understand correctly, it works like this:


This is not what I wanted

Did you offer me to do it?

In my example, the scheduler can only interrupt the task between ticks?

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.

Does that make sense?

You’re right.
Based on this code, I want to understand how to work with tasks (multitasking).