Testing a Port

Testing a port is fairly easy to do if you have the proper tools. Here we will assume that you have access to a good debugging tool, preferably built around an Integrated Development Environment (IDE). The debugger should allow you to load your code into your target, single step through your code, set breakpoint, examine CPU registers, look at memory contents and more.

Creating a Simple Test Project

At this point, you should have the µC/CPU and µC/OS-III port file created. To test the port files, you need to create a simple project. When you download and unzip the µC/OS-III source code you will have a directory structure similar to what is shown on the next page. When you see ‘My’ in front of a directory name it means a directory you will need to create for your tests of the port.

As you will see, you can start with the template files provided with µC/CPU and µC/OS-III with little or no modifications.

Test Code Directory Structure
\Micrium
  \Software
    \EvalBoards
      \MyBoardManufacturer    
        \MyBoardName          
          \MyToolsName        
            \MyBSP            
               bsp.c            
               bsp.h            
               bsp_os.c                       <- See the section µC/OS-III port
               bsp_os_a.asm (optional)        <- See the section µC/OS-III port
               bsp_os.h                       <- See the section µC/OS-III port
               cpu_bsp.c        
               cpu_bsp.h
            \MyTest           (1)
              app.c           (2)
              app_cfg.h       (3)
              cpu_cfg.h       (4)    <- Copied from \Micrium\Software\uC-CPU\Cfg\Template
              lib_cfg.h              <- Copied from \Micrium\Software\uC-LIB\Cfg\Template
              os_app_hooks.c         <- Copied from \Micrium\Software\uCOS-III\Cfg\Template
              os_app_hooks.h         <- Copied from \Micrium\Software\uCOS-III\Cfg\Template
              os_cfg.h        (5)    <- Copied from \Micrium\Software\uCOS-III\Cfg\Template
              os_cfg_app.h           <- Copied from \Micrium\Software\uCOS-III\Cfg\Template
              os_type.h              <- Copied from \Micrium\Software\uCOS-III\Source
\Micrium
  \Software
    \uC-CPU
      cpu_core.c
      cpu_core.h
      cpu_def.h
      \MyCPUName              
        \MyToolsName          
          cpu.h                      <- See the section µC/CPU
          cpu_a.asm                  <- See the section µC/CPU
\Micrium
  \Software
    \uC-LIB
      lib_ascii.c
      lib_ascii.h
      lib_def.h
      lib_math.c
      lib_math.h
      lib_mem.c
      lib_mem.h
      lib_str.c
      lib_str.h
\Micrium
  \Software
    \uC/OS-III
      \Cfg
        os_app_hooks.c
        os_app_hooks.h
        os_cfg.h
        os_cfg_app.h
      \Ports
        \MyCPUName
          \MyToolsName
            os_cpu.h                 <- See the section µC/OS-III port
            os_cpu_a.asm             <- See the section µC/OS-III port
            os_cpu_a.inc             <- See the section µC/OS-III port
            os_cpu_c.c               <- See the section µC/OS-III port
      \Source
        os.h
        os_cfg_app.c
        os_core.c
        os_dbg.c
        os_flag.c
        os_mem.c
        os_msg.c
        os_mutex.c
        os_prio.c
        os_q.c
        os_sem.c
        os_stat.c
        os_task.c
        os_tick.c
        os_time.c
        os_tmr.c
		os_trace.c
        os_type.h
        os_var.c


(1) MyTest is the name of the directory that will contain the project source files.

(2) app.c is the test file that contains main() and should look as shown below.

