2010年2月18日 星期四

8051簡單多工2:基本設計規格及原理

在8051上想做多工,安裝RTOS又太大,大的不是程式本身,而是使用於Task registers記憶體太多了。
為了省下使用記憶體及加速切換,改用函式指標的方式做為多工切換的方法。
在多組程式運行的系統上,必須有FuncID做為各別task區分用。
所以核心的程式樣貌是這樣:
idata unsigned char FuncID = 0;
idata void (*TaskFunc[TASK_NUM])(void) = {Dummy,Dummy,TestSkip0,LED_Light};
void main(void)
{
    Init_Device();
    while (1)
    {
        register void (*Current_Func)(void);
        EA = 0;
        Current_Func = TaskFunc[FuncID]; // may break with interrupt
        EA = 1;
        Current_Func();
        FuncID = (++FuncID) % TASK_NUM;
    }
}
其實這段程式原是想要這樣執行:
while (1)
{
    TaskFunc[0]();
    TaskFunc[1]();
    TaskFunc[2]();
    TaskFunc[3]();
}
而TaskFunc[]內容是可以由程式改變,而原先初值設定為Dummy(),就會執行Dummy()。
如果要做程式移轉,只要做TaskFunc[0]=Dummy2;就會改為執行Dummy2()。
兩個程式若是要對跳,可以這樣寫:
void Test1(void)
{
    // do 1
    TaskFunc[0] = Test2;
}

void Test2(void)
{
    // do 2
    TaskFunc[0] = Test1;
}
然後執行時就可以看到TaskFunc[0]()執行動作為,一次執行Test1(),一次執行Test2()不停循環下去。
為了省去寫程式去記目前Task使用的FuncID,所以加入一組函式來做。
void Task_Set_Next(void (*p_func)(void))
{
    TaskFunc[FuncID]=p_func;
}
程式就可以簡單寫為
void Test1(void)
{
    // do 1
    Task_Set_Next(Test2);
}

void Test2(void)
{
    // do 2
    Task_Set_Next(Test1);
}

但各程式塊不想看到其他程式,也就是要有管理程式來分隔。
所要加入的管理功能,首要是時間控制。有二種時間控制是Bee需要的。
1.短暫時間控制
    定義為不使用中斷方式,以程式跳過執行權方式做延時。跳過次數可以設定。
2.ms級時間控制
    定義為使用中斷方式,在執行多少ms後回復執行權。
其實並不是真正的放棄執行權,而是函式指標暫時以Dummy填入,待延時結束後才填回使用者的程式。
這時FuncID的用處出來了,因為它可以知道是那一組可以做回復動作。

最後,主程式要去開關中斷又是如何?就是EA=0後取得TaskFunc[FuncID]內容後,然後才執行。因為就結果來看來多此一舉。
如果您認為多此一舉,那代表您在中斷上使用還不夠熟。
因為若是直接寫
TaskFunc[FuncID]();
會發生死當,但機率不高。以組合語言追蹤當機的指令時,會發現一個很少見的狀況。
因為要取得TaskFunc[FuncID]的內容,8051要取用二次資料,這是因為8051是8位元,但指標是16位元。
就在取出第一個byte內容時,剛好中斷發生,跑去執行中斷程式,若又剛好是回復執行的動作,會回填新的函式指標值,然後退出中斷。
接下來取出第二個byte,但這個是回復的指標值,和原先的不同,接下來去執行就跑去怪地方死當了。
所以在取出函式指標時要加入關閉中斷,取完後回復中斷。

取出後,為了不浪費記憶體及加速,所以使用暫存器宣告register告知編譯器放在暫存器即可。
所以
register void (*Current_Func)(void);
這行是宣告一個暫存器變數,型態為函式指標。這可不是函式原型宣告哦!


沒有留言:

張貼留言