You are here
Home > embedded >

How To Write RTOS Safe ISR For ATSAME

In our application it is required to use FreeRTOS API functions from an interrupt service routine(ISR). Good thing about FreeRTOS is it has provided interrupt safe API, meaning one can use FreeRTOS API functions from ISR, as they have provided two versions of some API. Functions intended for use from ISRs have “FromISR” appended to their name eg. xQueueSendFromISR

How to set correct priority levels if I want to call an ISR-safe function from interrupt? Because the maximum level of configKERNEL_INTERRUPT_PRIORITY is 1 and the minimum preemption Priority level of any interrupt source e.g. and edge interrupt is 15. That means it is above the max. level of configKERNEL_INTERRUPT_PRIORITY . In our application we were using xQueueSendFromISR() from IRQ10 ie ‘EIC_10_IRQn’, to send message to other task, code snippet is as below

void sendMsgToIoTaskFromISR(int eventId, int data)
Task_EventMsg_t evtMsg;
BaseType_t xHigherPriorityTaskWoken = pdFALSE;   	// wake up the higher priority task
evtMsg.eventId = eventId;
evtMsg.iData = data;    
if( xQueueSendFromISR( gIo.ioMgrQHandle, ( void * ) &evtMsg, &xHigherPriorityTaskWoken ) != pdPASS )
/* If the task with handle xTaskToNotify was blocked waiting for the notification
then sending the notification will have removed the task from the Blocked
state.  If the task left the Blocked state, and if the priority of the task
is higher than the current Running state task (the task that this interrupt
interrupted), then lHigherPriorityTaskWoken will have been set to pdTRUE
internally within vTaskNotifyGiveFromISR().  Passing pdTRUE into the
portEND_SWITCHING_ISR() macro will result in a context switch being pended to
ensure this interrupt returns directly to the unblocked, higher priority,
task.  Passing pdFALSE into portEND_SWITCHING_ISR() has no effect. */	
portEND_SWITCHING_ISR( xHigherPriorityTaskWoken );

With this it is calling assert using function “portASSERT_IF_INTERRUPT_PRIORITY_INVALID();” when we did some digging it is failing because of the condition ‘ucCurrentPriority >= ucMaxSysCallPriority’ . The reason to fail this condition is ‘ ucCurrentPriority’ is ‘0’ and ucMaxSysCallPriority is 128 (0x80). After reading FreeRTOS documentation on how ISR priorities are set. It is understood that ISR priorities are set to ‘0’ by controller and RTOS expects the priority to be ‘128’ or above, as recommended below:

 Remember that ARM Cortex-M cores use numerically low priority numbers to represent HIGH priority interrupts. This can seem counter-intuitive and is easy to forget! If you wish to assign an interrupt a low priority do NOT assign it a priority of 0 (or other low numeric value) as this will result in the interrupt actually having the highest priority in the system – and therefore potentially make your system crash if this priority is above configMAX_SYSCALL_INTERRUPT_PRIORITY. Also, do not leave interrupt priorities unassigned, as by default they will have a priority of 0 and therefore the highest priority possible.

That is suggestion from FreeRTOS, but how to change priority of interrupts, To solve this issue we have done workaround, probably it might help someone. In ‘hpl_eic.c’ file from “hpl\eic” folder, in function “_ext_irq_init” change the priority of IRQ10 to 128 or more that that. Simply add below line

NVIC_SetPriority(EIC_10_IRQn, NVIC_SIGNAL_PRI_80);

Your new code should look like

NVIC_SetPriority(EIC_10_IRQn, NVIC_SIGNAL_PRI_80);

Compile and test this new code and you will see interrupts are working in your RTOS.

This trick of setting interrupt priority to 0x80 works well for other interrupts as well, like timer interrupt, External interrupts etc. Just make sure that in init function for that I/O you are calling NVIC_SetPriority() function… Happy RTOS safe ISRing..

Further Reading [PDF]Mastering the FreeRTOS™ Real Time Kernel

Leave a Reply