Each task has a corresponding task structure that holds information such as the priority and the saved register state. You allocate task structures by declaring them as C variables.

CTL_TASK_t mainTask;

You create the first task using the ctl_task_init function which turns the main program into a task. This function takes a pointer to the task structure that represents the main task, it’s priority and a name as parameters.

ctl_task_init(&mainTask, 255, "main");

This function must be called before any other CrossWorks tasking library calls are made. The priority (second parameter) must be between 0 (the lowest priority) and 255 (the highest priority). It is advisable to create the first task with the highest priority which enables the main task to create other tasks without being descheduled. The name should point to a zero terminated ASCII string for debug purposes. When this function has been called global interrupts will be enabled, so you must ensure that any interrupt sources are disabled before calling this function.

You can create other tasks using the function ctl_task_run which initialises a task structure and may cause a context switch. You supply the same arguments as task_init together with the function that the task will run and the memory that the task will use as its stack.

The function that a task will run should take a void * parameter and not return any value.

void task1Fn(void *parameter)
{
  // task code goes in here
  …
}

The parameter value is supplied to the function by the ctl_task_run call. Note when a task function returns the ctl_task_die function is called which terminates the task.

You have to allocate the stack for the task as an C array of unsigned.

unsigned task1Stack[64];

The size of the stack you need depends on the CPU type (the number of registers that have to be saved), the function calls that the task will make and (depending upon the CPU) the stack used for interrupt service routines. Running out of stack space is common problem with multi-tasking systems and the error behaviour is often misleading. It is recommended that you initialise the stack to known values so that you can check the stack with the CrossWorks debugger if problems occur.

memset(task1Stack, 0xba, sizeof(task1Stack));

Your ctl_task_run function call should look something like this.

ctl_task_run(&task1Task,
             12,
             task1Fn,
             0,
             "task1",
             sizeof(task1Stack) / sizeof(unsigned),
             task1Stack,
             0);

The first parameter is a pointer to the task structure. The second parameter is the priority (in this case 12) the task will start executing at. The third parameter is a pointer to the function to execute (in this case task1Fn). The fourth parameter is the value that is supplied to the task function (in this case zero). The fifth parameter is a null terminated string that names the task for debug purposes. The sixth parameter is the size of the stack in words. The seventh parameter is the pointer to the stack. The last parameter is for systems that have a seperate call stack and is the number of words to reserve for the call stack.

You can change the priority of a task using the ctl_task_set_priority function call which takes a pointer to a task structure and the new priority as parameters.

ctl_task_set_priority(&mainTask, 0);
Example

The following example turns main into a task and creates another task. The main task ultimately will be the lowest priority task that switches the CPU into a power save mode when it is scheduled - this satisfies the requirement of always having a task to execute and enables a simple power saving system to be implemented.

#include <ctl_api.h>
void task1(void *p)
{
  // task code, on return task will be terminated
}
static CTL_TASK_t mainTask, task1Task;
static unsigned task1Stack[64];
int
main(void)
{
  // Turn myself into a task running at the highest priority.
  ctl_task_init(&mainTask, 255, "main");

  // Initialise the stack of task1.
  memset(task1Stack, 0xba, sizeof(task1Stack)/sizeof(unsigned));
  
  // Make another task ready to run.
  ctl_task_run(&task1Task, 1, task1, 0, "task1", sizeof(task1Stack) / sizeof(unsigned), task1Stack, 0);

  // Now all the tasks have been created go to lowest priority.
  ctl_task_set_priority(&mainTask, 0);

  // Main task, if activated because task1 is suspended, just
  // enters low power mode and waits for task1 to run again
  // (for example, because an interrupt wakes it).
  for (;;)
    {
      // Go into low power mode
      sleep();
    }
}

Note that initially the main task is created at the highest priority whilst it creates the other tasks, it then changes its priority to the lowest task. This technique can be used when multiple tasks are created to enable all of the tasks to be created before they start to execute.

Note the usage of sizeof when passing the stack size to ctl_task_run.