uC-OS-III Port

Porting µC/OS-III

The table following the figure below below shows the name of µC/OS-III files and where they are typically found.

For the purpose of demonstrating how to do a port, we will assume the generic 32-bit processor as described in Context Switching and shown in the figure below.

Generic 32-bit CPU

Our generic CPU contains 16 integer registers (R0 to R15), a separate ISR stack pointer, and a separate status register (SR). Every register is 32 bits wide and each of the 16 integer registers can hold either data or an address. The return address of a function call is placed in the Link Register ( LR ). The program counter (or instruction pointer) is R15 and there are two separate stack pointers labeled R14 and R14’. R14 represents a task stack pointer (TSP), and R14’ represents an ISR stack pointer (ISP). The CPU automatically switches to the ISR stack when servicing an exception or interrupt. The task stack is accessible from an ISR (i.e., we can push and pop elements onto the task stack when in an ISR), and the interrupt stack is also accessible from a task. The Status Register (SR) contains the interrupt mask as well as various status such as the Carry, Zero, Sign, Overflow, Parity, etc.

µC/OS-III Files and Directories
FileDirectory
bsp_os.c
bsp_os.h
bsp_os_a.asm
\Micrium\Software\EvalBoards\<manufacturer>\<board>\BSP\OS\uCOS-III\
os_cpu.h\Micrium\Software\uCOS-III\Ports\<processor>\<compiler>\
os_cpu_a.asm\Micrium\Software\uCOS-III\Ports\<processor>\<compiler>\
os_cpu_a.inc\Micrium\Software\uCOS-III\Ports\<processor>\<compiler>
os_cpu_c.c\Micrium\Software\uCOS-III\Ports\<processor>\<compiler>\

Here, <processor> is the name of the processor that the os_cpu*.* files apply to, and <compiler> is the name of the compiler that these files assume because of the different assembly language directives that different toolchain uses.

The table below shows where you can find template files that will help you create a µC/OS-III port from scratch. You would simply copy these files in a folder specific to your processor/compiler as shown in the table above and then change the contents of these files per your processor/compiler.

µC/OS-III Template Files
FileDirectory
os_cpu.h\Micrium\Software\uCOS-III\Ports\Template\
os_cpu_a.asm\Micrium\Software\uCOS-III\Ports\Template\
os_cpu_a.inc\Micrium\Software\uCOS-III\Ports\Template\
os_cpu_c.c\Micrium\Software\uCOS-III\Ports\Template\

bsp_os.c, bsp_os_a.asm and bsp_os.h — Periodic Tick

Board Support Package (BSP) specific code is generally needed to provide µC/OS-III with a periodic time source. This is typically obtained from a hardware timer that is configured to generate a ‘tick rate’ between 10 and 1000 Hz. By convention, we decided to call these files bsp_os.c, bsp_os_a.asm (optional) and bsp_os.h. These files, part of the BSP, typically contain code for just a couple of functions: BSP_OS_TickInit() and BSP_OS_TickISR().

BSP_OS_TickInit()

This function must be called by the first task that executes under µC/OS-III and after you called CPU_Init(). Alternatively, you can place BSP_OS_TickInit() in os_cpu_c.c depending on whether or not the tick ISR is generic for the CPU architecture you are using. In other words, if the CPU or MCU has a dedicated timer that can be assigned for the tick ISR so that it’s the same, irrespective of the target application then BSP_OS_TickInit() can be placed in os_cpu_c.c. The pseudo-code for this function is shown in the listing below.

BSP_OS_TickInit() Pseudo-code
void  BSP_OS_TickInit (CPU_INT32U  freq)                                           (1)
{
    Install the interrupt vector for the timer used to generate tick interrupts;   (2)
    Configure the timer to generate interrupts at 'freq' Hz;                       (3)
    Enable timer interrupts;                                                       (4)
}

BSP_OS_TickISR()

If ISRs are implemented in assembly language this function could be placed in a file called bsp_os_a.asm (or other filename extension as needed by your assembler). Alternatively, you can place BSP_OS_TickISR() in os_cpu_a.asm depending on whether or not the tick ISR is generic for the CPU and, whether it needs to be implemented in assembly language. In other words, if the CPU or MCU has a dedicated timer that can be assigned for the tick ISR so that it’s the same, irrespective of the target application then BSP_OS_TickISR() can be placed in os_cpu_a.asm. The pseudo code for this function is shown below (the C-like code needs to be implemented in assembly language). You should note that all ISRs should be modeled after BSP_OS_TickISR().

