How to reduce event delivery time - FreeRTOS

Hi, I have a requirement to perform an operation within 200us of receiving an interrupt. The ISR when triggered will post an event and once the event is delivered to the waiting task ,It works normal when only has one task(taskA) in the system.

When add the other task(taskB,this one is Lower priority,but takes about 5ms deal with some things), there is a delay in this event received by about 0.1~5 microseconds (random).

The TaskA has higher priority than the TaskA, why the TaskA can’t preempt the TaskB?

I am using this on STM32G473(Cortex M4) and have already used portYIELDFROM ISR() after xEventGroupSetBitsFromISR() in the ISR.

Thanks

Assuming this is a typo – and that your delay is currently 0.1 to 5.0 milliseconds not microseconds.

Can you post the ISR contents? Also, what is the value of configTICK_RATE_HZ?

#define configENABLE_FPU                         1
#define configENABLE_MPU                         0

#define configUSE_PREEMPTION                     1
#define configSUPPORT_STATIC_ALLOCATION          1
#define configSUPPORT_DYNAMIC_ALLOCATION         1
#define configUSE_IDLE_HOOK                      0
#define configUSE_TICK_HOOK                      0
#define configCPU_CLOCK_HZ                       ( SystemCoreClock )
#define configTICK_RATE_HZ                       ((TickType_t)1000)
#define configMAX_PRIORITIES                     ( 56 )
#define configMINIMAL_STACK_SIZE                 ((uint16_t)128)
#define configTOTAL_HEAP_SIZE                    ((size_t)20480)
#define configMAX_TASK_NAME_LEN                  ( 16 )
#define configUSE_TRACE_FACILITY                 1
#define configUSE_16_BIT_TICKS                   0
#define configUSE_MUTEXES                        1
#define configQUEUE_REGISTRY_SIZE                8
#define configUSE_RECURSIVE_MUTEXES              1
#define configUSE_COUNTING_SEMAPHORES            1
#define configUSE_PORT_OPTIMISED_TASK_SELECTION  0


ISR contents:

  if(LL_USART_IsActiveFlag_IDLE(USART3))
  {
    LL_USART_ClearFlag_IDLE(USART3);
    READ_REG(USART3->ISR);
    READ_REG(USART3->RDR);

    LL_DMA_DisableChannel(DMA1, LL_DMA_CHANNEL_8);
    LL_USART_DisableDMAReq_RX(USART3);

    Bus1.PhyConnect.RxLen=sizeof(Bus1.PhyConnect.RxBuf)- 
    LL_DMA_GetDataLength(DMA1,LL_DMA_CHANNEL_8);
    uint32_t xResult = osEventFlagsSet(myEvent01Handle, (1<<BUS1_RXED));
   if(xResult )
      ch26pin_toggle();
  }

TaskB contents:

osThreadId_t TaskB_TaskHandle;
const osThreadAttr_t TaskB_Task_attributes = {
  .name = "Bus1_Task",
  .priority = (osPriority_t) osPriorityNormal3,
  .stack_size = 256 * 4
};

void TaskB(void *argument)
{
  /* USER CODE BEGIN 5 */

  uint32_t bus1Flag;

  /* Infinite loop */
  for(;;)
  {
    bus1Flag = osEventFlagsWait(myEvent01Handle, (1<<BUS1_RXED), osFlagsWaitAny, osWaitForever);
    if(bus1Flag & (1<<BUS1_RXED))
    {
      ch25pin_toggle();
      //BusX_Packet_Parse(&Bus1);
    }
  }
  /* USER CODE END 5 */
}

TaskA contents:

osThreadId_t defaultTaskHandle;
const osThreadAttr_t defaultTask_attributes = {
  .name = "defaultTask",
  .priority = (osPriority_t) osPriorityNormal,
  .stack_size = 256 * 4
};

void StartDefaultTask(void *argument)
{
  /* USER CODE BEGIN 5 */
  Bus1TaskInit();
  /* Infinite loop */
  for(;;)
  {
    osDelay(10);
   Attitude_Calculation();//it takes about 5ms,can be Replaced with  **HAL_Delay(5);**
    
  }
  /* USER CODE END 5 */
}

you can see there is a delay betweent CH25 and CH26(A1~A2).

Thanks

What is osEventFlagsSet() doing? Is it a wrapper for a FreeRTOS event group? If so, that is a very inefficient way to unblock a task from an interrupt because event groups are not deterministic as you don’t know how many tasks setting an event bit will unblock. Because it’s not deterministic, FreeRTOS won’t perform the action in the task ISR itself, but instead defer the operation to the timer task. The configuration constants you show don’t include the timer task priority - but no matter what it is - the timer task will run before the task you are trying to unblock. Using a direct to task notification will be much faster.

Hi rtel,

it is defined in the cmsis_os2.c:

