MCU應用程式架構整理(轉)
阿新 • • 發佈:2018-12-18
應用程式架構
- 簡單的前後臺順序執行程式:多數人的使用方法,無需考慮程式的具體架構,直接通過順序編寫應用程式即可;
- 時間片輪詢法: 介於順序執行與作業系統之間的一種方法;
- 作業系統:應用程式編寫的最優辦法,對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秒執行以下時鐘顯示,因為我們的時鐘最小單位是1s,所以在秒變化才顯示一次足夠了;
- 由於按鍵在按下時會引數抖動,而我們知道一般按鍵的抖動大概是20ms,那麼我們在順序執行的函式中一般是延伸20ms,而這裡我們每20ms掃描一次,是非常不錯的處理,既達到了消抖的目的,也不會漏掉按鍵輸入;
- 為了能夠顯示按鍵後的其他提示和工作介面,我們這裡設計每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(); // 任務處理 } }