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

这篇具有很好参考价值的文章主要介绍了自用纯C语言实现任务调度(可用于STM32、C51等单片机)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

前言

  这个任务调度模块的实现是形成于毕设项目中的,用在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的把握还有点问题,这个等待后面有机会优化下。

现添加如下代码优化执行间隔的问题 @6.12

void loop_add_tick(void){
	char i;
	Task_Obj *tasknode;
	tasknode = &task_headnode;
	for(i = 0;i<task_num;i++){
		tasknode = tasknode->next;
		if(tasknode->task_status == STATUS_RUN)
			tasknode->tickcnt++;
	}
}

把这段代码放在Systick中断函数中,然后修改循环调度函数中的判断执行间隔的地方。这种方式的好处是可以精细化到每一个任务的执行间隔,而不是之前只在一条时间线上,进行多个任务的时间间隔的处理,并且由于是在中断中同时在进行自增,所以减少了之前任务的额外时间消耗。

调度实现

  • 任务链表结构
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();//任务轮询
}

结语

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

到了这里,关于自用纯C语言实现任务调度(可用于STM32、C51等单片机)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Keil5软件安装方法(兼容stm32与c51方法)

    目录 一、下载软件包 二、安装软件 1、安装C51v960a.exe (1)右键以管理员权限运行程序  (2)开始安装软件  (3)勾选协议 (4)选择安装路径  (5)填写名字与邮箱  (6)等待安装 (7)安装完毕  (8)以管理员打开软件 (9)打开注册机 (10)破解成功 2、安装mdk528.exe

    2023年04月24日
    浏览(31)
  • Keil5同时兼容C51和stm32的方法(win11实测有效)

           相信有很多人在学习C51单片机之后,再学习stm32都会发现Keil无法兼容,这给我们的学习带来了很大的麻烦,今天给大家带来我当时尝试了很多次后找到的好方法,win11系统亲测有效,并附上下载包。 目录 一、安装C51v954 二、安装MDK528 三、兼容C51和stm32  四、破解Keil

    2024年02月10日
    浏览(49)
  • 【毕业设计】基于超声波智能跟随小车 - 单片机 物联网 stm32 c51

    自动跟随小车系统由两部分组成:跟随小车和移动目标携带装置。 工作原理:跟随小车系统通过无线通信模块发送寻找信号,同时超声波接收器开始计时,如果移动目标接收到无线寻找信号,则立即发送超声波信号。这样小车的三角超声波接收器陆续收到超声波信号,CPU通过

    2023年04月08日
    浏览(54)
  • 汇编语言实现C51单片机点亮流水灯

    P0作输出口,接8只发光二极管,编写程序实现二极管循环点亮 二极管为共阳极连接时,即二极管正极已接通电源,单片机输出接阴极,所以单片机输出为低电平有效。 所以为实现二极管轮流点亮,单片机的输出应为:0FEH,0FDH,0FBH,....0FEH,每输出一种状态,需要延时0.1us. 最后在

    2024年02月06日
    浏览(48)
  • [AT89C51 ]用汇编语言实现流水灯仿真(含keil与Proteus)

           前言:笔者发文主要是为了记录笔者单片机学习课程,可能实用性不多,大佬看着玩就行。         :51单片机;AT89C51;流水灯仿真;初学 要求:使用AT89C51实现流水灯,使用汇编语言。 思路:用51单片机8个P1口输出实现8个LED灯依次亮灭,实现流水目标    

    2024年02月05日
    浏览(67)
  • C++语言Qt实现 实时任务调度仿真软件 任务参数可配置和随机生成支持多核调度

    我遇到个需求: 目标: 开发一个实时任务调度仿真软件,我们在学习操作系统这门课时候,经常需要观察任务动态调度情况,来更好的直观学习操作系统任务调度过程和调度算法。 内部原理: 操作系统任务调度实际上是一个有限状态机,任务的各种状态不断的转换过程,我

    2023年04月25日
    浏览(30)
  • 单片机语言--C51语言数据类型与存储类型以及C51的基本运算

    C51的基本语法与标准C相同,C51在标准C的基础上进行了适合于51系列单片机硬件的扩展。 深入理解Keil C51对标准C的扩展部分以及不同之处,是掌握C51语言的关键之一。 C51与标准C的主要区别如下: (1)库函数的不同。 (2)数据类型有一定的区别。 (3)C51的变量存储模式与标

    2024年04月10日
    浏览(38)
  • 51-16 FusionAD 用于自动驾驶预测与规划任务的多模态融合论文精读

    今天要分享的是基于BEV的多模态、多任务、端到端自动驾驶模型FusionAD,其专注于自动驾驶预测和规划任务。这项工作首次发表于2023年8月2日,性能超越了2023 CVPR最佳论文UniAD模型。论文题目是FusionAD: Multi-modality Fusion for Prediction and Planning Tasks of Autonomous Driving,主要作者来自

    2024年01月24日
    浏览(38)
  • C51单片机开发心形灯流水灯(C语言)

    利用Keil uVision4编程程序,在Proteus 8 Professional中创建仿真电路 仿真电路 16进制样式花型 延迟函数 循环调用函数 led1() 是指操作P0端口, 以此类推,led4() 是调用四个输出端口,循环闪烁 C语言代码 仿真电路和代码 链接:https://pan.baidu.com/s/1vx33QiXO0uhXMq1ItUebNQ 提取码:5151

    2024年02月07日
    浏览(35)
  • C51实现流水灯

    1、 先八盏灯从左至右依次点亮,同一时刻仅有一盏灯处于被点亮状态,每盏灯亮0.5s,然后八盏灯从右至左依次点亮,同一时刻仅有一盏灯处于被点亮状态,每盏灯亮0.5s,循环两遍; 2、 八盏灯同时闪烁,亮1s,灭0.5s;,实现4次; 3、 上述过程周而复始的循环运行; 代码如

    2024年02月07日
    浏览(34)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包