找回密碼
 立即注冊(cè)

QQ登錄

只需一步,快速開始

搜索
查看: 23726|回復(fù): 14
收起左側(cè)

玩兒大了~給arduino上操作系統(tǒng)了~!

  [復(fù)制鏈接]
ID:112317 發(fā)表于 2016-4-9 20:08 | 顯示全部樓層 |閱讀模式
恩。。好久沒(méi)發(fā)帖了,不過(guò)這幾天也沒(méi)閑著
前幾天我在求助區(qū)發(fā)了個(gè)關(guān)于定時(shí)器的問(wèn)題,結(jié)果沒(méi)有讓我滿意的答案
于是自己百度+谷歌,終于找到了一個(gè)非常強(qiáng)大的庫(kù)--ProtoThreads!
一個(gè)非常強(qiáng)大的多任務(wù)庫(kù),非常適合arduino這種資源非常有限的單片機(jī)
別急,先上一段簡(jiǎn)單的代碼look look
  1.     #include <pt.h>
  2.      
  3.     static int counter1,counter2,state1=0,state2=0;
  4.      
  5.     static int protothread1(struct pt *pt)
  6.     {  
  7.       PT_BEGIN(pt);  
  8.       while(1)
  9.       {  
  10.         PT_WAIT_UNTIL(pt, counter1==1);
  11.         digitalWrite(12,state1);
  12.         state1=!state1;
  13.         counter1=0;   
  14.       }
  15.       PT_END(pt);
  16.     }
  17.      
  18.      
  19.     static int protothread2(struct pt *pt)
  20.     {
  21.       PT_BEGIN(pt);
  22.       while(1) {   
  23.         PT_WAIT_UNTIL(pt, counter2==5);
  24.         counter2=0;
  25.         digitalWrite(13,state2);
  26.         state2=!state2;
  27.       }
  28.       PT_END(pt);
  29.     }
  30.      
  31.      
  32.     static struct pt pt1, pt2;
  33.     void setup()
  34.     {
  35.      
  36.       pinMode(12,OUTPUT);
  37.       pinMode(13,OUTPUT);
  38.       PT_INIT(&pt1);
  39.       PT_INIT(&pt2);
  40.     }
  41.      
  42.     void loop ()
  43.     {
  44.         protothread1(&pt1);
  45.         protothread2(&pt2);
  46.         delay(1000);
  47.         counter1++;
  48.         counter2++;
  49.       }
復(fù)制代碼

此段代碼演示了如何使用PT庫(kù)來(lái)實(shí)現(xiàn)12、13腳led分別隔1秒、5秒閃爍,已經(jīng)在arduino09上測(cè)試通過(guò)
sorry,無(wú)注釋。。別急,這只是個(gè)演示
這篇文章會(huì)不斷更新,分別講述PT庫(kù)的原理和應(yīng)用
讓大家能開發(fā)出更復(fù)雜的程序

