基于单片机的简单的任务调度器

近来工作之余,研究了一下APM的源码。

APM源码连接https://pan.baidu.com/s/17Dg1oEJT_fj12DM1BmZWxA

发现源码中有一个简单的任务调度器,不太重要的任务都在Scheduler中完成。为什么需要Scheduler呢??

飞行器姿态信息需要角速度积分,位置信息也需要光流的积分,也就是需要获取精确的时间间隔,对时间敏感
Scheduler的主要作用是保证主函数运行的时间相同,什么原理呢?

假设我们主循环中有6个任务, 每个任务执行时间为大概20us - 1ms之间,而我们的控制周期要求是5ms,任务1、2、3是必要任务,每次循环都要执行的,比如飞行器的姿态解算,任务4、5是周期任务,任务4每隔4个周期执行一次,任务5每隔10个周期执行一次,任务6是非必要任务比如LED闪烁任务,一般情况下10个周期执行一次。我们可以写成如下伪代码,但是这样十分繁琐,并且修改移植起来十分麻烦,任务调度器就是代替这段代码的,方便移植用的。

int main()
{
	uint32_t count = 0;
	uint32_t time  = 0;
    while(1)
    {
		if(get_now_time_ms() - time > 5) //判断5ms是否过去
		{
			count ++;
			time =  get_now_time_ms();   //记录当前时间
			
			Task1();   //任务1   1ms

	        Task2();   //任务2   1ms
	
	        Task3();   //任务3   1ms
	
			if(get_now_time_ms() - time > 1  &&  count % 4 == 0)
			{
				Task4();   //任务4   1ms
			}
	        
	    	if(get_now_time_ms() - time > 1  &&  count % 10 == 0)
			{
				Task5();   //任务5   1ms
				
				if(get_now_time_ms() - time > 1 )
				{
					Task6();   //任务6   1ms
				}
			}
		}
    }
}

Scheduler也可以使代码达到上述代码的效果,换句话来说就是保证控制周期稳定。控制周期稳定有什么用呢? PID! 我们运用PID控制电机时,一般都会默认控制周期不变,从而将PID参数简化为Kp、Ki、Kd,注意使用Ki、Kd的前提是控制周期不变,而控制周期不变就可以Scheduler来解决了。

下面就是一个简单的任务调度器代码, 注意任务调度器需要一个时计器来提供时基。

scheduler.h

#ifndef __SCHEDULER_H
#define __SCHEDULER_H

#include "stdint.h"

typedef struct
{
	void (*task_func)(void);   //函数指针
	uint16_t interval_ticks;   //每隔多少周期执行一次
	uint16_t max_time_micros;  //该函数运行一次的大概时间us
	uint32_t last_tick_counter;//上一次函数运行时tick
}sched_task_t;


typedef struct 
{
	void (* init )(uint8_t num);
	void (* tick )(void);
	void (* run )(uint32_t time_available);

}sched_t;

extern sched_t sched;





#endif //__SCHEDULER_H

scheduler.c

#include "Scheduler.h"
#include "stdio.h"

/* debug 调试宏定义 */
#define _MY_DEBUG_  1

#ifdef  _MY_DEBUG_ 
	#define    MY_DEBUG_PRINT_INFO(...)      printf(__VA_ARGS__)
	#define    MY_DEBUG_PRINT_VAR(X, ...)    printf("file: "__FILE__", Line: %d:"X"\r\n",__LINE__,##__VA_ARGS__)
#else
	#define    MY_DEBUG_PRINT_INFO(...)
	#define    MY_DEBUG_PRINT_VAR(X, ...)
#endif

/* 获取当前时间 us  注意这里需要换成自己的获取当前时间函数 */
#define GET_NOW_TIME_US   systime.get_time_us()


/* 声明时分调度器任务表 */
extern sched_task_t sched_task[];


/* 任务数量 */
static uint8_t num_task; 



/**
  * @brief    时分任务调度器初始化
  *
  * @param    
  *
  * @return   
  *
  * @note     获取任务数量
  *
  * @example  
  *
  * @date     2019/6/29 星期六
  */
void shceduler_init(uint8_t task_num)
{
	num_task = task_num;
}

/* shceduler_run执行次数 */
static uint32_t tick_counter = 0;


/**
  * @brief    记录执行周期数
  *
  * @param    
  *
  * @return   
  *
  * @note     
  *
  * @example  
  *
  * @date     2019/6/29 星期六
  */
void shceduler_tick()
{
	tick_counter++;
}


/**
  * @brief    调度
  *
  * @param    
  *
  * @return   
  *
  * @note     
  *
  * @example  
  *
  * @date     2019/6/29 星期六
  */