main() Example
          /* app.c */
          #include  <os.h>
          #include  "app_cfg.h"
          static  OS_TCB        App_TaskStartTCB;
          static  CPU_STK_SIZE  App_TaskStartStk[APP_CFG_TASK_START_STK_SIZE];
          static  void          App_TaskStart(void  *p_arg);
 
          void  main (void)
          {
              OS_ERR  err;
 

              OSInit(&err);

              OSTaskCreate(&App_TaskStartTCB,
                           "App Task Start",
                            App_TaskStart,
                            0,
                            APP_CFG_TASK_START_PRIO,
                           &App_TaskStartStk[0],
                           (APP_CFG_TASK_START_STK_SIZE / 10u),
                            APP_CFG_TASK_START_STK_SIZE,
                            0,
                            0,
                            0,
                           (OS_OPT_TASK_STK_CHK | OS_OPT_TASK_STK_CLR),
                           &err);

              OSStart(&err);
          }

          static  void  App_TaskStart (void *p_arg)
          {
              OS_ERR  err;
 

              OSTaskSuspend(0, &err);
          }


(3) app_cfg.h should have the following defines:

APP_CFG_TASK_START_PRIO                    2u
APP_CFG_TASK_START_STK_SIZE       256u

(4) The remaining files are copied from the directories shown in the listing at the top of this page, and should not be changed at this point.

(5) You need to edit os_cfg.h and set the following #defines to the values shown below:

OS_CFG_PRIO_MAX                       32u
OS_CFG_STAT_TASK_EN          DEF_DISABLED
OS_CFG_TMR_EN                DEF_DISABLED
OS_CFG_SCHED_ROUNDROBIN_EN   DEF_DISABLED

This is done to ensure that we only have two tasks in the test application, OS_IdleTask() and OS_TickTask().

For this test, you need to add one line of code in OSIdleTaskHook() as shown below. Once we verify OSInit()OSTaskStkInit()OSCtxSw()OS_CTX_SAVE and OS_CTX_RESTORE, we’ll remove this code:

Testing the Application with OSIdleTaskHook()
          void  OSIdleTaskHook (void)
          {
              OSTimeTick();
          #if OS_CFG_APP_HOOKS_EN > 0u
              if (OS_AppIdleTaskHookPtr != (OS_APP_HOOK_VOID)0) {
                  (*OS_AppIdleTaskHookPtr)();
              }
          #endif
          }

Verifying Task Context Switches

In this section, we will verify the proper operation of the following functions/macros:

OSInit()             (os_core.c)
OSStartHighRdy()     (os_cpu_a.asm)
OSTaskStkInit()      (os_cpu_c.c)
OSCtxSw()            (os_cpu_a.asm)
OS_CTX_SAVE          (os_cpu_a.inc)
OS_CTX_RESTORE       (os_cpu_a.inc)
CPU_SR_Save()        (cpu_a.asm)
CPU_SR_Restore()     (cpu_a.asm)
 

Our first test is to verify that µC/OS-III gets properly initialized and that the code in OSTaskStkInit() properly initializes a task’s stack.

Recall that our application consist of app.c which contains the code shown below:

          /* app.c */
          #include  "os.h"
          #include  "app_cfg.h"
          static  OS_TCB        App_TaskStartTCB;
          static  CPU_STK_SIZE  App_TaskStartStk[APP_CFG_TASK_START_STK_SIZE];
          static  void          App_TaskStart(void  *p_arg);
 
          void  main (void)
          {
              OS_ERR  err;

              OSInit(&err);

              OSTaskCreate(&App_TaskStartTCB,
                           "App Task Start",
                            App_TaskStart,
                            0,
                            APP_CFG_TASK_START_PRIO,
                           &App_TaskStartStk[0],
                           (APP_CFG_TASK_START_STK_SIZE / 10u),
                            APP_CFG_TASK_START_STK_SIZE,
                            0,
                            0,
                            0,
                           (OS_OPT_TASK_STK_CHK | OS_OPT_TASK_STK_CLR),
                           &err);

              OSStart(&err);
          }
          static  void  App_TaskStart (void *p_arg)
          {
              OS_ERR  err;

              OSTaskSuspend(0, &err);
          }

STEP 1

You now need to build and download this project to your target. Building is obviously highly toolchain specific. Of course, if you encounter errors during the build, you will need to resolve those before being able to move to the next step.

