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.
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.
File | Directory |
|---|---|
|
|
|
|
|
|
|
|
|
|
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.
File | Directory |
|---|---|
|
|
|
|
|
|
|
|
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_EXITos_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_arg. p_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.
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
}