All modern microprocessors and microcontrollers contain any interrupt mechanisms. These mechanisms are necessary to ensure the “responsiveness” required by many applications. Of course, responsiveness and predictability are the main goal when using RTOS, but they also oppose each other. Using interrupts can disrupt the integrity of the real-time OS. This problem and its solution were addressed in a previous article (# 4 and # 6). In this article, we will look at the interrupt handling strategy used in Nucleus SE. In all cases, interrupts are NOT controlled by Nucleus SE: they are processed when they occur according to priority and vectors in the usual way. The execution time is simply “stolen” from the available time in the code of the main application and the scheduler. Obviously, it follows from this that all interrupt handlers should be simple, short and fast.

Previous articles in the series: Regular and managed interrupts
Nucleus SE offers two ways to handle interrupts: “native” or “regular” (Native), in which interrupts do not represent anything special and to some extent have limited interaction with the OS (at least when using the priority scheduler), and Managed ”, in which you can access a much larger number of available API calls from an interrupt handler.
With the help of input / output macros, the interrupt handler in Nucleus SE can be used both in the standard and in the controlled version.
')
Regular interrupts
Nucleus SE regular interrupts are the standard interrupt handler; they can be considered “unmanaged”. They are usually used when an interrupt can occur with a high frequency and requires processing with a low utilization of computing resources. Such a handler is most likely written in C, since many modern embedded compilers support the development of interrupt handlers using the interrupt keyword. Only the context information that the compiler deems necessary is saved. This leads to significant limitations in what standard interrupt handlers can do, as we will see soon.
To form a regular interrupt handler in Nucleus SE, you simply write a normal interrupt handler, including calling the
NUSE_NISR_Enter () macro at the beginning and calling
NUSE_NISR_Exit () at the end. These macros are defined in the
nuse_types file
. h and set the global variable
NUSE_Task_State to
NUSE_NISR_CONTEXT .
Managed Interrupts
If you need greater flexibility in the operations performed by the interrupt handler, Nucleus SE controlled interrupts may be the appropriate solution. The key difference from the standard interrupt is the preservation of the context. Instead of allowing the compiler to keep several registers on the stack, the managed interrupt saves the entire task context (in its own context block) as an input. Then the context of the current task is restored from the context block at the output. This provides the ability to change the current task with the interrupt handler code, which is possible using the priority scheduler. A full description of the preservation and restoration of the context in Nucleus SE was given in a previous article (
# 10 ).
Obviously, the complete preservation of the context entails an increase in the use of computational resources as compared to the preservation of several registers on the stack, which occurs with a standard interrupt. Such a price must be paid for additional flexibility, and it is this that is the reason why the choice of interrupt handling approach is provided.
A managed interrupt is built using the
NUSE_MANAGED_ISR () macro, described in
nuse_types.h . This macro creates a function that contains the following actions:
- preserving task context;
- NUSE_Task_State assignment to NUSE_MISR_CONTEXT ;
- user-supplied interrupt handler function code;
- restoring NUSE_Task_State to its previous state;
- restore task context.
The macro takes two parameters: the name of the interrupt, used as the name of the function for the handler being created, and the name of the function that contains the user logic of the interrupt handler.
API calls from interrupt handler
The set of API functions that can be called from a standard or managed interrupt handler depends on which scheduler is used. In general terms, the use of the priority scheduler provides many possibilities for accessing the scheduler by calling an API function, which is difficult when using a standard interrupt handler.
API calls to the standard interrupt handler when using the priority schedulerWhen using the priority scheduler, a limited number of API function calls are allowed from the standard interrupt handler. This restriction is a result of the flexibility of the Nucleus SE API: many calls can cause the task to be ready and the scheduler can not be called by the standard interrupt handler (because the task context is not saved). Disabling task blocking will provide even more flexibility.
The following API calls are always allowed:
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 useful one is
NUSE_Signals_Send () , since it provides a convenient way to
tell a task to perform an action.
If blocking is disabled, that is, tasks cannot be transferred to a ready state by many API calls, additional API calls become 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()
Some API calls are always unavailable to standard interrupt handlers, since they will inevitably require the work of the scheduler:
NUSE_Task_Suspend() NUSE_Task_Resume() NUSE_Task_Sleep() NUSE_Task_Relinquish() NUSE_Task_Reset() NUSE_Signals_Receive()
Calls to the API of the managed interrupt handler or standard interrupt handler when using any scheduler other than the priority schedulerMuch more API functions can be called from the interrupt handler when using the Run to Completion, Round Robin or Time Slice schedulers. If a priority scheduler is used, managed interrupt handlers have a similar set of functions. This is because calls are allowed that can lead to scheduling another task. This feature is provided by the
NUSE_Reschedule () code, which detects the call context in the interrupt handler and suppresses the context change (allowing it to occur at the end of the interrupt handler). A full analysis of the work scheduler was given in one of the previous articles (
# 9 ).
The key requirement is that API calls inside an interrupt handler should not suspend the current task, for example, waiting for a resource to be released.
In other words, such calls should be made with the
NUSE_NO_SUSPEND pause
parameter .
With this in mind, the following API calls can 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()
Some calls are always barred, as they directly relate to the current task:
NUSE_Task_Relinquish() NUSE_Signals_Receive() NUSE_Task_Sleep()
Interrupt Handler Real Time Clock
The Real Time Clock Interrupt Handler (RTC) is the only complete interrupt handler in Nucleus SE. In addition to providing all the necessary time management functionality in Nucleus SE, it also serves as an example of writing a managed interrupt handler.
RTC Interrupt Handler Operations
The functions provided by the RTC interrupt handler were listed in a previous article that dealt with the broad topic of system time in Nucleus SE (
# 27 ). The described functionality is optional depending on the application configuration.
Below is the complete code for the RTC interrupt handler. #if NUSE_TIMER_NUMBER != 0 { U8 timer; for (timer=0; timer<NUSE_TIMER_NUMBER; 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<NUSE_TASK_NUMBER; 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
Next, we look at the four main areas of functionality for the RTC interrupt handler.
TimersIf application timers are configured, the interrupt handler goes into a loop to process each timer by decreasing its counter value by 1. If the timer ends the count (i.e., the counter reaches 0), two actions are possible:
- if the timer completion handler is configured and the timer has a valid (not NULL ) function pointer (in NUSE_Timer_Expiration_Routine_Address [] ), the handler is executed, taking the parameter from NUSE_Timer_Expiration_Routine_Parameter [] ;
- if the timer is configured to initialize after completion (i.e. NUSE_Timer_Reschedule_Time [] has a non-zero value), the timer is reset with this value.
Application timers have been described in detail in a previous article (# 28).
System Timer (System Clock)If a system timer is configured, the
NUSE_Tick_Cloc k value is simply incremented by 1. For more information, see article # 28.
Task SleepIf support for suspending tasks is enabled (i.e. the
NUSE_Task_Sleep () API call is configured), the timeout counter for each task (the value in
NUSE_Task_Timeout_Counter [] ) is checked, and if it is not zero, it is reduced by 1. If it reaches zero, the corresponding task is resumed .
Time Slice Scheduling PlanningIf the time slice scheduler (Time Slice) is used, the scheduler counter (
NUSE_Time_Slice_Ticks ) is decremented. If it reaches zero, the scheduler is called. The call
NUSE_Reschedule () is responsible for resetting the counter.
Managed Interrupt
It is necessary to explain why the RTC interrupt handler is manageable, since under certain circumstances the user may decide to rewrite it as a standard interrupt in order to reduce the use of computing resources. For example, if only one system time function is used (i.e., there are no application timers, there is no task suspension, and there is no Time Slice scheduler), the staff interrupt is completely appropriate. A managed interrupt is required in the following cases:
- if timers are used and handlers are configured to complete them, since these handlers can execute API calls (from the interrupt context), which will trigger new scheduling. They have the same restrictions as API calls made from interrupt handlers (see earlier in this article);
- if a priority scheduler is used, the completion of a task pause may require awakening a task with a higher priority;
- if the Time Slice scheduler is used, it will necessarily be called from the RTC interrupt handler, therefore, a managed interrupt is mandatory.
Nucleus RTOS Compatibility
Since the implementation of the Nucleus SE interrupts is very different from the Nucleus RTOS, you should not expect compatibility in this regard. Nucleus RTOS has a standard / low-level / high-level interrupt scheme, which is a bit like the standard / managed interrupt scheme in Nucleus SE.
Low Level and High Level Interrupt Handlers
Low Level Interrupt HandlersThe Low Level Interrupt Service Routin (LISR) low level interrupt handler is executed in the same way as a regular interrupt handler, including using the current stack. Nucleus RTOS saves the context until the low-level interrupt handler is called and restores the context after the handler completes. Therefore, a low-level interrupt handler can be written in C and can call other handlers in C. However, only a few Nucleus RTOS services are available to a low-level handler. If interrupt handling requires additional Nucleus RTOS services, you need to activate a high-level interrupt handler. Nucleus RTOS supports the use of several low-level interrupt handlers.
High level interrupt handlerHigh-level Interrupt Service Routin (HISR) handlers are created and deleted dynamically. Each high-level handler has its own stack space and its own control unit. Memory is allocated by the application. And, of course, a high-level interrupt handler must be created before a low-level interrupt handler can activate it.
Since the high-level interrupt handler has its own stack and control unit, it can be temporarily blocked if it tries to access the Nucleus RTOS data structure that is currently in use.
There are three priority levels available to the high-level interrupt handler. If a higher-level handler with a higher priority is activated while the handler with a lower priority is running, a low-priority handler will be executed as the task runs. High-level interrupt handlers with the same priority are executed in the order they are activated. All activated high-level interrupt handlers must be completed before continuing to schedule tasks in normal mode.
Nucleus RTOS API service calls to interrupt
Nucleus RTOS has several API calls to support interrupts. None of these are implemented in the Nucleus SE.
For standard interrupts, API calls provide the following functions:
- control (enable / disable) interrupt (locally and globally);
- set interrupt vector.
For low-level interrupts:
- registering a low-level kernel interrupt handler.
For high-level interrupts:
- create / delete high-level interrupts;
- high-level interrupt activation;
- getting the number of high-level interrupts in the application (at the moment);
- receiving pointers to control blocks of all high-level interrupts;
- receiving pointers to current high-level interrupt control units;
- receiving information about high-level interruption.
Global interrupt controlThis call activates or deactivates interrupts regardless of the task. Consequently, an interrupt deactivated by this call will remain so until it is activated by reusing this call.
Service Call Prototype:
INT NU_Control_Interrupts (INT new_level);
Options:
new_level - new interrupt level for the system. It can always be
NU_DISABLE_INTERRUPTS (deactivates all interrupts) and
NU_ENABLE_INTERRUPTS (activates all interrupts). Depending on the architecture, other values may be available.
Return value:
This service call returns the previous level of activated interrupts.
Local Interrupt ControlThis service call allows you to activate or deactivate interrupts depending on the task. This call changes the status register to the specified value. The status register will be returned to the value specified by the last call to
NU_Control_Interrupts () the next time the context changes.
Service Call Prototype:
INT NU_Local_Control_Interrupts (INT new_level);
Options:
new_level - new interrupt level for the current task. It can always be
NU_DISABLE_INTERRUPTS (deactivates all interrupts) and
NU_ENABLE_INTERRUPTS (activates all interrupts). Depending on the architecture, other values may be available.
Return value:
This service call returns the previous level of activated interrupts.
Set interrupt vectorThis service call replaces the interrupt vector indicated by the vector controlled by the interrupt handler.
Service Call Prototype:
VOID *NU_Setup_Vector (INT vector, VOID *new);
Options:
vector - interrupt vector for which the interrupt will be registered;
new is an interrupt handler written to the vector.
Return value:
This service call returns a pointer to the interrupt handler previously registered for the interrupt vector.
Register low-level interruptThis service call links the function of the low-level interrupt handler to the interrupt vector. The system context is automatically saved before calling the specified low-level interrupt handler and is restored when the interrupt handler completes.
Service Call Prototype:
STATUS NU_Register_LISR (INT vector, VOID (*lisr_entry) (INT), VOID (**old_lisr) (INT);
Options:
vector - interrupt vector for which the interrupt will be registered;
lisr_entry - the function that will be registered for the vector; the
NU_NULL value will clear the vector;
old_lisr is a function previously registered for the specified vector.
Return value:
NU_SUCCESS - the call was successfully completed;
NU_INVALID_VECTOR - incorrect vector;
NU_NOT_R video - at the moment the vector is not registered, since de-registration was specified in l
isr_entry ;
NO_MORE_LISRS - the maximum number of registered low-level interrupt handlers has been reached.
Creating a high-level interrupt handlerThis service call creates a high-level interrupt handler.
Service Call Prototype:
STATUS NU_Create_HISR (NU_HISR *hisr, CHAR *name, VOID (*hisr_entry) (VOID), OPTION priority, VOID *stack_pointer, UNSIGNED stack_size);
Options:
hisr is a pointer to a user-provided control block for a high-level interrupt handler;
name - pointer to the 7-character name for the high-level interrupt handler with terminating zero;
hisr_entry - entry point of the function of the high-level interrupt handler;
priority - there are three priorities for high-level interrupt handlers (0-2); priority 0 - the highest;
stack_pointer - pointer to the stack area of the high-level interrupt handler;
stack_size - the number of bytes in the stack of the high-level interrupt handler.
Return value:
NU_SUCCESS - the call was successfully completed;
NU_INVALID_HISR - a null pointer to a high level interrupt handler control unit (
NULL ) or a control block is already in use;
NU_INVALID_ENTRY - null pointer to the entry point of the high-level interrupt handler (
NULL );
NU_INVALID_PRIORITY - incorrect priority of high-level interrupt handler;
NU_INVALID_MEMORY - invalid pointer to the stack;
NU_INVALID_SIZE - stack size too small.
Removing high-level interrupt handlerThis service call removes the previously created high-level interrupt handler.
Service Call Prototype:
STATUS NU_Delete_HISR (NU_HISR *hisr);
Options:
hisr is a pointer to the control unit of the high-level interrupt handler provided by the user.
Return value:
NU_SUCCESS - the call was successfully completed;
NU_INVALID_HISR - invalid pointer to high-level interrupt handler.
Activate high-level interrupt handlerThis service call activates a high-level interrupt handler. If the specified high-level interrupt handler is currently in progress, the activation request is not executed until the handler stops working. The high-level interrupt handler is executed once for each activation request.
Service Call Prototype:
STATUS NU_Activate_HISR (NU_HISR *hisr);
Options:
hisr is a pointer to the control block of a high-level interrupt handler.
Return value:
NU_SUCCESS - the call was successfully completed;
NU_INVALID_HISR - incorrect pointer to the control block of the high-level interrupt handler.
Getting the number of high-level interrupt handlers in the systemThis service call returns the number of installed high-level interrupt handlers. All created high-level interrupt handlers are considered set. Remote high-level interrupt handlers are not considered installed.
Service Call Prototype:
UNSIGNED NU_Established_HISRs(VOID);
Options:
None.
Return value:
This service call returns the number of installed high-level interrupt handlers in the system.
Getting pointers to control blocks for high-level interrupt handlersThis service call creates a sequential list of pointers to all high-level interrupt handlers installed in the system.
Service Call Prototype:
UNSIGNED NU_HISR_Pointers(NU_HISR **pointer_list, UNSIGNED maximum_pointers);
Options:
pointer_list - pointer to array of pointers
NU_HISR ; this array will be filled with pointers to the high-level interrupt handlers installed in the system;
maximum_pointers - the maximum number of
NU_HISR pointers that can be placed in an array; it is usually equal to the size of the
pointer_list array.
Return value:
This service call returns the number of active high-level interrupt handlers in the system.
Getting a pointer to the current high-level interrupt handlerThis service call returns a pointer to the currently executing high-level interrupt handler.
Service Call Prototype:
NU_HISR *NU_Current_HISR_Pointer(VOID);
Options:
None.
Return value:
This service call returns a pointer to the control block of the currently executing high-level interrupt handler. If a non-high level interrupt handler calls this function,
NU_NULL is returned.
Getting information about the high-level interrupt handlerThis service call returns various information about the specified high-level interrupt handler.
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);
Options:
hisr is a pointer to a high-level interrupt handler;
name - pointer to the 8-character field for the name of the high-level interrupt handler, including the terminating zero;
scheduled_count - pointer to the variable for the total number of times this high-level interrupt handler was scheduled;
priority - a pointer to a variable to store the priority of the high-level interrupt handler;
stack_base - pointer to the pointer to store the original pointer to the stack; this is the same pointer that was passed when creating the high-level interrupt handler;
stack_size - a pointer to a variable to hold the total stack size of the high-level interrupt handler;
minimum_stack - a pointer to a variable to store the minimum amount of available stack space detected by the high-level interrupt handler.
Return value:
NU_SUCCESS - the call was successfully completed;
NU_INVALID_HISR - invalid pointer to high-level interrupt handler.API calls from interrupt handlers
Utility API calls from low-level interrupt handlers A low-level interrupthandler can use only the following Nucleus RTOS functions: NU_Activate_HISR() NU_Local_Control_Interrupts() NU_Current_HISR_Pointer() NU_Current_Task_Pointer() NU_Retrieve_Clock()
API service calls from high-level interrupt handlersHigh-level interrupt handlers have access to most Nucleus RTOS functions, with the exception of self-suspending functions, since the high-level interrupt handler cannot suspend Nucleus RTOS, the parameter must always be NU_NO_SUSPEND .The next article in the series will look at initialization and startup procedures for the Nucleus SE.About the author:Colin Walls has been working in the electronics industry for over thirty years, devoting much of his time to embedded software. He is now an embedded software engineer in Mentor Embedded (a division of Mentor Graphics). Colin Walls often speaks at conferences and seminars, author of numerous technical articles and two books on embedded software. Lives in the UK. Colin's professional blog , e-mail: colin_walls@mentor.com.