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

別具匠心發表於2021-02-14

@

時間片輪詢系統

通過時間片的劃分,可以利用一個定時器或者系統滴答定時器,通過多工輪詢方法,實現一個多工的基於時間輪詢排程的系統——Schedule.

版權宣告

本文展示的原始碼為網路上所獲取的資源,如有侵權,請告知刪除。
此處僅為交流學習使用。

檔案結構

有三個檔案構成,兩個h檔案一個c檔案。
sch_chg.h 型別定義檔案
schedule.c 原始碼實現
shcedule.h 巨集定義和配置檔案

原始碼

sch_chg.h

#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

#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

#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

定時器呼叫

定時器呼叫任務輪詢函式

/*
定時器可設定為10ms週期,或其他
*/
void TIM2_IRQHandler(void)
{ 		    		  			    
	TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
    SCH_TimeTick(); 
}

建立任務

/*******************************************************************************
 * 函式名稱: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();
}
/*******************************************************************************

任務初始化

	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);     
    }

任務呼叫

int main(void)
{

    SCH_Init();
    Time2_Init();
    while (1)
    {	
        SCH_TaskRun(Thread_InitMain, 0);  //這裡入口引數的第二個引數為任務id,
        //id不能重複
        SCH_TaskRun(Thread_Menu,    1);     
    }
}

Schedule資源下載

相關文章