void shceduler_run(uint32_t time_available)
{
    uint64_t now;
    uint16_t dt;
    uint16_t interval_ticks;
    
	for (uint8_t i = 0; i < num_task; i++)
	{
		now = GET_NOW_TIME_US;//获取当前时间
		dt = tick_counter - sched_task[i].last_tick_counter;//计算任务上次运行到现在相隔多少周期
        interval_ticks = sched_task[i].interval_ticks;  //任务相隔多少周期运行一次

		if( dt >= interval_ticks)
		{
			//调试时使用,任务长时间未运行,说明任务的运行周期太长或者shceduler_run的可运行时间太短
			if (dt >= interval_ticks*2)
			{
				MY_DEBUG_PRINT_VAR("Scheduler slip task[%u] (%u/%u/%u)\n",
									  (unsigned)i,
									  (unsigned)dt,
									  (unsigned)interval_ticks,
									  (unsigned)sched_task[i].max_time_micros);


            }
			if (sched_task[i].max_time_micros <= time_available)
			{
				//运行任务
				sched_task[i].task_func();
				//更新last_tick_counter
				sched_task[i].last_tick_counter = tick_counter;

				#ifdef _MY_DEBUG_
				uint32_t end_time = GET_NOW_TIME_US - now;
//如果任务运行过后发现时间超出shceduler_run的可运行时间time_available,说明任务的max_time_micros设置小了
				if(end_time > time_available)
				{
					MY_DEBUG_PRINT_VAR("Scheduler slip task[%u] (%u/%u)\n",
                                          (unsigned)i,
                                          (unsigned)end_time,
                                          (unsigned)sched_task[i].max_time_micros);
					return;

				}
				#endif //debug打印

			}

		}
		//更新time_available
		time_available -= (GET_NOW_TIME_US - now);
	}
	//更新tick_counter
	sched.tick();  // 该函数作用为tick_counter++
}


sched_t sched =
{
	shceduler_init,
	shceduler_tick,
	shceduler_run,
};

在使用时,调度器需要使用定时器,因此需要先行初始化定时器,确保 GET_NOW_TIME_US 可以使用,在对调度器进行初始化,确定任务数量

// 任务调度结构体,注意重要任务放在前面
sched_task_t sched_task[] =
{

//    函数指针     每隔多少周期执行一次  该函数运行一次的大概时间us  上一次函数运行时tick 
    { loop_200hz   ,         1,                   500,                      0   }, 
    { loop_100hz   ,         2,                  4000,                      0   }, 
    { loop_50hz    ,         4,                   100,                      0   },
    { loop_20hz    ,        10,                  4000,                      0   },
    
};

/* 初始化时分任务调度器 */
sched.init(sizeof (sched_task) / sizeof (sched_task[0]));

最后在循环中调用调度器即可,调度器会自动对任务函数进行周期分配

/* 记录当前时间差 */
uint32_t current_time;   
    
/* 记录上一次时间 */
uint32_t last_time;

while(1)
    {
        current_time = systime.get_time_us() - last_time;
        if(current_time >= 5000)  
        {         
            last_time = systime.get_time_us();
            sched.run(5000);                
        }
    }
  • 5
    点赞
  • 41
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
1. 请介绍一下AVR单片机的基本特点和优势。 AVR单片机采用哈佛结构,具有高性能、低功耗、易于学习和使用、易于扩展等优点。其核心是RISC架构,指令集精简,运算速度快,功耗低,因此非常适合用于嵌入式系统中。此外,AVR单片机还具有较大的程序存储空间和数据存储空间,并且支持多种通信接口和外设,非常适合用于工业控制等应用场景。 2. 在工业控制中,AVR单片机一般会用到哪些外设和通信接口? AVR单片机可以通过SPI、I2C、UART等通信接口与其他设备进行通信,例如与传感、执行机构等进行数据交互。同时,AVR单片机还可以通过定时、PWM、ADC等外设实现精确的定时控制、模拟信号采集和输出等功能。 3. 在工业控制设计中,如何保证系统的稳定性和可靠性? 首先,需要对硬件电路进行充分的稳定性和可靠性测试,保证电路设计符合规范,电源、地线、信号线等连接正确可靠。其次,在软件设计上,需要进行严格的测试和验证,保证系统的稳定性和可靠性。例如,可以使用看门狗定时检测系统是否出现死机等异常情况,并采取相应的措施进行复位或报警。 4. 在工业控制的软件设计中,如何实现实时控制? 实时控制需要对任务优先级进行合理划分和调度,采用中断、定时等方式实现任务的及时响应。同时,需要对代码进行充分优化,避免出现长时间阻塞的情况。另外,可以采用RTOS等实时操作系统来简化任务调度和管理,提高系统的实时性。 5. 在工业控制中,如何进行故障诊断和排除? 故障诊断和排除需要在硬件和软件两个方面进行。在硬件方面,可以通过检查电路连接、元件状态等进行排查。在软件方面,需要对程序进行调试和监测,采用断点调试、日志输出等方式进行故障定位和排查。同时,建议在系统设计阶段考虑到故障处理机制,例如通过报警、备份等方式保证系统的可靠性和稳定性。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值