一、概述
在编写单片机裸机程序的时候,通常是在main函数中不断循环,如果有多个功能的时候,为了系统能够及时响应,通常使用定时器为每个功能模块进行延时后置标志位,在主函数中查询,实现各个功能模块的时间片调度,但这样的架构随心所欲,不够严谨,所以编写了一个以定时器为时基的时间片软件架构。
二、软件设计
当一个函数需要时间片进行轮询调度的话,我们首先要定义几个属性:
1、执行标志;
2、执行后延时的时间;
3、延时的时间计数器;
4、函数的句柄(函数指针);
根据该属性,定义一个结构体
#ifndef TREU
#define TRUE 1
#endif
#ifndef FALSE
#define FALSE 0
#endif
#define TASKS_MAX 10 //最大可运行任务数
typedef void (*TaskHook)(void); //函数指针
typedef struct
{
uint8_t run; // 程序运行标记:0-不运行,1运行 置1时可以运行程序了
uint32_t timecount; // 计时器计数值,定时器调度时间
uint32_t reloadcount; // 为0时不能重复调用,否者为重复调用的时间,可更改
TaskHook task_handle; // 要运行的任务函数
}TASK_COMPONENTS; // 结构体名称
然后定义一个结构体数组,存放任务函数(功能模块)
TASK_COMPONENTS TaskComps[TASKS_MAX];
然后需要几个任务控制函数:
1、任务块初始化
该初始化主要是初始化任务列表,就是初始化前面定义的结构体数组,具体代码如下:
/********************************************************************
*@函数名:TaskInitList
*@功 能:初始化任务结构体
*@形 参:NULL
*@返回值:NULL
*@备 注:NULL
********************************************************************/
void TaskInitList(void)
{
uint8_t id = 0;
for (id=0; id<TASKS_MAX; id++)
{
TaskComps[id].run = FALSE; // 首先停止
TaskComps[id].timecount = 0; // 计数值
TaskComps[id].reloadcount = 0; // 重装值
TaskComps[id].task_handle = 0; // 函数名,指针类型
}
}
2、任务创建
该函数主要是向上面定义的结构体数组中添加任务块,以便后面可以实时控制,具体代码如下:
/********************************************************************
*@函数名:TaskCreate
*@功 能:创建定时任务
*@形 参:NULL
*@返回值:NULL
*@备 注:NULL
********************************************************************/
void TaskCreate(uint32_t count, uint32_t reload, TaskHook task)
{
uint8_t id;
if(task == NULL) return;
for (id=0; id<TASKS_MAX; id++)
{
if (TaskComps[id].task_handle == 0)
{
TaskComps[id].run = FALSE;
TaskComps[id].timecount = count;
TaskComps[id].reloadcount = reload;
TaskComps[id].task_handle = task;
break;
}
}
}
第一个参数 count 主要是创建后多长时间启动,reload 主要是设置软件函数的运行周期,这个周期是相对延时周期,不是绝对延时周期,最后一个参数是函数主体,通过函数指针指向函数体本身,后续使用指针函数对进行调用;
3、任务删除
该函数主要是对加入到结构体数组中的任务块进行删除,然后使用逐级填补的方式填补结构体数组,具体函数如下:
/********************************************************************
*@函数名:TaskDelete
*@功 能:删除定时任务
*@形 参:NULL
*@返回值:NULL
*@备 注:NULL
********************************************************************/
void TaskDelete(TaskHook task)
{
uint8_t id = 0;
if (task == NULL)
return;
for(id=0; id<TASKS_MAX; id++)
{
if (TaskComps[id].task_handle == task)
{
break;
}
}
if(id == TASKS_MAX)
{
return;
}
TaskComps[id].run = FALSE;
TaskComps[id].timecount = 0;
TaskComps[id].reloadcount = 0;
TaskComps[id].task_handle = 0;
while((id+1) < TASKS_MAX)
{
TaskComps[id].run = TaskComps[id + 1].run;
TaskComps[id].timecount = TaskComps[id + 1].timecount;
TaskComps[id].reloadcount = TaskComps[id + 1].reloadcount;
TaskComps[id].task_handle = TaskComps[id + 1].task_handle;
id++;
}
}
函数的形参是任务块或者任务函数本身,通过函数找到其在结构体数组中的位置进行删除,并将后面的任务函数在结构体数组中进行前移,保证结构体数组的完整性。
4、任务运行标志位检查
该函数主要是在定时器中断中检查任务的运行时间,每次减1,减到0的时候说明时间到,然后置标志位,在任务调度主体函数中检测到标志位的时候通过函数指针运行该函数,具体代码如下:
/********************************************************************
*@函数名:TaskRemarks
*@功 能:任务函数的检查
*@形 参:NULL
*@返回值:NULL
*@备 注:在定时器中断中调用此函数
********************************************************************/
void TaskRemarks(void)
{
uint8_t i;
for(i=0; i<TASKS_MAX; i++)
{
/* 正在执行的函数不进行递减操作 */
if((TaskComps[i].timecount)&&(TaskComps[i].run == FALSE))
{
TaskComps[i].timecount--;
if (TaskComps[i].timecount == 0)
{
TaskComps[i].timecount = TaskComps[i].reloadcount;
TaskComps[i].run = TRUE;
}
}
}
}
5、任务调度主体
该函数主要是在main函数中运行,不断检查任务的 run 标志位,然后判断是否运行,具体代码如下:
void TaskProcess(void)
{
static uint8_t task_cnt = 0;
if (TaskComps[task_cnt].run == TRUE)
{
if(TaskComps[task_cnt].task_handle != NULL)
{
TaskComps[task_cnt].task_handle();
}
TaskComps[task_cnt].run = FALSE;
}
if (++task_cnt > TASKS_MAX)
task_cnt = 0;
}
最后,当一切准备好之后,编写一个定时器函数,每1ms中断一次,将 TaskRemarks 在中断服务函数中进行回调;具体实例代码如下:
/* 函数定义 */
void fun1(void);
void fun2(void);
/* 主函数循环 */
int main(void)
{
/* 初始化定时器时钟 */
FL_Init();
FL_DelayMs(100);
bsp_Uart0_Init(115200);
DEBUG("Hello, this is a demo for uart0.\r\n");
LED_GPIO_Init();
TaskInitList();
TaskCreate(200, 200, fun1);
TaskCreate(100, 1000, fun2);
bsp_BTimer_Init(TaskRemarks);
while(1)
{
TaskProcess();
}
}
void fun1(void)
{
LED1_TOGGLE();
}
void fun2(void)
{
DEBUG("Hello, this is fun2.\r\n");
}
该任务调度系统在复旦微平台 FM33LE0XX 单片机上进行实例验证,需要的朋友自行下载软件包查看。技术交流,资源共享,共同进步。