CORTEX-M0 Interrupt latency

dmarples wrote on Monday, February 23, 2015:

Folks,

I am trying to handle an interrupt with very low latency on a CORTEX-M0 (NXP LPC11C24). To do this I don’t let FreeRTOS get in the way, nor do I call any FreeRTOS routines from the interrupt. Most of the time it is working well, but every now and again (3-4 times per second in my specific use case) I see delays of anything up to 20uS in interrupt response.

The interrupt is fired from a timer match, and I have tried various priorities (at the moment it’s set as NVIC_SetPriority(TIMER_32_0_IRQn,0) which, I hope, will give me the highest priority/pre-emption available).

The problem is much worse when I have configTICK_RATE_HZ set to a high value (e.g. 1000), and much better when I have it set lower (100)…but it’s still present in all conditions.

I’m pretty sure the problem occurs because FreeRTOS is doing its housekeeping in response to its tick, but I would expect my high priority interrupt to take precidence…it isn’t :-/ This is obviously sticky fingers and I am not understanding something about CORTEX-M0 interrupt configuration - does the M0 loose the prioritisation and pre-emption features of the larger CORTII, or am I neglecting to perform some incantation thats needed to make it fly?

Thanks in advance for help or advice

DAVE

richard_damon wrote on Monday, February 23, 2015:

The ARM series of processors (Including the M0) do not naturally nest interrupts, so a higher priority interrupt does not interrupt a lower priority interrupt unless the software goes to special care to save state allow for nesting. Interrupt priority affects which interrupt will get serviced first if multiple are pending when they are being serviced.

What the ARM does provide (I think even in the M0) is an extra layer of interrupts called the “Fast Interrupt” which CAN interrupt the normal Vectored Interrupt.It sounds like if you configure your timer to generate the fast interrupt, it would meet your needs.

rtel wrote on Monday, February 23, 2015:

Unlike the older ARM7 and ARM9 parts, the Cortex-M parts do naturally
nest interrupts. In the FreeRTOS Cortex-M3/4/7 ports there are some
interrupt priorities that are never effected by the RTOS, but those
interrupt priorities cannot use FreeRTOS API function. On the
Cortex-M3/4/7 this is achieved by using the BASEPRI register to only
mask a subset of interrupts. However, the Cortex-M0 is much simpler
than its bigger 3/4/7 brothers and does not have a BASEPRI register, so
instead has to use the global interrupt mask - therefore on the M0
critical sections do effect all interrupts.

That information was not clear on this page…

http://www.freertos.org/RTOS-Cortex-M3-M4.html

…which was written before the M0 was introduced. I have just added a
note to this effect at the top of the page.

Regards.

dmarples wrote on Monday, February 23, 2015:

Richard,

Ah, so I’m out of luck trying to do this on an M0 then? It certainly explains the behaviour I see and it would stop me being able to do this particular trick on a low end CPU…oh well, back to seperate CPU and CAN transceivers again then I guess :slight_smile:

However, this does mean I don’t understand the purpose of the priority registers in the M0. If I look at the LPC11 series manual (Pg 69 of UM10398 if anyone is interested, register description on Pg 505) it states that the CPU has 4 programmable interrupt priority levels with hardware priority level masking…and its retained the tail chaining and other nice features from its big brothers.

So, before I go off hacking the portability layer, am I missing something here, or does this mean that the M0 port could be made to respect these priorites? This doesn’t seem to be a NXP specific feature, as I see the same material in Section 4.2.6 of the Cortex-M0 Generic User Guide (although obviously the number of priority bits implemented is manufacturer specific).

Thanks for the super-quick reply btw.

Regards

DAVE

davedoors wrote on Monday, February 23, 2015:

The M0 does have different interrupt priorities and interrupts do nest according to their priority. Richard is talking about critical sections which mask all interrupts. That doesnt prevent interrupts nesting when you are not in a critical section, but does mean all interrupts will experience some jitter in timing.

One solution for you would be to change the critical nesting functions so they disable individual peripheral interrupts that are used in your program rather than global interrupts. The standard M0 port cant do that because it doesnt know which interrupts are going to be used.

dmarples wrote on Monday, February 23, 2015:

Dave,

(Too many Daves and Richards in this thread).

So, if I hack the portability layer to basically mask/unmask different priroties then I should get what I need? Of course the OS needs a critical section from its perspective, but (apart from timing) I’ve deliberately kept my time critical task completely away from any OS-affecting stuff…at the moment the M0 port seems to use cpsid/cpsie instructions to create critical sections (it also sets/clears PRIMASK), if I modify that code to give me an ‘unmaskable’ high priority interrupt then I should get what I need, shouldn’t I?

Sorry to ask so many questions, but I’ve been down these rabbit holes before, and there’s nothing worse than coming back out of one and someone who’s already suffered the pain saying “Oh, well, of course that couldn’t work”. I’m not afraid of doing the work, but I’d rather get input from others that this is a sensible thing to do before embarking on it (and I do recognise that by default all M0 interrupts are set to have the higest priority, so there’s a bit of housekeeping to be done in the startup code too for this to fly).

Regards

DAE

rtel wrote on Monday, February 23, 2015:

I think the idea is thus: If you wanted to leave some interrupts
enabled then you would have to make a list of all the interrupts in use
by the system (including the tick interrupt, which uses the systick
timer), then implement an application specific enter/exit critical
function that disabled just those specific interrupts by writing to the
interrupt enable bits in the peripherals - leaving global interrupts
enabled.

Depending on the number of interrupts in use by your system that could
potentially become quite a long function - which is not good.

Regards.