和大家一樣,我是按照 ../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可以看到節點的輸出。