BSP_OS_TickISR() Pseudo-code
 BSP_OS_TickISR:                                                         (1)
    OS_CTX_SAVE                                                          (2)
    Disable Interrupts;                                                  (3)
    OSIntNestingCtr++;                                                   (4)
    if (OSIntNestingCtr == 1) {                                          (5)
        OSTCBCurPtr->StkPtr = SP;
    }
    Clear tick interrupt;                                                (6)
    OSTimeTick();                                                        (7)
    OSIntExit();                                                         (8)
    OS_CTX_RESTORE                                                       (9)
    Return from Interrupt/Exception;                                    (10)

(1) BSP_OS_TickISR() is generally invoked automatically by the interrupt controller when the tick interrupt occurs. Assuming again our generic 32-bit CPU, it’s assumed here that the SR and PC of the interrupted task are pushed automatically onto the stack of the interrupted task.

(2) Again, OS_CTX_SAVE saves the CPU context onto the current task’s stack. For our generic 32-bit CPU, OS_CTX_SAVE would push R0 through R13 onto the stack, in that order.

(3) Interrupts should be disabled here. On some processors, interrupts are automatically disabled when the processor accepts the interrupt. Some processors support multiple interrupt levels. In fact, some interrupts are allowed to make kernel calls while others are not. Typically, interrupts that do not make kernel calls (called Non-Kernel Aware Interrupts) would generally be high priority interrupts and kernel aware interrupts would all be grouped (in priority) below these. For example, if a processor has 16 different interrupt levels and level 0 is the lowest priority interrupt then, all kernel aware interrupts would be assigned from 0 to some number N (let’s say 12) and N+1 to 15 would be assigned to be non-kernel aware interrupts.

(4) BSP_OS_TickISR() then needs to increment the interrupt nesting counter. This tells µC/OS-III that the code is servicing an interrupt. The nesting counter indicates how many levels of interrupts we are currently servicing (in case the application supports nested interrupts).

(5) If this interrupt interrupts a task then we need to save the stack pointer of that task into the OS_TCB of that task.

(6) You need to clear the interrupting device so that it doesn’t re-issue the same interrupt upon returning from interrupts. This can be done here or, in the device handler (see below).

(7) At this point, BSP_OS_TickISR() calls OSTimeTick() which is responsible for notifying the tick task that a tick occurred.

If you model your ISR like BSP_OS_TickISR() then you would call your own C function to service the interrupting device.

(8) OSIntExit() is then called at the end of the ISR to notify µC/OS-III that you are done processing the ISR. µC/OS-III decrements the nesting counter and if OSIntNestingCtr reaches 0, µC/OS-III knows it’s returning to task level code. So, if the ISR made a more important task ready-to-run (more important than the interrupted task), µC/OS-III will context switch to that task instead of returning to the interrupted task.

(9) If the interrupted task is still the most important task then OSIntExit() returns and the ISR will need to restore the saved registers. OS_CTX_RESTORE does just that. For our generic 32-bit CPU, OS_CTX_RESTORE would pop CPU registers R13 through R0 from the stack, in that order.

(10) Finally, the Return from Interrupt/Exception restores the Program Counter (PC) and the Status Register (SR) in a single instruction. At this point, the interrupted task will resume execution, exactly where it was interrupted.

It is actually possible to simplify the code for  BSP_OS_TickISR()  or any of your ISRs. Notice that the code at the beginning and end of the ISR is common for all ISRs. Because of that, it’s possible to create two assembly language macros,  OS_ISR_ENTER  and  OS_ISR_EXIT  in  os_cpu_a.inc . The new  BSP_OS_TickISR()  code would now look as shown below:

BSP_OS_TickISR() Pseudo-code using the OS_ISR_ENTER and OS_ISR_EXIT macro
BSP_OS_TickISR:
    OS_ISR_ENTER
    Clear tick interrupt;
    OSTimeTick();
    OS_ISR_EXIT

os_cpu.h

OS_TASK_SW()

OS_TASK_SW() is a macro that is called by OSSched() to perform a task-level context switch. The macro can translate directly to a call to OSCtxSw(), trigger a software interrupt, or a TRAP. If a software interrupt or TRAP is used then you would most likely need to add the address of OSCtxSw() in the interrupt vector table. The choice depends on the CPU architecture.

