恩。。好久沒發帖了,不過這幾天也沒閑著
前幾天我在求助區發了個關于定時器的問題,結果沒有讓我滿意的答案
于是自己百度+谷歌,終于找到了一個非常強大的庫--ProtoThreads!
一個非常強大的多任務庫,非常適合arduino這種資源非常有限的單片機
別急,先上一段簡單的代碼look look
- #include <pt.h>
-
- static int counter1,counter2,state1=0,state2=0;
-
- static int protothread1(struct pt *pt)
- {
- PT_BEGIN(pt);
- while(1)
- {
- PT_WAIT_UNTIL(pt, counter1==1);
- digitalWrite(12,state1);
- state1=!state1;
- counter1=0;
- }
- PT_END(pt);
- }
-
-
- static int protothread2(struct pt *pt)
- {
- PT_BEGIN(pt);
- while(1) {
- PT_WAIT_UNTIL(pt, counter2==5);
- counter2=0;
- digitalWrite(13,state2);
- state2=!state2;
- }
- PT_END(pt);
- }
-
-
- static struct pt pt1, pt2;
- void setup()
- {
-
- pinMode(12,OUTPUT);
- pinMode(13,OUTPUT);
- PT_INIT(&pt1);
- PT_INIT(&pt2);
- }
-
- void loop ()
- {
- protothread1(&pt1);
- protothread2(&pt2);
- delay(1000);
- counter1++;
- counter2++;
- }
復制代碼
此段代碼演示了如何使用PT庫來實現12、13腳led分別隔1秒、5秒閃爍,已經在arduino09上測試通過
sorry,無注釋。。別急,這只是個演示
這篇文章會不斷更新,分別講述PT庫的原理和應用
讓大家能開發出更復雜的程序
好介紹開始了~
Protothread是專為資源有限的系統設計的一種耗費資源特別少并且不使用堆棧的線程模型,其特點是:
◆ 以純C語言實現,無硬件依賴性;
◆ 極少的資源需求,每個Protothread僅需要2個額外的字節;
◆ 可以用于有操作系統或無操作系統的場合;
◆ 支持阻塞操作且沒有棧的切換。
使用Protothread實現多任務的最主要的好處在于它的輕量級。每個Protothread不需要擁有自已的堆棧,所有的Protothread 共享同一個堆棧空間,這一點對于RAM資源有限的系統尤為有利。相對于操作系統下的多任務而言,每個任務都有自已的堆棧空間,這將消耗大量的RAM資源,而每個Protothread僅使用一個整型值保存當前狀態。
咱們來結合一個最簡單的例子來理解ProtoThreads的原理吧,就拿上面的閃爍燈代碼來說
- #include <pt.h>//ProtoThreads必須包含的頭文件
- static int counter1,counter2,state1=0,state2=0; //counter為定時計數器,state為每個燈的狀態
- static int protothread1(struct pt *pt) //線程1,控制燈1
- {
- PT_BEGIN(pt); //線程開始
- while(1) //每個線程都不會死
- {
- PT_WAIT_UNTIL(pt, counter1==1); //如果時間滿了1秒,則繼續執行,否則記錄運行點,退出線程1
- digitalWrite(12,state1);
- state1=!state1;//燈狀態反轉
- counter1=0; //計數器置零
- }
- PT_END(pt); //線程結束
- }
- static int protothread2(struct pt *pt) //線程2,控制燈2
- {
- PT_BEGIN(pt); //線程開始
- while(1) { //每個線程都不會死
- PT_WAIT_UNTIL(pt, counter2==5); //如果時間滿了5秒,則繼續執行,否則記錄運行點,退出線程2
- counter2=0; //計數清零
- digitalWrite(13,state2);
- state2=!state2; //燈狀態反轉
- }
- PT_END(pt); //線程結束
- }
- static struct pt pt1, pt2;
- void setup()
- {
- pinMode(12,OUTPUT);
- pinMode(13,OUTPUT);
- PT_INIT(&pt1); //線程1初始化
- PT_INIT(&pt2); //線程2初始化
- }
- void loop () //這就是進行線程調度的地方
- {
- protothread1(&pt1); //執行線程1
- protothread2(&pt2); //執行線程2
- delay(1000); //時間片,每片1秒,可根據具體應用設置大小
- counter1++;
- counter2++;
- }
復制代碼
看上面的代碼,你會發現很多大寫的函數,其實那些都是些宏定義(宏定義用大寫是約定俗成的..),如果把這些宏都展開你就能更好的理解他的原理了:
- #include <pt.h>//ProtoThreads必須包含的頭文件
- static int counter1,counter2,state1=0,state2=0; //counter為定時計數器,state為每個燈的狀態
- static int protothread1(struct pt *pt) //線程1,控制燈1
- {
- { char PT_YIELD_FLAG = 1;
- switch((pt)->lc) {//用switch來選擇運行點
- case 0: //此乃初始運行點,線程正常退出或剛開始都從這開始運行
- while(1) //每個線程都不會死
- {
- do {
- (pt)->lc=12;//記錄運行點
- case 12:
- if(!(counter1==1))
- {
- return PT_WAITING; //return 0
- }
- } while(0)
- digitalWrite(12,state1);
- state1=!state1;//燈狀態反轉
- counter1=0; //計數器置零
- }
- }
- PT_YIELD_FLAG = 0;
- pt->lc=0;
- return PT_ENDED; // return 1
- }
- }
- static int protothread2(struct pt *pt) //線程2,控制燈2
- {
- { char PT_YIELD_FLAG = 1;
- switch((pt)->lc) {//用switch來選擇運行點
- case 0: //線程開始
- while(1) //每個線程都不會死
- {
- do {
- (pt)->lc=39;
- case 39://記錄運行點
- if(!(counter2==5))
- {
- return PT_WAITING; //return 0
- }
- } while(0)
- counter2=0; //計數清零
- digitalWrite(13,state2);
- state2=!state2; //燈狀態反轉
- }
- }
- PT_YIELD_FLAG = 0;
- pt->lc=0;
- return PT_ENDED; // return 1
- }
- }
- static struct pt pt1, pt2;
- void setup()
- {
- pinMode(12,OUTPUT);
- pinMode(13,OUTPUT);
- pt1->lc=0; //線程1初始化
- pt2->lc=0; //線程2初始化
- }
- void loop () //這就是進行線程調度的地方
- {
- protothread1(&pt1); //執行線程1
- protothread2(&pt2); //執行線程2
- delay(1000); //時間片,每片1秒,可根據具體應用設置大小
- counter1++;
- counter2++;
- }
復制代碼
好了,終于擴展完了。。
分析一下上面的代碼,就知道其實ProtoThreads是利用switch case 來選擇運行點的,每個線程中的堵塞,其實就是判斷條件是否成立,不成立則return,所以說每個線程都很有雷鋒精神,舍己為人,呵呵。有一點要注意那就是每個線程只能夠在我們指定的地方堵塞,至于堵塞點,那就要看具體應用了。
由于線程是反復被調用的,因此,寫程序的時候不能像寫一般的函數一樣使用局部變量,因為每次重新調用都會把變量初始化了,如果要保持變量,可以把它定義為static的
在pt.h中定義了很多功能:
PT_INIT(pt) 初始化任務變量,只在初始化函數中執行一次就行
PT_BEGIN(pt) 啟動任務處理,放在函數開始處
PT_END(pt) 結束任務,放在函數的最后
PT_WAIT_UNTIL(pt, condition) 等待某個條件(條件可以為時鐘或其它變量,IO等)成立,否則直接退出本函數,下一次進入本 函數就直接跳到這個地方判斷
PT_WAIT_WHILE(pt, condition) 和上面一個一樣,只是條件取反了
PT_WAIT_THREAD(pt, thread) 等待一個子任務執行完成
PT_SPAWN(pt, child, thread) 新建一個子任務,并等待其執行完退出
PT_RESTART(pt) 重新啟動某個任務執行
PT_EXIT(pt) 任務后面的部分不執行,直接退出重新執行
PT_YIELD(pt) 鎖死任務
PT_YIELD_UNTIL(pt, cond) 鎖死任務并在等待條件成立,恢復執行
在pt中一共定義四種線程狀態,在任務函數退出到上一級函數時返回其狀態
PT_WAITING 等待
PT_EXITED 退出
PT_ENDED 結束
PT_YIELDED 鎖死
比如PT_WAIT_UNTIL(pt, condition) ,通過改變condition可以運用的非常靈活,如結合定時器的庫,把condition改為定時器溢出,那就是個時間觸發系統了,再把condition改為其他條件,就是事件觸發系統了
暫時寫這么多吧
***2014.3.16 這里有一另一篇關于protothread的文章,寫的挺好,感謝網友vincent_jie 的推薦:
http://www.kankanews.com/ICkengine/archives/107079.shtml
Devuino代表在 Arduino基礎上的開發平臺,Muti代表多線程

