Task priorities

Dear All,

I’m building my first RTOS with two tasks. One for updating the display and other for reading ADC. I have two timers with interrupts and both interrupts set flag and when this flag is set inside task loop something is done. However in ADC read task which has priority above normal I need to have osDelay(1) otherwise the whole MCU freeze. If I set the ADC task below normal I can delete the delay, but ADC is being read slowly. How should this be tackled so that ADC read task would not freeze MCU but still read at exact timer interrupt events? I even tried to start the timer interrupts inside this task. Here is the ADC read code with the osDelay(1); which is my head ache.

void Start_Task_Read_ADC(void *argument)
{
  /* USER CODE BEGIN Start_Task_Read_ADC */
	HAL_StatusTypeDef ret;
	uint8_t buf[16];
	uint8_t UpperByte;
	uint8_t LowerByte;
	HAL_TIM_Base_Start_IT(&htim16);
	HAL_TIM_Base_Start_IT(&htim17);
	 uint8_t data[2];
	 data[0] = 0b00000010;
	 data[1] = 0b01011000;

	 uint8_t data2[1];
	 data2[0] = 0b00010000;

	ret = HAL_I2C_Master_Transmit(&hi2c3, AD7992_ADDR, data, 2, HAL_MAX_DELAY);
	if ( ret != HAL_OK ) {

	}
	ret = HAL_I2C_Master_Transmit(&hi2c3, AD7992_ADDR, data2, 1, HAL_MAX_DELAY);

  /* Infinite loop */
  for(;;)
  {
    osDelay(1);
    if (FlagForADCRead)
    {
    	// Read ADC values
    	FlagForADCRead = false;
    	buf[0] = 0b00010000;
    	ret = HAL_I2C_Master_Transmit(&hi2c3, AD7992_ADDR, data2, 1, HAL_MAX_DELAY);
    	ret = HAL_I2C_Master_Receive(&hi2c3, AD7992_ADDR, buf, 2, HAL_MAX_DELAY);
    	UpperByte = buf[0];
    	LowerByte = buf[1];
    	UpperByte = UpperByte & 0x1F;
    	val = ((int16_t)UpperByte << 8 ) + LowerByte;
    	oldvalue = oldvalue+((val-oldvalue)>>4);
    }
  }
  /* USER CODE END Start_Task_Read_ADC */
}

Instead of polling a global flag in the task(s) set in an ISR you should signal an event from the timer ISR to the associated task to wake it up doing some work and sleep again.
That’s the preferred way to implement a multi-tasking application. Tasks should usually only run if there is something to do and give up the CPU on idle allowing other tasks to run (if they have something to do).
So in your case I’d use a task notification or a (binary) semaphore signaled in the timer ISR and instead of the osDelay’d polling just wait for the notification or the semaphore being signaled to do the required processing in the task loop.
See https://www.freertos.org/RTOS-task-notifications.html and/or https://www.freertos.org/Embedded-RTOS-Binary-Semaphores.html.
I don’t know how this API is wrapped by ST cube/CMSIS.

Excellent, thank you. I added code to suspend both tasks when not needed. Both tasks are needed only when timer interrupts so I wake them up like this:

  if (htim->Instance == TIM16) {
      // 0,25 second timer
	  BaseType_t checkIfYieldRequired1;
	  checkIfYieldRequired1 = xTaskResumeFromISR(Task_Display_UpHandle);
	  portYIELD_FROM_ISR(checkIfYieldRequired1);
    }
  if (htim->Instance == TIM17) {
      // 0,004 second timer
	  BaseType_t checkIfYieldRequired2;
	  checkIfYieldRequired2 = xTaskResumeFromISR(Task_Read_ADCHandle);
	  portYIELD_FROM_ISR(checkIfYieldRequired2);
    }

Hope this is ok?

It’s a step forward but it’s not the best way. Suspending and resuming tasks sound easy and useful but in practice in almost all cases they’re not.
Key is that this operations are not properly synchronized. For example an interrupt happens and the task is resumed but the task is not yet suspended. The resume then does nothing and fails to signal the event to the task. You’ll miss an interrupt.
Don’t do that. Better use the right tool for the work and that’s a task notification, a semaphore or even a queue. They’re designed for this purpose.
When using for instance a (binary) semaphore and an interrupt occurs while e.g. the ADC task is still busy with the post-processing of the previous event, the semaphore is signaled and remains in that state. So when the ADC task has finished it’s work and comes back to the otherwise blocking xSemaphoreTake call it get’s immediately woken up (in fact it doesn’t fall asleep) because the semaphore was already in the signaled state.

