STM32 裸机程序的任务调度(学习)
文章目录
目录
前言
一、裸机任务调度方案一(杂糅)
二、裸机任务调度方案二(按需求分配)
三、裸机任务调度方案三(按需求分配,实现软件调度框架)
前言
在开发单片机时如果不使用操作系统,任务调度是有点让人头疼的,尤其是业务功能模块比较多的情况,比如说有些功能需要几毫米就要执行一次,而有些只需要几百毫秒才用执行一次就可以,这种情况如果做不到及时的任务调度,那么使用起来的感受是非常不好的。
一、裸机任务调度方案一(杂糅)
代码如下(示例):
int main()
{
SysInit();
while(1)
{
Task1();
Task2();
Task3();
Task4();
}
}
这种是初学者经常使用的方案,在while循环中直接添加任务模块从上到下依次执行。但是,这种就有很多弊端,比如:Task1如果是采集环境值的就不需要很快的速度去采集,几秒钟或者几分钟采集一次就可以了;Task2是做人机交互的,是需要几个毫秒就去执行一次。遇见这种情况就不适用,如果再在业务中使用delay函数cpu就没办法去执行其他的功能,就是对cpu资源的一种浪费,在模块多的情况下这种是不可取的。
二、裸机任务调度方案二(按需求分配)
代码如下(示例):
int main()
{
SysInit();
while(1)
{
if(period30sFlag)
{
Task1();
period1sFlag = 0;
}
if(period1sFlag)
{
Task2();
period1sFlag = 0;
}
if(period5sFlag)
{
Task3();
period5sFlag = 0;
}
}
}
这种方式是按照对应的时间标志再去运行相应的功能模块,时间标志是采用定时器来来实现时间片轮转的。如果功能模块有很多个,就需要很多的if else语句,这种并不美观,而且可读性也不高。
三、裸机任务调度方案三(按需求分配,实现软件调度框架)
通过调度框架去调度各个功能模块,主程序代码如下:
int main()
{
SysInit();
while(1)
{
TaskHandler();
}
}
void TaskHandler(void)
{
uint8_t i;
for(i = 0; i < TASKNM; i++)
{
if(true == g_TaskComps[i].run)
{
g_TaskComps[i].pTaskFunc();
g_TaskComps[i].run = false;
}
}
}
通过判断结构体成员的运行标志来判断是否达到运行条件,如果是则运行结构体成员的函数指针。
具体实现流程如下:
typedef struct{
bool run; //运行标志 1,运行 0,挂起
uint32_t timeCount; //递减计算 时间片 毫秒触发
uint32_t timeRload; //重装载值
void (*pTaskFunc)(void); //函数指针
} TaskComps_stu;
构建一个结构体,里面有四个成员分别为:运行标志、递减计数、重装载值以及运行任务模块的函数指针。
#define TASKNM (sizeof(g_TaskComps)/sizeof(g_TaskComps[0])) //任务个数
void task1(void)
{
printf("I am task1, Runtime:%lld\r\n",getSysRunTime());
}
void task2(void)
{
printf("I am task2, Runtime:%lld\r\n",getSysRunTime());
}
void task3(void)
{
printf("I am task3, Runtime:%lld\r\n",getSysRunTime());
}
static TaskComps_stu g_TaskComps[] = {
{false, 1000, 1000, task1}, //任务一
{false, 3000, 3000, task2}, //任务二
{false, 500, 500, task3}, //任务三
};
这里实现了三个业务功能模模块,task1为1000毫秒运行一次;task2为3000毫秒运行一次;task3为500毫秒运行一次。
宏(TASKNM)为获取任务个数。
//task.c
void TaskTimeSlice(void)
{
uint8_t i;
for(i = 0; i < TASKNM; i++)
{
g_TaskComps[i].timeCount--;
if(0 == g_TaskComps[i].timeCount)
{
g_TaskComps[i].run = true;
g_TaskComps[i].timeCount = g_TaskComps[i].timeRload;
}
}
}
void TaskInit(void)
{
TaskCallback(TaskTimeSlice);
}
这里初始化函数TaskInit()为回调函数,将TaskTimeSlice()函数地址传给systick.c文件的pCallbackFunc函数指针并且一毫秒执行一次。当结构体成员变量timeCount递减为0时就改变为运行态。
//systick.c
static uint64_t sys_runTime = 0;
/**
****************************************************************
* @brief 系统滴答定时器初始化
* @parm
* @return
****************************************************************
*/
void SysTickInit(void)
{
if(SysTick_Config(SystemCoreClock / 1000 ))
{
while(1);
}
}
void (*pCallbackFunc)(void);
/**
****************************************************************
* @brief 回调函数
* @parm
* @return pFunc,需要调用的函数指针
****************************************************************
*/
void TaskCallback(void (*pFunc)(void))
{
pCallbackFunc = pFunc;
}
/**
****************************************************************
* @brief 滴答定时器任务
* @parm
* @return
****************************************************************
*/
void SysTick_Handler(void)
{
sys_runTime++;
pCallbackFunc();
}
/**
****************************************************************
* @brief 获取系统运行时间
* @parm
* @return sys_runTime,返回运行时间 单位 ms
****************************************************************
*/
uint64_t getSysRunTime(void)
{
return sys_runTime;
}
这里初始化了系统滴答定时器每一毫秒触发一次中断,并且获取到了上层传过来的回调函数,这里每触发一次中断就调用一次该函数。
此为运行结果图,task1为1000毫秒运行一次;task2为3000毫秒运行一次;task3为500毫秒运行一次。Runtime为系统运行时间,毫秒为单位。
作者:只想做好编程的小王