好介紹開始了~
Protothread是專為資源有限的系統(tǒng)設(shè)計(jì)的一種耗費(fèi)資源特別少并且不使用堆棧的線程模型,其特點(diǎn)是:  
◆ 以純C語(yǔ)言實(shí)現(xiàn),無(wú)硬件依賴性;  
◆ 極少的資源需求,每個(gè)Protothread僅需要2個(gè)額外的字節(jié);  
◆ 可以用于有操作系統(tǒng)或無(wú)操作系統(tǒng)的場(chǎng)合;  
◆ 支持阻塞操作且沒(méi)有棧的切換。
使用Protothread實(shí)現(xiàn)多任務(wù)的最主要的好處在于它的輕量級(jí)。每個(gè)Protothread不需要擁有自已的堆棧,所有的Protothread 共享同一個(gè)堆?臻g,這一點(diǎn)對(duì)于RAM資源有限的系統(tǒng)尤為有利。相對(duì)于操作系統(tǒng)下的多任務(wù)而言,每個(gè)任務(wù)都有自已的堆棧空間,這將消耗大量的RAM資源,而每個(gè)Protothread僅使用一個(gè)整型值保存當(dāng)前狀態(tài)。  
咱們來(lái)結(jié)合一個(gè)最簡(jiǎn)單的例子來(lái)理解ProtoThreads的原理吧,就拿上面的閃爍燈代碼來(lái)說(shuō)
  1.     #include <pt.h>//ProtoThreads必須包含的頭文件

  2.     static int counter1,counter2,state1=0,state2=0; //counter為定時(shí)計(jì)數(shù)器,state為每個(gè)燈的狀態(tài)

  3.     static int protothread1(struct pt *pt) //線程1,控制燈1
  4.     {  
  5.       PT_BEGIN(pt);  //線程開始
  6.       while(1) //每個(gè)線程都不會(huì)死
  7.       {  
  8.         PT_WAIT_UNTIL(pt, counter1==1); //如果時(shí)間滿了1秒,則繼續(xù)執(zhí)行,否則記錄運(yùn)行點(diǎn),退出線程1
  9.         digitalWrite(12,state1);
  10.         state1=!state1;//燈狀態(tài)反轉(zhuǎn)
  11.         counter1=0; //計(jì)數(shù)器置零
  12.       }
  13.       PT_END(pt); //線程結(jié)束
  14.     }


  15.     static int protothread2(struct pt *pt) //線程2,控制燈2
  16.     {
  17.       PT_BEGIN(pt); //線程開始
  18.       while(1) {    //每個(gè)線程都不會(huì)死
  19.         PT_WAIT_UNTIL(pt, counter2==5); //如果時(shí)間滿了5秒,則繼續(xù)執(zhí)行,否則記錄運(yùn)行點(diǎn),退出線程2
  20.         counter2=0;  //計(jì)數(shù)清零
  21.         digitalWrite(13,state2);
  22.         state2=!state2; //燈狀態(tài)反轉(zhuǎn)
  23.       }
  24.       PT_END(pt);  //線程結(jié)束
  25.     }


  26.     static struct pt pt1, pt2;
  27.     void setup()
  28.     {

  29.       pinMode(12,OUTPUT);
  30.       pinMode(13,OUTPUT);
  31.       PT_INIT(&pt1);  //線程1初始化
  32.       PT_INIT(&pt2);  //線程2初始化
  33.     }

  34.     void loop () //這就是進(jìn)行線程調(diào)度的地方
  35.     {
  36.         protothread1(&pt1);  //執(zhí)行線程1
  37.         protothread2(&pt2);  //執(zhí)行線程2
  38.         delay(1000);  //時(shí)間片,每片1秒,可根據(jù)具體應(yīng)用設(shè)置大小
  39.         counter1++;
  40.         counter2++;
  41.       }
復(fù)制代碼

