A way to wait for notifications like the vTaskDelayUntil function

Is there a good way to essentially merge the function of xTaskNotifyWait & vTaskDelayUntil? I would like to wait for notifications until the next supposed cycle of my task. I’ve looked into the vTaskDelayUntil code, and I see it is not that complex, but I don’t know if I can fully emulate its behaviour using the xTaskNotifyWait function.

Is it enough to just use taskENTER_CRITICAL, calculate the amount of ticks to wait for & call xTaskNotifyWait?

What about using this (pseudo code)

uint32_t TimeWait = CycleTime / portTICK_PERIOD_MS;
TimeOut_t TimeOut;
for ( ;; )
{
    vTaskSetTimeOutState( &TimeOut );
    xTaskNotifyWait( 0, EventMask, &Event, TimeWait );
    if ( xTaskCheckForTimeOut( &TimeOut, &TimeWait ) )
    {// refresh cylce time ticks on timeout
        TimeWait = CycleTime / portTICK_PERIOD_MS;  
    }
}

according to your needs ?

I don’t think that code does the same as the DelayUntil function. It seems that it would actually run much faster if a notification arrived, and the next loop after that would also time out much faster - as the TimeWait now contains the time left until the next cycle. Thus not guaranteeing a fixed execution time.
Though I guess this could be fixed by adding this to the if statement:

else
{
    vTaskDelay(TimeWait);
}

Though, it is really hard to follow the logic in this implementation & took me a few minutes of research to understand.

edit: maybe I understood the xTaskCheckForTimeOut function wrong, does it actually block the task, if a notification was received before the timeout?

No, xTaskCheckForTimeOut doesn’t block. It’s just timeout evalution.
Maybe I misunderstood your use case…
I thought you want to have a periodic loop but with immediate processing of arriving notifications. Loop timer / period expiration is checked by xTaskCheckForTimeOut regardless if there were some notifications signaled meanwhile.

I guess I should explain it better. Till now, I’ve been using this:

// Used to keep track of this tasks execution time
auto last_wake_time = xTaskGetTickCount();

for ( ;; ) {
	// task code
	...

	// Notification handling
	uint32_t n_bits;
	xTaskNotifyWait(0x0, kTaskAnyBit, &n_bits, 0);

	// process notifications
	...

	// Ensures fixed execution time, based on configured polling interval
	vTaskDelayUntil(&last_wake_time, pdMS_TO_TICKS(CONFIG_POLLING_RATE));
}

Although, this created a delay in which even if a notification arrived, it wasn’t processed until the next cycle. As one of the notification bits is a stop bit, this created a unnecessary delay when stopping the task. Which is why I wanted to optimize this, but I couldn’t find a easy solution as to how.

Well, then what’s wrong with my proposal which provides a fixed polling interval and immediate processing of incoming notifications ?
The xTaskCheckForTimeOut adjusts the TimeWait argument to make xTaskNotifyWait time out accordingly if no notifications arrive.

You are right, after some more head scratching I’ve come up with something that will maybe work. Still just pseudocode that I have to test.

uint32_t TimeWait = pdMS_TO_TICKS(CONFIG_POLLING_RATE);
TimeOut_t TimeOut;

for ( ;; ) {
	// Initialize TimeOut. This records the time at which this cycle was entered.
	vTaskSetTimeOutState( &TimeOut );

	// task code - processes data from buffers, calls user callbacks, etc
	...

	// Notification handling
	uint32_t n_bits;
	// Caculate the maximum allowed time spent waiting for notifications after taking
	// into account the execution time of the above task code
	xTaskCheckForTimeOut( &TimeOut, &TimeWait );

	xTaskNotifyWait(0x0, kTaskAnyBit, &n_bits, TimeWait);

	// process notifications
	...

	if ( ! xTaskCheckForTimeOut( &TimeOut, &TimeWait ) )
	{
		// NotifyWait didn't time out, need to wait for the next cycle
		vTaskDelay(TimeWait);
	}

	// Need to reset the time as it has now definitely expired
	TimeWait = pdMS_TO_TICKS(CONFIG_POLLING_RATE); 
}

The xTaskNotifyWait unfortunately still can’t take into account the time needed to process the notifications, but that should be negligible.

The processing time is covered when processing notification before xTaskCheckForTimeOut.
I’m still confused why you insist on thevTaskDelay to wait for the next cycle ?
The next cycle xTaskNotifyWait waits only the remaining time of the fixed period.
So you know exactly that on xTaskCheckForTimeOut returning true the fixed period has expired.
I’m also not sure about the purpose of the

// task code - processes data from buffers, calls user callbacks, etc

Usually everything is event resp. notification and in this case timeout triggered.
So all event-triggered processing is done after xTaskNotifyWait and ther periodic work after xTaskCheckForTimeOut.

Even thought that is true, I still don’t want the tasks code being executed multiple times per polling cycle, that is why the delay is necessary there. If I say, want 10 cycles per second, receiving notifications would result in that being more than 10.

I need to take into account the execution time of the whole task loop. If the xTaskNotifyWait waits for 100ms, and then the actual code is executed, and takes another 20ms, well, then you no longer have a fixed polling rate.

To explain exactly what the task does, which I refer to as the task code in comments,
it polls a shared memory buffer with another ‘task’ (It’s actually another processor, the ULP on ESP32) and checks if any new events have been written to it. If there are, it processes them, updates some state tables, and calls registered user callbacks (that could take any amount of time, even more than the polling rate would permit). After that, it finally checks the notifications that tell it to suspend, stop, manipulate the buffer somehow, etc - I can’t just stop or suspend the task using vTask... functions, as that could leave the shared memory in a corrupt state.

But the TL;DR of my goal is, I just wanted the task to perform just like it did before with the vTaskDelayUntil, as it was deterministic & reliable.

I understand the issues with my implementation. Multiple sequential notifications cannot be processed in one cycle, but instead always in the next one. I’m fine with that, the polling rate is configurable for exactly this reason.