TinyOS系统与nesC程序设计课内实验指导书

更新时间:2024-04-18 15:48:01 阅读量: 综合文库 文档下载

说明:文章内容仅供预览,部分内容可能不全。下载后的文档,内容与下面显示的完全一致。下载之前请确认下面内容是否您想要的,是否完整无缺。

TinyOS系统与nesC程序设计课内实验指导书

一、 课内实验项目一览表

序号 1 实验项目 编程环境的建立 学时 类型 2 2 每组 人数 1 基本教学要求 熟悉TinyOS安装过程,了解系统环境 通过Blink程序,理解TinyOS编程结构 通过实验了解任务的建立、调度及其作用 通过实验了解和使用基本通信接口和组件 了解数据包源和串口通信 了解节点如何从环境中采集数据并显示在PC上 了解TinyOS上永久数据存储方法及应用 了解线程工作原理和实现方法 2 Blink—TinyOS编程的“Hello World” 2 2 1 3 TinyOS任务及应用举例 2 2 1 4 节点间的无线通信 2 2 1 5 节点与PC的通信 2 2 1 6 传感 2 2 1 7 存储 2 2 1 8 多线程程序设计 2 2 1

二、详细实验指导书

实验一:编程环境建立

一 实验目的

1、 掌握Java基本开发环境(JDK)的安装和配置方法。

2、 掌握cygwin程序的安装,在建立windows下类似Unix环境。 3、 掌握Linux基本命令及cygwin下安装TinyOS的方法。 二 实验原理 无

三 实验环境

1、运行Windows的PC机,能够连接Internet 2、PC机配置要求: CPU 内存 硬盘容量 操作系统 最低CR 1GHz,建议P4 2.0以上 最低256M,建议512M以上 10G以上,cygwin安装盘符大于2 G Windows 2000 系列、Windows XP 系列

四 实验内容和步骤

1、 java jdk安装

首先,我们安装JAVA 开发工具JAVA JDK 1.6

官方下载地址:http://java.sun.com/javase/downloads/.

安装过程只需下一步??下一步便可,默认安装路径是C:\\Program Files\\Java

上图是TinyOS 2.0.2 安装时截取的JAVA JDK1.5的图,只供参考。

然后,我们需要设置电脑的环境变量,需要新建两个环境变量,以便使用JDK 具体过程如下:右击我的电脑——〉属性——〉高级——〉环境变量

先新建或编辑系统变量: 变量名(N):JAVA_HOME

变量值(V):JDK安装的路径,其默认的路径为: C:\\Program Files\\Java\\jdk1.6.0_10

变量名(N):CLASSPATH 变量值(V):

.;%JAVA_HOME%\\lib\\dt.jar;%JAVA_HOME%\\lib\\tools.jar;;%JAVA_HOME%\\bin;%JAVA_HOME%\\jre\\bin;

在系统变量(S)栏选中变量为Path的选项,点编辑

在变量值(V)的末尾添加:;%JAVA_HOME%\\bin; ;%JAVA_HOME%\\jre\\bin; 然后新建或编辑用户变量:

同样再设置一个JAVA_HOME,变量值也一样。

在用户变量的PATH中添加:;%JAVA_HOME%\\bin:$PATH;%JAVA_HOME%\\jre\\bin:$PATH;

这样,我们的环境变量已经设置完毕了。我们可以编个JAVA小程序简单地测试一下:打开记事本,输入下面这个小程序,另存为HelloWorld.java(注意扩展名是java,不是txt文本文件)。

