1. 程式人生 > >MCU應用程式架構整理(轉)

MCU應用程式架構整理(轉)

應用程式架構

  1. 簡單的前後臺順序執行程式:多數人的使用方法,無需考慮程式的具體架構,直接通過順序編寫應用程式即可;
  2. 時間片輪詢法: 介於順序執行與作業系統之間的一種方法;
  3. 作業系統:應用程式編寫的最優辦法,對mcu ram 有一定的要求。

詳細介紹

順序執行法

  • 這種方式較為簡單,實時性,並行性要求不高的情況下是一種不錯的選擇,程式設計簡單,思路較為清晰,但是當應用程式較為複雜的情況下,如果沒有一個完整的流程圖,恐怕很難讀懂程式的執行狀態;
  • 隨著程式功能的增加,不利於維護,也不利於程式碼優化;
  • 順序執行法的應用程式流程框架:
    /**************************************************************************************
    * FunctionName   : main()
    * Description    : 主函式
    * EntryParameter : None
    * ReturnValue    : None
    **************************************************************************************/
    int main(void)
    { 
        uint8 keyValue; 
        InitSys();                  // 初始化
        while (1)
        {
            TaskDisplayClock();
            keyValue = TaskKeySan();
            switch (keyValue)
           {
                case x: TaskDispStatus(); break;
                ...
                default: break;
            }
        }
    }
    

時間片輪詢法

  • 時間片輪詢法在很多書籍中有提到過,很多時候與作業系統一起出現,這裡所描述的時間片輪詢法,是在前後臺程式中使用此法;
  • 使用一個定時器(任意均可),我們需要作如下的工作:

    初始化暫存器

  • 假設定時器的定時中斷為1ms(可以根據需求改為任意值),與作業系統一致,中斷過於頻繁效率就低,中斷太長,實時性差;

    定義相關數值

    #define TASK_NUM   (3)                 //  這裡定義的任務數為3,表示有三個任務會使用此定時器定時。
    uint16 TaskCount[TASK_NUM] ;           //  這裡為三個任務定義三個變數來存放定時值。
    uint8  TaskMark[TASK_NUM];             //  同樣對應三個標誌位,為0表示時間沒到,為1表示定時時間到。
    

    新增中斷服務

  • 定時中斷服務函式,在中斷中逐個判斷,如果定時值為0,表示沒有使用此定時器或此定時器已經完成定時,不著處理。否則定時器減一,直到為零時,相應標誌位值1,表示此任務的定時值到了。
    /**************************************************************************************
    * FunctionName : TimerInterrupt()
    * Description : 定時中斷服務函式
    * EntryParameter : None
    * ReturnValue : None
    **************************************************************************************/
    void TimerInterrupt(void)
    {
        uint8 i;
        for (i=0; i<TASKS_NUM; i++)
        {
            if (TaskCount[i])
            {
                  TaskCount[i]--;
                  if (TaskCount[i] == 0)
                  {
                        TaskMark[i] = 0x01;
                  }
            }
       }
    }
    

    新增切換程式碼

  • 在我們的應用程式中,在需要的應用定時的地方新增如下程式碼,以任務1為例:
    TaskCount[0] = 20;       // 延時20ms
    TaskMark[0]  = 0x00;     // 啟動此任務的定時器
    
  • 到此我們只需要在任務中判斷TaskMark0是否為0x01即可,其他任務新增相同,至此一個定時器的複用問題就實現了。通過上面對1個定時器的複用我們可以看出,在等待一個定時的到來的同時我們可以迴圈判斷標誌位,同時也可以去執行其他函式。

時間片輪詢法架構

設計結構體

// 任務結構
typedef struct _TASK_COMPONENTS
{
    uint8 Run;                  // 程式執行標記:0-不執行,1執行
    uint8 Timer;                // 計時器
    uint8 ItvTime;              // 任務執行間隔時間
    void (*TaskHook)(void);     // 要執行的任務函式
} TASK_COMPONENTS;              // 任務定義

新增服務函式

/**************************************************************************************
* FunctionName   : TaskRemarks()
* Description    : 任務標誌處理
* EntryParameter : None
* ReturnValue    : None
**************************************************************************************/
void TaskRemarks(void)
{
    uint8 i;
    for (i=0; i<TASKS_MAX; i++)          // 逐個任務時間處理
    {
         if (TaskComps[i].Timer)          // 時間不為0
        {
            TaskComps[i].Timer--;         // 減去一個節拍
            if (TaskComps[i].Timer == 0)       // 時間減完了
            {
                 TaskComps[i].Timer = TaskComps[i].ItvTime;       // 恢復計時器值,從新下一次
                 TaskComps[i].Run = 1;           // 任務可以執行
            }
        }
   }
}