看上面的代碼,你會(huì)發(fā)現(xiàn)很多大寫的函數(shù),其實(shí)那些都是些宏定義(宏定義用大寫是約定俗成的..),如果把這些宏都展開你就能更好的理解他的原理了:
  1.     #include <pt.h>//ProtoThreads必須包含的頭文件

  2.     static int counter1,counter2,state1=0,state2=0; //counter為定時(shí)計(jì)數(shù)器,state為每個(gè)燈的狀態(tài)

  3.     static int protothread1(struct pt *pt) //線程1,控制燈1
  4.     {  
  5.       { char PT_YIELD_FLAG = 1;
  6.                     switch((pt)->lc) {//用switch來(lái)選擇運(yùn)行點(diǎn)
  7.                      case 0: //此乃初始運(yùn)行點(diǎn),線程正常退出或剛開始都從這開始運(yùn)行
  8.                                     while(1) //每個(gè)線程都不會(huì)死
  9.                                     {
  10.                                             do {      
  11.                                                             (pt)->lc=12;//記錄運(yùn)行點(diǎn)
  12.                                                             case 12:
  13.                                                                     if(!(counter1==1))
  14.                                                                     {
  15.                                                                             return PT_WAITING;        //return 0
  16.                                                                     }                                               
  17.                                             } while(0)
  18.                                             digitalWrite(12,state1);
  19.                                             state1=!state1;//燈狀態(tài)反轉(zhuǎn)
  20.                                             counter1=0; //計(jì)數(shù)器置零
  21.                                     }
  22.                     }
  23.       PT_YIELD_FLAG = 0;
  24.       pt->lc=0;
  25.       return PT_ENDED; // return 1
  26.       }
  27.     }


  28.     static int protothread2(struct pt *pt) //線程2,控制燈2
  29.     {
  30.       { char PT_YIELD_FLAG = 1;
  31.                     switch((pt)->lc) {//用switch來(lái)選擇運(yùn)行點(diǎn)
  32.                      case 0:     //線程開始
  33.                                     while(1) //每個(gè)線程都不會(huì)死
  34.                                     {
  35.                                             do {      
  36.                                                             (pt)->lc=39;
  37.                                                             case 39://記錄運(yùn)行點(diǎn)
  38.                                                                     if(!(counter2==5))
  39.                                                                     {
  40.                                                                             return PT_WAITING;        //return 0
  41.                                                                     }                                               
  42.                                             } while(0)
  43.                                             counter2=0;  //計(jì)數(shù)清零
  44.                                             digitalWrite(13,state2);
  45.                                             state2=!state2; //燈狀態(tài)反轉(zhuǎn)
  46.                                     }
  47.                     }
  48.                     PT_YIELD_FLAG = 0;
  49.                     pt->lc=0;
  50.                     return PT_ENDED; // return 1
  51.             }
  52.     }


  53.     static struct pt pt1, pt2;
  54.     void setup()
  55.     {

  56.       pinMode(12,OUTPUT);
  57.       pinMode(13,OUTPUT);
  58.       pt1->lc=0;  //線程1初始化
  59.       pt2->lc=0;  //線程2初始化
  60.     }

  61.     void loop () //這就是進(jìn)行線程調(diào)度的地方
  62.     {
  63.         protothread1(&pt1);  //執(zhí)行線程1
  64.         protothread2(&pt2);  //執(zhí)行線程2
  65.         delay(1000);  //時(shí)間片,每片1秒,可根據(jù)具體應(yīng)用設(shè)置大小
  66.         counter1++;
  67.         counter2++;
  68.       }
復(fù)制代碼