Once all build errors have been resolved, you need to download the target code onto the evaluation board you selected for the tests.

STEP 2

You then need to set a breakpoint at the OSStart() line. In other words, have your target stop AFTER executing OSInit(). You should then examine the contents of ‘err’ and confirm that it has the value OS_ERR_NONE (or, 0). If you get anything other than OS_ERR_NONE, the error code will tell you where the problem is (see section A-20).

STEP 3

If err is OS_ERR_NONE then you can ‘Step Into’ OSStart() (file os_core.c). You should see the following code:

          void  OSStart (OS_ERR  *p_err)
          {
          #ifdef OS_SAFETY_CRITICAL
              if (p_err == (OS_ERR *)0) {
                  OS_SAFETY_CRITICAL_EXCEPTION();
                  return;
              }
          #endif
 
              :
              :
 
              if (OSTaskQty <= kernel_task_cnt) {                         /* No application task created                          */
                  *p_err = OS_ERR_OS_NO_APP_TASK;
                   return;
              }
              if (OSRunning == OS_STATE_OS_STOPPED) {
                  OSPrioHighRdy   = OS_PrioGetHighest();                       (1)
                  OSPrioCur       = OSPrioHighRdy;
                  OSTCBHighRdyPtr = OSRdyList[OSPrioHighRdy].HeadPtr;          (2)
                  OSTCBCurPtr     = OSTCBHighRdyPtr;
                  OSRunning       = OS_STATE_OS_RUNNING;
                  OSStartHighRdy();                                            (3)
                 *p_err           = OS_ERR_FATAL_RETURN;                          
              } else {
                 *p_err           = OS_ERR_OS_RUNNING;             
              }
          }

STEP 4

Step into the code and stop just before executing OSStartHighRdy(). You should confirm that OSPrioCur is the same value as OS_CFG_TICK_TASK_PRIO (see os_cfg_app.h) and that OSTCBHighRdyPtr point at OSTickTaskTCB. In other words, the highest priority task should be the tick task because we should only have two task created after OSInit() and the tick task always has a higher priority than the idle task.

STEP 5

Now, ‘Step Into’ OSStartHighRdy() (file os_cpu_a.asm). You should see the assembly language shown below.

          OSStartHighRdy:
              OSTaskSwHook();
              SP = OSTCBHighRdyPtr->StkPtr;
              OS_CTX_RESTORE
              Return from Interrupt/Exception;

You can ‘Step Over’ OSTaskSwHook() and the code to load the stack pointer. However, you should set a breakpoint at the ‘Return from Interrupt/Exception’ instruction. Once you executed the OS_CTX_RESTORE macro, you should look at the CPU registers and confirm that they all have their expected value (0x12121212 for R120x05050505 for R5, etc.). If not then something is not quite right with either OSTaskStkInit() or the OS_CTX_RESTORE macro. Basically, OSTaskStkInit() sets up the stack and OS_CTX_RESTORE sets up the registers based on what’s on the stack.

STEP 6

If the CPU registers appear to have their proper value then you can ‘Single Step’ and execute the ‘Return from Interrupt/Exception’ instruction. If all is well, you should be looking at the OS_TickTask() code which should look something like this:

          void  OS_TickTask (void  *p_arg)
          {
              OS_ERR  err;
              CPU_TS  ts;
           
           
              p_arg = p_arg;                                      
              while (DEF_ON) {
                  (void)OSTaskSemPend((OS_TICK  )0,
                                      (OS_OPT   )OS_OPT_PEND_BLOCKING,
                                      (CPU_TS  *)&ts,
                                      (OS_ERR  *)&err); 
                  if (err == OS_ERR_NONE) {                        <- Set a BREAKPOINT here!
                      if (OSRunning == OS_STATE_OS_RUNNING) {
                          OS_TickListUpdate(); 
                      }
                  }
              }
          }