OS_TS_GET()

OS_TS_GET() is a macro that obtains the current time stamp. It is expected that the time stamp is type CPU_TS, which is typically declared as at least a 32-bit value.

OSCtxSw(), OSIntCtxSw() and OSStartHighRdy()

os_cpu.h declares function prototypes for OSCtxSw()OSIntCtxSw()OSStartHighRdy() and possibly other functions required by the port.

os_cpu_c.c

The functions are described in µC-OS-III API Reference .  os_cpu_c.c  can declare any functions needed by the port, however the functions described below are mandatory. These functions are already implemented in the template file, but those can certainly be extended as needed. You should not have to change this file unless you have specific requirements.

OSIdleTaskHook()

This function is called repeatedly when µC/OS-III does not have any task ready-to-run. The port implemented might choose to put the processor in low power mode if the product being designed is battery operated. However, it would be preferable to defer this choice to the application level. You can do this by putting the processor in low power mode in a function called App_OS_IdleTaskHook() and let the application decide whether or not it is appropriate to place the processor in low power mode. The template file contains the following code:

Typical OSIdleTaskHook()
void  OSIdleTaskHook (void)
{
#if OS_CFG_APP_HOOKS_EN > 0u
    if (OS_AppIdleTaskHookPtr != (OS_APP_HOOK_VOID)0) {
        (*OS_AppIdleTaskHookPtr)();
    }
#endif
}

OSInitHook()

This function is called by OSInit() at the very beginning of OSInit(). This is done to allow the port implemented to add functionality to the port while hiding the details from the µC/OS-III user. For one thing, the port implementer could setup an ISR stack in OSInitHook(). The template file contains the following code:

Typical OSInitHook()
void  OSInitHook (void)
{
}

OSRedzoneHitHook()

If Redzone Stack Checking is enabled (OS_CFG_TASK_STK_REDZONE_EN set to DEF_ENABLED in is os_cfg.h), this function is called when µC/OS-III determines that a task's stack has overflowed its Redzone. The function calls an application hook, if defined. If not, it calls a software exception. The application hook could try to fix the stack, report an error or simply call the software exception.

Typical OSRedzoneHitHook()
void  OSRedzoneHitHook (OS_TCB  *p_tcb)
{
#if OS_CFG_APP_HOOKS_EN > 0u
    if (OS_AppRedzoneHitHookPtr != (OS_APP_HOOK_TCB)0) {
        (*OS_AppRedzoneHitHookPtr)(p_tcb);
    }
#endif
    (void)p_tcb;
    CPU_SW_EXCEPTION(;);
}

OSStatTaskHook()

This function is called when the statistic task executes. This hook allows the port developer the opportunity to add his or her own statistics. The template file contains the following code:

Typical OSStatTaskHook()
void  OSStatTaskHook (void)
{
#if OS_CFG_APP_HOOKS_EN > 0u
    if (OS_AppStatTaskHookPtr != (OS_APP_HOOK_VOID)0) {
        (*OS_AppStatTaskHookPtr)();
    }
#endif
}

OSTaskCreateHook()

This function is called by OSTaskCreate() and is passed the address of the OS_TCB of the newly created task. OSTaskCreateHook() is called by OSTaskCreate() after initializing the OS_TCB fields and setting up the stack frame for the task. The template file contains the following code:

Typical OSTaskCreateHook()
void  OSTaskCreateHook (OS_TCB  *p_tcb)
{
#if OS_CFG_APP_HOOKS_EN > 0u
    if (OS_AppTaskCreateHookPtr != (OS_APP_HOOK_TCB)0) {
        (*OS_AppTaskCreateHookPtr)(p_tcb);
    }
#else
    (void)p_tcb;
}

OSTaskDelHook()

This function is called by OSTaskDel() after the task to delete has been removed either from the ready list or a wait list. The template file contains the following code:

Typical OSTaskDelHook()
void  OSTaskDelHook (OS_TCB  *p_tcb)
{
#if OS_CFG_APP_HOOKS_EN > 0u
    if (OS_AppTaskDelHookPtr != (OS_APP_HOOK_TCB)0) {
        (*OS_AppTaskDelHookPtr)(p_tcb);
    }
#else
    (void)p_tcb;
#endif
}