好了,終于擴(kuò)展完了。。
  分析一下上面的代碼,就知道其實(shí)ProtoThreads是利用switch case 來(lái)選擇運(yùn)行點(diǎn)的,每個(gè)線程中的堵塞,其實(shí)就是判斷條件是否成立,不成立則return,所以說(shuō)每個(gè)線程都很有雷鋒精神,舍己為人,呵呵。有一點(diǎn)要注意那就是每個(gè)線程只能夠在我們指定的地方堵塞,至于堵塞點(diǎn),那就要看具體應(yīng)用了。
  由于線程是反復(fù)被調(diào)用的,因此,寫程序的時(shí)候不能像寫一般的函數(shù)一樣使用局部變量,因?yàn)槊看沃匦抡{(diào)用都會(huì)把變量初始化了,如果要保持變量,可以把它定義為static的
  在pt.h中定義了很多功能:
PT_INIT(pt)   初始化任務(wù)變量,只在初始化函數(shù)中執(zhí)行一次就行
PT_BEGIN(pt)   啟動(dòng)任務(wù)處理,放在函數(shù)開始處
PT_END(pt)   結(jié)束任務(wù),放在函數(shù)的最后
PT_WAIT_UNTIL(pt, condition) 等待某個(gè)條件(條件可以為時(shí)鐘或其它變量,IO等)成立,否則直接退出本函數(shù),下一次進(jìn)入本     函數(shù)就直接跳到這個(gè)地方判斷
PT_WAIT_WHILE(pt, condition)  和上面一個(gè)一樣,只是條件取反了
PT_WAIT_THREAD(pt, thread) 等待一個(gè)子任務(wù)執(zhí)行完成
PT_SPAWN(pt, child, thread) 新建一個(gè)子任務(wù),并等待其執(zhí)行完退出
PT_RESTART(pt)   重新啟動(dòng)某個(gè)任務(wù)執(zhí)行
PT_EXIT(pt)   任務(wù)后面的部分不執(zhí)行,直接退出重新執(zhí)行
PT_YIELD(pt)   鎖死任務(wù)
PT_YIELD_UNTIL(pt, cond) 鎖死任務(wù)并在等待條件成立,恢復(fù)執(zhí)行
在pt中一共定義四種線程狀態(tài),在任務(wù)函數(shù)退出到上一級(jí)函數(shù)時(shí)返回其狀態(tài)
PT_WAITING  等待
PT_EXITED  退出
PT_ENDED  結(jié)束
PT_YIELDED  鎖死

比如PT_WAIT_UNTIL(pt, condition) ,通過(guò)改變condition可以運(yùn)用的非常靈活,如結(jié)合定時(shí)器的庫(kù),把condition改為定時(shí)器溢出,那就是個(gè)時(shí)間觸發(fā)系統(tǒng)了,再把condition改為其他條件,就是事件觸發(fā)系統(tǒng)了
暫時(shí)寫這么多吧
***2014.3.16  這里有一另一篇關(guān)于protothread的文章,寫的挺好,感謝網(wǎng)友vincent_jie 的推薦:
http://www.kankanews.com/ICkengine/archives/107079.shtml



Devuino代表在 Arduino基礎(chǔ)上的開發(fā)平臺(tái),Muti代表多線程


ProtoThread相比其他RTOS有什么突出的優(yōu)點(diǎn)呢?貌似它已經(jīng)停止更新了。

優(yōu)點(diǎn)挺多的
1、超輕量級(jí),他的庫(kù)基本上都是些宏定義,大小可以忽略不計(jì),而且每個(gè)線程只占用2個(gè)字節(jié)
2、無(wú)機(jī)器碼,純c實(shí)現(xiàn),所以可移植性很好
3、無(wú)堆棧
4、簡(jiǎn)單使用可以只把他當(dāng)個(gè)調(diào)度程序,復(fù)雜點(diǎn)也能把他當(dāng)操作系統(tǒng)來(lái)使
恩。。其實(shí)我也才接觸。。了解不全
現(xiàn)在邊研究邊更新吧,主要是這幾天太忙了。。
PT作者的首頁(yè)http://www.sics.se/~adam/pt/

問(wèn):新手,求教一下,看了下示例,不知這定時(shí)準(zhǔn)確度如何?執(zhí)行中斷后,這定時(shí)器時(shí)間是否會(huì)延長(zhǎng)?
答:上面的示例只是用了一個(gè)delay()來(lái)設(shè)定時(shí)間片,用來(lái)演示一個(gè)簡(jiǎn)單的程序罷了
寫程序最好不要用delay,一delay程序就不能干別的事了,最好的辦法是一遇到要delay就去干別的事,時(shí)間到了再接著運(yùn)行剛才的代碼,所以PT結(jié)合定時(shí)器可以很大的提高程序的實(shí)時(shí)性
上面的定時(shí)器庫(kù)采用的是內(nèi)部定時(shí)器,至于是TCN幾我也沒(méi)仔細(xì)看,不過(guò)他們與PT結(jié)合的不是很好,下次我再發(fā)一個(gè)定時(shí)器庫(kù)吧
中斷肯定會(huì)有影響的,只是影響的大小罷了,所以在中斷里一般都不會(huì)放大的程序,我用中斷一般都是用來(lái)計(jì)數(shù)、改變狀態(tài),這點(diǎn)時(shí)間可以忽略
附錄:
官方原版庫(kù)1.4
pt-1.4.zip (256.5 KB, 下載次數(shù): 50)
arduino版1.4.01
ProtoThreads.rar (12.31 KB, 下載次數(shù): 56)
新發(fā)現(xiàn)了幾個(gè)定時(shí)器庫(kù),還沒(méi)來(lái)得及試用,都是arduino Library上下載的,大家可以先拿過(guò)去試試

SimpleTimer.rar (4.27 KB, 下載次數(shù): 18)
TimedAction-1_6.zip (3.92 KB, 下載次數(shù): 20)
TimerOne-v9.zip (5.71 KB, 下載次數(shù): 18)


評(píng)分

參與人數(shù) 1黑幣 +15 收起 理由
xljxlj + 15 共享資料的黑幣獎(jiǎng)勵(lì)!

查看全部評(píng)分

相關(guān)帖子

回復(fù)

使用道具 舉報(bào)

ID:165672 發(fā)表于 2017-2-21 09:55 | 顯示全部樓層
謝謝分享!學(xué)些
回復(fù)

使用道具 舉報(bào)

ID:158981 發(fā)表于 2017-3-7 22:03 | 顯示全部樓層
非常感謝樓主分享
回復(fù)

使用道具 舉報(bào)

ID:152999 發(fā)表于 2017-3-12 20:40 | 顯示全部樓層
謝謝分享,怎么用呢?
回復(fù)

使用道具 舉報(bào)

ID:174361 發(fā)表于 2017-3-22 00:26 | 顯示全部樓層
學(xué)習(xí)了
回復(fù)

使用道具 舉報(bào)

ID:187329 發(fā)表于 2017-4-8 08:49 | 顯示全部樓層
感謝分享,最近在學(xué)習(xí)arduino,的確資源緊缺啊
回復(fù)

使用道具 舉報(bào)

ID:224986 發(fā)表于 2017-8-5 15:31 | 顯示全部樓層
不造這個(gè)一樣嗎

ProtoThreads.rar

56.95 KB, 下載次數(shù): 7, 下載積分: 黑幣 -5

回復(fù)

使用道具 舉報(bào)

ID:239822 發(fā)表于 2017-10-21 19:38 | 顯示全部樓層
貌似arduino能用的多線程也就這個(gè)了,只是這個(gè)多線程任務(wù)管理功能還有待加強(qiáng),做的更精細(xì)點(diǎn),極可能的減少線程假死或者沖突,沒(méi)辦法,誰(shuí)讓單片機(jī)的內(nèi)存太小呢
回復(fù)

使用道具 舉報(bào)

ID:243053 發(fā)表于 2017-10-26 10:29 | 顯示全部樓層
謝謝分享!黑幣不足啊
回復(fù)

使用道具 舉報(bào)

ID:244729 發(fā)表于 2017-10-31 21:28 | 顯示全部樓層
謝謝分享!黑幣不足
回復(fù)

使用道具 舉報(bào)

ID:252134 發(fā)表于 2017-11-22 14:27 | 顯示全部樓層
16位單片機(jī)還能玩這么強(qiáng),膜拜,大神
回復(fù)

使用道具 舉報(bào)

ID:255376 發(fā)表于 2017-12-6 22:01 | 顯示全部樓層
謝謝分享!
回復(fù)

使用道具 舉報(bào)

ID:39808 發(fā)表于 2018-1-21 10:47 來(lái)自手機(jī) | 顯示全部樓層
想知道這個(gè)庫(kù)是否為通用庫(kù),比如可以在不同的環(huán)境好不同核心使用。
回復(fù)

使用道具 舉報(bào)

ID:39808 發(fā)表于 2018-1-21 10:50 來(lái)自手機(jī) | 顯示全部樓層
我現(xiàn)在玩arduino用的是stm32的核,外設(shè)和avr完全不同,目前使用的系統(tǒng)是freertos,這個(gè)系統(tǒng)好處是標(biāo)準(zhǔn)系統(tǒng),功能完善,但是缺點(diǎn)顯而易見,占空間比較大,所以不懂樓主這個(gè)系統(tǒng)能否通用,占用空間如何?
回復(fù)

使用道具 舉報(bào)

ID:10784 發(fā)表于 2018-6-9 18:47 | 顯示全部樓層
謝謝分享!好東西
回復(fù)

使用道具 舉報(bào)

本版積分規(guī)則

手機(jī)版|小黑屋|51黑電子論壇 |51黑電子論壇6群 QQ 管理員QQ:125739409;技術(shù)交流QQ群281945664

Powered by 單片機(jī)教程網(wǎng)

快速回復(fù) 返回頂部 返回列表