找回密碼
 立即注冊

QQ登錄

只需一步,快速開始

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

小小調(diào)度器V2.0簡易版源碼與分析

  [復(fù)制鏈接]
ID:389812 發(fā)表于 2018-8-24 23:37 | 顯示全部樓層 |閱讀模式
本文作者:Gthgth

注意:小小調(diào)度器V2.0 作者:  兔子、smset

在作者和“兔子”大蝦的努力下,小小調(diào)度器迎來一個(gè)激動(dòng)人心的新版本。(2.0正式版,為了大家方便學(xué)習(xí)才有V2.0簡易版)

在作者和兔子的幫助下,開始學(xué)習(xí)V2.0簡易版。

V1.1版本和V2.0 簡易版本不沖突,是兩個(gè)相對獨(dú)立的版本,各有各的優(yōu)點(diǎn),V1.1突出強(qiáng)調(diào)小,省資源。并不是v2.0 的存在就取代了V1.1;和V1.1版本

相比,  簡易版,在 的基礎(chǔ)上,支持任務(wù)重入;當(dāng)然了 也是一如既往的小。
v2.0 V1.1 V2.0
在百度中查了一下:可重入代碼指可被多個(gè)函數(shù)或程序凋用的一段代碼(通常是一個(gè)函數(shù)),而且它保證在被任何一個(gè)函數(shù)調(diào)用時(shí)都以同樣的方式運(yùn)行。
在小小調(diào)度器V2.0 中:
子任務(wù)可以被多個(gè)主任務(wù)調(diào)用,主任務(wù)可以給子任務(wù)傳遞參數(shù)。子任務(wù)也可以訪問主任務(wù)的數(shù)據(jù)。每個(gè)任務(wù)之間可以相互訪問數(shù)據(jù)。

具體反映在:
1.把每個(gè)任務(wù)函數(shù)的私有變量和行號、延時(shí)時(shí)間等都獨(dú)立出去,保存在自己的結(jié)構(gòu)體變量里面了;
2.在運(yùn)行任務(wù)函數(shù)時(shí),有關(guān)數(shù)據(jù)不能直接傳給結(jié)構(gòu)體,而是地址進(jìn)去,進(jìn)去后轉(zhuǎn)換回結(jié)構(gòu)體。

一.主函數(shù)分析

voidmain(){
while(1){

delay_ms(1);//延時(shí)1毫秒
runtasks();
}
}

分析:很簡單,延時(shí)1ms執(zhí)行runtasks()函數(shù);這樣就相當(dāng)于每隔1ms 掃描一次runtasks()函數(shù)。沒有用到定時(shí)器中斷,這個(gè)1ms 時(shí)基可以根據(jù)要求修改;
如果用定時(shí)器,時(shí)基寫的很小就會(huì)頻繁的打斷CPU。用延時(shí)感覺時(shí)基選擇小一點(diǎn)這樣更節(jié)省CPU資源,如果延時(shí)太長,就會(huì)占用太長CPU。(問:作者為
什么用延時(shí)作為時(shí)基沒用定時(shí)器?答:那種都行,看情況;在示例中用延時(shí)作為時(shí)基是考慮到調(diào)度器中統(tǒng)一沒有涉及到中斷。)
(smset補(bǔ)充:一是由于以arduino為例,arduino默認(rèn)代碼沒有提供中斷,因此沒有采用中斷時(shí)基。
另一個(gè)原因是V2.0簡易版默認(rèn)使用short類型的任務(wù)Timer變量,如果使用中斷進(jìn)行UpdateTimer更新,是存在隱患的,所以
如果在中斷里進(jìn)行UpdateTimer更新,則必須使用unsignedchar類型的任務(wù)Timer變量)。

1,展開 runtasks();函數(shù)
voidruntasks(){

//指定led1任務(wù)驅(qū)動(dòng)的IO管腳
led1.pin=13;

//更新頂級任務(wù)的時(shí)間
UpdateTimer(led1);
UpdateTimer(breath1);
UpdateTimer(serial1);

//執(zhí)行頂級任務(wù)
RunTask(LedTask, led1);
RunTask(BreathTask, breath1);
RunTask(SerialTask,serial1);
}

LED 的I/O 管腳初始化其實(shí)可以寫在專門的初始化函數(shù)里面,這里是為了更好的說明,寫在了runtasks()函數(shù)里。編程很靈活。
一般來說有幾個(gè)任務(wù),就有幾個(gè)對應(yīng)更新頂級任務(wù)的時(shí)間函數(shù)和對應(yīng)的執(zhí)行頂級任務(wù)。
2把UpdateTimer(led1); 函數(shù)展開。
宏#define UpdateTimer(TaskVar) do{ if((TaskVar.task.timer!=0)&&(TaskVar.task.timer!=END))TaskVar.task.timer--; }  while(0)
帶入展開:{if((led1.task.timer!=0)&&(led1.task.timer!=END)) led1.task.timer--; }
延時(shí)時(shí)間unsignedshort timer; 變量值不等于0,也不等于END (65535),它的值就減一。等于0 就去執(zhí)行對應(yīng)的函數(shù);等于65535 就掛起。這個(gè)和1.1
版本是一樣的。
led1是個(gè)結(jié)構(gòu)體變量。在led1結(jié)構(gòu)體里包含了一個(gè)task個(gè)結(jié)構(gòu)體變量,要引用里面的元素,用.分隔開寫到里面的最小元素。
task結(jié)構(gòu)體里面有兩個(gè)變量:unsignedshort  timer; 和unsignedchar lc; (有關(guān)結(jié)構(gòu)體變量展開看后面的第5部分 :有關(guān)結(jié)構(gòu)體及其宏的展開。)

