Let an interrupt trigger a task

Hi all,

I’m building a smart kitchen timer with an graphical lcd on it.
I have one task that is counting down the time if it is greater then zero seconds and display it on the lcd. This task is obviously running every second.

When I rotate a rotary encoder knob, it triggers an interrupt on every rotary tick and increment/decrement my timer value. I’m planning to use a semaphore for this.
I want to keep my interrupts lightweight and only change my timer value.

The only issue is that I don’t see instant time updates on the lcd when rotating the knob.
Because my task is refreshing the screen every second and not every rotary tick.

Is there a way to trigger my task on every rotary tick? And if there is no rotary tick that it will continue running every second?

Yes. You could a FreeRTOS auto-reload timer (set for one second) and direct task notifications.

Your timer callback function can notify your task, and your knob ISR could notify your task too. See xTaskNotify(), xTaskNotifyFromISR(), and xTaskNotifyWait().

How much work you do in the ISR (restart the timer to synchronize the seconds to the knob turn, change the value to display) versus how much you defer to the task is up to you. Same goes for the work done in the timer callback (decrement the value to display) versus deferring that to the task. But it may work best to defer everything so the task handles any races between the timer expiration and the knob. If you decide you want your task to know the specific reason it is waking up (timer elapsed vs knob turn), you can use the notification value (not just the notification status). See eNotifyAction eSetBits.

One nice thing about your project is many of the race conditions involved are likely harmless because they merely induce an “extra” display painting. No harm done there. However you may find some of the more dangerous races are eliminated by making the OS daemon task (the timer task) the highest priority task.

I would have the update function wait on a semaphore. Have a timer give that semaphore once a second (and maybe even update the counter down), and any other update function also give the semaphore (after adjusting the timer). The timer changes should probably be in a critical section.

I didn’t know there are software timers in FreeRTOS :slight_smile:
I have to say I’m running this on a 8bit Atmega, so I have to be careful with resources.

My task that is counting down and updates the time on the screen has a vTaskDelayUntil(1second) but it is better to work with software timers?

Not sure if timers functionality consumes a lot of RAM space?

It is basically only an user experience issue, when I rotate the knob I want to see the time change on the screen instantly, faster then the vTaskDelayUntil(1second). Otherwise it is hard for the user to precisely adjust the time.

Individual timers don’t use much in the way of resources, but enabling them at all basically costs 1 task. They do make many things a lot simpler, and often having them can eliminate the need for some task, causing a net savings.

If that cost is to high, then an alternative is rather than having a timer give the semaphore every second, just before you wait on the semaphore you compute the time till the next second a limit you wait to being that long. This does say there is a small race window between the computation and getting into the semaphore wait, that if time passes (either the tick happens just then or you get preempted and a tick occurs) when the wait time get offset. If you program so that this sort of error can’t accumulate, and it limited in magnitude at any given instance, this can be made to work.

I build the xTaskNotifyFromISR() , and xTaskNotifyWait() structure, but the weird thing is that my task is executed only once.

So I have a 1 sec software timer paused:

TimerHandle_t timerHndl1Sec;

timerHndl1Sec = xTimerCreate(	"timer1Sec", /* name */
								pdMS_TO_TICKS(1000), /* period/time */
								pdTRUE, /* auto reload */
								(void*)0, /* timer ID */
								vTimerCallback1SecExpired); /* callback */

