Typical uC-OS-III Interrupt Service Routine

µC/OS-III requires that interrupt service routines be written in assembly language. However, if a C compiler supports in-line assembly language, the ISR code can be placed directly into a C source file. The pseudo-code for a typical ISR when using µC/OS-III is shown in Listing 9-1.

Listing - ISRs under µC/OS-III (assembly language)
MyISR:                                                            (1) 
    Disable all kernel aware interrupts;                          (2) 
    Save the CPU registers;                                       (3) 
    OSIntNestingCtr++;                                            (4) 
    if (OSIntNestingCtr == 1) {                                   (5) 
        OSTCBCurPtr->StkPtr = Current task's CPU stack pointer register value;
    }
    Clear interrupting device;                                    (6) 
    Re-enable kernel aware interrupts (optional);                 (7) 
    Call user ISR;                                                (8) 
    OSIntExit();                                                  (9) 
    Restore the CPU registers;                                   (10) 
    Return from interrupt;                                       (11) 

(1) As mentioned above, an ISR is typically written in assembly language. MyISR corresponds to the name of the handler that will handle the interrupting device.

(2) It is important that all ‘kernel aware’ interrupts are disabled before going any further. Some processors have interrupts disabled whenever an interrupt handler starts. Others require the user to explicitly disable interrupts as shown here. This step may be tricky if a processor supports different interrupt priority levels. However, there is always a way to solve the problem.

(3) The first thing the interrupt handler must do is save the context of the CPU onto the interrupted task’s stack. On some processors, this occurs automatically. However, on most processors it is important to know how to save the CPU registers onto the task’s stack. You should save the full “context” of the CPU, which may also include Floating-Point Unit (FPU) registers if the CPU used is equipped with an FPU.

Certain CPUs also automatically switch to a special stack just to process interrupts (i.e., an interrupt stack). This is generally beneficial as it avoids using up valuable task stack space. However, for µC/OS-III, the context of the interrupted task needs to be saved onto that task’s stack.

If the processor does not have a dedicated stack pointer to handle ISRs then it is possible to implement one in software. Specifically, upon entering the ISR, simply save the current task stack, switch to a dedicated ISR stack, and when done with the ISR switch back to the task stack. Of course, this means that there is additional code to write, however the benefits are enormous since it is not necessary to allocate extra space on the task stacks to accommodate for worst case interrupt stack usage including interrupt nesting.

(4) Next, either call OSIntEnter(), or simply increment the variable OSIntNestingCtr in assembly language. This is generally quite easy to do and is more efficient than calling OSIntEnter(). As its name implies, OSIntNestingCtr keeps track of the interrupt nesting level.

(5) If this is the first nested interrupt, you need to save the current value of the stack pointer of the interrupted task into its OS_TCB. The global pointer OSTCBCurPtr conveniently points to the interrupted task’s OS_TCB. The very first field in OS_TCB is where the stack pointer needs to be saved. In other words, OSTCBCurPtr->StkPtr happens to be at offset 0 in the OS_TCB (this greatly simplifies assembly language).

(6) At this point, you need to clear the interrupting device so that it does not generate the same interrupt. However, most people defer the clearing of the source and prefer to perform the action within the user ISR handler in “C.”

(7) At this point, it is safe to re-enable kernel aware interrupts if you want to support nested interrupts. This step is optional.

(8) At this point, further processing can be deferred to a C function called from assembly language. This is especially useful if there is a large amount of processing to do in the ISR handler. However, as a general rule, keep the ISRs as short as possible. In fact, it is best to simply signal or send a message to a task and let the task handle the details of servicing the interrupting device.

The ISR must call one of the following functions: OSSemPost()OSTaskSemPost()OSFlagPost()OSQPost() or OSTaskQPost(). This is necessary since the ISR will notify a task, which will service the interrupting device. These are the only functions able to be called from an ISR and they are used to signal or send a message to a task. However, if the ISR does not need to call one of these functions, consider writing the ISR as a “Non Kernel-Aware Interrupt Service Routine,” as described in the next section.

(9) When the ISR completes, you must call OSIntExit() to tell µC/OS-III that the ISR has completed. OSIntExit() simply decrements OSIntNestingCtr and, if OSIntNestingCtr reaches 0, this indicates that the ISR is about to return to task-level code (instead of a previously interrupted ISR). µC/OS-III will need to determine whether there is a higher priority task that needs to run because of one of the nested ISRs. In other words, the ISR might have signaled or sent a message to a higher- priority task waiting for this signal or message. In this case, µC/OS-III will context switch to this higher priority task instead of returning to the interrupted task. In this latter case, OSIntExit() does not actually return, but takes a different path.

(10) If the ISR signaled or sent a message to a lower-priority task than the interrupted task, OSIntExit() returns. This means that the interrupted task is still the highest-priority task to run and it is important to restore the previously saved registers.

(11) The ISR performs a return from interrupts and so resumes the interrupted task.

NOTE: From this point on, (1) to (6) will be referred to as the ISR Prologue and (9) to (11) as the ISR Epilogue.