3把RunTask(LedTask, led1);函數(shù)展開
宏#define RunTask(TaskName,TaskVar)  do{ if(TaskVar.task.timer==0)TaskVar.task.timer=TaskName(&(TaskVar)); }  while(0)
展開帶入:{if( led1.task.timer==0)  led1.task.timer=LedTask(&(led1)); }
假如延時(shí)時(shí)間到了timer==0,就執(zhí)行后面的函數(shù),執(zhí)行LedTask(&(led1))函數(shù),執(zhí)行完把結(jié)果賦值給led1.task.timer這個(gè)變量。這個(gè)和1.0版本是一樣的。
就是延時(shí)時(shí)間到,就去執(zhí)行任務(wù)函數(shù),然后把新的延時(shí)時(shí)間賦值給自己的timer,開始下一輪的循環(huán)。
因?yàn)樾⌒≌{(diào)度器是協(xié)作式的,假如某個(gè)任務(wù)的時(shí)間延時(shí)到了,并不意味著要馬上執(zhí)行這個(gè)任務(wù)函數(shù),要等上一個(gè)任務(wù)釋放掉CPU后才執(zhí)行本任務(wù)函
數(shù);這樣就意味著,我們在編制任務(wù)函數(shù)的時(shí)候?qū)θ蝿?wù)函數(shù)的執(zhí)行,時(shí)間要求不是那么的嚴(yán)格,在一定范圍內(nèi)執(zhí)行就可以了;同時(shí)也意味著CPU 只有把
某個(gè)任務(wù)函數(shù)執(zhí)行完,把本任務(wù)該做的事做完后,然后再做其他的事情;因?yàn)閏pu運(yùn)行速度是比較快的,一般情況下占用CPU資源比較多的是等待條件
滿足和延時(shí)(來個(gè)數(shù)學(xué)運(yùn)算或者什么的占用cpu時(shí)間較長怎么破?查表??,這和cpu有關(guān),和調(diào)度器沒關(guān)?);對于等待條件滿足的可以用宏#define
WaitUntil(A) ,對于延時(shí)的可以用宏#defineWaitX(ticks),這樣可以在本任務(wù)等待或者延時(shí)的時(shí)候,做其他事情,提高效率。
我們看一下這個(gè)函數(shù):執(zhí)行LedTask(&(led1))的結(jié)果是一個(gè)值,這個(gè)函數(shù)的原型是LedTask(C_LedTask*cp) 。
也就是取led1結(jié)構(gòu)體變量的首地址(&( led1))傳遞到函數(shù)的原型中定義的結(jié)構(gòu)體變量指針(C_LedTask*cp)。把它們對應(yīng)起來,就是定義一個(gè)結(jié)構(gòu)體指針,并指向led1 的首地址,這樣兩者對應(yīng)起來 (結(jié)構(gòu)體變量led1和結(jié)構(gòu)體指針C_LedTask*cp類型都是一樣的;有關(guān)結(jié)構(gòu)體變量展開看后面的第5 部分 :有關(guān)
結(jié)構(gòu)體及其宏的展開。)。這里用結(jié)構(gòu)體指針主要的目的就是把彼此剝離,為實(shí)現(xiàn)重入做好準(zhǔn)備。實(shí)現(xiàn)任務(wù)函數(shù)多次調(diào)用,彼此沒有影響。
為了書寫方便,作者做了一個(gè)宏#defineTaskFun(TaskName) TimeDefTaskName(C_##TaskName*cp){switch(me.task.lc){default:
因?yàn)檫@個(gè)函數(shù)有返回值,所以函數(shù)前面加了類型限制符unsignedshort (宏為:#defineTimeDef unsignedshort)。
為了書寫或者閱讀方便作者就做了一個(gè)語法糖 (就是一個(gè)宏#define me  (*cp),為了防止出錯(cuò),指針一定要加括號,涉及到優(yōu)先級的問題)。
其實(shí)所用的宏定義都可以認(rèn)為是語法糖,用糖把語法包裹著,就是為了方便書寫、理解等等。
4,把TaskFun(LedTask);函數(shù)展開
宏#defineTaskFun(TaskName) TimeDefTaskName(C_##TaskName*cp){switch(me.task.lc){default:
展開,替換后:
//TaskFun(LedTask){

unsignedshort LedTask(C_LedTask*cp){switch(me.task.lc){default:{ //編譯器初始化的時(shí)候給lc賦值為0。?
me.timelen=20;//LEDPWM總周期為20 毫秒。//一般在沒有進(jìn)入循環(huán)前,可以對一些變量賦值。V1.1版本用到私有變量一般是在這里定義的,為局部
pinMode(me.pin, OUTPUT);//設(shè)置管腳輸出 // 靜態(tài)變量;2.0版本用到的變量在前面統(tǒng)一定義,變量的應(yīng)用是通過指針進(jìn)行的。當(dāng)然了平
// 時(shí)怎么用就怎么寫。
while(1)
{

digitalWrite(me.pin,HIGH);//點(diǎn)亮LED
//WaitX(me.timeon);
//#defineWaitX(ticks) do{ me.task.lc=LINE; return(ticks);case LINE:;}while(0)
{ me.task.lc=LINE; return(me.timeon);caseLINE:;}

digitalWrite(me.pin,LOW);//關(guān)閉LED
WaitX(me.timelen-me.timeon);
}
}EndFun

展開后可以看到,里面用到的變量都是通過結(jié)構(gòu)體指針,指向我們當(dāng)初在“任務(wù)類及任務(wù)變量”那里定義的結(jié)構(gòu)體變量。這樣有關(guān)任務(wù)函數(shù)的操作
其實(shí)所有的數(shù)據(jù)都是存在任務(wù)自己所定義的變量里面;這樣函數(shù)重入就不出現(xiàn)問題,多次調(diào)用任務(wù)函數(shù)彼此不影響;當(dāng)然了運(yùn)行任務(wù)函數(shù)前要對先對任
務(wù)用到的變量定義,全部都是全局變量。這個(gè)也是V2.0 簡易版和v1.1板的一個(gè)區(qū)別。
返回me.timeon 是個(gè)unsignedchar類型的。返回去的時(shí)候類型被轉(zhuǎn)換為unsignedshort 型。其余和1.0版本一樣,在記錄行號的時(shí)候沒有用到靜態(tài)局
部變量,用的是每個(gè)任務(wù)變量里 task結(jié)構(gòu)體里面unsignedchar  lc;。lc 默認(rèn)為uchar 也就是說TaskFun(TaskName) 任務(wù)函數(shù)里面WaitX(ticks)的個(gè)數(shù)(不
包擴(kuò)它調(diào)用的子任務(wù))不能超過256 (0-255)個(gè),這個(gè)和1.1版本是一樣的。每個(gè)任務(wù)函數(shù)里面所寫語句的行數(shù)是沒有限制的,每個(gè)任務(wù)函數(shù)里面只能
有256個(gè)WaitX(ticks),如果發(fā)現(xiàn)編譯錯(cuò)誤,也是在前面增加空行。在V1.1版本的時(shí)候,有位網(wǎng)友在編制任務(wù)函數(shù)的時(shí)候,有一個(gè)里面用的WaitX(ticks)個(gè)
數(shù)比較多,編寫代碼的行數(shù)也比較多,他發(fā)現(xiàn)編譯錯(cuò)誤,就在WaitX(ticks)前面加空行,可是又和其他的WaitX(ticks)沖突,到后面每個(gè)WaitX(ticks)前面都
有數(shù)量不等的空行;為了避免這種事情出現(xiàn),一個(gè)是修改lc 變量的類型,這個(gè)在2.0版本是非常方便的,只要修改宏就行(#define LineDef unsignedchar)。
在V1.1也可以修改,只不過要修改兩三處地方。另外一個(gè)就是把函數(shù)優(yōu)化或者拆分等等,讓任務(wù)函數(shù)里面不要出現(xiàn)這么多的WaitX(ticks)。
執(zhí)行這個(gè)函數(shù),返回一個(gè)延時(shí)的數(shù)值,下次執(zhí)行的時(shí)候,通過SWITCH語句跳轉(zhuǎn)到上次執(zhí)行的位置,繼續(xù)執(zhí)行相關(guān)語句,并返回一個(gè)延時(shí)數(shù)值。這個(gè)
也是PT 的精華所在。如果不明白請參考1.0 版本的分解。

5.有關(guān)結(jié)構(gòu)體及其宏的展開。
(1).原型:
#defineClass(type) typedefstructC_##typeC_##type;struct C_##type
Class(task)
{
TimeDeftimer;
LineDeflc;
};

(2).把宏替換掉
typedefstructC_taskC_task;structC_task{
TimeDeftimer;
LineDeflc;
};

把宏替換掉后對于typedefstructC_taskC_task;structC_task{ 這句的理解分兩部分
a.紅色部分,用C_task代替structC_task。用C_task可以定義結(jié)構(gòu)體變量。
b.藍(lán)色部分 因?yàn)閟tructC_task沒有定義,它的定義在下面,告訴上面不是沒定義嘛,在這定義了 。
c.一般來說類型定義typedefstructC_taskC_task;,應(yīng)該放在它所重定義的類型的后面,就是應(yīng)該在結(jié)構(gòu)體定義后面,像u8,u16那樣。
d.先做typedef 類型定義,也就是說,這屬于事先聲明,之后才有具體定義,跟函數(shù)聲明一樣 。
(3).等價(jià)于
typedefstructC_taskC_task;
structC_task{
TimeDeftimer;
LineDeflc;
};

在這里要注意,宏展開后可以看到,可以用C_task 定義結(jié)構(gòu)體,這個(gè)結(jié)構(gòu)體變量里面只包含 unsignedshorttimer; 和unsignedcharlc;。
(4).任務(wù)類及任務(wù)變量展開:
//Class(LedTask)
typedefstructC_LedTaskC_LedTask;struct C_LedTask
{

C_tasktask;//每個(gè)任務(wù)類都必須有task變量,里面只包含timer和lc 變量
unsignedcharpin;//LED對應(yīng)的管腳
unsignedchartimeon;//LED點(diǎn)亮的時(shí)長
unsignedchartimelen;  //LED循環(huán)點(diǎn)亮的周期
}led1;

定義了一個(gè)名為led1 的結(jié)構(gòu)體變量。
在這里需要注意一點(diǎn):用C_LedTask可以定義結(jié)構(gòu)體變量。用如果是C_LedTaskled2; 這是定義了一個(gè)名為led2 的結(jié)構(gòu)體,里面的元素和led1里面的一樣;
要區(qū)分用C_LedTask和用C_task 定義結(jié)構(gòu)體的區(qū)別。

在這個(gè)任務(wù)類里面定義了每個(gè)任務(wù)函數(shù)所用到的私有變量,及每個(gè)任務(wù)函數(shù)用到的記錄執(zhí)行地址的lc變量和記錄需要延時(shí)的變量timer;是個(gè)完整的獨(dú)立的個(gè)體。用到子任務(wù)時(shí),在父任務(wù)結(jié)構(gòu)體中用 C_***task  定義一個(gè)子任務(wù)結(jié)構(gòu)體變量 ,從某種意義上講任務(wù)重入也是需要代價(jià)的。

有時(shí)候感覺繞來繞去,其實(shí)就是這個(gè)任務(wù)類的問題,
1.因?yàn)槿蝿?wù)類及任務(wù)變量定義中用Class 定義任務(wù)所用到的私有變量和一個(gè)獨(dú)立的C_tasktask,里面放著這個(gè)任務(wù)函數(shù)的行號和延時(shí)時(shí)間變量。用宏
C_***Task可以定義和本任務(wù)相關(guān)所有變量的結(jié)構(gòu)體。
2.如果任務(wù)類里面包含了其他的子任務(wù)。一般包含一個(gè)或者幾個(gè)用 C_***task  定義的結(jié)構(gòu)體變量;(其實(shí)就是相當(dāng)于把子任務(wù)中用到的所有變量在這
個(gè)父任務(wù)中又重新定義了一下)。父任務(wù)調(diào)用這個(gè)子任務(wù),會(huì)把數(shù)據(jù)放到這個(gè)子任務(wù)的結(jié)構(gòu)體里;同理其他父任務(wù)調(diào)用同一個(gè)子任務(wù)也會(huì)把數(shù)據(jù)放到自
己的子任務(wù)結(jié)構(gòu)體里。
3.父任務(wù)函數(shù)調(diào)用子任務(wù)函數(shù)時(shí),用到的數(shù)據(jù)是通過指針傳遞的,把這些變量傳遞給子任務(wù)函數(shù)。當(dāng)然了父任務(wù)函數(shù)變量的傳遞也是通過指針的。這樣結(jié)
合每個(gè)任務(wù)定義的結(jié)構(gòu)體變量,就能解決任務(wù)重入的問題了。子任務(wù)可以被多個(gè)主任務(wù)調(diào)用,主任務(wù)可以給子任務(wù)傳遞參數(shù)。主任務(wù)彼此獨(dú)立互不影響。

V1.1版本,記錄行號的變量是局部靜態(tài)變量,涉及到跨任務(wù)的變量都是靜態(tài)局部變量或者靜態(tài)全局變量;延時(shí)變量是個(gè)全局變量。
V2.0版本,每個(gè)任務(wù)函數(shù)用到的變量都是自己的私有全局變量,在調(diào)用的時(shí)候通過指針傳遞。


二.呼吸燈

在上面LED控制的基礎(chǔ)上設(shè)計(jì)一個(gè)呼吸燈,指示燈從暗到亮變化,分20個(gè)階段;再從亮到暗變化,也分20個(gè)階段,每個(gè)階段保持100ms
分析:
作為一個(gè)獨(dú)立的頂級任務(wù),設(shè)置的時(shí)候,就需要有自己的任務(wù)變量和任務(wù)函數(shù)。
把呼吸燈用到的變量統(tǒng)一放在一個(gè)結(jié)構(gòu)體中,起名:breath1;呼吸燈對應(yīng)的任務(wù)函數(shù)定義為BreathTask。編寫任務(wù)函數(shù)的時(shí)候,注意把他們定義的結(jié)構(gòu)體
名和函數(shù)名對應(yīng)起來,這樣就不容易弄混了。
1.呼吸燈任務(wù)類及任務(wù)變量
Class(BreathTask)//LED 呼吸燈控制任務(wù)
{

C_tasktask;//每個(gè)任務(wù)類都必須有task變量,里面存放著延時(shí)變量和行號。
unsignedchari;//呼吸燈變量,
}breath1;  //呼吸燈的結(jié)構(gòu)體變量名
2.呼吸燈任務(wù)函數(shù) (呼吸燈的具體動(dòng)作)
TaskFun(BreathTask){//實(shí)現(xiàn)呼吸燈效果
while(1)
{

//從暗到亮變化
for(me.i=0;me.i<20;me.i++){  //這個(gè)變量i就是我們在結(jié)構(gòu)體breath1 中定義的unsignedchari;//呼吸燈變量。
WaitX(100); //和1.0版本原理一樣,釋放CPU,過100ms 再往下執(zhí)行。
led1.timeon=me.i; //把呼吸燈的變量值,賦值給了LED任務(wù)函數(shù)里的變量了,因?yàn)槎x的結(jié)構(gòu)體都是全局變量的,可以相互調(diào)用,賦值。
}

//再從亮到暗變化
for(me.i=20;me.i>0;me.i--){
WaitX(100);

led1.timeon=me.i; //用到的本任務(wù)函數(shù)的變量是通過指針;在這里引用其他任務(wù)的變量,是直接引用的。執(zhí)行到LED任務(wù)函數(shù)的時(shí)候值變了。
}
}
}EndFun

3.任務(wù)函數(shù)里的變量,都是通過指針傳遞的,和各自定義的結(jié)構(gòu)體對應(yīng)起來。由于2.0版本需要支持重入,任務(wù)函數(shù)值不能直接傳給結(jié)構(gòu)體,
而是地址進(jìn)去,進(jìn)去后轉(zhuǎn)換回結(jié)構(gòu)體。
4.在這個(gè)呼吸燈的任務(wù)函數(shù)中用到了其他任務(wù)函數(shù)中的變量:led1.timeon=me.i;,通過賦值,下次執(zhí)行LED 函數(shù)的時(shí)候就會(huì)發(fā)生變化。也就是說
這個(gè)調(diào)度器支持任務(wù)之間的數(shù)據(jù)互訪。
5.如果這個(gè)呼吸燈任務(wù)函數(shù)里面不用數(shù)據(jù)互訪賦值,而用子任務(wù)調(diào)用,怎么寫?假如沒有這個(gè)呼吸燈的任務(wù)函數(shù),執(zhí)行l(wèi)ed任務(wù)函數(shù),led燈的狀態(tài)是什
么?

三.子任務(wù)分析
涉及到的宏#defineCallSub(SubTaskName,SubTaskVar) do{WaitX(0);SubTaskVar.task.timer=SubTaskName(&(SubTaskVar)); \
if(SubTaskVar.task.timer!=END)returnSubTaskVar.task.timer;}while(0)

看串口任務(wù)類及任務(wù)變量,
Class(SerialTask)
{

C_tasktask;  //每個(gè)任務(wù)類都必須有task變量
C_WaitsecTaskwaitsec1;//串口任務(wù)擁有一個(gè)秒延時(shí)子任務(wù)
Stringcomdata;//串口任務(wù)自己用的變量
}serial1;

定義了一個(gè)結(jié)構(gòu)體變量serial1,里面除了自己用的變量外,增加了一個(gè)C_WaitsecTaskwaitsec1; (定義了一個(gè)結(jié)構(gòu)體,里面包含了WaitsecTask任務(wù)函數(shù)所
用到的全部變量)接下來我們看一下串口的任務(wù)函數(shù)。

TaskFun(SerialTask){//串口任務(wù),定時(shí)輸出hello
Serial.begin(9600);
Serial.println("start");
while(1){

me.waitsec1.seconds=1;//總共延遲1+2=3秒
CallSub(WaitsecTask,me.waitsec1);
Serial.println("hello");
}
}EndFun

分解開來看一看
TaskFun(SerialTask){//串口任務(wù),定時(shí)輸出hello
根據(jù)上面分析的經(jīng)驗(yàn),執(zhí)行完任務(wù)函數(shù)的有關(guān)指令,返回一個(gè)延時(shí)函數(shù)給timer。
我們看一下有關(guān)語句:

Serial.begin(9600);Serial.println("start");不用關(guān)心,串口的波特率和起始位什么的,(猜的)。
程序執(zhí)行到me.waitsec1.seconds=1;很簡單,給自己里面子任務(wù)中的變量賦了一個(gè)值,看清楚是要求子任務(wù)延時(shí)1個(gè)單位。
接著繼續(xù)執(zhí)行到CallSub(WaitsecTask,me.waitsec1);我們看一下它的宏
#defineCallSub(SubTaskName,SubTaskVar) do{WaitX(0);SubTaskVar.task.timer=SubTaskName(&(SubTaskVar)); \
if(SubTaskVar.task.timer!=END)returnSubTaskVar.task.timer;}while(0)

把有關(guān)參數(shù)帶進(jìn)去。
{WaitX(0);me.waitsec1.task.timer=WaitsecTask(&(me.waitsec1)); if(me.waitsec1.task.timer!=END)returnme.waitsec1.task.timer;}

展開分析:
執(zhí)行WaitX(0);在這里設(shè)置一個(gè)“斷點(diǎn)”,讓任務(wù)下次從這里執(zhí)行。記錄當(dāng)前LC 位置,這樣如果子任務(wù)有WAIT(X),出來以后下次能順利進(jìn)去。(分析一下
如果沒有WaitX(0);會(huì)發(fā)生什么問題?)
執(zhí)行自己的子任務(wù)WaitsecTask(&(me.waitsec1)把結(jié)果賦值給自己定義的子任務(wù)變量里的timer變量,
在程序中找找到WaitsecTask(),函數(shù)的原型:
#defineTaskFun(TaskName) TimeDefTaskName(C_##TaskName *cp){switch(me.task.lc){default:

TaskFun(WaitsecTask){//實(shí)現(xiàn)指定的秒數(shù)延遲 (me.waitsec1.seconds=1;在本例中賦值為1S),之后再加上2秒延遲
for(me.i=0;me.i<me.seconds;me.i++){
WaitX(1000);
}

CallSub(Wait2Task,me.wait2);//這里通過調(diào)用2秒固定延遲子任務(wù),實(shí)現(xiàn)額外的2秒延遲。
}EndFun

執(zhí)行完自己指定的延時(shí)后,繼續(xù)執(zhí)行自己子任務(wù)里面子任務(wù)調(diào)用的它的子任務(wù),CallSub(Wait2Task,me.wait2),再實(shí)現(xiàn)2S 的延時(shí),展開略。

通過上面的分析,我們很清楚的看到用Class(task)定義結(jié)構(gòu)體用起來是很方便的,除了考慮自己父任務(wù)函數(shù)里必須的變量外,對于子函數(shù)的調(diào)用只要
定義一個(gè)宏,(其實(shí)是把每一層的變量都放在了自己定義的宏里面了),用CallSub(SubTaskName,SubTaskVar)函數(shù)調(diào)用就可以了。只要你的內(nèi)存大你可以無限的調(diào)用,無論子程序怎么調(diào)用,彼此互不影響。
定義了任務(wù)類(Class(task)),在函數(shù)變量應(yīng)用和子程序變量定義的時(shí)候很靈活,減少我們的書寫量,每個(gè)任務(wù)函數(shù)用到的數(shù)據(jù),都保持在自己獨(dú)立
定義的變量中;函數(shù)調(diào)用用指針;這樣,函數(shù)就可以實(shí)現(xiàn)重入。任務(wù)函數(shù)可以相互調(diào)用;只要你的內(nèi)存足夠大,就可以無限調(diào)用。
以上展開后都在強(qiáng)調(diào)為任務(wù)重入做準(zhǔn)備,其實(shí)如果不用到任務(wù)重入功能,把time變量改為uchar感覺V2.0 簡易版和V1.1所用的資源相差不多,V2.0
用到的變量全部是全局變量,V1.1用到的變量涉及到任務(wù)之間的切換都是局部靜態(tài)變量。其實(shí)v2.0 簡易版這種寫法感覺比V1.1 的更加清晰。


四.總結(jié)

通過上面的分解,我們再回頭看一下作者smset 對V2.0 的評價(jià)
主要改進(jìn):
1)徹底解決了任務(wù)重入問題
2)很好的解決了任務(wù)之間的通信問題
3)引入面向任務(wù)對象的概念
4)任務(wù)具有自己的變量,提高了程序封裝程度