I agree with Hartmut.

I am a fan of structuring applications like this as communicating state machines. I think Miro Samek has some good ideas. See:

Modern Embedded Programming: Beyond the RTOS
State Machines for Event-Driven Systems

for some leads.

In my current project, I have several state machines implemented in the Samek way, each running an event loop as a FreeRTOS Task, receiving messages from a FreeRTOS Queue. This approach is easy, maintainable, and it is easily efficient enough for my purposes.

1 Like

Sorry to still bother on this. I got the binary semaphore working with following. Hopefully it is correctly done:

    //Definitions
    xSemaphoreHandle DisplayUpdate_sem;
    xSemaphoreHandle ADC_sem;

    // Create semaphores
     vSemaphoreCreateBinary(DisplayUpdate_sem);
     vSemaphoreCreateBinary(ADC_sem);
     
     // Display update Task
    void Start_Task_Display_Up(void *argument)
    {
    for(;;)
      {
    	  if (xSemaphoreTake(DisplayUpdate_sem, portMAX_DELAY))
    	  {
    		// Do stuff	  
    	  }
      }
    }

    // ADC Read Task
    void Start_Task_Read_ADC(void *argument)
    {
    for(;;)
      {
    	  if (xSemaphoreTake(ADC_sem, portMAX_DELAY))
    	  {
    		// Do stuff	  
    	  }
      }
    }

    // Timer ISR
    void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
    {  
    	long task_woken = 0;  
      if (htim->Instance == TIM6) {
        HAL_IncTick();
      }
      if (htim->Instance == TIM16) {
          // 0,25 second timer
    	  xSemaphoreGiveFromISR(DisplayUpdate_sem, &task_woken);
        }
      if (htim->Instance == TIM17) {
          // 0,004 second timer	
    	  xSemaphoreGiveFromISR(ADC_sem, &task_woken);
        }
      portYIELD_FROM_ISR(task_woken);
    }

However I got tempted to try the notify task as it is said in documentation that it is not that heavy than Semaphore. However I didn’t get it to work, it just blocked whole MCU. I assume create somehow the notify task, but I didn’t understand how it’s done. I’m not sure either whether it is suitable for two different tasks and different times like this Semaphore is. Anyhow, here is the ripped code:

    #include "task.h"

    static TaskHandle_t xHandlingTask;

    // Display update Task
    void Start_Task_Display_Up(void *argument)
    {
    for(;;)
      {
    	 xTaskNotifyTake(pdFALSE,portMAX_DELAY);
    	// Do something
      }
    }

    // Timer interrupt handler
    void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
    { 
    	if (htim->Instance == TIM17) {
          // 0,004 second timer	
    		xHigherPriorityTaskWoken = pdFALSE;
    		vTaskNotifyGiveFromISR(xHandlingTask, &xHigherPriorityTaskWoken);
    		portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
    	}
    }

What am I missing?

Hello corn,

How about this?

void Start_Task_Display_Up(void *argument)
{
  xHandlingTask = xTaskGetCurrentTaskHandle(); <-- HERE
  for(;;)
    {
       xTaskNotifyTake(pdFALSE,portMAX_DELAY);
       // Do something
    }
}

Best regards,
NoMaY

I’d like to add to @NoMaY-jp’s proposal that you should activate the TIMer or a least its IRQ after initializing the task handle used in the ISR. Otherwise the timer might already fire and the ISR uses an uninitialized task handle…

Hi, unfortunately the
xHandlingTask = xTaskGetCurrentTaskHandle();
didn’t help… Now it doesn’t completely block the MCU but that task never runs…

Strange … it’s correct and should work. I think there is a minor bug somewhere else.
Is the Timer ISR entered and the right task(handle) notified ?
Do you have configASSERT() defined and also stack overflow checking enabled (for development/debugging) ?