单片机裸机开发之“时间片轮询架构”去实现“多任务”

本文例程基于新塘N76E003At20,文末附带例程

1. 背景

​ 对​于一些简单的单片机项目,没必要非得跑RTOS,因此,很多项目都是在“裸奔”(指纯循环加上中断的机制)。所以,开发出一套好用的裸机框架是非常有必要的,本文章带你手把手实现裸机中的“多任务”调度。

2. 基本知识

 需要掌握的基本知识并不多,也都是最基本的知识,总结如下

  • 定时器
  • 函数指针
  • 结构体数组

3. 代码实现 

整个系统的代码由 一个定时器中断发动,定时器 就像是一个发动机,每隔一定的周期发动一次中断,利用这一个中断的时间去对指定任务的时间片减1,在主循环中一直遍历系统中所有任务的时间片,如果时间片耗尽了,则执行该任务。

​ 首先创建一个任务对象,经过上述分析,首先该任务对象至少有个时间片来决定该任务到底多久执行一次,其次该任务对象得有个任务,这个任务就是一个函数,比如创建一个LED灯的任务,每隔1秒钟执行一次。相关代码如下,该机制完全可以通过一个链表给链起来,可以有,但没必要,裸机代码简单易懂是关键。

typedef struct _task
{
  void (*task)(void); 
  uint32_t tasktick; 
  uint8_t  runstate;  //任务状态  1就绪态  0未就绪 
}task_t;

通过 task_t  这个结构体去实例化一个对象,如下:

task_t led_task;//实例化出led_task对象
led_task.tasktick = 1000;
led_task.task = xxxx_func();
led_task.runstate  = 0; 

这样就将一个led任务的对象给创建出来了,但是系统中不可能仅存在一个任务,因此使用一个结构体数组将全部任务集中到一起,如下:

#define TASKS_MAX  (6)

task_t  task[] = {
	{led_task,500,0},
	{tim_capture_task,20,0},
	{muliti_button_task,5,0},
	{mpu6050_task,10,0},
	{mpu6050_display_task,100,0},
	{srf_05_task,30,0}
};

​ 这个Task 数组中集合了该系统中的所有任务,就只剩下每个时钟周期( 定时器中断 )减去每个任务的tick即可。这里涉及到一个定时器的中断周期问题,但一般都是1ms一次中断,定时器配置如下:

void Init_Sys(void)
{
		TIMER2_DIV_4;
		TIMER2_Auto_Reload_Delay_Mode;
  	
		RCMP2L = TIMER_DIV4_VALUE_1ms;
		RCMP2H = (TIMER_DIV4_VALUE_1ms)>>8;
		TL2 = 0;
		TH2 = 0;
	
		set_ET2;                                    // Enable Timer2 interrupt
		set_EA;
		set_TR2;
}

​ 直接将配置定时器相关代码加到系统时钟初始化函数中即可,需要注意的是将定时器 中断优先级调至系统最高,这样其他中断来临时,不会打断正在执行的定时器中断

接下来就就需要在服务函数中递减每个任务的时间片了,代码如下:

void Timer2_ISR (void) interrupt 5
{
   static uint8_t j;

   RCMP2L = TIMER_DIV4_VALUE_1ms;
   RCMP2H = (TIMER_DIV4_VALUE_1ms)>>8; 

	for(j = 0 ; j < TASKS_MAX ; j ++)
	{
		if(TaskData.timer[j]) 
		{
		  TaskData.timer[j]--;
			if(TaskData.timer[j] == 0)
			{
			  TaskData.running[j]  = 1; //任务就绪,可以运行
			}
		}
	}
    
    clr_TF2;
}

​ 上述代码中的 task_timer[]与running[];需要经过 Task[] 这个结构体数组初始化,如下:

void Task_Init(void) 
{
	uint8_t i = 0;
	for(i = 0; i < TASKS_MAX; i++)
	TaskData.timer[i] = task[i].tasktick;
    TaskData.running[i]  = task[i].runstate;
}

经过以上简单的几步整个系统这时候就能够跑起来了,不过还需最后一步,也就是在 while(1) 中添加任务运行相关的代码,如下:

void  task_run(void)
{
   uint8_t i = 0;

   for(i = 0; i < TASKS_MAX ; i++)
	{
	  if(TaskData.running[i] == 1)
		{
		  TaskData.timer[i] = task[i].tasktick;//重新给当前任务定时器付上初值,这里还可以再给 
                                               //对象添加一个周期任务与单次任务,需要的自行添
                                               //加即可
			(task[i].task)();//调用任务函数

			task[i].runstate  = 0;//将任务状态归0
		} 
	}
}
​
void main(void)
{ 
  Init_Sys();
  INIT_GROUP();//硬件相关初始化
	
   while(1)
   { 
	  task_run();
   }
}

​

就这么简单几步就能实现了,赶快用起来吧!!!

源码链接:https://pan.baidu.com/s/1p9oVW3gQcNV898u6FzlEog?pwd=qu00 
提取码:qu00

  • 8
    点赞
  • 57
    收藏
    觉得还不错? 一键收藏
  • 10
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值