Single Task Application

The listing below shows the top portion of a simple application file called app.c.

Listing - app.c (1st Part)
/*
***********************************************************************************************
*                                     INCLUDE FILES
***********************************************************************************************
*/
#include <app_cfg.h>                                                        (1)
#include <bsp.h>                                                            
#include <os.h>
/*
***********************************************************************************************
*                                 LOCAL GLOBAL VARIABLES
***********************************************************************************************
*/
static  OS_TCB           AppTaskStartTCB;                                   (2)
static  CPU_STK          AppTaskStartStk[APP_TASK_START_STK_SIZE];          (3)
/*
***********************************************************************************************
*                                  FUNCTION PROTOTYPES
***********************************************************************************************
*/
static  void  AppTaskStart (void *p_arg);                                   (4)

(1) As with any C programs, you need to include the necessary headers to build the application.

app_cfg.h is a header file that configures the application. For our example, app_cfg.h contains #define constants to establish task priorities, stack sizes, and other application specifics.

bsp.h is the header file for the Board Support Package (BSP), which defines macros (#defines) and function prototypes, such as BSP_Init()BSP_LED_On()OS_TS_GET() and more. The actual BSP might contain multiple header files. bsp.h is shown generically here.

os.h is the main header file for µC/OS-III.

 

(2) We will be creating an application task and it is necessary to allocate a task control block (OS_TCB) for this task. The OS_TCB data type will be described in About Task Management.

(3) Each task created requires its own stack. A stack must be declared using the CPU_STK data type as shown. The stack can be allocated statically as shown here, or dynamically from the heap using malloc(). It should not be necessary to free the stack space, because the task should never be destroyed, and thus, the stack would always be used.

(4) This is the function prototype of the task that we will create.


Most C applications start at main() as shown in in the listing below:

Listing - app.c (2nd Part)
void  main (void)
{
    OS_ERR  err;
 
    BSP_IntDisAll();                                                               (1)
    OSInit(&err);                                                                  (2)
    if (err != OS_ERR_NONE) {
        /* Something didn't get initialized correctly ...                    */
        /* ... check os.h for the meaning of the error code, see OS_ERR_xxxx */
    }
    OSTaskCreate((OS_TCB     *)&AppTaskStartTCB,                                   (3)
                 (CPU_CHAR   *)"App Task Start",                                   (4)
                 (OS_TASK_PTR )AppTaskStart,                                       (5)
                 (void       *)0,                                                  (6)
                 (OS_PRIO     )APP_TASK_START_PRIO,                                (7)
                 (CPU_STK    *)&AppTaskStartStk[0],                                (8)
                 (CPU_STK_SIZE)APP_TASK_START_STK_SIZE / 10,                       (9)
                 (CPU_STK_SIZE)APP_TASK_START_STK_SIZE,                           (10)
                 (OS_MSG_QTY  )0,                                                  
                 (OS_TICK     )0,
                 (void       *)0,
                 (OS_OPT      )(OS_OPT_TASK_STK_CHK | OS_OPT_TASK_STK_CLR),       (11)
                 (OS_ERR     *)&err);                                             (12)
    if (err != OS_ERR_NONE) {
        /* The task didn't get created.  Lookup the value of the error code ... */
        /* ... in os.h for the meaning of the error                             */
    }
    OSStart(&err);                                                                (13)
    if (err != OS_ERR_NONE) {
        /* Your code is NEVER supposed to come back to this point.              */
    }
}

(1) The startup code for the compiler will bring the CPU to main()main() then starts by calling a BSP function that disables all interrupts. On most processors, interrupts are disabled at startup until explicitly enabled by application code. However, it is safer to turn off all peripheral interrupts during startup.

(2) OSInit() is the called to initialize µC/OS-III. OSInit() initializes internal variables and data structures, and also creates between two (2) and four (4) internal tasks. Unless otherwise specified in the configuration file (os_cfg.h), at a minimum µC/OS-III creates the idle task (OS_IdleTask()), which executes when no other task is ready-to-run. µC/OS-III also creates the tick task, which is responsible for keeping track of time.

Depending on the value of #define constants, µC/OS-III will create the statistic task (OS_StatTask()) and the timer task (OS_TmrTask()). Those are all discussed in About Task Management.

Most of µC/OS-III’s functions return an error code via a pointer to an OS_ERR variable, err in this case. If OSInit() was successful, err will be set to OS_ERR_NONE. If OSInit() encounters a problem during initialization, it will return immediately upon detecting the problem and set err accordingly. If this occurs, look up the error code value in os.h. Specifically, all error codes start with OS_ERR_.

It is important to note that OSInit() must be called before calling any other µC/OS-III function.

(3) You create a task by calling OSTaskCreate()OSTaskCreate() requires 13 arguments. The first argument is the address of the OS_TCB that is declared for this task. About Task Management provides additional information about tasks.

(4) OSTaskCreate() allows a name to be assigned to each of the tasks. µC/OS-III stores a pointer to the task name inside the OS_TCB of the task. There is no limit on the number of ASCII characters used for the name.

(5) The third argument is the address of the task code. A typical µC/OS-III task is implemented as an infinite loop as shown:

Listing - app.c (3rd Part)
          void  MyTask (void *p_arg)
          {
              /* Do something with "p_arg".
              while (1) {
                  /* Task body */
              }
          }

The task receives an argument when it first starts. As far as the task is concerned, it looks like any other C function that can be called by the code. However, your code must not call MyTask(). The call is actually performed through µC/OS-III.

