views:

454

answers:

4

I'd have some code that needs to be run as the result of a particular interrupt going off.

I don't want to execute it in the context of the interrupt itself but I also don't want it to execute in thread mode.

I would like to run it at a priority that's lower than the high level interrupt that precipitated its running but also a priority that higher than thread level (and some other interrupts as well).

I think I need to use one of the other interrupt handlers.

Which ones are the best to use and what the best way to invoke them?

At the moment I'm planning on just using the interrupt handlers for some peripherals that I'm not using and invoking them by setting bits directly through the NVIC but I was hoping there's a better, more official way.

Thanks,

+1  A: 

Are you using an RTOS? Generally this type of thing would be handled by having a high priority thread that gets signaled to do some work by the interrupt.

If you're not using an RTOS, you only have a few tasks, and the work being kicked off by the interrupt isn't too resource intensive, it might be simplest having your high priority work done in the context of the interrupt handler. If those conditions don't hold, then implementing what you're talking about would be the start of a basic multitasking OS itself. That can be an interesting project in its own right, but if you're looking to just get work done, you might want to consider a simple RTOS.

Since you mentioned some specifics about the work you're doing, here's an overview of how I've handled a similar problem in the past:

For handling received data over a UART one method that I've used when dealing with a simpler system that doesn't have full support for tasking (ie., the tasks are round-robined i na simple while loop) is to have a shared queue for data that's received from the UART. When a UART interrupt fires, the data is read from the UART's RDR (Receive Data Register) and placed in the queue. The trick to deal with this in such a way that the queue pointers aren't corrupted is to carefully make the queue pointers volatile, and make certain that only the interrupt handler modifies the tail pointer and that only the 'foreground' task that's reading data off the queue modified the head pointer. A high-level overview:

  • producer (the UART interrupt handler):

    1. read queue.head and queue.tail into locals;
    2. increment the local tail pointer (not the actual queue.tail pointer). Wrap it to the start of the queue buffer if you've incremented past the end of the queue's buffer.
    3. compare local.tail and local.head - if they're equal, the queue is full, and you'll have to do whatever error handing is appropriate.
    4. otherwise you can write the new data to where local.tail points
    5. only now can you set queue.tail == local.tail
    6. return from the interrupt (or handle other UART related tasks, if appropriate, like reading from a transmit queue)
  • consumer (the foreground 'task')

    1. read queue.head and queue.tail into locals;
    2. if local.head == local.tail the queue is empty; return to let the next task do some work
    3. read the byte pointed to by local.head
    4. increment local.head and wrap it if necessary;
    5. set queue.head = local.head
    6. goto step 1

Make sure that queue.head and queue.tail are volatile (or write these bits in assembly) to make sure there are no sequencing issues.

Now just make sure that your UART received data queue is large enough that it'll hold all the bytes that could be received before the foreground task gets a chance to run. The foreground task needs to pull the data off the queue into it's own buffers to build up the messages to give to the 'message processor' task.

Michael Burr
I'm not using an RTOS.What I've got is a high priority interrupt that goes off every time I receive bytes over serial.It runs at a high (low priority number) priority so that I don't miss bytes.When a complete message has been received then I need to do some work and while I intend for this work to be completely non blocking in nature I don't want to restrict myself to having a maximum amount of time I allow for it (I won't be writing this code).I'm not doing any preemptive multitasking.
Captain NedD
What is the speed of the serial data? Is the rate really so high or other interrupt handlers so badly designed that that the interrupt priority level will make a difference? Why not use DMA transfers to reduce the interrupt frequency or avoid them altogether?
Clifford
We're running at 57600 but will probably move up to 115200. I can't use DMA transfers on the way in because what signals the end of a message is time between bytes exceeding a certain theshold. I've got three tasks that I need to run: 1. a serial message receiver/transmitter 2. a message processor 3. some background heavy lifting . The middle one is a state machine so that's _supposed_ to do nearly no work (just update the state). I'd like them to run in different interrupt levels because missing deadlines at lower levels is less difficult to debug (for me).
Captain NedD
Thanks for the in depth response. What I'm doing at the moment is effectively a queue of depth one. My application is _so_ simple that the decoding of a message, taking of action and encoding a reply happen immediately one after the other. This is a slave on a master slave bus. The kernel I have is just some code that acts as the message receive transmit code.
Captain NedD
@Captain NedD: Sorry, to Micheal; I thought I added my comment to the original question! You can use DMA and periodically poll the transfer count; if it stops increasing, the message is complete. Either way 115200 is 87 us per character; you may be worrying too much about missing characters; especially if the UART has a FIFO.
Clifford
+1  A: 

The "more official way" or rather the conventional method is to use a priority based preemptive multi-tasking scheduler and the 'deferred interrupt handler' pattern.

Clifford
@Clifford: Do you have a link to a description of the 'deferred interrupt pattern'?
simon
+3  A: 

ARM Cortex supports a very special kind of exception called PendSV. It seems that you could use this exception exactly to do your work. Virtually all preemptive RTOSes for ARM Cortex use PendSV to implement the context switch.

To make it work, you need to prioritize PendSV low (write 0xFF to the PRI_14 register in the NVIC). You should also prioritize all IRQs above the PendSV (write lower numbers in the respective priority registers in the NVIC). When you are ready to process the whole message, trigger the PendSV from the high-priority ISR:

*((uint32_t volatile *)0xE000ED04) = 0x10000000; // trigger PendSV

The ARM Cortex CPU will then finish your ISR and all other ISRs that possibly were preempted by it, and eventually it will tail-chain to the PendSV exception. This is where your code for parsing the message should be.

Please note that PendSV could be preempted by other ISRs. This is all fine, but you need to obviously remember to protect all shared resources by a critical section of code (briefly disabling and enabling interrupts). In ARM Cortex, you disable interrupts by executing __asm("cpsid i") and you enable interrupts by __asm("cpsie i"). (Most C compilers provide built-in intrinsic functions or macros for this purpose.)

Miro
Thanks, Miro! I was searching everywhere for information on how to trigger the PendSV interrupt, and this is where I finally found it! Though I suppose if I had read my Cortex M3 r1p1 Technical Reference Manual more carefully I would have found it on the page that describes the Interrupt Control State Register:http://infocenter.arm.com/help/topic/com.arm.doc.ddi0337e/DDI0337E_cortex_m3_r1p1_trm.pdf
David Grayson
From my reading, Almost all RTOS on CortexM3 use the Systick Timer interrupt (45 decimal,2D hex) to run their scheduler. Then the PendSV is the way to initiate a kind of special "syscall"? Hope I understand this.
Warren P
A: 

Check your processor documentation. Some processors will interrupt if you write the bit that you normally have to clear inside the interrupt. I am presently using a SiLabs c8051F344 and in the spec sheet section 9.3.1:

"Software can simulate an interrupt by setting any interrupt-pending flag to logic 1. If interrupts are enabled for the flag, an interrupt request will be generated and the CPU will vector to the ISR address associated with the interrupt-pending flag."

Jim Tshr