0.png

單片機(jī)源程序如下:
  1. #include "arduino.h"
  2. #include "xxddq.h"

  3. //-----任務(wù)類及任務(wù)變量在這里定義----------------
  4. Class(Wait2Task) //一個(gè)固定延時(shí)2秒的子任務(wù)
  5. {
  6.   C_task task; //每個(gè)任務(wù)類都必須有task變量
  7.   unsigned char i;
  8. };

  9. Class(WaitsecTask)
  10. {
  11.   C_task task; //每個(gè)任務(wù)類都必須有task變量
  12.   C_Wait2Task wait2; //waitsectask擁有一個(gè)Wait2Task的子任務(wù)
  13.   unsigned char seconds;
  14.   unsigned char i;
  15. };

  16. Class(SerialTask)
  17. {
  18.   C_task task;  //每個(gè)任務(wù)類都必須有task變量
  19.   C_WaitsecTask waitsec1;//串口任務(wù)擁有一個(gè)秒延時(shí)子任務(wù)
  20.   String comdata; //串口任務(wù)自己用的變量
  21. }serial1;

  22. Class(LedTask)
  23. {
  24.   C_task task;//每個(gè)任務(wù)類都必須有task變量
  25.   unsigned char pin;//LED對應(yīng)的管腳
  26.   unsigned char timeon;//LED點(diǎn)亮的時(shí)長
  27.   unsigned char timelen;  //LED循環(huán)點(diǎn)亮的周期
  28. }led1;

  29. Class(BreathTask)//LED呼吸燈控制任務(wù)
  30. {
  31.   C_task task;//每個(gè)任務(wù)類都必須有task變量,通過控制ledtask的timeon來實(shí)現(xiàn)呼吸燈亮滅效果
  32.   unsigned char i;
  33. }breath1;

  34. //------------------任務(wù)函數(shù)在這里實(shí)現(xiàn)------------------------------------------
  35. TaskFun(Wait2Task){//實(shí)現(xiàn)固定兩秒延遲
  36.      for (me.i=0;me.i<20;me.i++){
  37.         WaitX(100);      
  38.       }
  39. }EndFun

  40. TaskFun(WaitsecTask){//實(shí)現(xiàn)指定的秒數(shù)延遲,之后再加上2秒延遲
  41.      for (me.i=0;me.i<me.seconds;me.i++){
  42.         WaitX(1000);      
  43.       }
  44.      CallSub(Wait2Task,me.wait2);//這里通過調(diào)用2秒固定延遲子任務(wù),實(shí)現(xiàn)額外的2秒延遲。
  45. }EndFun

  46. TaskFun(LedTask){
  47.    me.timelen=20;//LED PWM總周期為20毫秒。
  48.    pinMode(me.pin, OUTPUT);//設(shè)置管腳輸出

  49.    while(1)
  50.    {
  51.       digitalWrite(me.pin,HIGH);//電亮LED
  52.       WaitX(me.timeon);   
  53.       digitalWrite(me.pin,LOW);//關(guān)閉LED
  54.       WaitX(me.timelen-me.timeon);  
  55.    }
  56. }EndFun

  57. TaskFun(BreathTask){//實(shí)現(xiàn)呼吸等效果
  58.   while(1)
  59.   {
  60.     //從暗到亮變化
  61.    for (me.i=0;me.i<20;me.i++){
  62.       WaitX(100);
  63.       led1.timeon=me.i;
  64.    }
  65.    //再從亮到暗變化
  66.    for (me.i=20;me.i>0;me.i--){
  67.       WaitX(100);
  68.       led1.timeon=me.i;
  69.    }
  70.   }
  71. }EndFun

  72. TaskFun(SerialTask){ //串口任務(wù),定時(shí)輸出hello
  73.    Serial.begin(9600);
  74.    Serial.println("start");
  75.    while(1){
  76.       me.waitsec1.seconds=1;//總共延遲1+2 =3秒
  77.       CallSub(WaitsecTask,me.waitsec1);
  78.       Serial.println("hello");      
  79.    }
  80. }EndFun
  81. //------------------------------------------------------------------------

  82. #define BUILTIN_LED 13
  83. void runtasks(){
  84.   //指定led1任務(wù)驅(qū)動(dòng)的IO管腳
  85.   led1.pin=13;

  86.   //更新頂級任務(wù)的時(shí)間
  87.   UpdateTimer(led1);
  88.   UpdateTimer(breath1);   
  89.   UpdateTimer(serial1);
  90.      
  91.   //執(zhí)行頂級任務(wù)
  92.   RunTask(LedTask, led1);
  93. ……………………

  94. …………限于本文篇幅 余下代碼請從51黑下載附件…………
復(fù)制代碼

所有資料51hei提供下載:
小小調(diào)度器V2.0 簡化版.zip (1.93 KB, 下載次數(shù): 164)
小小調(diào)度器V2.0 Simple 整理說明2.pdf (212.7 KB, 下載次數(shù): 127)


評分

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

查看全部評分

回復(fù)

使用道具 舉報(bào)

ID:389812 發(fā)表于 2018-8-24 23:38 | 顯示全部樓層
main.c

#include <limits.h>
#include <reg52.h>
#include "OS.h"

TaskFun(led)
{
        while(1)
        {
                if(me.pin==5)
                {
                        P0_5=0;
                        WaitX(1_s);
                        P0_5=1;
                        WaitX(1_s);
                }
                if(me.pin==6)
                {
                        P0_6=0;
                        WaitX(2_s);
                        P0_6=1;
                        WaitX(2_s);
                }
        }
}EndFun

TaskFun(display)
{
                CallSub(led,me.led);
}EndFun


void timer1() interrupt 3
{                       
        TR1 = 0;                        //暫停定時(shí)器
        n.b[0] = TH1;
        n.b[1] = TL1;
        n.a = n.a + CT;
        TH1 = n.b[0];
        TL1 = n.b[1];
        TR1 = 1;                        //啟動(dòng)定時(shí)器

        UpdateTimers();
               
}

void main(void)
{       
        TMOD = 0x15;
        ET1 = 1;
        EA = 1;
        TR1 = 1;

        display_1.led.pin=5;
        display_2.led.pin=6;

        while(1)
        {
                RunTask(display,display_1);
                RunTask(display,display_2);
        }
}

回復(fù)

使用道具 舉報(bào)

ID:445062 發(fā)表于 2019-4-14 22:04 | 顯示全部樓層
學(xué)習(xí)一下對比protothreads有哪些改進(jìn)。
回復(fù)

使用道具 舉報(bào)

ID:428114 發(fā)表于 2019-7-6 21:51 | 顯示全部樓層
學(xué)習(xí)了
回復(fù)

使用道具 舉報(bào)

ID:43342 發(fā)表于 2019-9-14 19:07 | 顯示全部樓層
謝謝樓主!
回復(fù)

使用道具 舉報(bào)

ID:641609 發(fā)表于 2019-11-14 00:46 | 顯示全部樓層
整理得不錯(cuò)
回復(fù)

使用道具 舉報(bào)

ID:641609 發(fā)表于 2019-11-14 19:24 | 顯示全部樓層
本帖最后由 CSM_Min 于 2019-11-14 23:14 編輯

Snipaste_2019-11-14_19-18-59.png
1, 代碼使用pic編譯出錯(cuò),  HI-TECH Software\PICC\9.83, 不知道到底是哪里不支持?

2, 用stm32的keil編譯,仿真功能正常


回復(fù)

使用道具 舉報(bào)

ID:641609 發(fā)表于 2019-11-15 11:44 | 顯示全部樓層
Snipaste_2019-11-15_11-40-19.png

弄到現(xiàn)在終于搞定了, 但是我還有疑問,就是
1, 那兩個(gè)子程序結(jié)尾的do whlie 有什么具體作用呢?   我認(rèn)為可以不要do while



回復(fù)

使用道具 舉報(bào)

ID:115836 發(fā)表于 2019-12-9 19:24 | 顯示全部樓層
這種寫法源于Linux內(nèi)核代碼。
do{...}while(0)這樣的寫法可以避免宏展開時(shí)的一些坑。
回復(fù)

使用道具 舉報(bào)

ID:40043 發(fā)表于 2021-4-13 12:56 | 顯示全部樓層
這個(gè)資料整理的很全面,真的不錯(cuò)!
回復(fù)

使用道具 舉報(bào)

ID:40043 發(fā)表于 2021-4-13 12:57 | 顯示全部樓層
這個(gè)小調(diào)度器,真心不錯(cuò),尤其適合哪種資源太小的MCU;
回復(fù)

使用道具 舉報(bào)

ID:105845 發(fā)表于 2022-11-8 15:09 | 顯示全部樓層
不錯(cuò)  好好研究一下
回復(fù)

使用道具 舉報(bào)

ID:67839 發(fā)表于 2023-1-8 16:12 | 顯示全部樓層
下載研究一下
回復(fù)

使用道具 舉報(bào)

ID:433219 發(fā)表于 2023-1-9 08:41 | 顯示全部樓層
時(shí)間輪片?
回復(fù)

使用道具 舉報(bào)

ID:87000 發(fā)表于 2023-1-9 09:00 | 顯示全部樓層
先收藏再說。覺得還可以
回復(fù)

使用道具 舉報(bào)

ID:87000 發(fā)表于 2023-2-13 11:21 | 顯示全部樓層
學(xué)習(xí)了,一直在找可用的小系統(tǒng)
回復(fù)

使用道具 舉報(bào)

ID:898721 發(fā)表于 2023-2-20 20:46 | 顯示全部樓層
編譯沒有通過,好像是宏不支持這種寫法,或者哪里沒搞對,大神幫忙看看有沒有遇到這種問題
Snipaste_2023-02-20_20-43-25.jpg
回復(fù)

使用道具 舉報(bào)

ID:339654 發(fā)表于 2023-3-21 19:03 | 顯示全部樓層
我覺得調(diào)度器還是用定時(shí)器來控制時(shí)基
回復(fù)

使用道具 舉報(bào)

ID:1109308 發(fā)表于 2024-1-18 10:12 | 顯示全部樓層
學(xué)習(xí)一下思想,看能不能移植
回復(fù)

使用道具 舉報(bào)

ID:1130560 發(fā)表于 2024-11-16 08:31 | 顯示全部樓層
大佬們,多任務(wù)操作同一硬件,譬如串口1,怎么做互斥處理呢?
回復(fù)

使用道具 舉報(bào)

ID:899151 發(fā)表于 2025-1-24 09:57 | 顯示全部樓層
不是真的2.0吧,
回復(fù)

使用道具 舉報(bào)

您需要登錄后才可以回帖 登錄 | 立即注冊

本版積分規(guī)則

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

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

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