注: 我看了,网上大多数博客都是用这份代码做讲解,所以我也是用它来说明自己的理解,我是在微信上看到的这篇文章,等我找到原作者,我再附上转载链接
序言
程序开发分为几种模式:
裸机开发,时间片轮询,系统级开发
。裸机开发,有过单片机开发经验的朋友应该都很熟悉了,这是一种没有任何架构设计的开发,一套程序从头到尾。时间片轮询,这是我们今天要介绍的对象,也是我最近新get到的技能,我们待会儿详说。系统级开发,这是我将来打算发展的方向,但是由于目前还在理论储备阶段,所以没有任何开发经验。等过几年再说。所以我们今天的主角是:
时间片轮询法
基本思想
举个例子,有一个函数
A
,我们给它设置一个
计数器B
(程序多久执行一次)和一个
状态标志位C
(用来判断这个程序是否需要执行)还有一个
计数填充值D
,其中的B会被
单片机自带的定时器中断服务函数
不断地刷新(递减刷新),当B到0的时候C会被置位,同时B会被重新填满D值,当主程序运行到A的时候不会直接去运行它,而是先检查C,如果C没有被置位,说明还没有到A的执行时间,不执行A,如果C被置位了,就执行A,执行完后将C复位,让C再等上一段时间才再次被执行。
代码实现
任务结构体
下面是任务结构体,它包含了一个任务所需要的所有信息,包括运行状态标志位,计数器,计数器数值填充器(任务运行间隔时间)以及这个任务对应的函数指针
// 任务结构
typedef struct _TASK_COMPONENTS
{
uint8 Run; // 程序运行标记:0-不运行,1运行
uint8 Timer; // 计时器
uint8 ItvTime; // 任务运行间隔时间
void (*TaskHook)(void); // 要运行的任务函数
} TASK_COMPONENTS; // 任务定义
任务数组
下面是任务数组,当我们需要添加一个任务到时间片轮询队列中的时候,我只需要再这里添加函数指针,设置好相关参数就可以了
static TASK_COMPONENTS TaskComps[] =
{
{0, 60, 60, TaskDisplayClock}, // 显示时钟
{0, 20, 20, TaskKeySan}, // 按键扫描
{0, 30, 30, TaskDispStatus}, // 显示工作状态
// 这里添加你的任务。。。。
};
任务列表
typedef enum _TASK_LIST
{
TAST_DISP_CLOCK, // 显示时钟
TAST_KEY_SAN, // 按键扫描
TASK_DISP_WS, // 工作状态显示
// 这里添加你的任务。。。。
TASKS_MAX // 总的可供分配的定时任务数目
} TASK_LIST;
标志位处理函数
这个函数是用来更新每个任务的计数器以及它们的运行标志位的,它是写在
单片机的定时器中断服务函数中的
。
void TaskRemarks(void)
{
uint8 i;
for (i=0; i<TASKS_MAX; i++) // 逐个任务时间处理
{
if (TaskComps[i].Timer) // 时间不为0
{
TaskComps[i].Timer--; // 减去一个节拍
if (TaskComps[i].Timer == 0) // 时间减完了
{
TaskComps[i].Timer = TaskComps[i].ItvTime; // 恢复计时器值,从新下一次
TaskComps[i].Run = 1; // 任务可以运行
}
}
}
}
任务处理函数
这个函数用来判断任务是否到了执行时间,它会逐个检查任务列表中的任务,如果到了就执行,同时清空执行标志位,没到就不执行
void TaskProcess(void)
{
uint8 i;
for (i=0; i<TASKS_MAX; i++) // 逐个任务时间处理
{
if (TaskComps[i].Run) // 时间不为0
{
TaskComps[i].TaskHook(); // 运行任务
TaskComps[i].Run = 0; // 标志清0
}
}
}
任务函数
/**************************************************************************************
* FunctionName : TaskDisplayClock()
* Description : 显示任务
* EntryParameter : None
* ReturnValue : None
**************************************************************************************/
void TaskDisplayClock(void)
{
}
/**************************************************************************************
* FunctionName : TaskKeySan()
* Description : 扫描任务
* EntryParameter : None
* ReturnValue : None
**************************************************************************************/
void TaskKeySan(void)
{
}
/**************************************************************************************
* FunctionName : TaskDispStatus()
* Description : 工作状态显示
* EntryParameter : None
* ReturnValue : None
**************************************************************************************/
void TaskDispStatus(void)
{
}
中断服务函数
/**************************************************************************************
* FunctionName : TimerInterrupt()
* Description : 定时中断服务函数
* EntryParameter : None
* ReturnValue : None
**************************************************************************************/
void TimerInterrupt(void)
{
TaskRemarks( );
}
主函数
/**************************************************************************************
* FunctionName : main()
* Description : 主函数
* EntryParameter : None
* ReturnValue : None
**************************************************************************************/
int main(void)
{
InitSys(); // 初始化
while (1)
{
TaskProcess(); // 任务处理
}
}
架构图解
下面这个图片可以帮助理解上面的代码的整体架构
总结
整个程序的执行流程就是:中断函数不断刷新着每一个任务的状态,任务处理函数根据这些状态来判断哪些函数要被执行,如果要执行,就立马执行。添加任务就是在任务数组中添加一个结构体信息,编写一个任务函数,在任务列表中添加一个函数。