Interrupts in the Nucleus SE RTOS

All modern microprocessors and microcontrollers have interrupt facilities of some sort. This capability is essential to provide the responsiveness required for many applications. Of course, being responsive and predictable is also a key objective behind the use of a real-time operating system, so the two topics do potentially conflict slightly. Using interrupts might compromise the real-time integrity of the OS. This subject, and the resolution of this conflict, was covered in an earlier article. Here, we will look at the interrupt handling strategy employed with Nucleus SE.

In all cases, interrupts are not controlled by Nucleus SE – they are processed when they occur according to priority and vectoring in the usual way. Their execution time is simply “stolen” from that available to run the mainline application code and the scheduler. Clearly this implies that all interrupt service routines should be simple, short and fast.

Native and Managed Interrupts

Nucleus SE does offer two ways to handle interrupts: “Native” interrupt service routines are nothing special and have somewhat limited opportunity to interact with the OS (at least they do when the priority scheduler is selected); “Managed” interrupt service routines have a much wider range of API calls that may be made.

By means of some entry/exit macros, an interrupt service routine used with a Nucleus SE application may be designated to be Native or Managed.

Native Interrupts

Nucleus SE native interrupts are standard interrupt service routines – you can think of them as “unmanaged”. They would typically be used when am interrupt may occur with high frequency and requires servicing with very low overhead. The routine is most likely to be coded in C, as many modern embedded compilers support the writing of interrupt service routines by means of the interrupt keyword. The only context information saved is that which the compiler deems necessary. This leads to significant limitations in what operations a native interrupt routine can perform, as we shall see shortly.

To construct a Nucleus SE native interrupt service routine, simply write the ISR in the usual way, including a call to the macro NUSE_NISR_Enter() at the beginning and a call to NUSE_NISR_Exit() at the end. These macros are defined in nuse_types.h and simply set the global variable NUSE_Task_State to NUSE_NISR_CONTEXT .

Managed Interrupts

If you need greater flexibility in what operations can be performed by an ISR, a Nucleus SE managed interrupt may be the solution. The key difference from a native interrupt is the context save. Instead of just allowing the compiler to stack a few registers, a managed interrupt saves the complete task context (in its context block) on entry. The current task’s context is then loaded from its context block at the end. This accommodates the possibility that the current task may be changed by the operation of the ISR code; this is entirely possible when the priority scheduler is in use. A complete description of Nucleus SE context save and restore was included in an earlier article.

Clearly the complete context save represents a higher overhead than the stacking of a few registers performed by a native interrupt. This is the price for extra flexibility and the reason why there is a choice of approaches to handling interrupts.

A managed interrupt is constructed using the macro NUSE_MANAGED_ISR() , which is defined in nuse_types.h . This macro constructs a function which contains the following sequence:

  • task context save

  • set NUSE_Task_State to NUSE_MISR_CONTEXT

  • call user-supplied ISR code function

  • restore NUSE_Task_State to previous setting

  • task context restore

The macro takes two parameters: a name for the interrupt, which is used as the function name for the constructed routine; the name of the function containing the user-supplied ISR logic.

The Nucleus SE real time clock ISR, which is described later in this article, serves as an example of a managed ISR.

API Calls from Interrupt Service Routines

The range of API functions that may be called from a native or managed ISR depends on which scheduler has been selected. Broadly, the use of the priority scheduler provides many opportunities for the scheduler to be invoked as a result of an API function call, which would be a problem in a native ISR.

API Calls from a Native ISR with Priority Scheduler

A limited range of API function calls are permitted from a native ISR with the priority scheduler. This limitation is the result of the flexibility of the Nucleus SE API – many calls can result in a task being made ready and it would not be possible for the scheduler to be called by a native ISR (as the task context is not saved). There is greater flexibility if task blocking is not enabled.

The following API calls are always permitted:

NUSE_Task_Current()
NUSE_Task_Check_Stack()
NUSE_Task_Information()
NUSE_Task_Count()
NUSE_Partition_Pool_Information()
NUSE_Partition_Pool_Count()
NUSE_Mailbox_Information()
NUSE_Mailbox_Count()
NUSE_Queue_Information()
NUSE_Queue_Count()
NUSE_Pipe_Information()
NUSE_Pipe_Count()
NUSE_Semaphore_Information()
NUSE_Semaphore_Count()
NUSE_Event_Group_Information()
NUSE_Event_Group_Count()
NUSE_Signals_Send()
NUSE_Timer_Control()
NUSE_Timer_Get_Remaining()
NUSE_Timer_Reset()
NUSE_Timer_Information()
NUSE_Timer_Count()
NUSE_Clock_Set()
NUSE_Clock_Retrieve()
NUSE_Release_Information()
 

