Task Notification not working as expected

I’m using Task Notifications to speed up SPI transfers in my system which previously used queues. Surprisingly there was no difference. I have a task (Task1) using SPI bus to update a DAC, this needs to happen as fast as possible, it sends data to the SPI device driver then calls ulNotifyTake(). The SPI device driver initiates the SPI transfer and then the SPI device driver ISR calls vTaskNotifyGiveFromISR() when the SPI transfer is complete. I’m watching the SPI transfers on an oscilloscope and it takes about 10us. In Task1 I set a GPIO bit High just before starting the SPI transfer and then set the GPIO bit Low after ulNotifyTake() returns. Monitoring the GPIO pulse shows it is 20ms which happens to be the length of one tick. I changed the tick rate so the tics are 10ms and then the GPIO pulse is10ms. So I’m puzzled by this because I expected the GPIO pulse to be a little longer than the 10us the SPI transfer takes, because the only tasks are Task1 and the Idle task? Can anyone explain this behavior to me?
Thanks, John

A couple of thoughts:

  1. Are you using the pxHigherPriorityTaskWoken parameter to vTaskNotifyGiveFromISR(). See the example source code on the linked page. If not the context switch won’t occur until the next tick interrupt.
  2. Is the task you are notifying the highest priority task in the system at that time? If not, it won’t run until it is.

Using a trace tool such as Percepio’s Tracealyzer or Segger’s System view would show you what the MCU was executing.

Thanks for your response. Yes I’m using the pxHigherPriorityTaskWoken parameter, and Yes the task I’m notifying is the highest priority task in the system. It appears to me that although I’m calling vTakNotifyGiveFromISR() the context switch doesn’t occur until the next tick, so I’m assuming the Idle task is running (since it is the only other task) until the next tick. I thought that calling vTaskNotifyGiveFromISR() would force a context switch after the ISR exits? With this behavior the fastest I can update the DACs is the tick rate which is too slow.

It’s exactly as you expect and as documented.
I guess you’ve enabled configUSE_PREEMPTION, right ?
BTW Which MCU/FreeRTOS port and version do you use ?

Also, please share your ISR code and FreeRTOSConfig.h.

Yes, configUSE_PREEMPTION = 1, and I’m using Xilinx/AMD ZYNQ with the FreeRTOS port included in the Xilinx Vitus tools which I beleive is version 10.3.1.

Silly question but is *pxHigherPriorityTaskWoken set to pdTRUE (1) by vTaskNotifyGiveFromISR as expected ?

Yes, when I set a breakpoint in the ISR pxHigherPriorityTaskWoken is set to true after vTaskNotiryGiveFromISR() returns.

Does your ISR end with the test of the flag and a call to the yield function? (Or a call to the end ISR macro that does that)

THAT is what causes the context switch.

In other words, do you have the following line at the end of your ISR:

portYIELD_FROM_ISR( xHigherPriorityTaskWoken );

…as shown in the code at the end of https://www.freertos.org/RTOS-Xilinx-Zynq.html#ConfigAndUsage

Yes, I call portYIELD_FROM_ISR( xHigherPriorityTaskWoken ) at the end of the ISR.

As per Gaurav’s request - please post the minimal ISR code that demonstrates the issue, and the FreeRTOSConfig.h. We may need to take a trace.

I think I’ve found the problem, trying to verify it now. I’ll keep you posted. All your help is very much appreciated.

Found the problem (my fault) and got it fixed, everything is working now as expected. Thanks again to everyone who responded.

Glad that you found the issue. It will be useful for others too if you can post your solution too.

Well it’s complicated, but long story short my FreeRTOS tasks are all C++ classes, my hardware drivers are all C++ classes. So a hardware driver that has an ISR has a static ISR function that’s not part of the class that gets passed the this pointer to the class from the interrupt handler. This static ISR uses the this pointer to call the ISR that is a class member function. Unfortunately the FreeRTOS macro portYIELD_FROM_ISR() can’t be used inside the class ISR member function, this macro also uses the FreeRTOS macro port_END_SWITCHING_ISR which accesses the FreeRTOS global variable ulPortYieldRequired which is set depending on the value of xHigherPriorityTaskWoken, but ulPortYieldRequired is not in the namespace of the class ISR. So the problem I had was the way I was calling portYIELD_FROM_ISR(). This call has to be made in the static ISR function not the class member ISR function. Once I resolved this problem everything works beautifully. All the questions raised here by everyone made me realize that portYIELD_FROM_ISR was not getting passed the xHigherPriorityTaskWoken from the class ISR so the task switch was never occurring until the next tick. Again, many thanks to all for leading me down the path to a solution.

I use something similar, and let me make a few comments.

Port_END_SWITCHI_ISR does NOT us a global, but the parameter passed to it, which should not refer to a global, but a local variable specific to that ISR. Sharing a variable between ISRs can cause loss of the need to context switch, as you clear that variable on entry to the ISR.
Second, I make the class member ISR function return the value of it xHigherPriorityTaskWoken flag from inside the isr, and the main ISR function directly called by the interrupt is basically something like:

void UART0_IRQHandler(void) { portEND_SWITCHI_ISR(uart0.isr()); }

UART0_IRQHandler might need a decorator to mark it as an ISR, depending on the processor.

Yep, that’s basically what I’m doing. ulPortYieldRequired is defined in port.c and declared extern in portEND_SWITCHING_ISR. So yes the xHigherPiorityTaskWoken variable is local to each ISR but it is used to update ulPortYieldRequired. So everyone that uses the macro has access to it, that makes it global to me.