(6) The fourth argument of OSTaskCreate() is the actual argument that the task receives when it first begins. In other words, the “p_arg of MyTask(). In the example a NULL pointer is passed, and thus “p_arg” for AppTaskStart() will be a NULL pointer.

The argument passed to the task can actually be any pointer. For example, the user may pass a pointer to a data structure containing parameters for the task.

(7) The next argument to OSTaskCreate() is the priority of the task. The priority establishes the relative importance of this task with respect to the other tasks in the application. A low-priority number indicates a high priority (or more important task). You can set the priority of the task to any value between 1 and OS_CFG_PRIO_MAX-2, inclusively. Avoid using priority 0, and priority OS_CFG_PRIO_MAX-1, because these are reserved for µC/OS-III. OS_CFG_PRIO_MAX is a compile time configuration constant, which is declared in os_cfg.h.

(8) The sixth argument to OSTaskCreate() is the base address of the stack assigned to this task. The base address is always the lowest memory location of the stack.

(9) The next argument specifies the location of a “watermark” in the task’s stack that can be used to determine the allowable stack growth of the task. See About Task Management for more details on using this feature. In the code above, the value represents the amount of stack space (in CPU_STK elements) before the stack is empty. In other words, in the example, the limit is reached when there is 10% of the stack left.

(10) The eighth argument to OSTaskCreate() specifies the size of the task’s stack in number of CPU_STK elements (not bytes). For example, if you want to allocate 1 Kbyte of stack space for a task and the CPU_STK is a 32-bit word, then you need to pass 256.

(11) The next three arguments are skipped as they are not relevant for the current discussion. The 12th argument to OSTaskCreate() specifies options. In this example, we specify that the stack will be checked at run time (assuming the statistic task was enabled in os_cfg.h), and that the contents of the stack will be cleared when the task is created.

(12) The last argument of OSTaskCreate() is a pointer to a variable that will receive an error code. If OSTaskCreate() is successful, the error code will be OS_ERR_NONE otherwise, you can look up the value of the error code in os.h (see OS_ERR_xxxx) to determine the cause of the error.

(13) The final step in main() is to call OSStart(), which starts the multitasking process. Specifically, µC/OS-III will select the highest-priority task that was created before calling OSStart().


A few important points are worth noting. For one thing, you can create as many tasks as you want before calling OSStart(). However, it is recommended to only create one task as shown in the example because, having a single application task allows µC/OS-III to determine the relative speed of the CPU. This allows µC/OS-III to determine the percentage of CPU usage at run-time. Also, if the application needs other kernel objects such as semaphores and message queues then it is recommended that these be created prior to calling OSStart(). Finally, notice that interrupts are not enabled. This will be discussed next by examining the contents of AppTaskStart(), which is shown in the listing below:

Listing - AppTaskStart()
static  void  AppTaskStart (void  *p_arg)                       (1)
{
    OS_ERR  err;
    
    
    p_arg = p_arg;
    BSP_Init();                                                 (2)
    CPU_Init();                                                 (3)
    BSP_OS_TickInit();                                          (4)
    BSP_LED_Off(0);                                             (5)
    while (1) {                                                 (6)
        BSP_LED_Toggle(0);                                      (7)
        OSTimeDlyHMSM((CPU_INT16U)  0,                          (8)
                      (CPU_INT16U)  0, 
                      (CPU_INT16U)  0, 
                      (CPU_INT32U)100, 
                      (OS_OPT    )OS_OPT_TIME_HMSM_STRICT,
                      (OS_ERR   *)&err);
        /* Check for 'err' */
    }
}

(1) As previously mentioned, a task looks like any other C function. The argument “p_arg” is passed to AppTaskStart() by OSTaskCreate(), as discussed in the previous listing description.

(2) BSP_Init() is a Board Support Package (BSP) function that is responsible for initializing the hardware on an evaluation or target board. The evaluation board might have General Purpose Input Output (GPIO) lines that might need to be configured, relays, sensors and more. This functionality is found in a file called bsp.c.

(3) CPU_Init() initializes the µC/CPU services. µC/CPU provides services to measure interrupt latency, obtain time stamps, and provides emulation of the count leading zeros instruction if the processor used does not have that instruction, and more.

(4) BSP_OS_TickInit() sets up the µC/OS-III tick interrupt. For this, the function needs to initialize one of the hardware timers to interrupt the CPU at a rate of: OSCfg_TickRate_Hz, which is defined in os_cfg_app.h (See OS_CFG_TICK_RATE_HZ).

(5) BSP_LED_Off() is a function that will turn off all LEDs. BSP_LED_Off() is written such that a zero argument means all the LEDs.

(6) Most µC/OS-III tasks will need to be written as an infinite loop.

(7) This BSP function toggles the state of the specified LED. Again, a zero indicates that all the LEDs should be toggled on the evaluation board. You simply change the zero to 1 and this will cause LED #1 to toggle. Exactly which LED is LED #1? That depends on the BSP developer. Specifically, access to LEDs are encapsulated through the functions: BSP_LED_On()BSP_LED_Off() and BSP_LED_Toggle(). Also, for sake of portability, we prefer to assign LEDs logical values (1, 2, 3, etc.) instead of specifying which port and which bit on each port.

(8) Finally, each task in the application must call one of the µC/OS-III functions that will cause the task to “wait for an event”. The task can wait for time to expire (by calling OSTimeDly(), or OSTimeDlyHMSM()), or wait for a signal or a message from an ISR or another task. In the code shown, we used OSTimeDlyHMSM() which allows a task to be suspended until the specified number of hours, minutes, seconds and milliseconds have expired. In this case, 100 ms. Time Management provides additional information about time delays.