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.\n", sim_time_string());
call Leds.led0Toggle();
}

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

event void Timer2.fired()
{
dbg("BlinkC", "Timer 2 fired @ %s.\n", 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.\n", sim_time_string());
call Leds.led0Toggle();
}

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

event void Timer2.fired()
{
dbg("BlinkC", "Timer 2 fired @ %s.\n", 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 程序的構成和編程思路。這理解當然還有很多其他的技巧。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM