Lesson 3: Introducing Tasks for Application Data Processing

Last updated 25 September 2002

This lesson introduces the TinyOS notion of tasks, which can be used to perform general-purpose "background" processing in an application. This lesson makes use of the SenseTask application, which is a revision of the Sense application from the previous lesson.

Task creation and scheduling

TinyOS provides a two-level scheduling hierarchy consisting of tasks and events. As we have seen, an event is a generalization of an interrupt handler, a call that is made from a lower-level component to a higher-level component in response to some event. Events must only do a small amount of work before completing, because events always run to completion and cannot be preempted. Tasks are used to perform longer processing operations, such as background data processing, that can be preempted by events.

A task is declared in your implementation module using the syntax

  task void taskname() { ... }
where taskname() is whatever symbolic name you want to assign to the task. Tasks must return void and may not take any arguments.

To dispatch a task for (later) execution, use the syntax

  post taskname();
A task can be posted from within a command, an event, or even another task.

The post operation places the task on an internal task queue which is processed in FIFO order. When a task is executed, it runs to completion before the next task is run; therefore, a task should not spin or block for long periods of time. Tasks do not preempt each other, but a task can be preempted by events. If you need to run a series of long operations, you should dispatch a separate task for each operation, rather than using one task that spins in a loop.

The SenseTask Application

To illustrate tasks, we have modified the Sense application from Lesson 2, which is found in apps/SenseTask. The SenseTaskM component maintains a circular data buffer that contains recent photo sensor samples; the putdata() function is used to insert a new sample into the buffer. The dataReady() event simply deposits data into the buffer and posts a task, called processData(), for processing.

SenseTaskM.nc
  // ADC data ready event handler
  event result_t ADC.dataReady(uint16_t data) {
    putdata(data);
    post processData();
    return SUCCESS;
  }

Some time after the event completes (there may be other tasks pending for execution), the processData() task will run. It computes the sum of the recent ADC samples and displays the upper three bits of the sum on the LEDs.

SenseTaskM.nc, continued
  task void processData() {
    int16_t i, sum=0;

    for (i=0; i ‹ maxdata; i++)
      sum += (rdata[i] ›› 7);
    display(sum ›› shiftdata);
  }

Exercises

Try to break up the processData() task so that each invocation of the task only adds one element of the data array to the sum. processData() should then post itself to continue processing the complete sum, and display the LEDs when the final element of the array has been processed. Be careful of concurrency issues - since processData() is also posted from ADC.dataReady(), you might want to add a flag to prevent a new instance of the task from being started before the previous sum has been computed!


< Previous Lesson | Next Lesson >