基於微控制器的多工輪詢系統
阿新 • • 發佈:2021-02-14
@[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