自用純C語言實現任務排程(可用於STM32、C51等微控制器)

pie_thn發表於2023-04-09

前言

  這個任務排程模組的實現是形成於畢設專案中的,用在STM32中,斷斷續續跨度2個月實現了一些基本功能,可能後面再做其他專案時會一點點完善起來,也會多學習相關知識來強化模組的實用性和高效性,畢竟用自己自主實現出來的功能還是蠻舒心的。

任務排程模式結構

  整體上的結構屬於線性結構,結合連結串列定時器來實現,我使用的是sysTick這個滴答時鐘,1ms的頻率,功能比較簡單,容易理解。

分片

  分片的模式,主要體現在函式分片時間分片在我之前就有使用在函式中,主要的思路是,把函式功能切片,分為幾個小部分,每次執行時按次序執行小部分,對於沒有時序要求的函式來說,可以把一個佔用CPU大的功能分攤開來實現,從而避免有些地方耗時長的問題。對於時間分片,其實就是定時器的一種應用,實際上,函式分片在執行的時候已經是一種時間分片了,不過現在加上人為的控制在裡面了。
  下面是函式分片的一般結構:

void func(char *fos,...){
    static char step=0;//順序控制變數,自由度比較高,可亂序,可迴圈,可延遲執行
    switch(step){
        case 0:{
            //...
            step++;
            break;
        }
        case 1:{
            //...
            step++;
            break;
        }
        //...
        default:{
            //step++;//可以藉助default實現延時的效果,即跳過幾次空白step
            break;
        }

    }
    return;
}

其中新增的引數變數*fos必要的,因為就是透過傳入每個任務的這個標誌位來判斷是否執行結束,而其他的引數,就得基於具體任務做不一樣的處理了。

輪詢

  • 執行框圖
自用純C語言實現任務排程(可用於STM32、C51等微控制器)

  可以看到這個框圖是一個頭尾相連閉環結構,從頭節點依次執行到尾節點後再從頭迴圈往復執行下去。

  • 輪詢函式
void loop_task(void){
	static Task_Obj *tasknode;
	
	tasknode=task_curnode->next;//repoint the curnode to the next
	if(tasknode==NULL){//tasknode is null,only the headnode have the attr
		return;//express the task space is none
	}
	else if(tasknode->task_type==TYPE_HEAD){//tasknode is headnode
		task_curnode=tasknode;
		return;
	}
	else{
		if(tasknode->run_type == RUN_WAIT){
            //等待型任務,透過ready標誌來確定是否執行,否則就跳過
			if(!tasknode->ready){
				if(task_curnode->next !=NULL){
					task_curnode=task_curnode->next;
					return;
				}
			}
		}
		if(tasknode->task_status==STATUS_INIT){

			tasknode->tickstart=HAL_GetTick();//獲取tick
			tasknode->task_status=STATUS_RUN;

		}
		else if(tasknode->task_status==STATUS_RUN){
			if((HAL_GetTick() - tasknode->tickstart) > (uint32_t)tasknode->task_tick){
				tasknode->task_name(&(tasknode->task_fos));//run the step task,transfer the fos
				tasknode->tickstart+=(uint32_t)tasknode->task_tick;//update the tickstart
			}
		}
		
	}
	if(tasknode->task_fos==FOS_FLAG){
		
		tasknode->ready=0;
		if(tasknode->waittask!=NULL){
            //置位該任務繫結的等待的任務準備執行標誌位,標識可以準備執行了
			tasknode->waittask->ready=1;
		}
        //執行結束就刪掉該任務
		delete_task(tasknode);
	}
	else if(tasknode->task_fos==FOC_FLAG){
        //迴圈執行該任務
		tasknode->task_status=STATUS_INIT;//continue running from start
		tasknode->task_fos=0;//RESET fos
		
	}
	if(task_curnode->next !=NULL){
		if(task_curnode->next->run_type==RUN_FORCE) return;//force-type's task
		
		else task_curnode=task_curnode->next;
		
	}
	

}

其中有幾個執行態和標誌位

#define FOS_FLAG 99//執行結束標誌
#define FOC_FLAG 100//執行結束後再次執行,相當於迴圈執行
#define TYPE_NOMAL 0//標識一般任務型別
#define TYPE_HEAD 1//標識頭任務型別
#define TYPE_END 2//標識尾任務型別
#define RUN_NORMAL 0//一般輪詢模式
#define RUN_FORCE 1//強制執行該任務,執行結束才繼續下一個任務
#define RUN_WAIT 2//等待指定的任務結束,才可以被執行
#define STATUS_INIT 0//任務的準備階段,用於獲取起始時間
#define STATUS_RUN 1//任務執行階段
#define STATUS_UNVAILED 2//無效狀態

執行時對時間間隔tick的把握還有點問題,這個等待後面有機會最佳化下。

排程實現

  • 任務連結串列結構
