首页 > 其他分享 >自用纯C语言实现任务调度(可用于STM32、C51等单片机)

自用纯C语言实现任务调度(可用于STM32、C51等单片机)

时间:2023-04-09 18:00:26浏览次数:53  
标签:task C语言 C51 next headnode tasknode 任务调度 tmpnode curnode

前言

  这个任务调度模块的实现是形成于毕设项目中的,用在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必要的,因为就是通过传入每个任务的这个标志位来判断是否运行结束,而其他的参数,就得基于具体任务做不一样的处理了。

轮询

  • 运行框图

  可以看到这个框图是一个头尾相连闭环结构,从头节点依次运行到尾节点后再从头循环往复执行下去。

  • 轮询函数
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();//任务轮询
}

结语

  整体实现说难不难,说简单不简单,但也是我第一次尝试这种偏向系统级应用的代码,而且都没有参照任何其他的资料和代码,完全以自己的对任务的理解和具体项目的需求来一点点实现,希望后面会把这个调度的代码进一步完善成一个通用型的调度方式,也方便后面项目的使用了。

标签:task,C语言,C51,next,headnode,tasknode,任务调度,tmpnode,curnode
From: https://www.cnblogs.com/pie-o/p/17300699.html

相关文章

  • C语言程序环境
    前言早期的程序员,使用的是二进制语言编写代码,之后发展出汇编语言,B语言,C语言等等,其中,二进制语言是机器可以识别的语言,但对于程序员来说理解可能较为困难,发展到C语言时,我们所写的代码已经经过不断优化,使得程序员能清晰的明白代码的含义,但最终,我们要想机器识别出我们所写的代码,还需要......
  • C语言的回调函数原来这么简单
    手机端阅读:C语言的回调函数原来这么简单 了解开发语言的朋友应该都会对回调函数有所了解,在很多的程序开发语言中都能看到回调的身影。很多场景下,当某个条件成立以后我们希望代码执行某些指定的部分,这个时候可以考虑使用回调函数的方式,这样做思路更加的清晰,也能使代码结构的逻辑更加......
  • 算法学习之冒泡排序【C语言】
    冒泡排序排序规则冒泡排序的规则是相邻的两个数字依次比较,如果前面的数字比后面的数字大,则交换它们的位置,否则保持不变,直到遍历完所有的数字。这个过程会不断地进行,直到所有的数字都按照从小到大的顺序排列好。双层循环在冒泡排序的算法中,需要使用两层循环来实现排序功能。for(int......
  • C语言本地变量的规则
    以以下一段代码为例:1#include<stdio.h>2//以下代码是否能交换两个变量的值?3voidswap(inta,intb);45intmain()6{7inta=5;8intb=6;910swap(a,b);1112printf("%d,%d",a,b);1314return0;15}16......
  • 算法学习之选择排序【C语言】
    选择排序排序规则选择排序是一种简单直观的排序算法,其基本思想是每次从待排序的数据元素中选出最小(或最大)的一个元素,存放到序列的起始位置,直到全部元素排序完成。具体步骤如下:1.从第一个数开始,与其后的数一一比较,如后小前大,则交换,依次比较直至最后一组数。2.通过上述步骤,得到参加循......
  • C语言
    目录1.数据类型:2.数据运算:3.基本语句:1.数据类型:2.数据运算:3.基本语句:本篇随笔为记录学习所用,内容大多来源于网络,如有侵权,请联系博主。......
  • C语言字符串的使用
    做题常常遇到和字符串相关的,发现自己对《字符串》和《字符数组》有误会,因为他们的声明太像了,我会搞混。于是我在DevC++上尝试了9种不同的声明方式,想看看什么时候才算是有效的字符串声明,都是我当前水平可能遇到的。#include<stdio.h>#include<string.h>/* 解释: 字符串和字符......
  • C51单片机的基本知识
    目录1.单片机介绍2.STC89C52单片机3.C51在keil5中的器件选型4.C51的时钟频率5.关于单片机的上电1.单片机介绍单片机,英文MicroControllerUnit,简称MCU内部集成了CPU、RAM、ROM、定时器、中断系统、通讯接口等一系列电脑的常用硬件功能单片机的任务是信息采集(依靠传感器)、处理......
  • c语言拾遗
    概述gcc-E则经过预编译变成.i文件,还是c代码只是对#中进行字符串的展开和复制-S则经过编译变成.s文件,为汇编代码-c则经过汇编变成.o文件,为二进制文件然后将许多(也许也就几个).o文件链接在一起生成linux中的可执行文件.out直接gcc会将上述整个如果要在编译停下来用......
  • 第 11 章Linux 实操篇-定时任务调度
    第11章Linux实操篇-定时任务调度11.1crond任务调度crontab进行定时任务的设置11.1.1概述任务调度:是指系统在某个时间执行的特定的命令或程序。任务调度分类:1.系统工作:有些重要的工作必须周而复始地执行。如病毒扫描等个别用户工作:个别用户可能希望执行某些程序,比......