• TinyOS编程


    原文地址:https://blog.csdn.net/utnewbear/article/details/6235519

    和大家一样,我是按照 ../tinyos/cygwin/opt/tinyos-1.x/doc/tutorial 中的8个lesson进行操作和学习的。虽然很痛苦,可是还真没有什么别的更好的方法来学习这门奇怪的嵌入式语言。相信绝大多数同学在面对NesC的时候,最大的问题就是不知道从哪里下手,和自己到底要写些什么。以下的步骤,至少可以让你知道,你要使用NesC去做什么。

    第一步,我们要根据实际情况去选择使用什么组件。 以编写blink为例:

    首先我们需要main, main是程序开始的组件,是每个的TinyOS 程序(application)都必须的组件。或者可以说是NesC程序的入口,类似于C语言的main(),“Main”调用其他的 component以实现程序的功能。

    第二,需要一个来控制程序逻辑的组件,或者说具体实现程序逻辑功能的组件。一般表达程序的逻辑思路,用和配置文件一样的名字,但是多了一个M,表示是module文件,本例中就是BlinkM,也就是我们上一篇当中提到的module文件所对应的组件。

    第三,因为程序中用到了LED,所以需要系统提供的ledc。 没办法,这个是只有多看系统lib才行。

    第四,因为程序需要时间控制,所以用到系统提供的timer(或者是用户定义的singletimer,其实用户定义的singletimer依然是调用了系统的timer. 后面会附上修改好去掉simpletimer的blink代码,需要的同学自己看)

    总结,没有任何好方法,只有对系统熟悉,才能完成对底层的控制,必须去了解和学习那些底层的interface,不然是没有办法学习nesC的。

    第二步,选择合适的组件之后就需要编写顶层配置文件(configuration)

    从逻辑上来说,当你选定了组件之后,就需要顶层配置文件来wiring组件们,让他们协同工作,以完成你需要的程序功能。

    事实上,一个程序中可以有多个配置文件,但一定要有一个顶级配置文件,通常会以application的名字来命名。


    配置文件configuration首先声明了其应用程序下的组件,关键字:components.

    本例中: components Main, BlinkM, SingleTimer, LedsC;


    声明了组件之后,通过->可以将两个组件的接口连接起来。


    本例中:Main.StdControl -> BlinkM.StdControl;

              Main.StdControl -> SingleTimer.StdControl;  

     BlinkM.Timer -> SingleTimer.Timer;   

    linkM.Leds -> LedsC

    回忆上一篇,我们说到:

    有两个关键字来实现wiring,我翻译成“连接”好了。关键字 “à”和“ß”永远是将一个使用(uses)的接口于一个提供(provides)的接口相连接。 也就是说只有使用者组件能够调用提供者组件的接口。反过来就不可以。

    Tinyos中,组件和接口是一个多对多的关系,即一个组件可以连接到很多接口,反过来说,很多组件都可以提供一个相同的接口!(核心!最难理解的地方!)

    前面说,不同的组件可以提供相同的接口,如果组件ComA,ComB都提供了某一个接口InterfaceC, 那么,当组件ComD需要去访问InterfaceC接口时,怎么办? 它访问的到底是ComA提供的InterfaceC还是ComB提供InterfaceC的呢? 要知道,虽然接口的名称是一样的,但是不同组件提供的相同接口却是实现不同的功能。


    那么这里, Main.StdControl -> BlinkM.StdControl;这行代码就是把组件main和blinkm的stdcontrol连接起来,这样,就建立了两个组件之间的联系。当调用main.stdcontrol的时候就相当于调用了blinkm.stdcontrol。Main.StdControl -> SingleTimer.StdControl; 这行代码就是把main和singleTimer的stdcontrol连接起来了,也建立了main和singletimer的联系。可以看到main这个user同时和两个provider连接。Main的stdcontrol在被调用的时候,blinkm.stdcontrol和SingleTimer.StdControl都会被调用。

    现在,我们已经知道某些组件提供和使用的某些接口,比如blinkM提供StdControl,因为他在箭头的后面(Main.StdControl -> BlinkM.StdControl),他是提供者;同时他还使用Timer和Leds,因为他在箭头的前面(BlinkM.Timer和linkM.Leds),他是使用者。而SingleTimer和LedsC都是提供者,因为他们都是系统提供的lib,让你去控制灯的闪烁和时间。

    总结:在tinyos中组件也是分层次的。最底层的组件贴近硬件部分,是经过一层一层封装才有了上层的组件,封装的过程中就使用了配置文件。而一个应用程序需要一个顶级配置文件,在所有其他的配置文件的更高一层,编译时会首先参照该文件进行编译。

    第三步,既然已经有了顶层配置文件,可以写module文件了。

    有了顶层配置文件相当于我们的房子已经有图纸,那么你知道我们的房子要建多少层,每层有多少房间,卫生间和厨房在什么位置。那么module文件就是在给你的程序添砖加瓦。让它真的能住人。

    前面刚刚提到,blinkM提供StdControl接口,使用singleTimer的Timer接口和LedsC的Leds接口。所以blinkM应该这样写:

    Blinkm.nc
    module BlinkM {
    provides {
    interface StdControl;
    }
    uses {
    interface Timer;
    interface Leds;
    }

    }

    我们前面说过:

    一个组件如果provide某个interface,就必须实现这个interface当中所有的command。

    现在blinkM provide StdControl,所以他必须提供StdControl的所有command。分别是init(),start(), stop(). 那么blinkM就变成:

    Blinkm.nc
    module BlinkM {
    provides {
    interface StdControl;
    }
    uses {
    interface Timer;
    interface Leds;
    }
    implementation {
    command result_t StdControl.init() {
    return SUCCESS;
    }
    command result_t StdControl.start() {
    }
    command result_t StdControl.stop() {
    }

    }

    原则:在tinyos中,要使用一个组件(模块)必须先要初始化(init)它。

    main是整个application的启动的入口,那么当然main可以启动与之相连接的模块。Main已经和谁关联了? 对,main和BlinkM以及SingleTimer都关联了。而main与他们关联的接口是什么呢? 没错,是stdcontrol。前面说了,当调用main.stdcontrol的时候就相当于调用了blinkm.stdcontrol和singleTimer.stdcontrol.那么blinkM和singleTimer都被启动了。

    那么可以看到,我们顶层配置文件当中的4个组件,main,BlinkM,SingleTimer都启动了,就剩ledC还没有初始化。 但是问题是ledC没有提供stdControl接口,所以不能用main与之关联的方式去启动它。观察LedsC提供的Leds接口, 发现leds接口中有init() command. 我们通过command result_t StdControl.init() 去call Leds.init();进行ledC的初始化。

    command result_t StdControl.init() {

    Leds.init();
    return SUCCESS;
    }

    至此,所有的组件都已经初始化完毕。而且blinkM 提供 stdControl接口,也已经实现它。但是还有一个问题:

             一个组件如果use某个interface,就必须实现这个interface当中的event。

    blinkM 使用了leds接口和timer接口。

    那么必须检查 leds 和timer接口,看是否有event,如果有event就必须实现。观察到leds是没有event,而timer接口是有event。
    Timer.nc
    interface Timer {
    command result_t start(char type, uint32_t interval);
    command result_t stop();
    event result_t fired();
    }
    Timer接口有两个command和一个event。Start()命令被用于指定timer 的类型和那些即将过期的时间间隔。我们使用毫秒来计算时间间隔。有TIMER_REPEAT 和TIMER_ONE_SHOT 两种可用的类型。在使用TIMER_REPEAT模式的时候,我们可以用Start()命令形成一个循环,在指定的时间间隔过后,timer 将会结束,下一个重复的timer 将会继续执行,直到被stop()命令所终止。而当一个间隔到来时,事件 fired()就会被触发。

    考虑程序的逻辑流程:

    在我们需要的所有组件都启动后,Timer然后开始记录时间,当一个时间间隔过后,fired()事件被触发,并控制led,让灯闪烁。

    所以把timer的start()放到blinkM的result_t StdControl.start()里,把timer的stop()放到blinkM的result_t StdControl.stop()里。所以最终的代码是:

    Blinkm.nc
    implementation {
    command result_t StdControl.init() {
    call Leds.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;
    }
    }

    看到这里,其实一个标准的NesC程序就差不多明白了。

    最后给出用tossim来模拟blink的方法,关键是给手头没有mote的同学看看tinyos程序的运行结果:

    1、开始

    在cygwin下,进入目录:c:/cygwin/opt/tinyos-1.x/apps/blink

    运行命令:make pc

    然后运行命令:export DBG=led

    最后运行:build/pc/main.exe 3(这里的3指设置了3个传感器节点)

    你就在console可以看到节点的输出


    原文地址:http://lemondy.github.io/2015/06/10/Blink-tinyos/

    简要的说下 Tinyos 的背景:它是 UC Berkeley(加州大学伯克利分校)开发的开放源代码操作系统,专为嵌入式无线传感网络设计,操作系统基于构件(component-based)的架构使得快速的更新成为可能,而这又减小了受传感网络存储器限制的代码长度。在物联网如火如荼的今天,这个系统大有作为。它是嵌入式,轻量级。可以对无线传感器节点,SmartNode 节点等各种设备进行编程监控环境等状况,物联网必将渗透在生活的各个角落。

    该系统中程序的开发是利用 Nesc 语言,该语言是的语法是类似于 C 语言,但是与 C 语言也有些区别。有 C 语言基础的稍微花几天时间就可以进行 Nesc 编程了。

    对于该系统的更多背景了解和一些配置教程请点击

    Nesc 中接口(interface)

    NesC 程序主要由各式组件(component)构成,组件和组件之间通过特定的接口(interface)互相沟通。一个接口内声明了提供相关服务的方法。例如数据读取接口(Read)内就包含了读取(read)、读取结束(readDone)函数。接口只是制定了组件之间交流的规范,也就是通过某一个接口,只能通过该接口提供的方法实现两个组件之间的交流。但是接口终归只是接口,只是一组函数的声明,并未包含对接口的实现。例如以下便是读取接口的代码:

    1
    2
    3
    4
    5
    6
    7
     interface Read<val_t> {

    command error_t read();


    event void readDone( error_t result, val_t val );
    }

    只有函数的声明,但是没有函数体,所以需要有一个组件来实现(implementation)这个接口。实现某一个接口的组件,称之为提供者(provider),而使用该接口进行通信的,称之为用户(user)。

    接口内的函数分两类,一类为命令(command),另一类为事件(event)。用户可以调用某一组件提供的接口命令,然后等待相应的事件被触发。也就是说命令是有外部主动的被调用,而事件是被动的执行。简单的假设就是:组件 A 提供了 Read 接口以便其他组件与之对话,组件 B 调用组件 A 的 Read 接口的 read 命令来读取某一个数据,例如温度,然后等温度读取完毕之后,系统返回一个 readDone(读取结束)的事件给组件 B。

    Nesc 中的组件(component)

    NesC 程序由组件构成。组件内主要是包含了对各类接口的使用(uses)和提供(provides)。例如组件 A 提供了Read 接口,那 A 就需要负责实现 Read 接口内的 read 命令,也就是 read 命令的函数体,即“具体这个值是如何读取出来的”。因为命令(command)是由接口的提供者(provider)负责实现的。如果组件 B 使用了 A 提供的Read 接口,那在读取数据结束以后,系统会返回给 B 一个“读取结束”的事件,而B则需要负责处理这个事件,即“数据读取完毕以后,我用这个数据干什么”,将值返回给计算机,或者是通过无线发送给其他传感器等等,所以事件(event)是由接口的使用者(user)来负责实现的。

    组件分为两类。分别是模块(module)和配置(configuration)。

    模块内包含了程序的主要逻辑实现代码,也就是对各类命令和事件的实现,是 NesC 程序的可执行代码的主体。而配置则是负责将各个模块,通过特定的接口连接起来,其本身并不负责实现任何特定的命令或者事件。

    以 TinyOS 附带的 Blink(闪烁发光二极管)程序为例,

    模块组件的代码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
     // BlinkC.nc
    #include "Timer.h"

    //模块组件
    module BlinkC @safe()
    {
    //声明使用的接口
    uses interface Timer<TMilli> as Timer0;
    uses interface Timer<TMilli> as Timer1;
    uses interface Timer<TMilli> as Timer2;
    uses interface Leds;
    uses interface Boot;
    }
    implementation
    {
    //event时间必须在使用方中实现
    event void Boot.booted()
    {
    call Timer0.startPeriodic( 250 );
    call Timer1.startPeriodic( 500 );
    call Timer2.startPeriodic( 1000 );
    }

    event void Timer0.fired()
    {
    dbg("BlinkC", "Timer 0 fired @ %s. ", sim_time_string());
    call Leds.led0Toggle();
    }

    event void Timer1.fired()
    {
    dbg("BlinkC", "Timer 1 fired @ %s ", sim_time_string());
    call Leds.led1Toggle();
    }

    event void Timer2.fired()
    {
    dbg("BlinkC", "Timer 2 fired @ %s. ", sim_time_string());
    call Leds.led2Toggle();
    }
    }

    配置组件的代码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
     代码
    //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;
    }

    Blink 程序由两个组件构成。BlinkC.nc 为模块,BlinkAppC.nc 为配置。

    在模块 BlinkC 的声明内(module BlinkC {…})内表明了该程序需要用到的全部接口。因为 Blink 程序的主要目的是将 TelosB 传感器上的三盏 LED 发光二极管以不同的频率闪烁。所以我们需要三个精度为毫秒(TMilli)的计时器接口(Timer),分辨使用as关键字重命名为 Timer0,Timer1 和 Timer2。既然需要点亮发光二极管,自然需要一个操控发光二极管的接口,也就是 Leds,最后就是程序启动负责初始化的接口 Boot。

    接下去在实现部分(implementation {…})。在实现部分需要实现所有我们用到的接口的事件,在这个程序里面,我们只是使用了接口,而作为这些接口的用户,我们只需要负责去实现他们的事件。这些接口内的命令,则由接口的提供者负责实现。
    这里主要是两个事件,一个是 Boot 接口的 booted 事件,另一个是计时器被触发的 fired 事件。在 booted 事件中,也就是程序启动以后,我们的主要任务就一个,启动三个计时器:

    1
    2
    3
    call Timer0.startPeriodic( 250 );
    call Timer1.startPeriodic( 500 );
    call Timer2.startPeriodic( 1000 );

    0号 Led 灯的频率为 4Hz,1 号 Led 灯的频率为 2Hz,2 号 Led 灯的频率为 1Hz。这里 startPeriodic 是一个启动计时器的命令,呼叫命令需要使用 call 关键字。同样,因为是命令,所以它们由接口的提供者负责实现,我们只负责使用就可以了。另一个需要我们处理的事件就是计时器的触发,因为有三个计时器,所以需要书写三个触发事件:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    event void Timer0.fired()
    {
    dbg("BlinkC", "Timer 0 fired @ %s. ", sim_time_string());
    call Leds.led0Toggle();
    }

    event void Timer1.fired()
    {
    dbg("BlinkC", "Timer 1 fired @ %s ", sim_time_string());
    call Leds.led1Toggle();
    }

    event void Timer2.fired()
    {
    dbg("BlinkC", "Timer 2 fired @ %s. ", sim_time_string());
    call Leds.led2Toggle();
    }

    先不关心 dbg 的那一行。我们可以看到 0 号计时器触发的时候,我们切换 0 号发光二极管的状态(如果是亮的则熄灭,如果是灭的则点亮);1 号计时器触发时则切换 1 号发光二极管;3 号计时器同理。同样的道理,led0Toggle,led1Toggle 和 led2Toggle 属于 Leds 接口的三个命令,只管用 call 调用使用便可。

    接下去是看配置 BlinkAppC。这个组件本身并不使用或者提供任何接口,所以在其声明部分为空(configuration BlinkAppC{})。而在其实现(implementation)部分则需要实现对组件的连接。因为 BlinkC 模块使用了 Boot、Leds 和 Timer接口,所以必须指明这些接口都是由其他哪些组件提供的。所以:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    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;

    先使用 component 关键字标明,这个程序当中,总共要用到哪几个组件。其中包括我们自己编写的 BlinkC 模块。还有负责提供 Boot 接口的 MainC 组件,负责提供 Leds 接口的 LedsC 组件。还有提供 Timer 接口的TimerMilliC,其属于泛型(generic)配置,支持被实例化。这里先不细说,因为我们需要用到三个计时器,所以需要使用 new 关键字创建三个计时器的实例,然后分别用 as 被重命名为 Timer0、Timer1 和 Timer2。

    再往下就是组件之间的连接了。BlinkC 使用了 Boot 接口,而 MainC 正好提供了 BlinkC 所需的 Boot 接口,所以我们将他们进行连接。箭头所指方向为从使用者指向提供者。

    1
    2
    3
    BlinkC->MainC.Boot
    // 或者像下面这样也是可以的。
    MainC.Boot<-BlinkC

    因为 BlinkC 内部就使用了一个 Boot 接口,所以 BlinkC 后面的 Boot 被省略了。完整的书写格式为:

    1
    2
    // 意为:Blink组件内使用的Boot接口由MainC组件提供。
    BlinkC.Boot->Mainc.Boot

    接着是控制发光二极管的 Leds 接口,由 LedsC 组件提供。这里也进行了简写,完整的书写格式为:

    1
    BlinkC.Leds->LedsC.Leds

    计数器的连接同理。
    通过 Blink 程序,可以帮助我们理解 NesC 程序的构成和编程思路。这理解当然还有很多其他的技巧。

  • 相关阅读:
    linux卸载rpm包
    Centos6.3手动rpm安装gcc,c++
    阿里云服务器挂载分区
    linux下svn目录管理
    mac搭建cordova的android环境
    mac下搭建cordova开发环境
    程序员除了写代码还可以做点啥
    php之soap使用
    linux中找不到/etc/sysconfig/iptables
    const 位置不一样导致的变化
  • 原文地址:https://www.cnblogs.com/sddai/p/13813636.html
Copyright © 2020-2023  润新知