If the debugger doesn’t show you this code then it’s possible that the PC and PSW are not properly setup on the task stack by OSTaskStkInit().

If you end up in OS_TickTask() your code for OSTaskStkInit() and the macro OS_CTX_RESTORE is correct.

You should now set a breakpoint on the line following OSTaskSemPend().

STEP 7

You need to set another breakpoint in OSCtxSw() as shown below.

          OSCtxSw:                                                      <- Set a BREAKPOINT here! 
              OS_CTX_SAVE
              OSTCBCurPtr->StkPtr = SP;
              OSTaskSwHook();
              OSPrioCur   = OSPrioHighRdy;
              OSTCBCurPtr = OSTCBHighRdyPtr;
              SP          = OSTCBCurPtr->StkPtr;
              OS_CTX_RESTORE
              Return from Interrupt/Exception;

You can now run the code at full speed. Because of the breakpoint in OSCtxSw(), the debugger should stop and show you the code for OSCtxSw().

Basically, what’s happening here is that OS_TickTask() will be waiting for the tick ISR to signal the task that a tick has expired. Since we haven’t setup the tick interrupt (not yet anyway), OS_TickTask() would never get to execute. However, I had you modify the idle task hook to simulate signaling the tick task so µC/OS-III will eventually switch back to this code. In the meantime, µC/OS-III will switch to the next task that’s ready-to-run. This happens to be the idle task. We’ll be following the code until we get to OS_IdleTask().

STEP 8

You can ‘Step Over’ OS_CTX_SAVE and verify that the stack (pointed to by SP) contains the value of the CPU registers saved in the same order as they are in OSTaskStkInit(). In fact, you can verify this when context switches back out of the idle task in just a few more steps.

STEP 9

‘Step Into’ the code one more time and verify that the SP was saved in OSTickTaskTCB.StkPtr.

STEP 10

