2010年2月18日 星期四

8051簡單多工1:整體程式

以前在雷兒電子網發表的文章,重貼在這裏。目前有使用在商品上,穩定性還不錯。分成幾篇來貼。

這是為了對付8051這種沒有多大堆疊的MCU發開出來的。

我懶的用組合語言寫,實際上是說我對8051組合語言不太熟,所以用純粹C寫。因為簡單,所以拿來教學應是不錯。

整個系統可以說是為了對付LCM這個對時間有點敏感的裝置設計出來的。
LCM的規格上每次下指令或資料的時間間隔為73us。說大不大,又說小不小,拿來做單純delay不做事也很浪費。所以就想出這種多工方法。
因有對時間有點敏感的Task存在,故採用固定輪詢法(Polling)來保障單一Task微量延時的時間。
基本程式如下:

#include "c8051f340.h"                       // SFR declarations
#include < stdio.h >

#define LCM_DB          P2      /* LCM Data Bus */
#include "F340Init.h"
sbit LED1    = 0xB4;
sbit LED2    = 0xB5;
sbit LED3    = 0xB6;
sbit LED4    = 0xB7;

// task start function
void LED_Light(void);
void LED_Black(void);

// measure skip time
void TestSkip0(void);
void TestSkip1(void);

// O.S function declaim
#define TASK_NUM     4
void Dummy(void);
idata unsigned char FuncID = 0;
idata unsigned char Task_Delay_Count[TASK_NUM]={0,0,0,0};
unsigned char Task_Skip_Count[TASK_NUM]={0,0,0,0};
idata void (*TaskFunc[TASK_NUM])(void) = {Dummy,Dummy,TestSkip0,LED_Light};
void (*TaskResumeFunc[TASK_NUM])(void) = {Dummy,Dummy,TestSkip0,LED_Light};

// O.S functions
void Dummy(void)
{
    return;
}

void Task_Set_Next(void (*p_func)(void))
{
    TaskFunc[FuncID]=p_func;
}

void Task_Delay_Next(void (*p_func)(void))
{
    TaskResumeFunc[FuncID]=p_func;
}

void Task_Delay_Ms(unsigned char n)
{
    if (! n ) n = 1;
    TaskFunc[FuncID] = Dummy; // to sleep
    Task_Delay_Count[FuncID] = n;
}

void Task_Skip_Next(void (*p_func)(void))
{
    TaskResumeFunc[FuncID]=p_func;
}

void Task_Skip(void)
{
    if ( Task_Skip_Count[FuncID] ) {
        Task_Skip_Count[FuncID]--;
        if (! Task_Skip_Count[FuncID]) TaskFunc[FuncID]=TaskResumeFunc[FuncID];
    }
}

void Task_Set_Skip(unsigned char n)
{
    if (! n ) n = 1;
    TaskFunc[FuncID] = Task_Skip; // to sleep
    Task_Skip_Count[FuncID] = n;
}

#define TASK_DELAY_MS_NEXT(n,s)     Task_Delay_Next(s);Task_Delay_Ms(n);
#define TASK_SKIP_N_NEXT(n,s)        Task_Skip_Next(s);Task_Set_Skip(n);

// O.S functions end

// main loop
void main(void)
{
    PCA0MD &= ~0x40;                    // Disable Watchdog timer
    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;
    }
}

void Timer0_ISR (void) interrupt INTERRUPT_TIMER0
{
    unsigned char i;

    TL0  = 0x80;
    TH0  = 0x44; // set delay for 1 ms
    for ( i=0; i < TASK_NUM ; i++ )
    {
        if ( Task_Delay_Count[i] )
       {

            Task_Delay_Count[i]--;
            if (! Task_Delay_Count[i]) TaskFunc[i]=TaskResumeFunc[i]; // wake up function
        }
    }
}

// Begin User Tasks
// Task 0

// Task 1

// Task 2 : measure skip time
void TestSkip0(void)
{
    LED4 = 0;
    TASK_SKIP_N_NEXT(10,TestSkip1);
}

void TestSkip1(void)
{
    LED4 = 1;
    TASK_SKIP_N_NEXT(20,TestSkip0);
}

// Task 3 : function for LED
void LED_Light(void)
{
    LED1 = 0;
    TASK_DELAY_MS_NEXT(250,LED_Black);
}

void LED_Black(void)
{
    LED1 = 1;
    TASK_DELAY_MS_NEXT(250,LED_Light);
}


2 則留言:

  1. 請教一下
    問題1:
    因為定義TASK_NUM 是4
    #define TASK_NUM     4

    但因為FuncID%4,所以只會有0-3,但TASK宣告時,為5個,請問這是故意的嗎?

    FuncID = (++FuncID) % TASK_NUM;

    問題2:
    為什麼要再宣告一個指向函數的指標
            register void (*Current_Func)(void);
           Current_Func();

    不直接使用定義好的指向函數的指標就好?
    (*TaskFunc[FuncID])( );
    謝謝
    [版主回覆11/21/2010 22:29:59]問題在於EA=0及EA=1二行中間。
    直接寫沒有EA=0及EA=1,Task會死掉。被中斷撞死。
    直接加的話也不對,變成在EA=0時就跳進函式。
    所以只好用另一個暫存器變數來取。

    回覆刪除
  2. @@  我看錯了,我把LED_Light    看成是 LED 和 Light , 所以我以為有5個Task
    所以才會有剛剛的問題1,抱歉

    回覆刪除