OSTaskReturnHook()

This function is called by OS_TaskReturn() if the user accidentally returns from the task code. The template file contains the following code:

Typical OSTaskReturnHook()
void  OSTaskReturnHook (OS_TCB  *p_tcb)
{
#if OS_CFG_APP_HOOKS_EN > 0u
    if (OS_AppTaskReturnHookPtr != (OS_APP_HOOK_TCB)0) {
        (*OS_AppTaskReturnHookPtr)(p_tcb);
    }
#else
    (void)p_tcb;
}

OSTaskStkInit()

OSTaskStkInit() is called by OSTaskCreate() and is one of the most difficult port functions to create because it establishes the stack frame of every task created. The template file contains the following code:

Generic OSTaskStkInit()
CPU_STK  *OSTaskStkInit (OS_TASK_PTR     p_task,
                         void          *p_arg,
                         CPU_STK       *p_stk_base,
                         CPU_STK       *p_stk_limit,
                         CPU_STK_SIZE   stk_size,
                         OS_OPT         opt)
{
    CPU_STK  *p_stk;
 
 
    (void)opt;
 
    p_stk    = &p_stk_base[stk_size];              (1)      
                                                            
    *--p_stk = (CPU_STK)0x00000000u;               (2)      
    *--p_stk = (CPU_STK)p_task;                    (3)      
    *--p_stk = (CPU_STK)p_arg;                     (4)      
    *--p_stk = (CPU_STK)0x01010101u;               (5)      
    *--p_stk = (CPU_STK)0x02020202u;                        
    *--p_stk = (CPU_STK)0x03030303u;                        
    *--p_stk = (CPU_STK)0x04040404u;                        
    *--p_stk = (CPU_STK)0x05050505u;                        
    *--p_stk = (CPU_STK)0x06060606u;                        
    *--p_stk = (CPU_STK)0x07070707u;                        
    *--p_stk = (CPU_STK)0x08080808u;                        
    *--p_stk = (CPU_STK)0x09090909u;                        
    *--p_stk = (CPU_STK)0x10101010u;                        
    *--p_stk = (CPU_STK)0x11111111u;                        
    *--p_stk = (CPU_STK)0x12121212u;                        
    *--p_stk = (CPU_STK)OS_TaskReturn;             (6)      
 
    return (p_stk);                                (7)      
}

(1) You need to initialize the top-of-stack. For our ‘generic 32-bit CPU, the top-of-stack (TOS) points at one location beyond the area reserved for the stack. This is because we will decrement the TOS pointer before storing a value into the stack.

If the stack for the processor grew from low memory to high memory, most likely you would have setup the TOS to point at the base of the memory or, &p_stk_base[0].

(2) Since we are simulating an interrupt and the stacking of registers in the same order as an ISR would place them on the stack, we start by putting the SR (Status Register, also called the Program Status Word) of the CPU onto the stack first.

Also, the value stored at this location must be such that, once restored into the CPU, the SR must enable ALL interrupts. Here we assumed that a value of 0x00000000 would do this. However, you need to check with the processor you are using to find out how this works on that processor.

(3) The address of the task code is then placed onto the next stack location. This way, when you perform a return from interrupt (or exception) instruction the PC will automatically be loaded with the address of the task to run.

(4) You should recall that a task is passed an argument, p_argp_arg is a pointer to some user define storage or function and its use is application specific. In the assumptions above, we indicated that a function called with a single argument gets this argument passed in R0. You will need to check the compiler documentation to determine where ‘p_arg’ is placed for your processor.

(5) The remaining registers are placed onto the stack. You will notice that we initialized the value of those registers with a hexadecimal number that corresponds to the register number. In other words, R12 should have the value 0x12121212 when the task starts, R11 should have the value 0x11111111 when the task starts and so on. This makes it easy to determine whether the stack frame was setup properly when you test the port. You would simply look at the register contents with a debugger and confirm that all registers have the proper values.

(6) Here we place the return address of the task into the location where the Link Register (LR) will be retrieved from. In this case, we force the return address to actually be OS_TaskReturn() allowing µC/OS-III to catch a task that is attempting to return. You should recall that this is not allowed with µC/OS-III.

(7) OSTaskStkInit() needs to return the new top-of-stack location. In this case, the top-of-stack points at the last element placed onto the stack.