However, the only one that is really useful is NUSE_Signals_Send() , as this provides a good way to indicate to a task that some work is required.

If blocking is disabled, which means that a task may not be made ready by many API calls, a number of additional API functions are available:

NUSE_Partition_Allocate()
NUSE_Partition_Deallocate()
NUSE_Mailbox_Send()
NUSE_Mailbox_Receive()
NUSE_Mailbox_Reset()
NUSE_Queue_Send()
NUSE_Queue_Receive()
NUSE_Queue_Jam()
NUSE_Queue_Reset()
NUSE_Pipe_Send()
NUSE_Pipe_Receive()
NUSE_Pipe_Jam()
NUSE_Pipe_Reset()
NUSE_Semaphore_Obtain()
NUSE_Semaphore_Release()
NUSE_Semaphore_Reset()
NUSE_Event_Group_Set()
NUSE_Event_Group_Retrieve()

Certain API calls are never allowed from native ISRs, as they inevitably require the scheduler to operate:

NUSE_Task_Suspend()
NUSE_Task_Resume()
NUSE_Task_Sleep()
NUSE_Task_Relinquish()
NUSE_Task_Reset()
NUSE_Signals_Receive()

API Calls from a Managed ISR or Native ISR with Non-priority Scheduler

A much wider range of API functions may be called from an ISR when the run to completion, round robin or time sliced scheduler is in use. If the priority scheduler is used, a managed ISR facilitates a similar wide range. This is because calls that may result in a different task being scheduled are permitted. This capability is facilitated by code in NUSE_Reschedule() that detects that the context of the call is an ISR and suppresses the context switch (allowing it to occur at the end of the ISR). Full details of the scheduler operation were covered in an earlier article.

A key requirement is that API calls within an ISR must not result in suspension of the current task – waiting upon a resource, for example. In other words, such calls should be made with the suspend option set to NUSE_NO_SUSPEND .

Given this proviso, the following API calls may be used:

NUSE_Task_Current()
NUSE_Task_Check_Stack()
NUSE_Task_Information()
NUSE_Task_Count()
NUSE_Task_Suspend()
NUSE_Task_Resume()
NUSE_Task_Reset()
NUSE_Partition_Allocate()
NUSE_Partition_Deallocate()
NUSE_Partition_Pool_Information()
NUSE_Partition_Pool_Count()
NUSE_Mailbox_Send()
NUSE_Mailbox_Receive()
NUSE_Mailbox_Reset()
NUSE_Mailbox_Information()
NUSE_Mailbox_Count()
NUSE_Queue_Send()
NUSE_Queue_Receive()
NUSE_Queue_Jam()
NUSE_Queue_Reset()
NUSE_Queue_Information()
NUSE_Queue_Count()
NUSE_Pipe_Send()
NUSE_Pipe_Receive()
NUSE_Pipe_Jam()
NUSE_Pipe_Reset()
NUSE_Pipe_Information()
NUSE_Pipe_Count()
NUSE_Semaphore_Obtain()
NUSE_Semaphore_Release()
NUSE_Semaphore_Reset()
NUSE_Semaphore_Information()
NUSE_Semaphore_Count()
NUSE_Event_Group_Set()
NUSE_Event_Group_Retrieve()
NUSE_Event_Group_Information()
NUSE_Event_Group_Count()
NUSE_Signals_Send()
NUSE_Timer_Control()
NUSE_Timer_Get_Remaining()
NUSE_Timer_Reset()
NUSE_Timer_Information()
NUSE_Timer_Count()
NUSE_Clock_Set()
NUSE_Clock_Retrieve()
NUSE_Release_Information()

A few API calls are never permitted, as they appertain specifically to the current task:

NUSE_Task_Relinquish()
NUSE_Signals_Receive()
NUSE_Task_Sleep()

Real Time Clock ISR

The real time clock (RTC) ISR is the only complete interrupt service routine provided with Nucleus SE. Apart from providing all the required timing functionality for Nucleus SE, it also serves as an example of how to code a managed interrupt.

RTC ISR Operations

The facilities provided by the RTC ISR were outlined in an earlier, which covered the broad topic of system time in Nucleus SE. All of the functionality is optional, depending on how the application is configured. Here is the complete code for the RTC ISR.