public class HelloWorld {

public static void main(String[] args) { System.out.println(\ } }

运行cmd,到HelloWorld.java所在目录,执行命令:

javac HelloWorld.java java HelloWorld

如果能正确输出Hello, World! 说明环境变量设置成功。

2、 cygwin软件平台安装

我们需要安装Cygwin这个软件来虚拟Linux平台,运行TinyOS-2.X。 下载地址1:www.cygwin.com.

下载地址2:http://cone.informatik.uni-freiburg.de/people/aslam/cygwin-files.zip

安装时,最好先将安装包下载到本地,然后选择Install from Local Directory安装。

紧接着需要设置安装目录,默认是C:\\cygwin

然后是选择下载的本地安装包源文件的文件路径

安装包策略选择:“Keep”、“Prev”、“Curr”、“Exp”等等选项

Keep, 意思就是说保持目前已经安装的版本不动,不替换你目前的版本。升级时比较方便。 Prev,意思是说安装上一个版本。

Curr,意思就是说把最新的版本下载下来安装,第一次安装时默认选项 \按钮是用来选择显示方式的

在选择安装包时候,一般默认,点下一步即可。

最后我们可以通过桌面上添加Cygwin的快捷方式或者运行安装目录下的cwgwin.bat文件,来运行该软件平台。第一次运行Cygwin会稍微慢点。

Cygwin启动界面如下图:

下面我们可以先来尝试一些简单的Linux命令:

help: 帮助命令。例如 ls --help 。显示ls命令使用说明。 pwd:显示所在路径。

rpm –qa :查看已经安装的程序,刚安装的Cygwin是没有安装任何系统的。 rpm –ivh 文件名:rpm文件安装。

rpm –ignoreos –force或者rpm -ivh --force --ignoreos:忽略错误,安装rpm文件 --ignoreos选项是用于忽略cygwin的版本号 rpm –uvh 文件名:rpm文件升级安装。

cd:切换目录 比如:cd /tmp 进入tmp文件。

cp:cp -R Blink BlinkSingle:复制文件或目录,复制Blink为BlinkSingle。 ls:显示当前目录下的文件。

whoami:显示登陆Windows帐号 uname –a: 显示版本信息

echo $PATH: 显示执行程式的搜索路径

ps ax: 显示process list ,显示目前有哪些process 执行 ctrl-D或exit 或logout:结束bash 视窗 motelist 查询当前设备 3、 本地编译器安装

当你给低功耗微控制器编译代码时,你需要可以产生适当的代码的编译器。如果你使用mica系列无线传感器节点,你需要的AVR工具;如果你使用的是telos系列节点,则需要的MSP430工具。

先以AVR为例,在根目录下创建文件夹avr,把下面所有rpm文件放在这个文件夹里。 ? avr-binutils-2.17tinyos-3.cygwin.i386.rpm :

http://www.tinyos.net/dist-2.1.0/tools/windows/avr-binutils-2.17tinyos-3.cygwin.i386.rpm ? avr-gcc-4.1.2-1.cygwin.i386.rpm :

http://www.tinyos.net/dist-2.1.0/tools/windows/avr-gcc-4.1.2-1.cygwin.i386.rpm ? avr-libc-1.4.7-1.cygwin.i386.rpm :

http://www.tinyos.net/dist-2.1.0/tools/windows/avr-libc-1.4.7-1.cygwin.i386.rpm ? avarice-2.4-1.cygwin.i386.rpm :

http://www.tinyos.net/dist-2.1.0/tools/windows/avarice-2.4-1.cygwin.i386.rpm ? avr-insight-6.3-1.cygwin.i386.rpm :

http://www.tinyos.net/dist-2.1.0/tools/windows/avr-insight-6.3-1.cygwin.i386.rpm ? avrdude-tinyos-5.6cvs-1.cygwin.i386.rpm :

http://www.tinyos.net/dist-2.1.0/tools/windows/avrdude-tinyos-5.6cvs-1.cygwin.i386.rpm 用cd命令进入该文件夹目录,输入:rpm -ivh --ignoreos rpmname.rpm 可以选择安装rpmname这个rpm包。

如果碰到missing /bin/sh的错误信息,则

rpm –ivh --ignoreos --force --nodeps rpmname.rpm

最简单的方法是,同时安装这个文件夹下所有的rpm包,输入命令: $ cd /avr

$ rpm -ivh --ignoreos --force --nodeps *.rpm

MSP430工具包的安装方法也是一样的。其rpm包如下: ? msp430tools-base-0.1-20050607.cygwin.i386.rpm :

http://www.tinyos.net/dist-2.0.0/tools/windows/msp430tools-base-0.1-20050607.cygwin.i386.rpm ? msp430tools-python-tools-1.0-1.cygwin.noarch.rpm :

http://www.tinyos.net/dist-2.0.0/tools/windows/msp430tools-python-tools-1.0-1.cygwin.noarch.rpm

? msp430tools-binutils-2.16-20050607.cygwin.i386.rpm :

http://www.tinyos.net/dist-2.0.0/tools/windows/msp430tools-binutils-2.16-20050607.cygwin.i386.rpm

? msp430tools-gcc-3.2.3-20050607.cygwin.i386.rpm :

http://www.tinyos.net/dist-2.0.0/tools/windows/msp430tools-gcc-3.2.3-20050607.cygwin.i386.rpm

? msp430tools-libc-20080808-1.cygwin.i386.rpm :

http://www.tinyos.net/dist-2.1.0/tools/windows/msp430tools-libc-20080808-1.cygwin.i386.rpm 在根目录下新建的MSP430文件夹,将所有rpm下载后全部放到这里,运行: $ cd /msp430

$ rpm -ivh --ignoreos --force --nodeps *.rpm

安装成功。(若提示安装不了,可以先安装第四步的TinyOS工具包再回头安装这里)。 4、 TinyOS工具包安装 下载rpm包: ? Nesc :

http://www.tinyos.net/dist-2.1.0/tinyos/windows/nesc-1.3.0-1.cygwin.i386.rpm ? deputy :

http://www.tinyos.net/dist-2.1.0/tinyos/windows/tinyos-deputy-1.1-1.cygwin.i386.rpm ? tinyos-tools :

http://www.tinyos.net/dist-2.1.0/tinyos/windows/tinyos-tools-1.3.0-1.cygwin.i386.rpm

安装方法同上面一样。

将rpm下载后全部放到一个根目录下新建的tinyos_tools文件夹,运行: $ cd /tinyos_tools

$ rpm -ivh --ignoreos *.rpm 注:在win7或者是vista下安装nesC时可能会报错如下:unpacking of archive failed on file /usr: cpio: chmod failed - Permission denied

解决方法:运行cygwim的时候右键选择 “以管理员身份运行”

5、 TinyOS 2.x安装 下载rpm包:

http://www.tinyos.net/dist-2.1.0/tinyos/windows/tinyos-2.1.0-2.cygwin.noarch.rpm 安装方法同上面一样 $ cd /tinyos

rpm -ivh --ignoreos *.rpm 安装成功。

6、 设置TinyOS2.x环境变量 Environment Variable TOSROOT /opt/tinyos-2.x TOSDIR TH $TOSROOT/tos nyos.jar;. same as in Cygwin same as in Cygwin yos.jar:. same as in Cygwin Windows Linux CLASSPAC:\\cygwin\\opt\\tinyos-2.x\\support\\sdk\\java\\ti$TOSROOT/support/sdk/java/tinMAKERUL$TOSROOT/support/make/Makerules ES PATH? /opt/msp430/bin:/opt/jflashmm:$PATH same as in Cygwin ?注:只有在使用msp430平台时才有必要设置PATH变量

用UltraEdit创建C:\\cygwin\\etc\\profile.d\\tinyos.sh,输入以下内容:(使用UNIX换行符-LF保存,这一步很关键,字符是有区别的)(配置文件已经写好了) # script for profile.d for bash shells, adjusted for each users # installation by substituting /opt for the actual tinyos tree # installation point.

export TOSROOT=\export TOSDIR=\

export CLASSPATH=\export CLASSPATH=\TH;.\

export MAKERULES=\export PATH=\TH\

export PATH=\执行命令: tos-install-jni 如果出现一下错误信息:

Installing 32-bit Java JNI code in /cygdrive/c/Program Files/Java/jdk1.6.0_10/jr e/bin ...

install: cannot stat `/usr/lib/tinyos/*-32.dll': No such file or directory 我们需要将以下目录中的toscomm.dll,重命名为:toscomm-32.dll C:\\cygwin\\lib\\tinyos

C:\\Program Files\\Java\\jdk1.6.0_10\\jre\\bin toscomm.dll ——toscomm-32.dll 重新tos-install-jni命令。

7、 安装 Graphviz

下载地址:http://webs.cs.berkeley.edu/tos/dist-1.1.0/tools/windows/graphviz-1.10.exe, 默认安装到C:\\Program Files\\ATT目录下。一直点NEXT就可以完成安装。

8、 测试安装 1.环境测试:

运行cygwin,输入命令: $ tos-check-env

如果上述安装成功,可能会出现WARNING:java 1.4 or java1.5,tos-check-env completed without error等,这是JAVA版本的问题,不必理会。

$ which java

正常情况出现:/cygdrive/c/Program Files/Java/jdk1.6.0_10/bin/java 如果出现/cygdrive/c/WINDOWS/system32/java

则尝试输入:export PATH=\

2、检查己经让TinyOS build system环境可运行的. 输入如下命令:

$ printenv MAKERULES

如果看到:/opt/tinyos-2.x/support/make/Makerules 这是正确的

五 实验报告要求

①实验名称 ②实验内容说明 ③程序源代码

④实验步骤,实验中出现的问题,观察到的结果 ⑤实验总结

实验二:Blink——TinyOS编程的“Hello World”

一 实验目的

1、 掌握Linux基本命令及其使用。

2、 掌握安装应用程序到节点(如telosb)的方法。

3、 掌握nesC语言语法风格、组织、命名和连接组件的机制。 4、 掌握组件中命令(command)和事件(event)接口的使用。

5、 掌握模块(module)和配件(configuration)的作用及使用方法。

二 实验原理

TinyOS操作系统、库和程序服务程序是用nesC写的。 ◆ nesC是一种开发组件式结构程序的语言。

◆nesC是一种C语法风格的语言,但是支持TinyOS的并发模型,以及组织、命名和连接组件成为健壮的嵌入式网络系统的机制。

◇nesC应用程序是由有良好定义的双向接口的组件构建的。 ◇nesC定义了一个基于任务和硬件事件处理的并发模型,并能在编译时检测数据流组件。 ◆规范

◇nesC应用程序由一个或多个组件连接而成 ◇一个组件可以提供或使用接口

●组件中command接口由组件本身实现 ●组件中event接口由调用者实现

●接口是双向的,调用command接口必须实现其event接口 ◆实现

◇modules

●包含应用程序代码,实现接口 ◇configurations

●装配模块,连接模块使用的接口到其提供者

●每个nesC应用程序都有一个顶级configuration连接内部模块 ●并发模型

◆TinyOS只能运行单个由所需的系统模块和自定义模块构成的应用程序 ◆两个线程 ◇任务

●一次运行完成,非抢占式 ◇硬件事件处理 ●处理硬件中断

●一次运行完成,抢占式

●用于硬件中断处理的command和event必须用async关键字声明

◆执行流程(race conditions)

◇nesC要避免任务排他性访问共享数据

◇nesC要避免所有共享数据访问都通过原子语句

◇nesC在编译过程中要检测数据流,但可能误报,可用norace关键字声明不检测,但对其使用 应格外小心

三 实验环境

1、JDK1.6 for windows 2、Cygwin with TinyOS 2.x 3、UltraEdit / EditPlus

四 实验内容和步骤

1、实验内容:

(1) 进入tmp文件夹并创建文件夹,取名Blink,进入这个文件夹。

cd /tmp mkdir Blink cd Blink

(2) 定义一个Blink配件BlinkAppC.nc。

configuration BlinkAppC { }

implementation {

components MainC, BlinkC, LedsC;

components new TimerMilliC() as Timer0; components new TimerMilliC() as Timer1; components new TimerMilliC() as Timer2;

BlinkC -> MainC.Boot; BlinkC.Timer0 -> Timer0; BlinkC.Timer1 -> Timer1; BlinkC.Timer2 -> Timer2; BlinkC.Leds -> LedsC; }

(3) 定义一个Blink配件BlinkC.nc。

#include \

module BlinkC {

uses interface Timer as Timer0; uses interface Timer as Timer1; uses interface Timer as Timer2; uses interface Leds; uses interface Boot; }

implementation {

event void Boot.booted() {

call Timer0.startPeriodic( 250 );

call Timer1.startPeriodic( 500 ); call Timer2.startPeriodic( 1000 ); }

event void Timer0.fired() {

call Leds.led0Toggle(); }

event void Timer1.fired() {

call Leds.led1Toggle(); }

event void Timer2.fired() {

call Leds.led2Toggle(); } }

(4) 新建一个Makefile文件。

COMPONENT=BlinkAppC include $(MAKERULES)

(5)新建一个说明文档README(可选) (6)编译程序 make telosb

(7)下载到telosb节点,观察实验结果 make telosb reinstall.ID bsl,serialport. (8) 用nesdoc显示程序结构和配件组成,然后进入/tinyos-2.x/doc/nesdoc,打开index.html make telosb docs (9)编写实验报告

五 实验报告要求

①实验名称 ②实验内容说明 ③程序源代码

④实验步骤,实验中出现的问题,观察到的结果 ⑤实验总结

实验三:TinyOS任务及应用举例

一 实验目的

1、 了解TinyOS两层调度策略:任务和硬件事件句柄。 2、 掌握任务(Task)的建立、提交及其作用。

3、 掌握nesC语法中的分相操作(split-phase operation)。 4、 理解BlinkTask应用程序。

二 实验原理

1、任务(Task)

TinyOS 提供任务和硬件事件句柄组成的两层调度策略。关键字 async 声明了可被硬件事件句柄执行的命令或事件。这意味着它可以在任何时候执行(可能会抢占其它代码的执行)。因此,用 async 声明的命令和事件所做的工作应该做尽量少,且要快速完成。此外,还得注意被异步命令或事件访问的数据可能存在的数据竞争。任务则被用来处理一些较长时间的操作,例如:后台数据处理,但任务可以被硬件事件句柄抢占。 一个任务可以用以下语法在你的实现模块中声明:

task void taskname() { //…… }

其中,taskname 是程序员任意指定的任务的标识,也就是“函数名”。一个任务的返回值类型必须是void,并且不能有任何参数。而向操作系统提交任务则可以用以下语法:

post taskname();

一个任务可以在命令、事件或其它任务内部向操作系统提交。

post 操作把任务放置到一个以先进先出为处理方式的内部任务队列中去。当一个任务开 始执行的时候,只有它运行结束,下一个任务才能开始运行;因此,一个任务不应该占用或阻塞太长时间。任务之间不可以互相抢占,但是会被硬件事件句柄抢占。如果你的任务需要执行一系列长时间的操作,最好把任务分成几个而不是使用一个大的任务。 2、分相(split-phase)操作

在TinyOS中每一个长时间运行的操作都是分相的。在阻塞系统中,当一个调用长时间运行的操作时,只有操作完成时调用才会返回;而在分相系统中,调用会立即返回,当操作完成会发出callback。

分相操作相对于顺序代码有些复杂,但他可以使操作并行并且节省内存。Timer.StartOneShot就是一个分相调用的例子。

阻塞(Blocking) 分相(Split-Phase)

if (send() == SUCCESS) { sendCount++; } // start phase send(); //completion phase void sendDone(error_t err) { if (err == SUCCESS) { sendCount++; } }

三 实验环境

1、JDK1.6 for windows 2、Cygwin with TinyOS 2.x 3、UltraEdit / EditPlus

四 实验内容和步骤

(1) 进入tmp文件夹并创建文件夹,取名BlinkTask,进入这个文件夹。

cd /tmp

mkdir BlinkTask cd BlinkTask

(2) 把Blink文件夹里的所有文件复制到BlinkTask中。

cp –r /opt/tinyos-2.x/apps/Blink/* /tmp/

(3) 现给程序设置一个较长的计算任务。用UltraEdit打开模块文件BlinkC.nc,并修改

如下:

#include \

module BlinkC {

uses interface Timer as Timer0; uses interface Timer as Timer1; uses interface Timer as Timer2; uses interface Leds; uses interface Boot;

}

implementation {

event void Boot.booted() {

call Timer0.startPeriodic( 250 ); call Timer1.startPeriodic( 500 ); call Timer2.startPeriodic( 1000 ); }

event void Timer0.fired() { uint32_t i;

for (i = 0; i < 400001; i++) { call Leds.led0Toggle(); } }

event void Timer1.fired() {

call Leds.led1Toggle(); }

event void Timer2.fired() {

call Leds.led2Toggle(); } }

(4) 编译程序并把它下载到节点观察三盏灯情况,记录结果。若第一盏灯没有闪烁,则

将值改成20001,100001再观察结果。

(5)一个长的计算操作会一直占用CPU,从而阻塞其他操作的执行。所以,我们需要

使用建立任务(Task)任务的方法来解决上述问题,重新修改程序。 implementation{ …

task void computeTask() { uint32_t i;

for (i = 0; i < 400001; i++) {} }

event void Timer0.fired() { call Leds.led0Toggle(); post computeTask(); } …

}

(6)编译、安装程序,观察记录结果。若第一盏灯仍然没有闪烁(想想为什么?),则

将代码再次修改如下: uint32_t i;

task void computeTask() { uint32_t start = i;

for (;i < start + 10000 && i < 400001; i++) {} if (i >= 400000) { i = 0; } else {

post computeTask(); } }

(7)再次编译并安装程序,观察记录结果。 (8)编写实验报告

五 实验报告要求

①实验名称 ②实验内容说明 ③程序源代码

④实验步骤,实验中出现的问题,观察到的结果 ⑤实验总结

实验四:节点间的无线通信

一 实验目的

1、 了解TinyOS消息缓冲抽象—message_t。

2、 掌握用于抽象底层通信服务的基本通信接口名称及它们所实现的功能。 3、 掌握活动消息(AM)接口。 4、 掌握基本通信组件。

5、 理解BlinkToRadio程序代码,实现节点间的无线通信。

二 实验原理

1、message_t:TinyOS的消息缓冲

(1)TinyOS提供了许多接口去抽象底层通信服务,所有的这些接口以及许多提供这些接口的组件使用共同的消息缓冲抽象:message_t(底层通信数据的结构),用nesC结构体实现。

(2)message_t结构: typedef nx_struct message_t { nx_uint8_t header[sizeof(message_header_t)]; nx_uint8_t data[TOSH_DATA_LENGTH]; nx_uint8_t footer[sizeof(message_footer_t)]; nx_uint8_t metadata[sizeof(message_metadata_t)]; } message_t; 2、通信接口与组件 ? 基本通信接口:

(1)Packet:提供对message_t的基本访问

a. 清除消息内容(命令)

b. 得到有效载荷的长度(命令)

c. 得到指向有效载荷地址的指针(命令) (2)Send:提供与地址无关的发送接口

a. 发送消息内容(命令)

b. 取消挂起消息的发送(命令) c. 消息是否发送成功(事件) d. 得到有效载荷的长度(函数)

e. 得到指向有效载荷地址的指针(函数) (3)Receive:提供基本消息的接收接口

a. 接收消息(事件)

b. 得到有效载荷的长度(命令)

c. 得到指向有效载荷地址的指针(命令)

(4)PacketAcknowledgements:为每个包的请求确认提供机制 (5)RadioTimeStaming:提供时间戳 ? 活动消息接口

(1)AM层次实现了对radio的多路访问。“AM tpye”涉及到多路复用的领域。 (2)支持AM服务的接口:

a. AMPacket:提供基本的AM访问 b. AMSend:提供基本的AM发送接口

(3)一个节点的AM地址可以在初始化的时候设置:

make install.n or meke reinstall.n ? 通信组件:

许多组件实现了基本通信接口与AM接口

(1)AMReceiverC - Provides: Receive, Packet, and AMPacket.

(2)AMSenderC-Provides: AMSend, Packet, AMPacket, RacketAcknowledgements as

Acks.

(3)AMSnooperC - Provides Receive, Packet, and AMPacket.

(4)AMSnoopingReceiverC - Provides Receive, Packet, and AMPacket.

(5)ActiveMessageAddressC – 提供用来得到或设置节点AM地址的命令(少用)

三 实验环境

1、JDK1.6 for windows 2、Cygwin with TinyOS 2.x 3、UltraEdit / EditPlus

四 实验内容和步骤 1、重新实现Blink

(1)进入tmp文件夹并创建文件夹,取名BlinkToRadio,进入这个文件夹。

cd /tmp

mkdir BlinkToRadio cd BlinkToRadio

(2)新建模块文件BlinkToRadio.nc

#include #include \module BlinkToRadioC { uses interface Boot; uses interface Leds; uses interface Timer as Timer0; } implementation { uint16_t counter = 0; event void Boot.booted() { call Timer0.startPeriodic(TIMER_PERIOD_MILLI); } event void Timer0.fired() { counter++; call Leds.set(counter); } } (3)TIMER_PERIOD_MILLI的值被定义头文件BlinkToRadio.h中

#ifndef BLINKTORADIO_H #define BLINKTORADIO_H enum { TIMER_PERIOD_MILLI = 250 }; #endif (4)编写配件BlinkToRadioApp.nc,连接提供和使用各接口的组件 #include #include \ configuration BlinkToRadioAppC { } implementation { components MainC; components LedsC; components BlinkToRadioC as App; components new TimerMilliC() as Timer0; App.Boot -> MainC; App.Leds -> LedsC; App.Timer0 -> Timer0; } (5)最后创建Makefile文件 COMPONENT=BlinkToRadioAppC include $(MAKERULES) 2、定义消息结构

typedef nx_struct BlinkToRadioMsg { nx_uint16_t nodeid; nx_uint16_t counter; } BlinkToRadioMsg; 除了struct和uint16_t前面的nx_前缀之外,上面的消息结构与C语言结构体基本一样。它是nesC所特有的,表示struct和uint16_t为外部类型(external types)。nx_前缀表示数据为外部类型的大端(big-endian)数据,小端(little-endian)前缀为nxle_。不同类型的节点会采用不同类型的处理器,它们对于字节、字等的存储机制有所不同,因而会引发计算机通信领域中一个很重要的问题,即通信双方交流的信息单元应该以什么样的顺序进行传送。如果不达成一致的规则,通信双方将无法进行正确的编/译码从而导致通信失败。TCP/IP各层协议将字节序定义为big-endian,因此TCP/IP协议中使用的字节序通常称之为网络字节序。nesC语言定义了外部类型,可以让程序员不必考虑字节序的重新排列。 3、发送数据包

定义好消息的数据结构,我们接下去要学习怎么把指定结构的数据包发送出去。首先我们因明确程序要完成的功能:在定时器每次触发的时候(i)计数器+1;(ii)把计数器值的最低三位显示在三盏灯上;(iii)把由发送节点的节点号和计数器值组成数据包通过无线收发器发送出去。为了完成上述功能,我们将程序分成以下几步:

(1)找出相应的接口和组件,使我们能够控制收发器(radio)并对message_t进行操作。我们使用AMSend接口来发送数据包,Packet和AMPacket接口进入message_t抽象数据类型。这些接口都用组件AMSendC来提供,虽然它们也可以由ActiveMessageC来提供,但是AMSendC提供了虚拟抽象(virtualized abstraction)。因为收发器是一个公共资源,不同的组件都可以使用,所以需要将它虚拟化以避免不同组件间的相互干扰(后面课程会讲)。同时,我们使用由组件ActiveMessageC提供的SplitControl来开启和关闭收发器。 (2)更新BlinkToRadio.nc中的module板块代码,用uses语句添加上述接口。 module BlinkToRadioC { ... uses interface Packet; uses interface AMPacket; uses interface AMSend; uses interface SplitControl as AMControl; } 注意,这里SplitControl被重命名为AMControl,通过as关键字。 (3)声明新变量,并初始化。

implementation { bool busy = FALSE; //标记无线电收发器是否忙碌 message_t pkt; ... }

在定时器开始之前,首先要彻底开启收发器。 event void Boot.booted() { call AMControl.start(); } event void AMControl.startDone(error_t err) { if (err == SUCCESS) { call Timer0.startPeriodic(TIMER_PERIOD_MILLI); } else { call AMControl.start(); } } event void AMControl.stopDone(error_t err) { } (4)添加程序逻辑。首先要检查busy标记,保证没有数据包正在发送。然后用一个指针指向一个数据包的payload部分,往payload里面各个域填写数据。最后通过send命令把数据包发送给所有接受范围内的节点(因为目的地址为AM_BROADCAST_ADDR)。如果成功(返回SUCCESS),则证实AM层已经收到数据包,准备发送。

event void Timer0.fired() { ... if (!busy) { BlinkToRadioMsg* btrpkt = (BlinkToRadioMsg*)(call Packet.getPayload(&pkt,NULL)); btrpkt->nodeid = TOS_NODE_ID; btrpkt->counter = counter; if (call AMSend.send(AM_BROADCAST_ADDR, &pkt, sizeof(BlinkToRadioMsg)) == SUCCESS){ busy = TRUE; } } } (5)实现所有用到的接口中所包含的事件(events)。通过观察Packet,AMPacket,AMSend接口,发现只有一个事件需要实现,即AMSend.sendDone:

/** * Signaled in response to an accepted send request. msg is * the message buffer sent, and error indicates whether * the send was successful. * * @param msg the packet which was submitted as a send request * @param error SUCCESS if it was sent successfully, FAIL if it was not, * ECANCEL if it was cancelled * @see send * @see cancel */ event void sendDone(message_t* msg, error_t error); 在AMSend.sendDone中,我们需要检查下所发送出去的消息缓存是否为本地消息缓存。因为假如有两个组件同时连接到一个AMSend,只要其中任何一个组件执行send命令后,这两个组件都将接受到sendDone事件。这样就会产生干扰。 event void AMSend.sendDone(message_t* msg, error_t error) { if (&pkt == msg) { busy = FALSE; } } (6)更新配件中implementation板块中的代码,添加相应的组件。

implementation { ... components ActiveMessageC; components new AMSenderC(AM_BLINKTORADIO); ... } 其中,关键字new表示实例化一个AMSenderC,参数AM_BLINKTORADIO代表AMSenderC的AM类型。扩展头文件BlinkToRadio.h内容: ... enum { AM_BLINKTORADIO = 6, TIMER_PERIOD_MILLI = 250 }; ... (7)把本应用程序所用到的接口与提供该接口的组件连接起来。 implementation { ... App.Packet -> AMSenderC; App.AMPacket -> AMSenderC; App.AMSend -> AMSenderC; App.AMControl -> ActiveMessageC; } 4、接受数据包 数据包接受过称的步骤与发送过程类似。 (1)找到提供访问收发器并可以操作message_t类型的接受和组件。通过查找,我们使用Receive接口来接受数据包。

(2)更新BlinkToRadio.nc中的module板块代码,添加Receive接口。 module BlinkToRadioC { ... uses interface Receive; } (3)声明新变量,并初始化。这里不需要添加任何新的变量。 (4)添加程序逻辑。数据接受是一个基于事件驱动的过称,所以不需要添加任何命令。 (5)实现所有用到的接口中所包含的事件(events)。receive事件首先需要确认所接受到的数据包长度是否符合之前定义的数据结构长度。定义一个指针指向收到的数据包的payload部分,然后,将数据包中的counter值显示在节点的灯上。

event message_t* Receive.receive(message_t* msg, void* payload, uint8_t len) { if (len == sizeof(BlinkToRadioMsg)) { BlinkToRadioMsg* btrpkt = (BlinkToRadioMsg*)payload; call Leds.set(btrpkt->counter); } return msg; } (6)更新配件中implementation板块中的代码,添加相应的组件。 implementation { ... components new AMReceiverC(AM_BLINKTORADIO); ... } (7)把本应用程序所用到的接口与提供该接口的组件连接起来。 implementation { ... App.Receive -> AMReceiverC; } (8)测试你的程序,观察实验结果。 编译程序:make telosb。 安装到两个节点上:make telosb reinstall,ID bsl,COM

(9)编写实验报告

五 实验报告要求

①实验名称 ②实验内容说明 ③程序源代码

④实验步骤,实验中出现的问题,观察到的结果 ⑤实验总结

实验五:节点与PC的通信

一 实验目的

1、 了解节点与PC通信的基本抽象—数据包源(packet source)。 2、 掌握PC如何从网络中收集数据,如何向节点发送命令。

3、 掌握BaseStation程序和net.tinyos.tools.Listen,读出串口信息。

4、 掌握MIG的使用,并能运用它来生成消息结构的Java、Python或C接口。 5、 了解SerialForwarder和其它包源。

二 实验原理

1、包源(packet source)与TestSerial

? 包源:对于mote-pc间通信的一个基本抽象。实际上,它是一个通信媒介,一个应

用可以通过它从节点接收包,并发送包到节点上。

? 测试串行端口的第一步就是把TestSerial应用程序安装到节点上(使用make)。一

旦安装好后,就需要通过串行接口运行相应的JAVA应用程序进行通信: 如:java TestSerial –comm serial@/dev/ttyUSB0:telos 其中-comm是用字符串的形式明确包源 ? MOTECOM

如果没有-comm参数,那么工具就会检查MOTECOM环境变量,MOTECOM没有设定包源,则缺省的为SerialForwarder。 设置MOTECOM:

export MOTECOM=serial@COM1:19200

2、基站(BaseStation)和net.tinyos.tools.Listen

? BaseStation是TinyOS基本的实用应用程序。它是连接串行端口和无线通信网的桥

梁。因为TinyOS有工具(MIG)可以产生包并且通过串行端口发送包到节点,所以使用一个BaseStation允许PC工具直接和节点的网络通信。

? net.tinyos.tools.Listen 是一个包嗅探器,它以二元的形式打印侦听到的包。 $ java net.tinyos.tools.Listen 3、MIG(Message Interface Generator)

? Litsen是与节点交流的最基本的方法,信息比较难提取。所以TinyOS提供了一些

支持:从包描述中自动产生信息对象(使用mig工具产生java,python或C接口)。 ? mig有三个基本的参数: ①为那种语言产生代码

②从哪个文件找到结构体 ③结构体的名字

4、SerialForwarder

? 如果用串行端口,只能 一个pc与mote交互,并且必须是物理连接。SerialForwarder

工具则消除了这两个限制。

? SerialForwarder是第二种包源,语句如下: Sf@HOST:PORT

三 实验环境

1、JDK1.6 for windows

2、Cygwin with TinyOS 2.x 3、UltraEdit / EditPlus

四 实验内容和步骤

1、包源(packet source)与TestSerial

首先把/apps/tests/TestSerial应用程序安装到一个节点,该程序每秒向串口发送一个数据包,节点收到该数据包时,将它的序号通过三盏灯显示出来。

程序安装到节点之后,我们需要运行相应的Java程序来与串口进行通信。在应用程序TestSerial目录下,编辑: $ java TestSerial

若提示如下信息:

The java class is not found: TestSerial 这说明你还没有编译Java代码(再次运行make telosb),或是没有在Java 环境变量 CLASSPATH中加入.(当前目录)。 因为你还没有指定包源,TestSerial将包源默认为SerialForwarder,此时,程序会由于无法联系而退出。下面,我们把包源指定为串口(serial port),并使用-comm参数。具体语句如下:

serial@: PORT依赖于你所使用的节点平台和插在电脑上的位置。具体可通过在cygwin中运行或在我的电脑->管理->设备管理器->端口(COM和LPT)中查看。

$ motelist Reference CommPort Description ---------- ---------- ------------------------------------- M4AP1122 COM4 tmote sky SPEED参数可以是数字,也可以使平台名。它们之间的对照表可在support/sdk/java/net/packet/BaudRate.java中找到,具体如下:

Platform Speed (baud) telos telosb tmote micaz mica2 iris eyes

115200 115200 115200 57600 57600 57600 115200

mica2dot 19200 intelmote2 115200

当你带上正确参数之后,再运行TestSerial程序你将看到如下结果:

$java TestSerial -comm serial@COM4:telos Sending packet 1 Received packet sequence number 4 Sending packet 2 Received packet sequence number 5 Sending packet 3 Received packet sequence number 6 Sending packet 4 Received packet sequence number 7 Received packet sequence number 8 Sending packet 5 Received packet sequence number 9 Sending packet 6 2、MOTECOM

如果没有-comm参数,那么工具就会检查MOTECOM环境变量,MOTECOM没有设定包源,则缺省的为SerialForwarder。因此,假如你经常要用串口作为数据包源来跟节点通信的话,你可以先将MOTECOM值设置好,之后你在打命令的时就不用再加-comm及后面的参数了。设置方法如下:

export MOTECOM=serial@COM1:19200 # mica baud rate export MOTECOM=serial@COM1:mica # mica baud rate, again export MOTECOM=serial@COM2:mica2 # the mica2 baud rate, on a different serial port export MOTECOM=serial@COM3:57600 # explicit mica2 baud rate 试试运行下不带-comm参数的TestSerial程序,观察结果是否一样。 3、基站(BaseStation)和net.tinyos.tools.Listen 基站程序(BaseStation)是TinyOS中一个最基本也是最有用的程序,它在串口和无线收发器之间扮演着桥梁的作用。当节点从串口收到数据包时,程序马上将该数据包发送给收发器,同时切换(原本亮的,变灭;反之亦然)LED 0;相反,当它从无线收发器接收到数据包时,程序会将此数据包发送给串口,同时切换LED 1;若出现丢包(当一个节点发送速率大于另一个节点的接受速率),则切换LED 2。 现在,我们拿出两个节点,其中一个安装BlinkToRadio(见第三课),另一个安装BaseStation。你将观察到,装有BaseStation程序的节点的LED 1在闪烁。 Listen 是Java工具包中一个基本的包嗅探器,它以二元的形式打印侦听到的包。我们运行Listen观察结果:

$ java net.tinyos.tools.Listen –comm serial@COMID:telos 注:若之前已设置MOTECOM,则可省略–comm serial@COMID:telos 00 FF FF 00 00 04 22 06 00 02 00 01 00 FF FF 00 00 04 22 06 00 02 00 02 00 FF FF 00 00 04 22 06 00 02 00 03 00 FF FF 00 00 04 22 06 00 02 00 04 00 FF FF 00 00 04 22 06 00 02 00 05 00 FF FF 00 00 04 22 06 00 02 00 06 00 FF FF 00 00 04 22 06 00 02 00 07 00 FF FF 00 00 04 22 06 00 02 00 08 00 FF FF 00 00 04 22 06 00 02 00 09 00 FF FF 00 00 04 22 06 00 02 00 0A 00 FF FF 00 00 04 22 06 00 02 00 0B Listen把来自节点的数据包打印出来,每个数据包都包含几个域。第一个字节(00)表示该数据包是AM包。接下去的几个域是一般的自动消息域,它们在tinyos-2.x/tos/lib/serial/Serial.h中有定义。最后剩下的域是消息的负载部分,它在BlinkToRadio.h中被定义:

typedef nx_struct BlinkToRadioMsg { nx_uint16_t nodeid; nx_uint16_t counter; } BlinkToRadioMsg;

整个消息结构解析如下(忽略第一个字节00): ? 目的地址(Destination address)2个字节 ? 源地址(Link source address)2个字节 ? 负载长度(Message length)1个字节 ? 组号(Group ID)1个字节

? 消息处理类型(Active Message handler type)1字节 ? 负载(payload)最多28个字节:

? 源节点地址(source mote ID)2个字节 ? 采样计数值(sample counter)2个字节

知道整个消息结构之后,我们就可以解释上面打印出来的数据包了:

Link source addr 00 00 Msg len GroupID handlerID Source addr 04 22 06 00 02 counter 00 0B dest addr ff ff 4、MIG(Message Interface Generator):产生数据包对象

Listen只是简单的把从串口收到的数据包以十六进制的形式打印出来,我们需要手动 分析数据包的结构。然而,我们更希望看到的是将传感数据更清楚的显示出来。TinyOS提供了mig工具来根据定义的消息结构来建立Java,Python或C接口,从而通过包的描述自动产生信息对象。

mig有三个基本的参数:①为那种语言产生代码;②从哪个文件找到结构体;③结构 体的名字。 重新回到TestSerial文件夹,输入make clean;make,你讲看到:

2、Cygwin with TinyOS 2.x 3、UltraEdit / EditPlus

四 实验内容和步骤 1、Sense应用程序 Sense应用程序在tinyos-2.x/apps/Sense可以找到,我们先看下它的配置文件SenseApp.nc:

configuration SenseAppC { } implementation { components SenseC, MainC, LedsC, new TimerMilliC(); components new DemoSensorC() as Sensor; SenseC.Boot -> MainC; SenseC.Leds -> LedsC; SenseC.Timer -> TimerMilliC; SenseC.Read -> Sensor; } 该文件与BlinkAppC基本类似,再看看模块文件SenseC.nc:

module SenseC { uses { interface Boot; interface Leds; interface Timer; interface Read; } } 与BlinkC.nc一样,SenseC.nc也使用Boot,Leds,Timer接口。另外,它还使用了Read接口。接下来SenseC.nc要做的事情如下:用Boot启动周期性计时器;计时器一到时间,SenseC.nc就signal一个timer事件;读数据是分相操作的,Read.read()和Read.readDone();读完用LEDs显示数据第三位。

Read接口(tinyos-2.x/tos/interface)用来读取传感器的数据 interface Read { /** * Initiates a read of the value. * * @return SUCCESS if a readDone() event will eventually come back. */ command error_t read(); /** * Signals the completion of the read(). * * @param result SUCCESS if the read() was successful * @param val the value that has been read */ event void readDone( error_t result, val_t val ); } 仔细观察,会发现Read接口带有一个类型参数,我们把这样的接口称为泛型接口。泛型接口至少有一个类型参数,提供和使用该接口的组件必须带有同样的类型参数。接下去,我们找出SenseC.nc中使用的Read的提供者:

components new DemoSensorC() as Sensor; SenseC.Read -> Sensor; 值得注意的是,Sense.nc无法知道他所连接的传感器,甚至无法知道是否从传感器得到数据,因为它能导通到任何提供Read接口的组件。

事实上DemoSensorC在不同的平台下是不一样的。平台依赖于DemoSensorC组件来定义应用程序从哪个传感器来采样。例如, DemoSensorC.nc在telosb上的实现与在micaz上的实现是不一样的。 2、DemoSensorC组件 这一节,我们仔细看看DemoSensorC组件的内容,不用的节点平台对它的实现部分是不一样的。举例来说,在telosb上,DemoSensorC实例化了一个组件,名为VoltageC,它从MCU内部的电压传感器中读取数据。但是,在micaz节点没有内置的传感器,所以它返回“假的”传感数据。 然而,怎样才能采集其他传感器的数据,而不是默认传感器呢?其实,这只需要改变DemoSensorC里面的一行代码就可以了。例如,你想要把telosb中的VoltageC组件替换为ConstantSensorC,只要将下面语句更换如下:

components new VoltageC() as DemoSensor; components new ConstantSensorC(uint16_t, 0xbeef) as DemoSensor; 传感器组件常位于每个节点平台的子目录下(tinyos-2.x/tos/platforms),各自的传感器板子目录(tinyos-2.x/tos/sensorboards),或者是在微处理器传感器子目录下(tinyos-2.x/tos/chips)。ConstantSensorC和SinSensorC在tinyos-2.x/tos/system下。 把程序安装到节点,观察结果。 3、Oscilloscope 应用程序 Oscilloscope程序将传感器采集到的数据显示在电脑上。安装此程序的节点周期性的采样默认传感器(通过DemoSensorC),当采集到10个数据时,把它作为一个数据包广播出去。我们再拿一个节点,安装BaseStation程序,它把收到的数据包显示到电脑上。 OscilloscopeAppC.nc配件如下:

configuration OscilloscopeAppC { } implementation { components OscilloscopeC, MainC, ActiveMessageC, LedsC, new TimerMilliC(), new DemoSensorC() as Sensor, new AMSenderC(AM_OSCILLOSCOPE), new AMReceiverC(AM_OSCILLOSCOPE); OscilloscopeC.Boot -> MainC; OscilloscopeC.RadioControl -> ActiveMessageC; OscilloscopeC.AMSend -> AMSenderC; OscilloscopeC.Receive -> AMReceiverC; OscilloscopeC.Timer -> TimerMilliC; OscilloscopeC.Read -> Sensor; OscilloscopeC.Leds -> LedsC; }

部分OscilloscopeC.nc代码如下:

module OscilloscopeC { uses { interface Boot; interface SplitControl as RadioControl; interface AMSend; interface Receive; interface Timer; interface Read; interface Leds; } } 安装有Oscilloscope程序的节点每发送一个数据包,就切换第二盏灯;若是从其他节点那接收到数据包(用于告之自己的序列号或是更改了自己的采样频率),则切换第三盏灯;为了防止无线收发器之间的竞争,会切换第一盏灯; 为了显示传感器采集到的数据,我们首先在cygwin进入/tinyos-2.x/apps/Oscilloscope/java目录,然后输入make,把java文件编译成相应的消息类以及Oscilloscope GUI。 输入./run,将会出现以下界面:

每条不同颜色的线代表一个节点,x坐标表示数据包计数值,y坐标表示传感器数据采集到的数据。 五 实验报告要求

①实验名称 ②实验内容说明 ③程序源代码

④实验步骤,实验中出现的问题,观察到的结果 ⑤实验总结

实验七:存储

一 实验目的

1、 了解TinyOS中的永久数据存储方法。

2、 了解几种不同的需要永久存储的数据及对应的抽象类型。

3、 了解卷(volume)的概念,读懂BlinkConfig和PacketParrot程序。

二 实验原理 1、基本介绍

1.1. 不同种类的永久数据存储:小对象,日志,大对象,TinyOS 2.x分别提供了三个基本存

储抽象:小对象,循环日志和大对象。 1.2. 相关接口: BlockRead

BlockWrite Mount

ConfigStorage LogRead LogWrite Storage.h

1.3. 卷(volume)

①在编译时,TinyOS用一个XML文件把闪存(flash)分成一个或多个固定大小的卷,这些文件叫卷表。每一卷提供一个单独类型的存储对象(配置,日志,块存储)。 ②卷是针对特殊应用的,所以必须放在应用程序的文件夹中且命名为volumes-CHIPNAME.xml(CHIPNAME是特定平台的闪存芯片名)。 2、存储配置数据

2.1. 配置数据可能出现在以下特点:这些数据的大小有限;它们的值不统一;值未知;值与

硬件相关。

2.2. 为什么配置数据必须存留:

①校准:传感器的校准系数是出场配置的,持久的

②设备标识:设备标识一旦分配给一个节点,这些值应该持久。

③定位:节点位置数据可能在编译时不知,只有在运行时可见,需要存储。 ④传感:传感与发出信号过程的参数 这些都需要这种类型的数据存储。 2.3. 如何使用Mount和configuration 3、日志数据

3.1. 事件和小数据项的自动日志记录是一种常见的应用。日志数据不应该丢失,日志可以是

线性的,也可以是循环的。

3.2. LogStorage抽象支持上面的日志要求。日志是基于记录的,每一次调用LogWrite.append()

时就会创建一个新的日志。

3.3. PacketParrot:展示了怎样使用LogWrite和LogRead、

三 实验环境

1、JDK1.6 for windows

2、Cygwin with TinyOS 2.x 3、UltraEdit / EditPlus

四 实验内容和步骤 1、卷

一个卷表具体的写法如下:

卷表是针对特殊应用的,所以必须放在应用程序的文件夹中且命名为volumes-CHIPNAME.xml(CHIPNAME是特定平台的闪存芯片名)。举例来说,telos节点使用的是M25P闪存家族的ST微电子器件,这种芯片的驱动在/tos/chips/stm25p目录下。 2、存储配置数据

本节介绍如何从闪存读/写配置数据。配置数据有以下几个特点:这些数据的大小有限,从几十到几百字节不等;这些值在不同节点平台上是不统一的;这些值有时是未知的,有时又与硬件相关。由于上述特点,在节点重启,重新供电,或是对它重新写程序时,配置数据必须依然存在并且能够重新存储。配置数据包括以下几种:

? 校验 传感器节点的系数校验是在节点出厂时就已经配置好的,这些数据需要在节

点中永久存在。例如一个假想的温度传感器可能会有偏移(offset)和获取(gain)两个数据需要校验,它们必须永久的存储着,这样才能将输出由电压值转换成摄氏度。这些校验数据如下结构:

typedef struct calibration_config_t { int16_t temp_offset; int16_t temp_gain; } calibration_config_t; ? 识别 设备识别信息,像IEEE MAC地址或是TinyOS的TOS_NODE_ID参数。它

们虽然不是由硬件指定的,但是一旦分配了,就得永久的存储起来。 typedef struct radio_config_t { ieee_mac_addr_t mac; uint16_t tos_node_id; } radio_config_t; ? 定位 节点位置数据在编译时是不知道的,只有当部署时才会有用。应用程序可能

会像下面给出的坐标方式存储:

typedef struct coord_config_t { uint16_t x; uint16_t y; uint16_t z; } coord_config_t; ? 传感 传感器采集和处理数据过程中,使用的参数像采样周期,过滤系数和检测阈

值等配置数据可能会进行调整。 typedef struct sense_config_t { uint16_t temp_sample_period_milli; uint16_t temp_ema_alpha_numerator; uint16_t temp_ema_alpha_denominator; uint16_t temp_high_threshold; uint16_t temp_low_threshold; } sense_config_t; 上面介绍了几种配置数据以及为什么要使用这种存储,接下来,我们要看看如何使用 它们。/tinyos-2.x/apps/tutorials/BlinkConfig/ 在程序第一次运行之前,一个卷它不包含任何有用数据。所以,我们的代码需要检测卷的第一次使用并采取合适的措施(事先加载默认值)。类似的,当卷的数据布局改变了(例如,应用程序需要不同的配置变量时),程序也要检测出来并采取相应的动作。因此,我们需要追踪卷的版本号,定义的配置结构包含版本号和灯闪烁周期:

typedef struct config_t { uint16_t version; uint16_t period; } config_t; (1)卷表volumes-stm25p.xml(telosb)信息用于创建一个包含文件(include file)。这个文件是系统自己生成的,但是需要在声明ConfigStorageC组件的配置文件(BlinkConfigAppC.nc)中手动添加一句话: #include \

(2)BlinkConfigC使用了Mount和ConfigStorage接口。

module BlinkConfigC { uses { ... interface ConfigStorage as Config; interface Mount; ... } } (3)这两个接口都是由ConfigStorageC(VOLUME_CONFIGTEST)提供的,这是一个泛型组件。

configuration BlinkConfigAppC { } implementation { components BlinkConfigC as App; components new ConfigStorageC(VOLUME_CONFIGTEST); ... App.Config -> ConfigStorageC.ConfigStorage; App.Mount -> ConfigStorageC.Mount; ... }

(4)在闪存芯片被使用之前,必须先将它挂载(mount)。

event void Boot.booted() { conf.period = DEFAULT_PERIOD; if (call Mount.mount() != SUCCESS) { // Handle failure } } (5)当挂载成功后,接下来需要检查各卷是否有用(valid)。若可用,则读闪存(ConfigStore.read);不可用,则调用Config.commit(该命令也可以将缓存数据刷到闪存里)使它变得可用。

event void Mount.mountDone(error_t error) { if (error == SUCCESS) { if (call Config.valid() == TRUE) { if (call Config.read(CONFIG_ADDR, &conf, sizeof(conf)) != SUCCESS) // Handle failure } else { // Invalid volume. Commit to make valid. call Leds.led1On(); if (call Config.commit() == SUCCESS) { call Leds.led0On(); } else { // Handle failure } } } else{ // Handle failure } } (6)如果读成功,我们检查版本号。若版本号相同,则把配置数据复制到本地变量,并调整它的值;不然,设为默认值。最后,调用Config.write命令。

event void Config.readDone(storage_addr_t addr, void* buf, storage_len_t len, error_t err) __attribute__((noinline)) { if (err == SUCCESS) { memcpy(&conf, buf, len); if (conf.version == CONFIG_VERSION) { conf.period = conf.period/2; conf.period = conf.period > MAX_PERIOD ? MAX_PERIOD : conf.period; conf.period = conf.period < MIN_PERIOD ? MAX_PERIOD : conf.period; } else { // Version mismatch. Restore default. call Leds.led1On(); conf.version = CONFIG_VERSION; conf.period = DEFAULT_PERIOD; } call Leds.led0On(); call Config.write(CONFIG_ADDR, &conf, sizeof(conf)); } else { // Handle failure. } }

(7)在Config.write之后,再用Config.commit来确保数据写到闪存中。 event void Config.writeDone(storage_addr_t addr, void *buf, storage_len_t len, error_t err) { // Verify addr and len if (err == SUCCESS) { if (call Config.commit() != SUCCESS) { // Handle failure } } else { // Handle failure } } (8)安装程序到节点,观察实验现象。

(9)将节点拔下后重新插上供电,观察实验现象。 (10)重复(9),观察实验现象。

3、日志数据 可靠的记录事件或是小的数据项是一个普通应用程序的要求。当系统崩溃时,那些数据不应被丢失。我们可以线性的记录这些数据(当卷满时停止记录)或是循环的记录它们(当卷满时,把最不常用的数据项覆盖掉)。 TinyOS中LogStorage抽象支持这些需求。演示程序PacketParrot,向大家展示了如何使用LogWrite和LogRead抽象。它先将接收到的数据包写入日志,当下一个电力循环(power cycle)开始时,发送记录到日志的数据包,同时擦除日志。 (1)首先定义我们想要存储的日志结构:

typedef nx_struct logentry_t { nx_uint8_t len; message_t msg; } logentry_t; (2)与Config存储不同,Log存储不要求在应用程序中明确地挂载卷。一个简单的读命令就足够了。

event void AMControl.startDone(error_t err) { if (err == SUCCESS) { if (call LogRead.read(&m_entry, sizeof(logentry_t)) != SUCCESS) { // Handle error } } else { call AMControl.start(); } } (3)读出的数据长度如果与期望的相同,我们就把数据发送出去;如果不同,我们假设此日志是空的或是没有同步,将其擦除。

event void LogRead.readDone(void* buf, storage_len_t len, error_t err) { if ( (len == sizeof(logentry_t)) && (buf == &m_entry) ) { call Send.send(&m_entry.msg, m_entry.len); call Leds.led1On(); } else { if (call LogWrite.erase() != SUCCESS) { // Handle error. } call Leds.led0On(); } } (4)在接收事件中,我们把接收到的数据包及其长度记录到本地结构体中,然后调用LogWrite.append。 event message_t* Receive.receive(message_t* msg, void* payload, uint8_t len){ call Leds.led2On(); if (!m_busy) { m_busy = TRUE; m_entry.len = len; m_entry.msg = *msg; if (call LogWrite.append(&m_entry, sizeof(logentry_t)) != SUCCESS) { m_busy = FALSE; } } return msg; } (5)在LogWrite.appendDone事件中,返回了源缓存地址,写数据的长度,以及数据是否有丢失,错误代码。若没有错误发生,则把要写的数据包原子的、连续的、一致的写入闪存。

event void LogWrite.appendDone(void* buf, storage_len_t len, bool recordsLost, error_t err) { m_busy = FALSE; call Leds.led2Off(); } 3、存储大的对象 块(block)存储常常用来存储大的数据对象,这些数据对象不能很好的在RAM中存放。Block是一个低层的系统接口,在使用它的时候需要非常小心。因为它是一个写一次模型,重新写需要耗费很长的时间。 具体可以到tinyos-2.x/apps/tests/storage/Blockj目录下,查看使用Block抽象的演示程序。 五 实验报告要求

①实验名称 ②实验内容说明 ③程序源代码

④实验步骤,实验中出现的问题,观察到的结果 ⑤实验总结

event message_t* Receive.receive(message_t* msg, void* payload, uint8_t len){ call Leds.led2On(); if (!m_busy) { m_busy = TRUE; m_entry.len = len; m_entry.msg = *msg; if (call LogWrite.append(&m_entry, sizeof(logentry_t)) != SUCCESS) { m_busy = FALSE; } } return msg; } (5)在LogWrite.appendDone事件中,返回了源缓存地址,写数据的长度,以及数据是否有丢失,错误代码。若没有错误发生,则把要写的数据包原子的、连续的、一致的写入闪存。

event void LogWrite.appendDone(void* buf, storage_len_t len, bool recordsLost, error_t err) { m_busy = FALSE; call Leds.led2Off(); } 3、存储大的对象 块(block)存储常常用来存储大的数据对象,这些数据对象不能很好的在RAM中存放。Block是一个低层的系统接口,在使用它的时候需要非常小心。因为它是一个写一次模型,重新写需要耗费很长的时间。 具体可以到tinyos-2.x/apps/tests/storage/Blockj目录下,查看使用Block抽象的演示程序。 五 实验报告要求

①实验名称 ②实验内容说明 ③程序源代码

④实验步骤,实验中出现的问题,观察到的结果 ⑤实验总结

本文来源:https://www.bwwdw.com/article/f0dp.html

Top