The figure below shows how the stack frame looks like just before the function returns.  OSTaskCreate()  will actually save the new top-of-stack ( p_stk ) into the  OS_TCB  of the task being created.

Stack Frame created by OSTaskStkInit()

OSTaskSwHook()

The typical code for µC/OS-III’s context switch hook is shown below. What OSTaskSwHook() does is highly dependent on a number of configuration options.

Typical OSTaskSwHook()
void  OSTaskSwHook (void)
{
#if OS_CFG_TASK_PROFILE_EN > 0u
    CPU_TS  ts;
#endif
#ifdef  CPU_CFG_INT_DIS_MEAS_EN
    CPU_TS  int_dis_time;
#endif
 
 
#if OS_CFG_APP_HOOKS_EN > 0u
    if (OS_AppTaskSwHookPtr != (OS_APP_HOOK_VOID)0) {                         (1)
        (*OS_AppTaskSwHookPtr)();
    }
#endif
 
#if OS_CFG_TASK_PROFILE_EN > 0u
    ts = OS_TS_GET();                                                         (2)
    if (OSTCBCurPtr != OSTCBHighRdyPtr) {
        OSTCBCurPtr->CyclesDelta  = ts - OSTCBCurPtr->CyclesStart;
        OSTCBCurPtr->CyclesTotal += (OS_CYCLES)OSTCBCurPtr->CyclesDelta;
    }
 
    OSTCBHighRdyPtr->CyclesStart = ts;
#endif
 
#ifdef  CPU_CFG_INT_DIS_MEAS_EN
    int_dis_time = CPU_IntDisMeasMaxCurReset();                               (3)
    if (OSTCBCurPtr->IntDisTimeMax < int_dis_time) {
        OSTCBCurPtr->IntDisTimeMax = int_dis_time;
    }
#endif
 
#if OS_CFG_SCHED_LOCK_TIME_MEAS_EN > 0u
    if (OSTCBCurPtr->SchedLockTimeMax < OSSchedLockTimeMaxCur) {              (4)
        OSTCBCurPtr->SchedLockTimeMax = OSSchedLockTimeMaxCur;
    }
    OSSchedLockTimeMaxCur = (CPU_TS)0;
#endif
 
#if (OS_CFG_TASK_STK_REDZONE_EN == DEF_ENABLED)
    stk_status = OSTaskStkRedzoneChk(DEF_NULL);                               (5)
    if (stk_status != DEF_OK) {
        OSRedzoneHitHook(OSTCBCurPtr);
    }
#endif
}

(1) If the application code defined a hook function to be called during a context switch then this function is called first. You application hook function can assume that OSTCBCurPtr points to the OS_TCB of the task being switched out while OSTCBHighRdyPtr points to the OS_TCB of the task being switched in.

(2) OSTaskSwHook() then computes the amount of time the current task ran. However, this includes the execution time of all the ISRs that happened while the task was running.

We then take a timestamp to mark the beginning of the task being switched in.

(3) OSTaskSwHook() then stores the highest interrupt disable time into the OS_TCB of the task being switched out. This allows a debugger or µC/Probe to display maximum interrupt disable time on a per-task basis.

(4) OSTaskSwHook() then captures the highest scheduler lock time and stores that in the OS_TCB of the task being switched out.

(5) If Redzone Stack Checking is enabled, the hook checks to see if the task that is about to be switched out has overflowed its stack.

OSTimeTickHook()

This function is called by OSTimeTick() and is called before any other code is executed in OSTimeTick(). The template file contains the following code. If the application code defines an application hook function then it is called as shown.

Typical OSTimeTickHook()
void  OSTimeTickHook (void)
{
#if OS_CFG_APP_HOOKS_EN > 0u
    if (OS_AppTimeTickHookPtr != (OS_APP_HOOK_VOID)0) {
        (*OS_AppTimeTickHookPtr)();
    }
#endif
}

os_cpu_a.asm

This file contains the implementation of the following assembly language functions:

OSStartHighRdy()

This function is called by OSStart() to start multitasking. OSStart() will have determined the highest priority task (OSTCBHighRdyPtr will point to the OS_TCB of that task) that was created prior to calling OSStart() and will start executing that task. The pseudo code for this function is shown below (the C-like code needs to be implemented in assembly language):