uint32_t osEventFlagsSet (osEventFlagsId_t ef_id, uint32_t flags) {
EventGroupHandle_t hEventGroup = (EventGroupHandle_t)ef_id;
uint32_t rflags;
BaseType_t yield;

if ((hEventGroup == NULL) || ((flags & EVENT_FLAGS_INVALID_BITS) != 0U)) {
rflags = (uint32_t)osErrorParameter;
}
else if (IS_IRQ()) {
#if (configUSE_OS2_EVENTFLAGS_FROM_ISR == 0)
(void)yield;
/* Enable timers and xTimerPendFunctionCall function to support osEventFlagsSet from ISR */
rflags = (uint32_t)osErrorResource;
#else
yield = pdFALSE;

if (xEventGroupSetBitsFromISR (hEventGroup, (EventBits_t)flags, &yield) == pdFAIL) {
  rflags = (uint32_t)osErrorResource;
} else {
  rflags = flags;
  portYIELD_FROM_ISR (yield);
}

#endif
}
else {
rflags = xEventGroupSetBits (hEventGroup, (EventBits_t)flags);
}

return (rflags);
}

It appears that the timer task priority is set lower than osPriorityNormal (24), so the timer task must wait for task A to finish Attitude_Calculation() – a 5ms operation. Only then can task B wake up, even though it has the highest priority of all.

As Richard suggests, using direct-to-task notification would resolve all of this, and your synchronization latency will end up even shorter than if you increase the priority of the timer task and stay with event groups.

Thank you !
it works as expected when set the timer task priority = 40.The delay is less than 30us.

i do a test with the notify, it still has a delay from 10us~1ms.

As Richard explained task notifications are the fastest way to signal an event to a task providing lowest latency and jitter. Do you properly use them following the example in the API docs ?
Especially using the xHigherPriorityTaskWoken flag ?

BaseType_t xHigherPriorityTaskWoken = pdFALSE;
vTaskNotifyGiveIndexedFromISR( xTaskToNotify, 0, &xHigherPriorityTaskWoken );
portYIELD_FROM_ISR( xHigherPriorityTaskWoken );

See also FreeRTOS task notifications, fast Real Time Operating System (RTOS) event mechanism for some common signaling mechanisms realized with task notifications.

One note about the event groups:

Event groups can be used without FreeRTOS timers (configUSE_TIMERS). One example from the FreeRTOS+TCP library: when an API waits for the IP-task, the function xEventGroupWaitBits() is called. When the IP-task has an answer, it will call xEventGroupSetBits() to wake up to signal user task.

Only when “FromISR” event group functions are called, the FreeRTOS timers feature (configUSE_TIMERS) might be needed.

Thank you for reminding me.

i used the API: void vTaskNotifyGiveFromISR( TaskHandle_t xTaskToNotify, BaseType_t *pxHigherPriorityTaskWoken )

So did it fix the excessive context switch time of 1 ms as expected when using task notifications ?

no, the 1ms still there.i will do more test about that.

Hi Wade, everybody,

I face the same issue.

Seems like notification is somehow synced to the systick.

Did you find something out about that?

Simon

No, it’s not. I think the problem is somewhere else…

Dear Hartmut,

I increased SysTick to 10khz and the unwanted “sync to systick” (sympthom description, not description of found and understood error) reduced to 100us.

This is my config, can you see something missconfigured that explains the behaviour?

#define configUSE_PREEMPTION 1
#define configUSE_IDLE_HOOK 1
#define configUSE_TICK_HOOK 1
#define configCPU_CLOCK_HZ (SystemCoreClock)
#define configTICK_RATE_HZ ((TickType_t)10000)
#define configMAX_TASK_NAME_LEN (16)
#define configUSE_TRACE_FACILITY 0
#define configUSE_16_BIT_TICKS 0
#define configIDLE_SHOULD_YIELD 1
#define configUSE_MUTEXES 0
#define configQUEUE_REGISTRY_SIZE 8
#define configCHECK_FOR_STACK_OVERFLOW 0
#define configUSE_RECURSIVE_MUTEXES 0
#define configUSE_MALLOC_FAILED_HOOK 0
#define configUSE_APPLICATION_TASK_TAG 0
#define configUSE_COUNTING_SEMAPHORES 1
#define configGENERATE_RUN_TIME_STATS 0

Given you have a similar scenario and use correctly pxHigherPriorityTaskWoken with vTaskNotifyGiveFromISR AND the notified task is waiting for a notification AND has the highest prio, it gets scheduled as soons as the ISR is left and no other ISR is running, of course.
If there is another task with the same prio currently running (owning the CPU) there can be a latency until the next systick when the scheduler selects the other (the previously notified) task with equal prio. That’s how the preemptive scheduler works (as documented).
Since you’ve enabled the TICK and IDLE_HOOK there might be something else int the respective hook functions causing the behaviour mentioned.

1 Like

it takes 37us~1000us randomly when use the notify(SysTick 1khz) with the API:vTaskNotifyGiveFromISR(xTaskToNotify, false);

when use those:

BaseType_t xHigherPriorityTaskWoken = pdFALSE;
vTaskNotifyGiveIndexedFromISR( xTaskToNotify, 0, &xHigherPriorityTaskWoken );
portYIELD_FROM_ISR( xHigherPriorityTaskWoken );

it takes less than 25us

1 Like

Thanks for insights. Its 8.3us on my device, if priority of taking task is highest.

If the taking task is not highest-rated priority, it is synced to systick. Can this be changed? I mean, CPU is ideling around while a notification is pending…

Simon

This can only happen if your application task has the same prio as the idle task i.e. the lowest possible prio. Then the scheduler schedules idle task and your application task in a round robin/time sliced scheme. This is not really the idea of the idle task which should be the absolute lowest prio task and hence only run if there is really nothing else to do.
So better start at lowest/idle prio +1 with your application task priorities.