#if NUSE_TIMER_NUMBER != 0
{
      U8 timer;
      for (timer=0; timer      {
            if (NUSE_Timer_Status[timer])
            {
                      if (–NUSE_Timer_Value[timer] == 0)
                  {
                        NUSE_Timer_Expirations_Counter[timer]++;
                        #if NUSE_TIMER_EXPIRATION_ROUTINE_SUPPORT ||
                            NUSE_INCLUDE_EVERYTHING
                           if (NUSE_Timer_Expiration_Routine_Address[timer]
                               != NULL)
                           {
                              ((PF1)NUSE_Timer_Expiration_Routine_Address[timer])
                               NUSE_Timer_Expiration_Routine_Parameter[timer]);
                           }
                        #endif
                        /* reschedule? */
                            if (NUSE_Timer_Reschedule_Time[timer] != 0)
                            {                    /* yes: set up time */
                              NUSE_Timer_Value[timer] =
                                 NUSE_Timer_Reschedule_Time[timer];
                        }
                        else
                        {                    /* no: disable */
                              NUSE_Timer_Status[timer] = FALSE;                              
                        }
                  }
            }
      }
}
#endif
#if NUSE_SYSTEM_TIME_SUPPORT || NUSE_INCLUDE_EVERYTHING
   NUSE_Tick_Clock++;