OSStartHighRdh() Pseudo-code
OSStartHighRdy:
    OSTaskSwHook();
    SP = OSTCBHighRdyPtr->StkPtr;                       (1)
    OS_CTX_RESTORE                                      (2)
    Return from Interrupt/Exception;                    (3)

(1) The Stack Pointer (SP) for the first task to execute is retrieved from the OS_TCB of the highest priority task that was created prior to calling OSStart(). The figure below shows the stack frame as pointed to by OSTCBHighRdy->StkPtr.

(2) OS_CTX_RESTORE is a macro (see os_cpu_a.inc) that restores the context of the CPU (R0 through R13) from the new task’s stack.

(3) The Return from Interrupt/Exception restores the Program Counter (PC) and the Status Register (SR) in a single instruction. At this point, the task will start executing. In fact, the task will think it was called by another function and thus, will receive ‘p_arg’ as its argument. Of course, the task must not return.

Stack Frame pointed to by OSTCBHighRdy->StkPtr

OSCtxSw()

This function implements the task level context switch which is invoked by the OS_TASK_SW() macro declared in os_cpu.h. The pseudo code for this function is shown below (the C-like code needs to be implemented in assembly language). You should also refer to Chapter 8.

OSCtxSw() Pseudo-code
OSCtxSw:                                                                   (1)
    OS_CTX_SAVE                                                            (2)
    OSTCBCurPtr->StkPtr = SP;                                              (3)
    OSTaskSwHook();
    OSPrioCur   = OSPrioHighRdy;
    OSTCBCurPtr = OSTCBHighRdyPtr;
    SP          = OSTCBCurPtr->StkPtr;                                     (4)
    OS_CTX_RESTORE                                                         (5)
    Return from Interrupt/Exception;                                       (6)

(1) OSCtxSw() is invoked by OS_TASK_SW() which is typically implemented as a software interrupt instruction or, a trap instruction. These types of instructions generally simulate the behavior of an interrupt, but is synchronous with the code. OSCtxSw() is thus the entry point for this instruction. In other words, if a software interrupt or TRAP is used then you would most likely need to add the address of OSCtxSw() in the interrupt vector table.

(2) OS_CTX_SAVE is a macro (see os_cpu_a.inc) that saves the CPU context onto the current task’s stack. For our generic 32-bit CPU, OS_CTX_SAVE would push R0 through R13 onto the stack, in that order.

(3) OSCtxSw() then needs to save the current top-of-stack pointer (i.e. R14 or SP) into the OS_TCB of the current task.

(4) The stack pointer for the new task is retrieved from the OS_TCB of the new current task.

(5) OS_CTX_RESTORE is a macro (see os_cpu_a.inc) that restores the context of the CPU from the new task’s stack. For our generic 32-bit CPU, OS_CTX_RESTORE would pop CPU registers R13 through R0 from the stack, in that order.

(6) The Return from Interrupt/Exception restores the Program Counter (PC) and the Status Register (SR) in a single instruction. At this point, the new task will resume execution, exactly where it was preempted.

OSIntCtxSw()

This function implements the interrupt level context switch which is called by OSIntExit() (see os_core.c). The pseudo code for this function is shown below (the C-like code needs to be implemented in assembly language). Refer also to Context Switching.

OSIntCtxSw() Pseudo-code
OSIntCtxSw:                                                                 (1)
    OSTaskSwHook();
    OSPrioCur   = OSPrioHighRdy;
    OSTCBCurPtr = OSTCBHighRdyPtr;
    SP          = OSTCBCurPtr->StkPtr;                                      (2)
    OS_CTX_RESTORE                                                          (3)
    Return from Interrupt/Exception;                                        (4)

(1) OSIntCtxSw() is called by OSIntExit() at the end of all nested ISRs. The ISR is assumed to have saved the context of the interrupted task onto that task’s stack. Also, the ISR is assumed to have saved the new top-of-stack of the interrupted task into the OS_TCB of that task.

(2) The stack pointer for the new task is then retrieved from the OS_TCB of the new current task.

(3) OS_CTX_RESTORE is a macro (see os_cpu_a.inc) that restores the context of the CPU from the new task’s stack. For our generic 32-bit CPU, OS_CTX_RESTORE would pop CPU registers R13 through R0 from the stack, in that order.