ProtoThread相比其他RTOS有什么突出的優點呢?貌似它已經停止更新了。
優點挺多的
1、超輕量級,他的庫基本上都是些宏定義,大小可以忽略不計,而且每個線程只占用2個字節
2、無機器碼,純c實現,所以可移植性很好
3、無堆棧
4、簡單使用可以只把他當個調度程序,復雜點也能把他當操作系統來使
恩。。其實我也才接觸。。了解不全
現在邊研究邊更新吧,主要是這幾天太忙了。。
PT作者的首頁http://www.sics.se/~adam/pt/
問:新手,求教一下,看了下示例,不知這定時準確度如何?執行中斷后,這定時器時間是否會延長?
答:上面的示例只是用了一個delay()來設定時間片,用來演示一個簡單的程序罷了
寫程序最好不要用delay,一delay程序就不能干別的事了,最好的辦法是一遇到要delay就去干別的事,時間到了再接著運行剛才的代碼,所以PT結合定時器可以很大的提高程序的實時性
上面的定時器庫采用的是內部定時器,至于是TCN幾我也沒仔細看,不過他們與PT結合的不是很好,下次我再發一個定時器庫吧
中斷肯定會有影響的,只是影響的大小罷了,所以在中斷里一般都不會放大的程序,我用中斷一般都是用來計數、改變狀態,這點時間可以忽略
附錄:
官方原版庫1.4
pt-1.4.zip
(256.5 KB, 下載次數: 50)
2016-4-9 20:05 上傳
點擊文件名下載附件
arduino版1.4.01
ProtoThreads.rar
(12.31 KB, 下載次數: 56)
2016-4-9 20:05 上傳
點擊文件名下載附件
新發現了幾個定時器庫,還沒來得及試用,都是arduino Library上下載的,大家可以先拿過去試試
SimpleTimer.rar
(4.27 KB, 下載次數: 18)
|