#endif
#if NUSE_TASK_SLEEP || NUSE_INCLUDE_EVERYTHING
{
      U8 task;
      for (task=0; task      {
            if (NUSE_Task_Timeout_Counter[task] != 0)
            {
                  NUSE_Task_Timeout_Counter[task]–;
                  if (NUSE_Task_Timeout_Counter[task] == 0)
                  {
                        NUSE_Wake_Task(task);
                  }
            }
      }
}
#endif
#if NUSE_SCHEDULER_TYPE == NUSE_TIME_SLICE_SCHEDULER
    if (–NUSE_Time_Slice_Ticks == 0)
    {
          NUSE_Reschedule();
    }
#endif

We will look at each of the four areas of functionality in the RTC ISR:

Timers

If any application timers are configured, the ISR loops around to service each one by decrementing its counter value. If a timer expires (i.e. the counter reaches zero), two actions are effected:

  • If timer expiration routines are configured and the timer has a valid (not NULL ) pointer to a function (in the NUSE_Timer_Expiration_Routine_Address[] ), the routine is executed, receiving a parameter from NUSE_Timer_Expiration_Routine_Parameter[] .

  • If the timer has a reschedule time (i.e. a non-zero value in NUSE_Timer_Reschedule_Time[] ) the timer is reloaded with that value.

Application timers were described in more detail in a previous article.

System Clock

If a system clock is configured, the value of NUSE_Tick_Clock is simply incremented. Further discussion on system time may be found in a previous article.

Task Sleep

If task sleep is enabled (i.e. the API call NUSE_Task_Sleep() is configured), each task’s timeout counter (entry in NUSE_Task_Timeout_Counter[] ) is checked and, if non-zero, decremented. If any counter reaches zero, the corresponding task is woken up.

Time Slice Scheduling

If the time slice scheduler is in use, the time slice counter (NUSE_Time_Slice_Ticks ) is decremented. If it reaches zero, the scheduler is called. The call to NUSE_Reschedule() takes care of resetting the counter.

A Managed Interrupt

Some explanation of the reasons why the RTC ISR is a managed interrupt may be useful, as, under the right circumstances, the user may want to recode it as a native interrupt to reduce the overhead. For example, if only the system time facility is used (i.e. no application timers, no task sleep and not the time slice scheduler) a native interrupt would be fine. The needs for a managed interrupt are as follows:

  • If timers are used and expiration routines configured, these routines may make API calls (from the interrupt context) that will cause a reschedule. These are subject to the same limitations as API calls made from ISRs (see earlier in this chapter).

  • If the priority scheduler is in use, an expiration of task sleep may require the scheduling of a higher priority task.

  • If the time slice scheduler is in use, it will definitely be called from the RTC ISR, so a managed interrupt is mandatory.

Nucleus RTOS Compatibility

Since interrupts are implemented in a very different way in Nucleus SE, compared with Nucleus RTOS, no particular compatibility should be expected. Nucleus RTOS has a native/low-level/high-level interrupt scheme, which is somewhat analogous to native and managed interrupts in Nucleus SE.

Low- and High-Level ISRs

Low-Level ISR

A Low-Level Interrupt Service Routine (LISR) executes as a normal ISR, which includes using the current stack. Nucleus RTOS saves context before calling an LISR and restores context after the LISR returns. Therefore, LISRs may be written in C and may call other C routines. However, there are only a few Nucleus RTOS services available to an LISR. If the interrupt processing requires additional Nucleus RTOS services, a High-Level Interrupt Service Routine (HISR) must be activated. Nucleus RTOS supports nesting of multiple LISRs.

High-Level ISR

HISRs are created and deleted dynamically. Each HISR has its own stack space and its own control block. The memory for each is supplied by the application. Of course, the HISR must be created before it is activated by an LISR.

Since an HISR has its own stack and control block, it can be temporarily blocked if it tries to access a Nucleus RTOS data structure that is already being accessed.

There are three priority levels available to HISRs. If a higher priority HISR is activated during processing of a lower priority HISR, the lower priority HISR is preempted in much the same manner as a task gets preempted. HISRs of the same priority are executed in the order in which they were originally activated. All activated HISRs are processed before normal task scheduling is resumed.

Nucleus RTOS Interrupt API Calls

Nucleus RTOS has a number of API calls to support its interrupt structure. None of these are implemented in Nucleus SE.

For native interrupts, API calls provide the following facilities:

  • Control (enable/disable) an interrupt (locally and globally)

  • Set up interrupt vector

For low-level interrupts:

  • Register a low-level ISR with the kernel

For high-level interrupts:

  • Create/delete high-level interrupts

  • Activate a high-level interrupt

  • Obtain the number of high-level interrupts (currently) in the application

  • Obtain pointers to the control blocks for all the high-level interrupts

  • Obtain the pointer to the control block of the current high-level interrupt

  • Obtain information about a high-level interrupt

Controlling an Interrupt Globally

This service enables or disables interrupts in a task-independent manner. Therefore, an interrupt disabled by this service remains disabled until enabled by a subsequent call to this service.

Service call prototype:

INT NU_Control_Interrupts(INT new_level);

Parameters:

new_level – new interrupt level for the system. The options NU_DISABLE_INTERRUPTS (disable all interrupts) and NU_ENABLE_INTERRUPTS (enable all interrupts) are always available. Other options may be available depending on architecture.

Returns:

This service returns the previous level of enabled interrupts.

Controlling an Interrupt Locally

This service enables or disables interrupts in a task-dependent manner. This service changes the Status Register to the value specified. The Status Register will be set back to value set by the last call to NU_Control_Interrupts() on the next context switch.

Service call prototype:

INT NU_Local_Control_Interrupts(INT new_level);

Parameters:

new_level – new interrupt level for the current task. The options NU_DISABLE_INTERRUPTS (disable all interrupts) and NU_ENABLE_INTERRUPTS (enable all interrupts) are always available. Other options may be available depending on architecture.

Returns:

This service returns the previous level of enabled interrupts.

Set Up an Interrupt Vector

This service replaces the interrupt vector specified by vector with a custom Interrupt Service Routine (ISR).

Service call prototype:

VOID *NU_Setup_Vector(INT vector, VOID *new);

Parameters:

vector – the interrupt vector at which to register the interrupt
new – the ISR to register at the vector

Returns:

This service returns a pointer to the ISR previously registered at the interrupt vector.

Register a LISR Interrupt

This service associates the LISR function with an interrupt vector. System context is automatically saved before calling the specified LISR and is restored after the LISR returns.

Service call prototype:

STATUS NU_Register_LISR(INT vector, VOID(*lisr_entry)(INT),
VOID (**old_lisr)(INT));

Parameters:

vector – the interrupt vector at which to register the interrupt
lisr_entry – the function to register at the vector; a value of NU_NULL clears the vector
old_lisr – the subroutine previously registered at the specified vector

Returns:

NU_SUCCESS – successful completion of the service
NU_INVALID_VECTOR – the specified vector is invalid
NU_NOT_REGISTERED – the vector is not currently registered as de-registration was specified by lisr_entry
NU_NO_MORE_LISRS – the maximum number of registered LISRs has been exceeded

Create a HISR

This service creates a High-Level Interrupt Service Routine (HISR).

Service call prototype:

STATUS NU_Create_HISR(NU_HISR *hisr, CHAR *name,
VOID (*hisr_entry)(VOID), OPTION priority, VOID  *stack_pointer, UNSIGNED stack_size);

Parameters:

hisr – pointer to a user-supplied HISR control block
name – pointer to a 7-character, null-terminated name for the HISR
hisr_entry – the function entry point of the HISR
priority – there are three HISR priorities (0-2); priority 0 is the highest
stack_pointer – pointer to the HISR’s stack area
stack_size – number of bytes in the HISR stack

Returns:

NU_SUCCESS – successful completion of the service
NU_INVALID_HISR – the HISR control block pointer is NULL or is already in use
NU_INVALID_ENTRY – the HISR entry pointer is NULL
NU_INVALID_PRIORITY – the HISR priority is invalid
NU_INVALID_MEMORY – the stack pointer is NULL
NU_INVALID_SIZE – the stack size is too small

Delete a HISR

This service deletes a previously created HISR.

Service call prototype:

STATUS NU_Delete_HISR(NU_HISR *hisr);

Parameters:

hisr – pointer to a user-supplied HISR control block

Returns:

NU_SUCCESS – successful completion of the service
NU_INVALID_HISR – the HISR pointer is invalid

Activate a HISR

This service activates a HISR. If the specified HISR is currently executing, this activation request is not processed until the current execution is complete. A HISR is executed once for each activation request.

Service call prototype:

STATUS NU_Activate_HISR (NU_HISR *hisr);

Parameters:

hisr – pointer to the HISR control block

Returns:

NU_SUCCESS – successful completion of the service
NU_INVALID_HISR – the HISR control block pointer is not valid

Obtain the Number of HISRs in a System

This service returns the number of established HISRs. All created HISRs are considered established. Deleted HISRs are no longer considered established.

Service call prototype:

UNSIGNED NU_Established_HISRs(VOID);

Parameters:

none

Returns:

This service call returns the number of established HISRs in the system

Obtain Pointers to HISR Control Blocks

This service builds a sequential list of pointers to all established HISRs in the system.

Service call prototype:

UNSIGNED  NU_HISR_Pointers(NU_HISR  **pointer_list,
UNSIGNED maximum_pointers);

Parameters:

pointer_list – pointer to an array of NU_HISR pointers; this array will be filled with pointers of established HISRs in the system
maximum_pointers – the maximum number of NU_HISR pointers to place into the array; typically, this will be the size of the pointer_list array

Returns:

This service call returns the number of HISRS that are active in the system

Obtain a Pointer to the Current HISR

This service returns the currently executing HISR’s pointer.

Service call prototype:

NU_HISR *NU_Current_HISR_Pointer(VOID);

Parameters:

none

Returns:

This service call returns a pointer the currently executing HISR’s control block. If the caller is not an HISR, the value returned is NU_NULL .

Obtain Information About a HISR

This service returns various information about the specified HISR.

Service call prototype:

STATUS NU_HISR_Information(NU_HISR *hisr, char *name,
UNSIGNED  *scheduled_count, DATA_ELEMENT  *priority,
VOID  **stack_base, UNSIGNED *stack_size,
UNSIGNED *minimum_stack);

Parameters:

hisr – pointer to the HISR
name – pointer to an 8-character destination area for the HISR’s name; this includes space for the null terminator
scheduled_count – pointer to a variable for holding the total number of times this HISR has been scheduled
priority – pointer to a variable for holding the HISR’s priority
stack_base – pointer to a pointer for holding the original stack pointer; this is the same pointer supplied during creation of the HISR
stack_size – pointer to a variable for holding the total size of the HISR’s stack
minimum_stack – pointer to a variable for holding the minimum amount of available stack space detected during HISR execution

Returns:

NU_SUCCESS – successful completion of the service
NU_INVALID_HISR – the HISR pointer is invalid

API Calls from ISRs

API Calls from LISRs

A LISR may only make use of the following Nucleus RTOS services:

NU_Activate_HISR()
NU_Local_Control_Interrupts()
NU_Current_HISR_Pointer()
NU_Current_Task_Pointer()
NU_Retrieve_Clock()

API Calls from HISRs

HISRs are allowed access to most Nucleus RTOS services, with the exception of self-suspension services. Additionally, since an HISR cannot suspend on a Nucleus RTOS service, the “suspend” parameter must always be set to NU_NO_SUSPEND .

The next RTOS Revealed article will look at initialization and start-up of Nucleus SE.


Colin Walls has nearly forty years’ experience in the electronics industry, largely dedicated to embedded software. A frequent presenter at conferences and seminars and author of numerous technical articles and two books on embedded software, Colin is an embedded software technologist with Mentor, a Siemens business, and is based in the UK. His regular blog is located at: http://blogs.mentor.com/colinwalls. He may be reached by email at colin_walls@mentor.com

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.