(4) The Return from Interrupt/Exception restores the Program Counter (PC) and the Status Register (SR) in a single instruction. At this point, the new task will resume execution, exactly where it was preempted.

os_cpu_a.inc

This file contains the implementation of assembly language macros that are used to simplify the implementation of os_cpu_a.asm. A macro replaces many assembly language instructions with a single macro invocation.

OS_CTX_SAVE

This macro is used to save the CPU context onto the current stack. OS_CTX_SAVE needs to save the CPU registers in the same order as they are pushed in OSTaskStkInit() which is described later. OS_CTX_SAVE only saves the CPU registers that are not automatically saved by the CPU when the CPU accepts an interrupt. In other words, if the CPU automatically saves the PSW and PC onto the stack upon initiating an ISR then OS_CTX_SAVE only needs to save the remaining CPU registers.

OS_CTX_SAVE macro Pseudo-code
OS_CTX_SAVE
    Save all the CPU registers onto the current task stack
      (in the same order as in OSTaskStkInit())

Assuming our generic 32-bit CPU, OS_CTX_SAVE would be implemented as follows.

OS_CTX_SAVE assuming 32-bit CPU
OS_CTX_SAVE  MACRO
    PUSH R0
    PUSH R1
    PUSH R2
    PUSH R3
    PUSH R4
    PUSH R5
    PUSH R6
    PUSH R7
    PUSH R8
    PUSH R9
    PUSH R10
    PUSH R11
    PUSH R12
    PUSH R13
ENDM

OS_CTX_RESTORE

This macro is used to reverse the process done by OS_CTX_SAVE. In other words, OS_CTX_RESTORE loads the CPU registers from the stack in the reverse order.

OS_CTX_RESTORE macro Pseudo-code
OS_CTX_RESTORE
    Restore all the CPU registers from the new task's stack
      (in the reverse order that they were in OSTaskStkInit())

Assuming our generic 32-bit CPU, OS_CTX_RESTORE would be implemented as follows.

OS_CTX_RESTORE assuming generic 32-bit CPU
OS_CTX_RESTORE  MACRO
    POP R13
    POP R12
    POP R11
    POP R10
    POP R9
    POP R8
    POP R7
    POP R6
    POP R5
    POP R4
    POP R3
    POP R2
    POP R1
    POP R0
ENDM

OS_ISR_ENTER

This macro allows you to simplify your assembly language ISRs. OS_ISR_ENTER is basically the first line of code you would add to the ISR. The pseudo code for OS_ISR_ENTER is shown below.

OS_ISR_ENTER macro Pseudo-code
 OS_ISR_ENTER
     OS_CTX_SAVE
     OSIntNestingCtr++;
     if (OSIntNestingCtr == 1) {
         OSTCBCurPtr->StkPtr = SP;
     }

Assuming our generic 32-bit CPU, OS_ISR_ENTER would be implemented as follows. You should note that the C-like code would actually be implemented in assembly language.

OS_ISR_ENTER assuming generic 32-bit CPU
OS_ISR_ENTER  MACRO
    PUSH R0
    PUSH R1
    PUSH R2
    PUSH R3
    PUSH R4
    PUSH R5
    PUSH R6
    PUSH R7
    PUSH R8
    PUSH R9
    PUSH R10
    PUSH R11
    PUSH R12
    PUSH R13
    OSIntNestingCtr++;
    if (OSIntNestingCtr == 1) {
        OSTCBCurPtr->StkPtr = SP;
    }
ENDM

OS_ISR_EXIT

This macro allows you to simplify your assembly language ISRs. OS_ISR_EXIT is basically the last line of code you would add to the ISR. The pseudo code for OS_ISR_EXIT is shown below.

OS_ISR_EXIT macro Pseudo-code
OS_ISR_EXIT
    OSIntExit();
    OS_CTX_RESTORE
    Return from Interrupt/Exception

Assuming our generic 32-bit CPU, OS_ISR_EXIT would be implemented as follows. You should note that the C-like code would actually be implemented in assembly language.

OS_ISR_EXIT assuming generic 32-bit CPU
OS_ISR_EXIT  MACRO
    OSIntExit();
    POP R13
    POP R12
    POP R11
    POP R10
    POP R9
    POP R8
    POP R7
    POP R6
    POP R5
    POP R4
    POP R3
    POP R2
    POP R1
    POP R0
    Return from Interrupt/Exception; 
ENDM