‘Step Into’ the code and stop just before executing the ‘Return from Interrupt/Exception’ instruction. At this point, the CPU registers should contain the proper register values (similar to what we had when we restored the CPU registers for OSTickTask() (but this time it’s for OS_IdleTask()).

STEP 11

‘Step Into’ the return from interrupt/exception instruction and the CPU should now jump into the idle task (os_core.c) as shown below. You should then set a breakpoint as shown.

          void  OS_IdleTask (void  *p_arg)
          {
              CPU_SR_ALLOC();
           
           
              p_arg = p_arg;  
              while (DEF_ON) {
                  CPU_CRITICAL_ENTER();                                 <- Set a BREAKPOINT here!
                  OSIdleTaskCtr++;
          #if OS_CFG_STAT_TASK_EN > 0u
                  OSStatTaskCtr++;
          #endif
                  CPU_CRITICAL_EXIT();
           
                 <strong> OSIdleTaskHook();</strong> 
              }
          }

STEP 12

‘Step Into’ the idle task and then, ‘Step Into’ OSIdleTaskHook(). Recall that I had you modify the idle task hook as shown below. What we’re doing here is simulate the occurrence of the tick interrupt.

          void  OSIdleTaskHook (void)
          {
              <strong>OSTimeTick();</strong>
          #if OS_CFG_APP_HOOKS_EN > 0u
              if (OS_AppIdleTaskHookPtr != (OS_APP_HOOK_VOID)0) {
                  (*OS_AppIdleTaskHookPtr)();
              }
          #endif
          }

STEP 13

Have your debugger run the code at full speed. You should actually hit the breakpoint in OSCtxSw() as shown below. What happened here is that µC/OS-III signaled the tick task and since the tick task is more important than the idle task, µC/OS-III is switching back to the tick task.

          OSCtxSw:                                           <- BREAKPOINT here.
              OS_CTX_SAVE
              OSTCBCurPtr->StkPtr = SP;
              OSTaskSwHook();
              OSPrioCur   = OSPrioHighRdy;
              OSTCBCurPtr = OSTCBHighRdyPtr;
              SP          = OSTCBCurPtr->StkPtr;
              OS_CTX_RESTORE
              Return from Interrupt/Exception;

STEP 14

You can run the target at full speed and the debugger should bring you back at the breakpoint in OS_TickTask().

If you were to repeatedly run the target at full speed, your debugger should now stop at the following places:

OSCtxSw()
OS_IdleTask()
OSCtxSw()
OS_TickTask()
OSCtxSw()
OS_IdleTask()
OSCtxSw()
etc.

Verifying Interrupt Context Switches

In this section, we will verify the proper operation of the following functions/macros:

BSP_OS_TickISR()     (bsp_os.c, bsp_os_a.asm, os_cpu_c.c or os_cpu_a.asm)
OSIntCtxSw()         (os_cpu_a.asm)
OS_ISR_ENTER         (os_cpu_a.inc)
OS_ISR_EXIT          (os_cpu_a.inc)
CPU_INT_EN()         (cpu.h)       
CPU_IntEn()          (cpu_a.asm)
 

You can now remove the code we added in OSIdleTaskHook(). The code should now be as shown below.

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

You should now setup the tick interrupt in main() (app.c) as shown below.

          /* app.c */
          #include  <os.h>
          #include  "app_cfg.h"
          static  OS_TCB        App_TaskStartTCB;
          static  CPU_STK_SIZE  App_TaskStartStk[APP_CFG_TASK_START_STK_SIZE];
          static  void          App_TaskStart(void  *p_arg);
           
          void  main (void)
          {
              OS_ERR  err;
           
              OSInit(&err);
              /* (1) Install interrupt vector for OSTickISR()                           */
              /* (2) Initialize the tick timer to generate interrupts every millisecond */
              OSTaskCreate(&App_TaskStartTCB,
                           "App Task Start",
                            App_TaskStart,
                            0,
                            APP_CFG_TASK_START_PRIO,
                           &App_TaskStartStk[0],
                           (APP_CFG_TASK_START_STK_SIZE / 10u),
                            APP_CFG_TASK_START_STK_SIZE,
                            0,
                            0,
                            0,
                           (OS_OPT_TASK_STK_CHK | OS_OPT_TASK_STK_CLR),
                           &err);
              CPU_INT_EN();       /* (3) Enable interrupts                              */
              OSStart(&err);
          }

 
          static  void  App_TaskStart (void *p_arg)
          {
              OS_ERR  err;
 

              OSTaskSuspend(0, &err);
          }

(1) You need to setup the interrupt vector for the tick ISR. Where this is done greatly depends on the CPU architecture. On some processors, you would simply insert a pointer to BSP_OS_TickISR() in the interrupt vector table while on others, you would need to call a function to install the vector in a RAM table.

(2) You can setup the timer you will use to generate interrupts here. You need to make sure that the interrupt will not occur immediately but instead 1 millisecond after the timer is initialized. You may recall that I told you to always initialize the tick interrupt from the first task that executes when we start multitasking. However, since we are testing the port, it’s safe to initialize the timer here since we have control over when the first interrupt will actually occur.

(3) This macro is used to enable global CPU interrupts. It’s assumed that the startup code runs with interrupts disabled and thus, those need to be explicitly enabled.

At this point, you need to remove all breakpoints you inserted to test the task level context switch code and insert the following breakpoints. You should note that the C-like code should actually be replaced with assembly language instructions for your processor.

          OSIntCtxSw:                                                   <- Set BREAKPOINT here!
              OSTaskSwHook();
              OSPrioCur   = OSPrioHighRdy;
              OSTCBCurPtr = OSTCBHighRdyPtr;
              SP          = OSTCBCurPtr->StkPtr;
              OS_CTX_RESTORE
              Return from Interrupt/Exception;
           
           
          BSP_OS_TickISR:                                               <- Set BREAKPOINT here!
              OS_ISR_ENTER
              Clear tick interrupt;
              OSTimeTick();
              OS_ISR_EXIT

STEP 1

Reset the CPU and run the code until you hit the first breakpoint. If you properly initialized the tick timer then you should be looking at the BSP_OS_TickISR() code. If not, you need to determine why you are not getting the tick interrupt.

If the tick interrupt is properly setup then you should verify that is pointing at OSIdleTaskTCB since your application should have been looping around the idle task until the tick interrupt occurred.

STEP 2

You should step into the BSP_OS_TickISR() code and verify that OS_ISR_ENTER increments OSIntNestingCtr (you should be able to look at that variable with the debugger and notice that it should have a value of 1). Also, you should verify that the current SP is saved in OSTCBCurPtr->StkPtr (it should be the same as OSIdleTaskTCB.StkPtr).

STEP 3

You should now step through the code that clears the tick interrupt and verify that it’s doing the proper thing.

STEP 4

You can now ‘Step Over’ the call to OSTimeTick()OSTimeTick() basically signals the tick task and thus makes it ready-to-run. Instead of returning from interrupt from BSP_OS_TickISR(), µC/OS-III will instead exit through OSIntCtxSw() because the tick ISR has a higher priority than the idle task.

STEP 5

You should now ‘Step Into’ the code for OS_ISR_EXIT and ‘Step Over’ OSIntExit() (os_core.c). OSIntExit() should not return to its caller but instead, call OSIntCtxSw() (os_cpu_a.asm) as shown below. At this point, OSTCBHighRdyPtr should point at OSTickTaskTCB.

          OSIntCtxSw:
              OSTaskSwHook();
              OSPrioCur   = OSPrioHighRdy;
              OSTCBCurPtr = OSTCBHighRdyPtr;
              SP          = OSTCBCurPtr->StkPtr;
              OS_CTX_RESTORE
              Return from Interrupt/Exception;

STEP 6

Before going any further in the code, you should set a breakpoint in OS_TickTask() (os_tick.c) as shown below.

void OS_TickTask (void *p_arg)
{
    OS_ERR err;
    CPU_TS ts;

    p_arg = p_arg;
    while (DEF_ON) {
        (void)OSTaskSemPend((OS_TICK )0,                   <- Set BREAKPOINT here!
                            (OS_OPT  )OS_OPT_PEND_BLOCKING,
                            (CPU_TS *)&ts,
                            (OS_ERR *)&err);
        if (err == OS_ERR_NONE) {
            OS_CRITICAL_ENTER();
            OSTickCtr++;
            OS_CRITICAL_EXIT();
            ts_delta_dly = OS_TickListUpdateDly();
            ts_delta_timeout = OS_TickListUpdateTimeout();
            ts_delta = ts_delta_dly + ts_delta_timeout;
            if (OSTickTaskTimeMax < ts_delta) {
                OSTickTaskTimeMax = ts_delta;
            }
        }
    }
}

STEP 7

You should then go back to os_cpu_a.asm and ‘Step Into’ the code for OS_CTX_RESTORE and then execute the Return from Interrupt/Exception instruction.

This should cause the code to context switch into OS_TickTask(). In fact, you will be in the context of OS_TickTask() but you will not be in the OS_TickTask() code itself. This is because µC/OS-III is actually returning to the point where it invoked the scheduler to switch to the idle task. µC/OS-III is simply returning to that point. You can step through code to see the path µC/OS-III is taking. However, this corresponds to quite a few lines of code. It’s probably simpler to simply run the CPU at full speed and have the debugger stop when you hit the breakpoint in OS_TickTask().

If you were to repeatedly run the target at full speed, your debugger should now stop at the following breakpoints:

  • OSTickISR()
  • OSIntCtxSw()
  • OS_TickTask()
  • BSP_OS_TickISR()
  • OSIntCtxSw()
  • etc.

At this point, the port tests are complete. You should be able to use the µC/OS-III port in your target application.