Embedded_Systems
嵌入式系统教学与研究实验室
posts - 2,comments - 3,trackbacks - 0
Lesson 1: Getting Started with TinyOS and nesC

Last updated 9 September 2003

This lesson introduces the basic concepts of TinyOS and the nesC language in which the system is written. It includes a quick overview of the nesC language concepts and syntax to help you get started with programming in this environment.
Introduction

The TinyOS system, libraries, and applications are written in nesC, a new language for programming structured component-based applications. The nesC language is primarily intended for embedded systems such as sensor networks. nesC has a C-like syntax, but supports the TinyOS concurrency model, as well as mechanisms for structuring, naming, and linking together software components into robust network embedded systems.(不只是一个简单的语言编译器,还包含了一种基于组件和并发的OS模型在里面,直接生成一个含OS的完整系统,有必要对NesC进行进一步的分析了解

   The principal goal is to allow application designers to build components that can be easily composed into complete, concurrent systems, and yet perform extensive checking at compile time.

TinyOS defines a number of important concepts that are expressed in nesC:

  • First, nesC applications are built out of components with well-defined, bidirectional interfaces
  • Second, nesC defines a concurrency model, based on tasks and hardware event handlers, and detects data races at compile time.(NesC定义了一个基于Task和硬件事件处理的并发模型)

Components

  Specification
  一个nesC应用包含一个或多个components,它们连接在一起构成一个可执行的应用.

  一个component 提供和使用接口. 这些接口是双向的,是访问component的唯一访问点.

  interface声明称为commands的interface提供者必须实现的函数集合,同时声明另一个称为events的函数集,该接口的使用者必须实现这些事件处理函数.当一个component调用接口中的commands时,它必须实现该接口的events. 单个component可以use或provide多个interfaces和同一接口的多个实例.

  Implementation

  nesC中包含两类components:

  • modules:提供应用代码,实现一个或多个interface.
  • configurations:用来组装其他components到一起, 连接这些components使用的interfaces到其他组件提供的interfaces. 这也称为wiring(连线,类似VHDL中的结构模型). 每个nesC应用通过把内部组建连接起来的top-level configuration进行描述.

   nesC对所有源文件使用".nc"扩展名 -- interfaces, modules, and configurations. Please see TinyOS Coding and Naming Conventions for more information on naming conventions.

Concurrency Model

   TinyOS仅执行一个程序,包含运行应用所需的系统组件和定制组件(用户定义编制的). 存在两条执行threads:

  • tasks:任务就是函数,其执行是预留的(defered),一旦调度,它们就被执行,直至完成,无法被剥夺.
  • hardware event handlers硬件中断响应程序,同样运行到完成,但是可以剥夺task和其他hardware event handler的执行。作为硬件事件处理程序的一部分执行的Commands和events必须用async关键字声明.

    因为任务和硬件事件处理程序可以被其他异步(asynchronous)代码占先执行,nesC程序可能会出现某种竞争条件(race conditions). 这可以通过访问任务间互斥的共享数据来避免,也可以通过在atomic语句内完成所有对共享数据的访问. nesC编译器可以在编译时报告潜在的data races,存在误报的可能,这种情况可以使用norace关键字来声明相关变量,但是该关键字的使用要非常慎重.

   Please see the nesC Language Reference Manual for more information on programming in nesC.
 
An example application: Blink

   So far this is all fairly abstract - let's look at a concrete example: the simple test program "Blink" found in apps/Blink in the TinyOS tree. This application simply causes the red LED on the mote to turn on and off at 1Hz.

   Blink application is composed of two components:

  • a module:"BlinkM.nc"
  • a configuration:"Blink.nc". 
   Remember that all applications require a top-level configuration file, which is typically named after the application itself. In this case Blink.nc is the configuration for the Blink application and the source file that the nesC compiler uses to generate an executable file. BlinkM.nc, on the other hand, actually provides the implementation of the Blink application. As you might guess, Blink.nc is used to wire the BlinkM.nc module to other components that the Blink application requires.

The reason for the distinction between modules and configurations is to allow a system designer to quickly "snap together" applications. For example, a designer could provide a configuration that simply wires together one or more modules, none of which she actually designed. Likewise, another developer can provide a new set of "library" modules that can be used in a range of applications.

Sometimes (as is the case with Blink and BlinkM) you will have a configuration and a module that go together. When this is the case, the convention used in the TinyOS source tree is that Foo.nc represents a configuration and FooM.nc represents the corresponding module. While you could name an application's implementation module and associated top-level configuration anything, to keep things simple we suggest that you adopt this convention in your own code. There are several other naming conventions used in TinyOS code; a summary is provided
The Blink.nc configuration

The nesC compiler, ncc, compiles a nesC application when given the file containing the top-level configuration. Typical TinyOS applications come with a standard Makefile that allows platform selection and invokes ncc with appropriate options on the application's top-level configuration.

Let's look at Blink.nc, the configuration for this application first:

Blink.nc
configuration Blink {
 //其中可以说明uses和provides语句,配置可以使用和提供接口
}
implementation { //配置的具体实现
  components Main, BlinkM, SingleTimer, LedsC;//本配置涉及到的组件

//下面是连线,类似VHDL语言中的结构模型中信号的连接
//左边的接口(use)绑定到右边的接口实现(provide)
  Main.StdControl -> BlinkM.StdControl;
  Main.StdControl -> SingleTimer.StdControl;
  BlinkM.Timer -> SingleTimer.Timer;
  BlinkM.Leds -> LedsC; //相当于BlinkM.Leds -> LedsC.Leds
}

The first thing to notice is the key word configuration, which indicates that this is a configuration file. The first two lines,

  configuration Blink {
  }
simply state that this is a configuration called Blink. Within the empty braces here it is possible to specify uses and provides clauses, as with a module. This is important to keep in mind: a configuration can use and provide interfaces!

The actual configuration is implemented within the pair of curly bracket following key word implementation . The componentsline specifies the set of components that this configuration references, in this case Main, BlinkM, SingleTimer, and LedsC. The remainder of the implementation consists of connecting interfaces used by components to interfaces provided by others.

Main是TinyOS应用第一个执行的组件.更精确的说,Main.StdControl.init()命令TinyOS执行的第一个命令,紧跟着是Main.StdControl.start()命令. 因此任何TinyOS应用必须在其顶层配置中包含Main组件StdControl是一个用于初始化和启动TinyOS组件的公共interface. Let us have a look at tos/interfaces/StdControl.nc:

StdControl.nc
interface StdControl {
  command result_t init(); //组件初始化时被调用,可调用多次但必须在start和stop之前
  command result_t start();//组件启动,即组件实际第一次执行,可调用多次
  command result_t stop(); //停止组件,可调用多次
}

We see that StdControl defines three commands

  • init():called when a component is first initialized,init() can be called multiple times, but will never be called after either start() or stop are called.
  • start():called when a component is started, that is, actually executed for the first time;
  • stop():called when the component is stopped, for example, in order to power off the device that it is controlling.  
    Specifically, the valid call patterns of StdControl are init*(start | stop)* . All three of these commands have "deep" semantics; calling init() on a component must make it call init() on all of its subcomponents. The following 2 lines in Blink configuration
  Main.StdControl -> SingleTimer.StdControl;
  Main.StdControl -> BlinkM.StdControl;
wire the StdControl interface in Main to the StdControl interface in both BlinkM and SingleTimer. SingleTimer.StdControl.init()andBlinkM.StdControl.init() will be called by Main.StdControl.init(). The same rule applies to the start() and stop() commands.

Concerning used interfaces, it is important to note that subcomponent initialization functions must be explicitly called by the using component. For example, the BlinkM module uses the interface Leds, so Leds.init() is called explicitly in BlinkM.init().

nesC uses arrows to determine relationships between interfaces. Think of the right arrow (->) as "binds to". The left side of the arrow binds an interface to an implementation on the right side. In other words, the component that uses an interface is on the left, and the component provides the interface is on the right.

The line

  BlinkM.Timer -> SingleTimer.Timer;
is used to wire the Timer interface used by BlinkM to the Timer interface provided by SingleTimer. BlinkM.Timer on the left side of the arrow is referring to the interface called Timer (tos/interfaces/Timer.nc), while SingleTimer.Timer on the right side of the arrow is referring to the implementation of Timer (tos/lib/SingleTimer.nc). Remember that the arrow always binds interfaces (on the left) to implementations (on the right).

nesC supports multiple implementations of the same interface. The Timer interface is such a example. The SingleTimer component implements a single Timer interface while another component, TimerC, implements multiple timers using timer id as a parameter. Further discussions on timers can be found in Lesson 2.

Wirings can also be implicit. For example,

  BlinkM.Leds -> LedsC;
is really shorthand for
  BlinkM.Leds -> LedsC.Leds;
If no interface name is given on the right side of the arrow, the nesC compiler by default tries to bind to the same interface as on the left side of the arrow.
 
 
The BlinkM.nc module

Now let's look at the module BlinkM.nc:

BlinkM.nc
module BlinkM {
  provides {
    interface StdControl;
  }
  uses {
    interface Timer;
    interface Leds;
  }
}
// Continued below...

The first part of the code states that this is a module called BlinkMand declares the interfaces it provides and uses.  The BlinkM  module provides the interface StdControl.  This means that BlinkM implements the StdControl interface.  As explained above, this is necessary to get the Blink component initialized and started.  The BlinkM module also uses two interfaces: Leds and Timer. 这意味着BlinkM可以调用它use的接口中声明的任何command,而且必须实现这些接口中声明的任何events.

The Leds interface defines several commands like redOn(),redOff(), and so forth, which turn the different LEDs (red, green, or yellow) on the mote on and off. Because BlinkM uses the Leds interface, it can invoke any of these commands. Keep in mind, however, that Leds is just an interface: the implementation is specified in the Blink.nc configuration file.

Timer.nc is a little more interesting:

Timer.nc
interface Timer {
  command result_t start(char type, uint32_t interval);
  command result_t stop();
  event result_t fired();//本接口会触发fired事件,使用者必须实现该事件的响应程序
}

Here we see that Timer interface defines the start() and stop() commands, and the fired() event.

The start() command is used to specify the type of the timer and the interval at which the timer will expire. The unit of the interval argument is millisecond. The valid types are TIMER_REPEAT and TIMER_ONE_SHOT. A one-shot timer ends after the specified interval, while a repeat timer goes on and on until it is stopped by the stop() command.

How does an application know that its timer has expired? The answer is when it receives an event. The Timer interface provides an event:

  event result_t fired();
An event is a function that the implementation of an interface will signal when a certain event takes place. In this case, the fired() event is signaled when the specified interval has passed. This is an example of a bi-directional interface: interface不仅提供commands供 接口使用者调用, 同时也会触发events,这将调用使用者接口中定义的事件处理程序。可以把event当成一个接口实现将调用的回调函数. 使用某个接口的模块必须实现该接口中使用的事件处理程序.

Let's look at the rest of BlinkM.nc to see how this all fits together:

BlinkM.nc, continued
implementation {

  command result_t StdControl.init() {
    call Leds.init();//显式调用init
    return SUCCESS;
  }

  command result_t StdControl.start() {
    return call Timer.start(TIMER_REPEAT, 1000) ;
  }

  command result_t StdControl.stop() {
    return call Timer.stop();
  }

  event result_t Timer.fired()
  {
    call Leds.redToggle();
    return SUCCESS;
  }
}

This is simple enough. As we see the BlinkM module implements the StdControl.init(), StdControl.start(), and StdControl.stop() commands, since it provides the StdControl interface. It also implements the Timer.fired() event, which is necessary since BlinkM must implement any event from an interface it uses.

The init() command in the implemented StdControl interface simply initializes the Leds subcomponent with the call to Leds.init(). The start() command invokes Timer.start() to create a repeat timer that expires every 1000 ms. stop() terminates the timer. Each time Timer.fired() event is triggered, the Leds.redToggle() toggles the red LED.

You can view a graphical representation of the component relationships within an application. TinyOS source files include metadata within comment blocks that ncc, the nesC compiler, uses to automatically generate html-formatted documentation. To generate the documentation, type make <platform> docs from the application directory. The resulting documentation is located in docs/nesdoc/<platform>.docs/nesdoc/<platform>/index.html is the main index to all documented applications.
 
Compiling the Blink application

TinyOS supports multiple platforms. Each platform has its own directory in the tos/platform directory. In this tutorial, we will use the mica platform as an example. If you are in the TinyOS source tree, compiling the Blink application for the Mica mote is as simple as typing

  make mica
in the apps/Blink directory. Of course this doesn't tell you anything about how the nesC compiler is invoked.

nesC itself is invoked using the ncc command which is based on gcc. For example, you can type

  ncc -o main.exe -target=mica Blink.nc
to compile the Blink application (from the Blink.nc top-level configuration) to main.exe, an executable file for the Mica mote. Before you can upload the code to the mote, you use
  avr-objcopy --output-target=srec main.exe main.srec
to produce main.srec, which essentially represents the binary main.exe file in a text format that can be used for programming the mote. You then use another tool (such as uisp) to actually upload the code to the mote, depending on your environment. In general you will never need to invoke ncc or avr-objcopy by hand, the Makefile does all this for you, but it's nice to see that all you need to compile a nesC application is to run ncc on the top-level configuration file for your application. ncc takes care of locating and compiling all of the different components required by your application, linking them together, and ensuring that all of the component wiring matches up.
 
 
Programming the mote and running Blink

Now that we've compiled the application it's time to program the mote and run it. This example will use the Mica mote and the parallel-port-based programming board (mib500). Instructions on how to use the other programming boards are here. To download your program onto the mote, place the mote board (or mote and sensor stack) into the bay on the programming board, as shown below. You can either supply a 3 volt supply to the connector on the programming board or power the node directly. The red LED (labeled D2) on the programming board will be on when power is supplied. If you are using batteries to power the mote, be sure the mote is switched on (the power switch should be towards the connector).

Plug the 32-pin connector into the parallel port of a computer configured with the TinyOS tools, using a standard DB32 parallel port cable.

mica-offboard.jpgmica-onboard.jpg

Type: make mica install. If you get the error:

  uisp -dprog=dapa --erase 
  pulse
  An error has occurred during the AVR initialization.
   * Target status:
      Vendor Code = 0xff, Part Family = 0xff, Part Number = 0xff

  Probably the wiring is incorrect or target might be `damaged'.
  make: *** [install] Error 2
check whether the power is on. You can also get this error message if the mote is low on batteries (if you are using batteries), or if the wrong version of the uisp programming utility is installed (be sure to use the version in the TinyOS distribution).

If you are using an IBM ThinkPad, it may be necessary to tell the tools to use a different parallel port. You can do this by adding the line

  PROGRAMMER_EXTRA_FLAGS = -dlpt=3
to the apps/Makelocal file (create it if it doesn't exist). The Makelocal file is for user-specific Makefile configuration.

If the installation is successful you should see something like the following:

  compiling Blink to a mica binary
  ncc -board=micasb -o build/mica/main.exe -Os -target=mica  -Wall -Wshadow -DDEF_TOS_AM_GROUP=0x7d -finline-limit=200 -fnesc-cfile=build/mica/app.c  Blink.nc -lm
  avr-objcopy --output-target=srec build/mica/main.exe
  build/mica/main.srec
      compiled Blink to build/mica/main.srec
      installing mica binary
  uisp -dprog=dapa  --erase 
  pulse
  Atmel AVR ATmega128 is found.
  Erasing device ...
  pulse
  Reinitializing device
  Atmel AVR ATmega128 is found.
  sleep 1              
  uisp -dprog=dapa  --upload if=build/mica/main.srec
  pulse
  Atmel AVR ATmega128 is found.
  Uploading: flash
  sleep 1              
  uisp -dprog=dapa  --verify if=build/mica/main.srec
  pulse
  Atmel AVR ATmega128 is found.
  Verifying: flash
You can now test the program by unplugging the mote from the programming board and turning on the power switch (if it's not already on). With any luck the red LED should light up every second - congratulations!

Typing make clean in the Blink directory will clean up the compiled binary files.

If you are still having errors, then you need to check your TinyOS installation and check the Mica hardware. See System and Hardware Verification for details.
 
Exercises

To test your new-found TinyOS programming skills, try out the following:

  • Modify Blink to display the lower three bits of a counter in the LEDs.
Conclusion

This tutorial has just scratched the surface of nesC's syntax and features. Rather than document everything extensively, we refer the reader to the nesC Project Pages as well as the documentation included with the nesC distribution in nesc/doc. These sources contain more complete documentation on the language.

Hopefully this should be enough of a start to get you going on programming in this fun new language.

posted on 2005-11-28 00:54 井冈山 阅读(1971) 评论(0)  编辑 收藏 引用 所属分类: tinyOS
只有注册用户登录后才能发表评论。