轉載請注明:@小五義http://www.cnblogs.com/xiaowuyi QQ群:64770604
感謝小V分享給大家的博文。
我在做產品設計的課題的時候,小五義推薦我使用Protothread這個庫來進行編寫,研究了之后應用於自己的設計上效果還不錯,應小五義的請寫了這個Protothread的介紹,談不上懂,就淺淺談一談我的理解,幫助大家應用,如果有錯誤的,歡迎指教。
Protothread(以下簡稱PT),是牛逼哄哄的來自瑞典皇家啥啥啥的Adam Dunkels大師編寫的用於協程的一個庫,你說協程是啥,簡單的理解就是類似多任務處理器。當你開始做有關硬件C語言的編程之后,你會發現你的核心芯片不是無敵的,資源是有限的,這個時候合理安排資源就極為重要了,對於8位16位的單片機來說,用真正的多任務處理幾乎是無法承受的,這種時候就需要PT的出馬了。
舉個例子,你有兩個觸發端,分別是你老媽和你老婆,當你老婆生氣的時候,你要跪鍵盤10秒,你老媽叫你的時候,你就要去給她捶背15秒,問題是你的腦子只有8位或者16位,一個時間點只能做一件事情。現在你老婆生氣了,你就跪在鍵盤上,用最基礎的delay函數數數:1秒,2秒,3秒……這時候你老媽叫你給她捶背,可是你聽不見,因為你在數數,如果你老媽耐心好還好,等你跪完再叫你給她捶背,如果她老人家耐心差,叫一遍就不叫了,那可就完蛋了,直接跟你老婆開干,當然你唯一收獲的就是不用跪了,准備完蛋吧。
PT的有一個能力就是你跪下之后,不用你數數,它幫你數數,然后你膝蓋就保持跪的狀態,然后雙手給你老媽捶背,10秒到后,它跟你講:“誒,氣管炎,10秒到了。”這個時候你想不想起來看你自己了,反正老婆給的任務完成了。
當然這只是一個很狹窄的例子,不要狹隘的理解為PT只是給你計數,timer只是PT的一個方便的應用,這是使PT提醒的原因不是計時多久,而是一個條件,條件是數完十秒,所以說PT很靈活,你可以給把觸發端給PT,比如你為了向老婆展示你的誠心,就一直跪着鍵盤,這時候不是叫PT數數,告訴PT,如果老媽叫你,就提醒你去敲背,這樣都是可以的。
原理大致如此,然后上幾個常用的宏定義吧:
PT_INIT(pt) 初始化任務變量,只在初始化函數中執行一次就行
PT_BEGIN(pt) 啟動任務處理,放在函數開始處
PT_END(pt) 結束任務,放在函數的最后
PT_WAIT_UNTIL(pt, condition) 等待某個條件(條件可以為時鍾或其它變量,IO等)成立,否則直接退出本函數,下一次進入本函數就直接跳到這個地方判斷
想要真正的理解PT,自然是需要看懂源代碼的,我不會那么無聊給你一個一個羅列,我就給你看幾個精髓的,
#define PT_BEGIN(pt) switch((pt)->lc) {case 0; #define PT_WAIT_UNTIL(pt,condition) (pt)->lc = __LINE__; case __LINE__: if(!(condition)) return
啊哈哈哈哈,是不是沒看懂!!!我一開始也是這樣的!!!如果你看懂了你現在真的可以離開了,我沒啥可以告訴你的了。。。
如果你一頭霧水,心生怯意,那我來給你打一級強心針吧!
#define PT_END(pt) }
怎么樣!是不是不敢相信!就一個“}”,所以那些大神跟你講你PT_BEGIN之后,一定要一個PT_END,知道為啥了吧!因為你看看PT_BEGIN,里面一開始有個“{”。
其實你懂了PT_WAIT_UNTIL之后,你對PT就算是基本的了解,我們一句一句來,首先pt是啥,pt就是一個struct變量,里面有什么呢?里面就一個lc,lc就是用來記住當前的位置,然后 下次進入函數的時候就直接從當前位置開始的,那又怎么做到從當前位置開始呢!這個時候就是展現Adam牛逼哄哄的時候了,_LINE_閃亮登場,_LINE_的作用就是能取到當前_LINE_所在位置的行數,現在你知道了當前的位置,你怎么到這個位置呢,就用的switch case語句,用static pt記錄_LINE_的行數,然后通過switch((pt)->lc)跳到case _LINE_,怎么樣,理解了之后是不是感嘆Adam機智!
最后就簡單的我下面列一個示例吧,是按照arduino的編譯器格式寫的,就按照上面說的老媽老婆的例子,我個人不敢給老媽老婆分高下,一個管我一生,一個管我下半身,所以就老婆叫跪就跪10秒,不然不跪,老媽叫敲背就敲背15秒,否則浪費。
#include<pt.h>//聲明protothread #include<PT_timer.h>//聲明pt.timer #define ANGEROFWIFE 1//老婆的憤怒 #define MOTHERCALL 2//老母的呼喚 #define KNEE 3//遭殃的膝蓋 #define HAND 4//幸福的雙手 static struct pt pt1,pt2; PT_timer servotimer1; PT_timer servotimer2;//定義兩個計時器 void setup() { pinMode(ANGEROFWIFE,INPUT); pinMode(MOTHERCALL,INPUT); pinMode(KNEE,OUTPUT); pinMode(HAND,OUTPUT);//定義端口輸入輸出 PT_INIT(&pt1); PT_INIT(&pt2);//兩個線程初始化,其實就是pt->lc歸零 } static int mission1(struct pt *pt) { PT_BEGIN(pt);//線程開始 while(1) { digitalWrite(KNEE,LOW);//老婆不生氣我不跪 PT_WAIT_UNTIL(pt,digitalRead(ANGEROFWIFE));//等老婆生氣 digitalWrite(KNEE,HIGH);//我擦真生氣了,趕緊跪 servotimer1.setTimer(10000);//設定跪10秒,這里單位1為一微妙 PT_WAIT_UNTIL(pt,(servotimer1.Expired()));//當時間溢出,這里是10秒就結束 } PT_END(pt);//線程結束 } Static int mission2(struct pt *pt) { PT_BEGIN(pt); While(1) { digitalWrite(HAND,LOW); PT_WAIT_UNTIL(pt,digitalRead(MOTHERCALL)); digitalWrite(HAND,HIGH); servotiemr2.setTimer(15000); PT_WAIT_UNTIL(pt,(servotimer2.Expired())); } PT_END(pt); } void loop() { mission1(&pt1); mission2(&pt2);//循環執行,特步,永不停息 }
上述只是關於PT一個很簡單的例子,有一點需要提醒的是,盡量不要在線程里聲明變量,如果要用也要用static變量,不然很容易出錯也不易察覺,更多的關於protothread詳細的源代碼和理解可以參照REFERENCE,TV領進門,修行在個人。多多應用,勇於嘗試,你就能熟練應用這一神器。
