1. 程式人生 > >基於微控制器的多工輪詢系統

基於微控制器的多工輪詢系統

@[TOC](Schedule) # 時間片輪詢系統 通過時間片的劃分,可以利用一個定時器或者系統滴答定時器,通過多工輪詢方法,實現一個多工的基於時間輪詢排程的系統——Schedule. # 版權宣告 本文展示的原始碼為網路上所獲取的資源,如有侵權,請告知刪除。 此處僅為交流學習使用。 # 檔案結構 有三個檔案構成,兩個h檔案一個c檔案。 sch_chg.h 型別定義檔案 schedule.c 原始碼實現 shcedule.h 巨集定義和配置檔案 # 原始碼 ## sch_chg.h ```c #ifndef __SCH_CFG_H_ #define __SCH_CFG_H_ //定義可裁剪部分 #define SCH_CFG_Q_EN 1u /* 任務內建訊息使能 */ #define SCH_MAX_TASKS 4 /* 最大任務數量可以增加最大255個*/ #define SCH_MBOX_EN 1 /* Enable (1) or Disable (0) 郵箱*/ #define SCH_TICKS_PER_SEC 1000 /* 1秒鐘多少個系統時鐘數 */ /*------------------------------------------------------------------------------ * 與編譯器相關資料型別定義 *-----------------------------------------------------------------------------*/ typedef unsigned char BOOLEAN; typedef unsigned char INT8U; /* Unsigned 8 bit quantity */ typedef signed char INT8S; /* Signed 8 bit quantity */ typedef unsigned short INT16U; /* Unsigned 16 bit quantity */ typedef signed short INT16S; /* Signed 16 bit quantity */ typedef unsigned int INT32U; /* Unsigned 32 bit quantity */ typedef signed int INT32S; /* Signed 32 bit quantity */ typedef float FP32; /* Single precision floating point */ typedef double FP64; /* Double precision floating point */ typedef unsigned int SCH_TICKS; //定義延時時鐘資料型別 //定義資料型別 typedef unsigned char SCH_UINT8; typedef unsigned int SCH_UINT16; #endif ``` ## schedule.h ```c #ifndef __SCHEDULE_H #define __SCHEDULE_H #ifdef __cplusplus extern "C" { #endif /*------------------------------------------------------------------------------ schedule 版本號 ------------------------------------------------------------------------------*/ #define SCH_VERSION 02200u /*------------------------------------------------------------------------------ * 包含標頭檔案 ------------------------------------------------------------------------------*/ #include "sch_cfg.h" #ifndef SCH_GLOBALS #define SCH_EXT extern #else #define SCH_EXT #endif /*------------------------------------------------------------------------------ * 系統狀態定義 ------------------------------------------------------------------------------*/ #define SCH_DLY_TYPE SCH_UINT16 #define SCH_ERR_NONE 0u #define SCH_ERR_TIMEOUT 10u #define SCH_TASK_RUN 0 #define SCH_TASK_PEND (SCH_DLY_TYPE)0xffff #if SCH_MAX_TASKS <= 255 #define SCH_MAX_TASK_TYPE SCH_UINT8 //最大任務數<=255時定義為u8 #else #define SCH_MAX_TASK_TYPE SCH_UINT16 //最大任務為>255則定義為u16 #endif /*------------------------------------------------------------------------------ * 資料結構定義 ------------------------------------------------------------------------------*/ typedef struct { SCH_TICKS TaskDly; /*延時資料*/ INT8U SubExitFlag; /*這個是什麼?*/ #if SCH_CFG_Q_EN > 0u void *pData; /*佇列指標*/ SCH_UINT8 Size; /*訊息的長度*/ #endif }SCH_TCB; /*----------------------------------------------------------------------------*/ /*----------------------------郵箱資料型別定義--------------------------------*/ typedef struct { SCH_TICKS Dly; /*延時量*/ void *pMbox; /*郵箱的指標*/ } MBOX; typedef SCH_TICKS SEM; //訊號量資料型別定義 /*------------------------------------------------------------------------------ * 全域性變數定義 ------------------------------------------------------------------------------*/ SCH_EXT SCH_TCB TaskTcb[SCH_MAX_TASKS]; //任務狀態資料結構 SCH_EXT INT8U CurRunTaskId; //當前執行任務的ID號 /*----------------------------------------------------------------------------*/ /*------------------------------------------------------------------------------ 任務相關巨集 【1】任務建立按例項建立: void Task_xxx(void) { SCH_TaskBegin(); while(1) { //使用者程式碼 SCH_TimeDly(SCH_TICKS_PER_SEC/100); //根據任務實際週期進行延時 } SCH_TaskEnd(); } 【注意】 1,任務必須是無限迴圈,不能返回; 2,任務中必須有延時函式或等待訊號量,便於任務出讓CPU使用權; 3,任務函式中使用的非靜態區域性變數會在任務出讓CPU後, 變數消失,因此要想某個變數不消失,則要使用靜態變數 【2】任務排程,按以下例項 SCH_TaskRun(Task_xxx,0); 【注意】 1,各任務的ID不能相同 ------------------------------------------------------------------------------*/ //@任務開始巨集在任務函式的變數聲明後引用 #define SCH_TaskBegin() static INT16U _lc=0u; switch(_lc){case 0: //@任務結束巨集在任務尾引用 #define SCH_TaskEnd() } //@任務呼叫巨集在主迴圈中呼叫 //@ TaskName:任務名 //@ TaskID:任務ID,最大值不能超過"SCH_MAX_TASKS"值,且每個任務的ID不能相同 #define SCH_TaskRun(TaskName,TaskID) \ do { \ CurRunTaskId = TaskID; \ if(TaskTcb[CurRunTaskId].TaskDly==0u) { \ TaskName(); \ } \ } while(0); /*----------------------------------------------------------------------------*/ /*------------------------------------------------------------------------------ 子任務相關巨集 -------------------------------------------------------------------------------- 【1】子任務建立按例項建立: void SubTask_xxx(void) { SCH_SubTaskBegin(); //使用者程式碼 SCH_TimeDly(SCH_TICKS_PER_SEC/100); //根據任務實際週期進行延時 SCH_SubTaskEnd(); } 【注意】 1,子任務不可以是無限迴圈,否則子任務後面程式無法執行; 2,子任務可以帶引數,但是不能返回; 3,子任務中必須有延時函式或等待訊號量,便於任務出讓CPU使用權; 4,子任務函式中使用的非靜態區域性變數會在任務出讓CPU後,變數消失, 因此要想某個變數不消失,則要使用靜態變數 【2】子任務排程,按以下例項 SCH_CallSubTask(SubTask_xxx()); ------------------------------------------------------------------------------*/ //@子任務開始巨集,在子任務函式的變數聲明後引用 #define SCH_SubTaskBegin() static INT16U _lc=0u;switch(_lc){case 0: //@子任務結束巨集,在子任務函式尾引用 #define SCH_SubTaskEnd() }_lc=0u;TaskTcb[CurRunTaskId].SubExitFlag=0u;return //@子任務呼叫巨集,在任務中呼叫,允許多層巢狀 //@ SubTaskName:子任務名 //@ p_arg:子執行緒函式輸入引數 #define SCH_CallSubTask(SubTaskFun) \ TaskTcb[CurRunTaskId].SubExitFlag=1u; \ while(1) { \ _lc=(__LINE__%65535);return;case(__LINE__%65535): { \ SubTaskFun; \ } \ if(TaskTcb[CurRunTaskId].SubExitFlag==0u) { \ break; \ } \ } /*------------------------------------------------------------------------------ * 延時巨集定義 ------------------------------------------------------------------------------*/ //@延時巨集,在任務開始巨集和結束巨集之間的任意位置引用; //@【注】由於此排程器運用了C語言的行號的巨集程式碼, //@ 因此此巨集定義最好放在一行程式碼中,不能拆分此巨集定義,防止行號返回出錯 //@ ticks:延時時間 #define SCH_TimeDly(ticks) do{_lc=(__LINE__%65535);TaskTcb[CurRunTaskId].TaskDly=ticks;TaskTcb[CurRunTaskId].SubExitFlag=1u;return;}while(0);case (__LINE__%65535): /*------------------------------------------------------------------------------ 訊號量相關呼叫巨集定義 ------------------------------------------------------------------------------*/ //@訊號量建立 #define SCH_SemCreate(Sem) Sem = 1u //@w訊號量等待 //@ sem--等待訊號量; //@ timeout--等待超時時間,0--直到成功等待訊號量; //@ err--等待訊號狀態,SCH_ERR_NONE-等待成功,SCH_ERR_TIMEOUT-等待超時 #define SCH_SemPend(sem,timeout,err) \ do { \ if(sem==0u) { \ sem = 1u; \ err = SCH_ERR_NONE; \ } else { \ sem = timeout + 1u; \ SCH_TimeDly(0u); \ if(timeout==0u) { \ if(sem>0u) { \ TaskTcb[CurRunTaskId].TaskDly = 0u; \ return; \ } \ sem = 1u; \ err = SCH_ERR_NONE; \ } else { \ if(sem>1u) { \ sem --; \ TaskTcb[CurRunTaskId].TaskDly = 1u; \ return; \ } else if(sem==1u) { \ err = SCH_ERR_TIMEOUT; \ } else { \ sem = 1u; \ err = SCH_ERR_NONE; \ } \ } \ } \ } while(0) //@ 發出一個訊號量 #define SCH_SemPost(Sem) \ do { \ Sem = 0u; \ } while(0) /*------------------------------------------------------------------------------ 郵箱相關呼叫巨集定義 ------------------------------------------------------------------------------*/ #if SCH_MBOX_EN > 0u //@建立郵箱 #define SCH_MboxCreate(mbox) \ do { \ mbox.Dly = 1u; \ mbox.pMbox = (void*)0; \ } while(0) //@ 郵箱等待 //@ mbox--等待郵箱; //@ pmsg--等待成功後接收郵箱資料指標; //@ timeout--等待郵箱超時時間,0--直到成功等待郵箱; //@ err--等待郵箱狀態,SCH_ERR_NONE-等待成功,SCH_ERR_TIMEOUT-等待超時 #define SCH_MboxPend(mbox,pmsg,timeout,err) \ do { \ if(mbox.pMbox!=(void*)0) { \ pmsg = mbox.pMbox; \ mbox.pMbox = (void*)0; \ err = SCH_ERR_NONE; \ } else { \ mbox.Dly = timeout + 1u; \ SCH_TimeDly(0); \ if(timeout==0u) { \ if(mbox.pMbox==(void*)0) { \ TaskTcb[CurRunTaskId].TaskDly = 0u; \ return; \ } \ pmsg = mbox.pMbox; \ mbox.pMbox = (void*)0; \ err = SCH_ERR_NONE; \ } else { \ if(mbox.pMbox!=(void*)0) { \ pmsg = mbox.pMbox; \ mbox.pMbox = (void*)0; \ err = SCH_ERR_NONE; \ } else { \ if(mbox.Dly>1u) { \ mbox.Dly --; \ TaskTcb[CurRunTaskId].TaskDly = 1u; \ return; \ } else if(mbox.Dly==1u) { \ pmsg = (void*)0; \ mbox.pMbox = (void*)0; \ err = SCH_ERR_TIMEOUT; \ } \ } \ } \ } \ } while(0) //@ 發出一個郵箱 #define SCH_MboxPost(mbox,pmsg) \ do { \ mbox.Dly = 0u; \ mbox.pMbox = (void*)pmsg; \ } while(0) #endif /*------------------------------------------------------------------------------ 操作指定任務,不常用 ------------------------------------------------------------------------------*/ //掛起(暫停)指定任務 #define SCHTaskPend(TaskPendTCB) TaskPendTCB.TimeCounter = SCH_TASK_PEND //恢復指定任務(執行) #define SCHTaskResume(TaskResumeTCB) TaskResumeTCB.TimeCounter = SCH_TASK_RUN //指定任務延時X個時間節拍後恢復 #define SCHTaskDly(TaskDlyTCB, Ticks) TaskDlyTCB.TimeCounter = Ticks /*------------------------------------------------------------------------------ 訊息佇列功能實現 ------------------------------------------------------------------------------*/ #if SCH_CFG_Q_EN > 0u #define SCH_Q_FREE 1 #define SCH_Q_BUSY 0 //等待訊息 #define SCHTaskQpend() {_lc=(__LINE__%65535);pCurTCB->TaskDly = SCH_TASK_PEND;pCurTCB->pData=(void *)0;pCurTCB->Size=0;}return;case (__LINE__%65535): //釋放訊息 #define SCHTaskQpost(PostTCB, pDat, Len) PostTCB.pData = pDat; PostTCB.Size = Len; PostTCB.TaskDly = SCH_TASK_RUN //查詢訊息列隊狀態,是否是自由(可用)或忙(不可用), //呼叫SCHTaskQpend()時會將其設定為自由狀態 #define SCHTaskGetQFree(TaskTCB, RetStatus) RetStatus = SCH_Q_BUSY; if (TaskTCB.TaskDly == SCH_TASK_PEND){RetStatus = SCH_Q_FREE;} #endif //------------------------------------------------------------------------------ /*------------------------------------------------------------------------------ 外部變數宣告 ------------------------------------------------------------------------------*/ extern SCH_TCB *pCurTCB; /*------------------------------------------------------------------------------ 函式申明 ------------------------------------------------------------------------------*/ void SCH_Init(void); void SCH_TimeTick(void); #ifdef __cplusplus } #endif #endif ``` ## shcedule.c ```c #define SCH_GLOBALS #include "schedule.h" SCH_TCB *pCurTCB; /*------------------------------------------------------------------------------ ** 函式名: SCH_Init ** 輸 入: 無 ** 輸 出: 無 ** 功能說明:排程器初始化函式,任務執行前呼叫 **----------------------------------------------------------------------------*/ void SCH_Init(void) { INT8U i; CurRunTaskId = 0; for(i=SCH_MAX_TASKS;i>0;i--) { TaskTcb[i-1].TaskDly = 0; TaskTcb[i-1].SubExitFlag = 0; } } /*------------------------------------------------------------------------------ ** 函式名: SCH_TimeTick ** 輸 入: 無 ** 輸 出: 無 ** 功能說明:排程器時鐘節拍函式,在定時中斷中呼叫 **----------------------------------------------------------------------------*/ void SCH_TimeTick(void) { INT8U i; for(i=SCH_MAX_TASKS;i>0;i--) { if(TaskTcb[i-1].TaskDly>0) { //任務延時時間處理 TaskTcb[i-1].TaskDly --; } } } /*-----------------------------End Of File------------------------------------*/ ``` 具體程式碼就不再解釋,可以結合原始碼註釋自行研究理解。 # 應用 基於STM32F103系列微控制器,展示實際應用。 ## 包含標頭檔案 schedule.h ## 定時器呼叫 定時器呼叫任務輪詢函式 ```c /* 定時器可設定為10ms週期,或其他 */ void TIM2_IRQHandler(void) { TIM_ClearITPendingBit(TIM2, TIM_IT_Update); SCH_TimeTick(); } ``` ## 建立任務 ```c /******************************************************************************* * 函式名稱:Thread_InitMain * 函式功能:主執行緒 * 入口引數:無 * 返 回 值:無 * 其他說明:無 *******************************************************************************/ void Thread_InitMain(void) { SCH_TaskBegin(); //注意這裡 while(1) //注意這是死迴圈 { SCH_TimeDly(150); //這是這個任務的演示函式,單位為定時器定時週期,如果 //如果定時器定時週期為10ms,則此處延時為150*10 } SCH_TaskEnd(); //注意這裡 } /******************************************************************************* * 函式名稱:Thread_Menu * 函式功能:選單執行緒 * 入口引數:無 * 返 回 值:無 * 其他說明:無 *******************************************************************************/ void Thread_Menu(void) { SCH_TaskBegin(); while(1) { KeyValue = Scan_key(); if(KeyValue.key != 0) { QueuePushIn(&KeyValue.key, 1); } Menu(); SCH_TimeDly(100); } SCH_TaskEnd(); } /******************************************************************************* ``` ## 任務初始化 ```c SCH_Init(); //注意這裡哦,先初始化 Time2_Init(); //注意這裡哦,再初始化 //SEGGER_RTT_Init(); Init_V9203(); //初始化V9203 UserData_Init(); InitQueue(&stQueue); ClearScreen(); DisplayFrequency = DISPLAY_FREQUENCY; while (1) { SCH_TaskRun(Thread_InitMain, 0); SCH_TaskRun(Thread_Menu, 1); } ``` ## 任務呼叫 ```c int main(void) { SCH_Init(); Time2_Init(); while (1) { SCH_TaskRun(Thread_InitMain, 0); //這裡入口引數的第二個引數為任務id, //id不能重複 SCH_TaskRun(Thread_Menu, 1); } } ``` [Schedule資源下載](https://download.csdn.net/download/huazidianzi/15