typedef struct TASK_CLASS{
	void (*task_name)(char *taskfos,...);//任務函式
	int task_tick;//任務的時間分片間隔
	uint32_t tickstart;//起始時間點,每次執行完須加上一個tick
	char task_fos;//執行結束標誌
	char task_type;//任務型別變數
	char task_status;//任務狀態
	char run_type;//執行狀態
	char ready;//準備執行標誌位
	struct TASK_CLASS *next;//下一任務
	struct TASK_CLASS *waittask;//等待執行的任務
} Task_Obj;
  • 新增任務

    • add_task
    void add_task(void (*taskname)(char *,...),int tasktick,int runtype){//可變參,這裡未做處理
    Task_Obj *tasknode,*tmpnode;
    char i;
    
    tasknode = (Task_Obj*)malloc(sizeof(Task_Obj));
    
    tasknode->task_name=taskname;
    tasknode->task_tick=tasktick;
    tasknode->task_fos=0;
    tasknode->task_status=STATUS_INIT;//initial status
    tasknode->task_type=TYPE_END; //set the new node to endnode
    tasknode->run_type=runtype;
    tasknode->next=&task_headnode;//the endnode point to the headnode
    
    tmpnode=&task_headnode;
    if(task_num==0){
    	tmpnode->next=tasknode;
    	task_num++;
    	return;
    }
    for(i=0;i<task_num;i++){
    	tmpnode=tmpnode->next;//reach the endnode
    }
    tmpnode->task_type=TYPE_NOMAL;//turn the last endnode to the normal node
    tmpnode->next=tasknode;
    task_num++;
    }
    
    • add_wait_task
    void add_wait_task(void (*taskname)(char *),void (*waitname)(char *),int tasktick){
    Task_Obj *tmpnode,*tasknode;
    char i,pos;
    
    tmpnode=&task_headnode;
    for(i=0;i<task_num;i++){
    	tmpnode=tmpnode->next;//reach the endnode
    	if(tmpnode->task_name==taskname){
    		pos=i;//獲取要等待任務的位置
    		break;
    	}
    }
    
    tasknode = (Task_Obj*)malloc(sizeof(Task_Obj));
    
    tasknode->task_name=waitname;
    tasknode->task_tick=tasktick;
    tasknode->task_fos=0;
    tasknode->task_status=STATUS_INIT;//initial status
    tasknode->task_type=TYPE_END; //set the new node to endnode
    tasknode->run_type=RUN_WAIT;//任務為等待執行
    tasknode->ready=0;
    tasknode->next=&task_headnode;//the endnode point to the headnode
    
    tmpnode->waittask=tasknode;//獲取新建的等待執行的任務地址,在執行結束後把等待執行的任務的準備執行標誌位置1
    
    tmpnode=&task_headnode;
    if(task_num==0){
    	tmpnode->next=tasknode;
    	task_num++;
    	return;
    }
    for(i=0;i<task_num;i++){
    	tmpnode=tmpnode->next;//reach the endnode
    }
    tmpnode->task_type=TYPE_NOMAL;//turn the last endnode to the normal node
    tmpnode->next=tasknode;
    task_num++;
    
    }
    
  • 刪除任務

    • delete_task(侷限性大,只針對當前執行的任務而言)
    void delete_task(Task_Obj *taskobj){
    if(task_curnode->task_type==TYPE_HEAD && task_num < 2){//if curnode is headnode,and tasknum=1
    	task_curnode->next=NULL;
    }
    else{
    	task_curnode->next=taskobj->next;//repoint the curnode next
    }
    free(taskobj);//free the space of where the taskobj pointed
    
    task_num--;
    
    }
    
    • delete_task_withname(刪除指定任務名的任務)
    void delete_task_withname(void (*taskname)(char *)){
    Task_Obj *tmpnode,*tmpnode2;
    char i,pos;
    
    tmpnode=&task_headnode;
    for(i=0;i<task_num;i++){
    	tmpnode=tmpnode->next;//reach the endnode
    	if(tmpnode->task_name==taskname){
    		pos=i;
    		break;
    	}
    }
    if(i==task_num) return;
    tmpnode=&task_headnode;
    for(i=0;i<pos+1;i++){
    	tmpnode2=tmpnode;
    	tmpnode=tmpnode->next;
    }
    if(tmpnode->next==NULL){//if tmpnode is endnode
    	tmpnode2->next=&task_headnode;
    }
    else{
    	tmpnode2->next=tmpnode->next;//repoint the curnode next
    }
    task_num--;
    free(tmpnode);
    }
    
  • 初始化任務空間

void non_task(char *taskfos){
	return;
}

void init_taskspace(void){
	task_headnode.task_name=non_task;
	task_headnode.task_type=TYPE_HEAD;
	task_headnode.task_status=STATUS_UNVAILED;
	task_headnode.next=NULL;
	task_curnode=&task_headnode;//頭節點是沒有任務需要執行的
	task_num=0;
}
  • 呼叫例項
add_task(task1,500,RUN_NORMAL);//500ms執行一次task1任務
add_wait_task(task1,task2,500);//task2等待task1結束才會執行,執行的時間間隔為500ms
delete_task_withname(task1);//刪除task1任務

while(1){
    //...
    loop_task();//任務輪詢
}

結語

  整體實現說難不難,說簡單不簡單,但也是我第一次嘗試這種偏向系統級應用的程式碼,而且都沒有參照任何其他的資料和程式碼,完全以自己的對任務的理解和具體專案的需求來一點點實現,希望後面會把這個排程的程式碼進一步完善成一個通用型的排程方式,也方便後面專案的使用了。

相關文章