修改任務處理函式

/**************************************************************************************
* FunctionName   : TaskRemarks()
* Description    : 任務標誌處理
* EntryParameter : None
* ReturnValue    : None
**************************************************************************************/
void TaskRemarks(void)
{
    uint8 i;
    for (i=0; i<TASKS_MAX; i++)          // 逐個任務時間處理
    {
         if (TaskComps[i].Timer)          // 時間不為0
        {
            TaskComps[i].Timer--;         // 減去一個節拍
            if (TaskComps[i].Timer == 0)       // 時間減完了
            {
                 TaskComps[i].Timer = TaskComps[i].ItvTime;  // 恢復計時器值,從新下一次
                 TaskComps[i].Run = 1;           // 任務可以執行
            }
        }
   }
}

應用

  • 假設我們目前有三個任務:時鐘顯示,按鍵掃描和工作狀態顯示。

    定義結構體

    /**************************************************************************************
    * Variable definition                           
    **************************************************************************************/
    static TASK_COMPONENTS TaskComps[] =
    {
        {0, 60, 60, TaskDisplayClock},            // 顯示時鐘
        {0, 20, 20, TaskKeySan},               // 按鍵掃描
        {0, 30, 30, TaskDispStatus},            // 顯示工作狀態
         // 這裡新增你的任務。。。。
    };
    
  • 在定義變數時,我們已經初始化了值,這些值的初始化,非常重要,跟具體的執行時間優先順序等有關係,這個需要自己掌握。
    1. 上述的定義顯示:我們有三個任務,每1秒執行以下時鐘顯示,因為我們的時鐘最小單位是1s,所以在秒變化才顯示一次足夠了;
    2. 由於按鍵在按下時會引數抖動,而我們知道一般按鍵的抖動大概是20ms,那麼我們在順序執行的函式中一般是延伸20ms,而這裡我們每20ms掃描一次,是非常不錯的處理,既達到了消抖的目的,也不會漏掉按鍵輸入;
    3. 為了能夠顯示按鍵後的其他提示和工作介面,我們這裡設計每30ms顯示一次,如果覺得反應慢了,可以讓這些值小一點。後面的名稱是對應的函式名,你必須在應用程式中編寫這函式名稱和這三個一樣的任務。

      建立任務列表

  • 這裡定義的任務清單的目的其實就是引數TASK_MAX的值,其他值是沒有具體的意義的,只是為了清晰的表面任務的關係而已。
    // 任務清單
    typedef enum _TASK_LIST
    {
        TAST_DISP_CLOCK,          // 顯示時鐘
        TAST_KEY_SAN,             // 按鍵掃描
        TASK_DISP_WS,             // 工作狀態顯示
         // 這裡新增其他任務。。。。
         TASKS_MAX                // 總的可供分配的定時任務數目
    } TASK_LIST
    

    編寫任務函式

    /**************************************************************************************
    * FunctionName   : TaskDisplayClock()
    * Description    : 顯示任務
    * EntryParameter : None
    * ReturnValue    : None
    **************************************************************************************/
    void TaskDisplayClock(void)
    {
    }
    /**************************************************************************************
    * FunctionName   : TaskKeySan()
    * Description    : 掃描任務
    * EntryParameter : None
    * ReturnValue    : None
    **************************************************************************************/
    void TaskKeySan(void)
    {
    }
    /**************************************************************************************
    * FunctionName   : TaskDispStatus()
    * Description    : 工作狀態顯示
    * EntryParameter : None
    * ReturnValue    : None
    **************************************************************************************/
    void TaskDispStatus(void)
    {
    }
    // 這裡新增其他任務。。。。。。。。。
    

    修改主函式

  • 關鍵注意點:TaskCompsi.TaskHook(); 如果執行時間較長,就會影響輪詢排程的週期,導致計時週期出問題。一定要確保函式的執行時間,小於執行任務時間間隔。
    /**************************************************************************************
    * FunctionName   : main()
    * Description    : 主函式
    * EntryParameter : None
    * ReturnValue    : None
    **************************************************************************************/
    int main(void)
    { 
        InitSys();                  // 初始化
        while (1)
        {
            TaskProcess();             // 任務處理
        }
    }