static void vTimerCallback1SecExpired(TimerHandle_t pxTimer) {
	xTaskNotify( TaskLcdTimerHandle, 0, eNoAction);

And my rotary ISR looks like this:

if(PORTC_IN & (PIN1_bm)) {
} else {

if(PORTC_IN & (PIN3_bm)) {
} else {


xTaskNotifyFromISR( TaskLcdTimerHandle, 0, eNoAction, NULL ); 


So at startup my vTimerCallback1SecExpired() isn’t running yet.
As soon as I rotate the rotary knob, my task is notified and executed only once and starts the software timer.
I hear the beep_alarm(); inside vTimerCallback1SecExpired() so software timer is running, but my task isn’t doing anything.

My task looks like this:

static void tsk_lcdtimer(void *pvParameters)
while (1) {
	xTaskNotifyWait(  0xffffffff ,      
                      0xffffffff , 
                     portMAX_DELAY );  /* Block indefinitely. */
	if(rotary_getPosition() > 0)
		xTimerStart(timerHndl1Sec, 0);
	} else {
		xTimerStop(timerHndl1Sec, 0);
	printf("LCD:%u\n", uxTaskGetStackHighWaterMark( NULL ));
    //xTaskNotifyStateClear( NULL );

Does xTaskNotifyWait() mark the task state non-pending automatically?

Would you please break in debugger and see what the code is doing? Try removing the printf call as it may not be thread safe.

You should also update xTaskNotifyFromISR to use the last parameter xHigherPriorityTaskWoken to ensure that ISR returns to the highest priority task. Here is the documentation about how to do it: https://www.freertos.org/xTaskNotifyFromISR.html


It goes once through my task, after that I paused/run a couple of times but it stays at static portTASK_FUNCTION(prvIdleTask, pvParameters)

portYIELD_FROM_ISR() does not exist for the 8 bit AVR, so how to do this correctly?

Yes, xTaskNotifyWait() clears the notification state when it returns.

Any ideas how I can build a proper portYIELD_FROM_ISR() on a 8 bit AVR?
It might be the issue maybe?

Not sure why xTaskNotifyWait() only listens once to a notify

I don’t think that’s the problem. Your task would just get control at the next tick.

What port are you using?

Does your timer callback function execute once per second when it is supposed to?

I’m running Atmega4808 and took the atmega4809 port

Yes the weird thing is that I hear the alarm beep function inside my timer callback, so it should fire TaskNotify

Once per second, right on time?

Yes by default my timer is off.
I turn the rotary knob which triggers the interrupt.
The interrupt increments my rotary value and sends a task notify.
The task notify is handled, because rotary_getPosition() is bigger then 0 the timer is started.
I hear the buzzer beep every seconds, but my task isn’t executed anymore


To summarize, the idle task is running nearly continuously. The timer task runs once per second to execute your callback function. And your task is in the ready state but never runs.

Something isn’t adding up here, so it could be something very basic. Do you have the malloc failed hook installed? Is stack-overflow checking enabled with a hook installed? Is configASSERT() defined?

Are you using the right port? I noticed a call to portYIELD_FROM_ISR() in this ATMega4809 demo: https://github.com/FreeRTOS/FreeRTOS/blob/7e1e35e179508e7e10ced38649bf98488e19bbce/FreeRTOS/Demo/AVR_ATMega4809_Atmel_Studio/RTOSDemo/serial/serial.c#L131
But I don’t know anything about AVR so I don’t know what port that demo uses.

In the FreeRTOS distribution, what folder has the port files you’re using?

Can it be something with nested interrupts ?
Because I stripped down my task as follows:

static void tsk_lcdtimer(void *pvParameters)
 while (1) {
	ulTaskNotifyTake( pdTRUE, portMAX_DELAY );

When I rotate the rotary knob I hear the short beep:

void alarm_middle(void)

But after a couple of fast turns, the program stuck.

I have Atmega4809 folder inside my portable folder. I think it is the same for 4808?

I don’t think there’s a nested interrupt issue.

I’m still not sure you’re using the right port. This FreeRTOS page indicates you should be using the port for ATMega-0, which is under portable[compiler]\AVR_Mega0. Note that the AVR_Mega0 port does provide a portYIELD_FROM_ISR() macro.

The port you reference (Atmega4809) doesn’t appear in any portable directory in the official FreeRTOS distribution as far as I can see. So I’m concerned about its origins. Again, full disclosure, I don’t use AVRs.

Again, be sure to turn on automatic stack-overflow checking (recommend method 2) and malloc-failure catching. And be sure to define configASSERT(). These basic steps can save you from some tricky debugging.

Great tips
I followed this microchip manual:

They say:
Starting from Atmel | START
The easiest way to create a FreeRTOS application is to download an example from Atmel | START and
work from there. This ensures that all FreeRTOS files will be included and working correctly. The demo
code can then be removed, and the application development can begin.

So that is what I did, but I’m becoming less and less exited by those startup wizards.
I took the Atmega4809 demo and stripped the code out of it.
I got warnings from my compiler about implicit declaration of portYIELD_FROM_ISR() somehow it can not find portmacro.h

I think I’m better of trying to implement freertos myself with the mega 0 series port you mentioned.

I just noticed the official Mega-0 port is new to the 10.4.0 release, so that may explain why Atmel was providing their own port or adaptation.

As for the preprocessor not being able to find portYIELD_FROM_ISR(), maybe the Atmel Start tool missed configuring that one include directory. It’s a little bit obscure as far as include directories go, especially with the target specific name “Atmega4809” in it.

If you do want to find a new starting point (not from Atmel), the FreeRTOS '4809 demo I linked earlier would make a great starting point. It’s actually quite challenging to start from scratch, so if it were me I would probably use the FreeRTOS demo.

Yes this is new code from Microchip. If it works the same way as the original port then there is no interrupt nesting and no need for a yield from isr function as the interrupt entry and exit macros perform that operation for you (if I recall correctly!).