TaskProc 组件的移植与使用。
简介
TaskProc 组件使用 C 语言编写,可以很方便移植到其它平台。该组件提供了创建任务、删除任务、挂起任务和将任务从挂起态恢复等操作,轻量简约,十分适用于单片机平台。TaskProc 组件使用系统中任一硬件定时器做为时钟源,在定时器中断里更新各个任务的时间,然后在主线程中轮询确认任务是否到时,若是时间到了则执行该任务。TaskProc 组件中所有任务均为周期任务,任务周期由创建任务时确定,并支持动态修改任务周期。TaskProc 组件支持系统时基与任务周期独立配置,例如硬件定时器周期可以设置到 10ms,任务周期可以设置到 9ms、11ms 等任意整数值,最小任务周期为 1ms。
头文件
TaskProc 组件头文件如下所示。TASK_PROC_DEBUG_ENABLE 宏定义仅用于调试,会自动输出一些信息,方便调试。
/*********************************************************************************************************
* 模块名称: TaskProc.h
* 摘 要: 时间片轮询模块
* 当前版本: 1.0.0
* 作 者: SZLY(COPYRIGHT 2019 SZLY. All rights reserved.)
* 完成日期: 2023年09月04日
* 内 容:
* 注 意: 一个任务函数只能对应一个任务,不可共用
**********************************************************************************************************
* 取代版本: 1.0.0
* 作 者: HRZ
* 完成日期: 2023年09月04日
* 修改内容: 添加动态注册、删除和修改任务周期 API 函数
* 修改文件: TaskProc.c/TaskProc.c
*********************************************************************************************************/
#ifndef _TASK_PROC_H_
#define _TASK_PROC_H_
/*********************************************************************************************************
* 包含头文件
*********************************************************************************************************/
/*********************************************************************************************************
* 宏定义
*********************************************************************************************************/
#define TASK_PROC_DEBUG_ENABLE (0)
/*********************************************************************************************************
* 枚举结构体定义
*********************************************************************************************************/
/*********************************************************************************************************
* API函数声明
*********************************************************************************************************/
void InitTaskProc(void); //初始化时间片轮询模块
void TaskTimerProc(unsigned int decTime); //时间片定时器中断任务标记处理
void TaskProcess(void); //时间片主程序任务处理
void TaskRegister(void (*taskHook)(void), unsigned int period, char* taskName); //任务注册
void TaskDelete(void (*taskHook)(void)); //删除任务
void TaskModifyPeriod(void (*taskHook)(void), unsigned int period); //修改任务周期
void TaskSuspend(void (*taskHook)(void)); //挂起任务
void TaskResume(void (*taskHook)(void)); //唤醒任务
#if TASK_PROC_DEBUG_ENABLE
void TaskPrintParam(void); //打印所有任务参数
#endif
#endif
源文件
TaskProc 组件源文件如下所示。为了避免使用 malloc 动态内存分配,以及方便用户使用(用户无需定义任务控制块),TaskProc 组件预制了任务控制块,因此有最大任务数量限制,可以通过 MAX_TASK_NUM 宏修改。MAX_TASK_NUM 宏要根据实际项目做适当修改。
/*********************************************************************************************************
* 模块名称: TaskProc.c
* 摘 要: 时间片轮询模块
* 当前版本: 1.0.1
* 作 者: SZLY(COPYRIGHT 2019 SZLY. All rights reserved.)
* 完成日期: 2023年09月04日
* 内 容:
* 注 意:
**********************************************************************************************************
* 取代版本: 1.0.0
* 作 者: HRZ
* 完成日期: 2023年09月04日
* 修改内容: 添加动态注册、删除和修改任务周期 API 函数
* 修改文件: TaskProc.c/TaskProc.c
*********************************************************************************************************/
/*********************************************************************************************************
* 包含头文件
*********************************************************************************************************/
#include "TaskProc.h"
#if TASK_PROC_DEBUG_ENABLE
#include "stdio.h"
#endif
/*********************************************************************************************************
* 宏定义
*********************************************************************************************************/
//最大任务数量
#define MAX_TASK_NUM (10)
//空指针定义
#ifndef NULL
#define NULL 0
#endif
/*********************************************************************************************************
* 枚举结构体定义
*********************************************************************************************************/
//任务结构体
typedef struct
{
unsigned int run; //程序运行计数,为 1 就代表要运行一次,2 则代表两次,以此类推
unsigned int timer; //计时器,单位是 ms。向下递减,递减到零则表示将 run 加一
unsigned int period; //任务运行周期,单位是 ms
void (*taskHook)(void); //要运行的任务函数,单位是 ms
char* taskName; //任务名
void* next; //下一项
}StructTaskCtr; //任务控制块
/*********************************************************************************************************
* 内部变量
*********************************************************************************************************/
//定时器轮询使能标志位,0-定时器中断可以执行轮询操作,其它-定时器禁止执行轮询操作
volatile static unsigned int s_iTimerPollFlag = 0;
//定时器向下递减总数,增加、删除任务的时候会无法计时,此变量用来记录中的向下递减的时间
volatile static unsigned int s_iTimerDecCnt = 0;
//是否初始化过标志位
volatile static unsigned char s_iHasInitFlag = 0;
//链表是否有修改过标志位
volatile static unsigned char s_iLinkUpdateFag = 0;
//任务链表以及链表项
static StructTaskCtr* s_pTaskHeader = NULL;
static StructTaskCtr s_arrTaskItem[MAX_TASK_NUM] = {0};
/*********************************************************************************************************
* 内部函数声明
*********************************************************************************************************/
static void TaskCallBack(void); //任务执行函数
static void TaskRemarks(unsigned int decTime); //任务标记
/*********************************************************************************************************
* 内部函数实现
*********************************************************************************************************/
/*********************************************************************************************************
* 函数名称: TaskCallBack
* 函数功能: 任务执行函数
* 输入参数: void
* 输出参数: void
* 返 回 值: void
* 创建日期: 2023年09月04日
* 注 意: 由主函数执行,负责做任务回调
*********************************************************************************************************/
static void TaskCallBack(void)
{
StructTaskCtr* node;
TASK_CALLBACK_BEGIN_MARK: //函数起始位置标记
s_iLinkUpdateFag = 0; //清空链表修改标志位
node = s_pTaskHeader; //获取链表头
while(NULL != node) //循环遍历整张链表
{
if(0 != node->run) //该任务需要运行
{
node->taskHook(); //执行任务回调函数
if(0 != s_iLinkUpdateFag) //任务回调函数中执行了修改任务链表相关操作,例如注册、删除任务
{
#if TASK_PROC_DEBUG_ENABLE
if(NULL != node->taskName){printf("TaskCallBack: Task(%s) update the link\r\n", node->taskName);}
else{printf("TaskCallBack: Task(?) update the link\r\n");}
#endif
goto TASK_CALLBACK_BEGIN_MARK; //返回该函数最开始位置,重新遍历一遍
}
s_iTimerPollFlag++; //禁止定时器更新
node->run--; //执行计数减一
s_iTimerPollFlag--; //恢复定时器更新
}
node = node->next; //递归到下一项
}
}
/*********************************************************************************************************
* 函数名称: TaskRemarks
* 函数功能: 任务标记
* 输入参数: decTime:递减的时间,ms
* 输出参数: void
* 返 回 值: void
* 创建日期: 2023年09月04日
* 注 意: 由定时器中断服务函数执行,负责时钟计数
*********************************************************************************************************/
static void TaskRemarks(unsigned int decTime)
{
StructTaskCtr* node;
unsigned int time;
node = s_pTaskHeader;
while(NULL != node)
{
//剩余时间大于需要递减的时间,直接减去响应的时间即可
if(node->timer > decTime)
{
node->timer = node->timer - decTime;
}
//剩余时间小于等于需要递减的时间,需要考虑递减时间大于剩余时间的情况
else
{
time = decTime - node->timer;
node->run += 1 + (time / node->period);
node->timer = node->period - (time % node->period);
}
//迭代到下一项
node = node->next;
}
}
/*********************************************************************************************************
* API函数实现
*********************************************************************************************************/
/*********************************************************************************************************
* 函数名称: InitTaskProc
* 函数功能: 初始化时间片轮询模块
* 输入参数: void
* 输出参数: void
* 返 回 值: void
* 创建日期: 2023年09月04日
* 注 意:
*********************************************************************************************************/
void InitTaskProc(void)
{
int i;
//标记已经初始化过
if(0 != s_iHasInitFlag)
{
return;
}
s_iHasInitFlag = 1;
//静态变量清零
s_iTimerPollFlag = 0;
s_iTimerDecCnt = 0;
s_iLinkUpdateFag = 0;
for(i = 0; i < MAX_TASK_NUM; i++)
{
s_arrTaskItem[i].run = 0;
s_arrTaskItem[i].timer = 0;
s_arrTaskItem[i].period = 0;
s_arrTaskItem[i].taskHook = NULL;
s_arrTaskItem[i].next = NULL;
}
}
/*********************************************************************************************************
* 函数名称: TaskTimerProc
* 函数功能: 任务标记
* 输入参数: decTime:向下递减的时间片数量,如果定时器中断服务函数周期为 1ms,那么 decTime 输入为 1;如果是周期是
* 10ms,那么 decTime 输入为 10。以此类推
* 输出参数: void
* 返 回 值: void
* 创建日期: 2023年09月04日
* 注 意: 1、需要放到定时器中断服务函数中周期执行。
* 2、在注册、删除任务时,因为任务链表是全局变量,所以需要用一个标志位提示中断服务函数任务链表正在被编辑,
* 请勿执行递减操作,此时中断服务函数要直接返回,不能对任务链表执行任何操作。
* 为了保证周期回调的精确性,在这里需要将总的递减时间记录下来,在下一次进入执行 TaskTimerProc 函数时,
* 一并减掉,这样就能保证其它任务的周期不被严重干扰。
*********************************************************************************************************/
void TaskTimerProc(unsigned int decTime)
{
//统计总的递减时间
s_iTimerDecCnt += decTime;
//任务链表为空、正在被编辑或未被初始化,直接返回
if((0 != s_iTimerPollFlag) || (NULL == s_pTaskHeader) || (0 == s_iHasInitFlag))
{
return;
}
//时间递减
TaskRemarks(s_iTimerDecCnt);
//清空递减时间
s_iTimerDecCnt = 0;
}
/*********************************************************************************************************
* 函数名称: TaskProcess
* 函数功能: 任务处理
* 输入参数: void
* 输出参数: void
* 返 回 值: void
* 创建日期: 2023年09月04日
* 注 意: 需要放到主函数的死循环中不停调用
*********************************************************************************************************/
void TaskProcess(void)
{
TaskCallBack();
}
/*********************************************************************************************************
* 函数名称: TaskRegister
* 函数功能: 任务注册
* 输入参数: taskHook:任务回调函数,period:任务执行周期(ms)
* 输出参数: void
* 返 回 值: void
* 创建日期: 2023年09月04日
* 注 意: 禁止将任务周期设定为 0
*********************************************************************************************************/
void TaskRegister(void (*taskHook)(void), unsigned int period, char* taskName)
{
int i;
StructTaskCtr* node;
//尚未初始化
if(0 == s_iHasInitFlag)
{
InitTaskProc();
}
//周期为 0,直接返回
if(0 == period)
{
return;
}
//校验该任务是否已经注册,若已经注册过则直接返回
node = s_pTaskHeader;
while(NULL != node)
{
if(taskHook == node->taskHook)
{
return;
}
node = node->next;
}
//寻找空的任务项
node = NULL;
for(i = 0; i < MAX_TASK_NUM; i++)
{
if(NULL == s_arrTaskItem[i].taskHook)
{
node = &s_arrTaskItem[i];
break;
}
}
//查找失败,说明系统中任务数量大于预设值,直接卡死
if(NULL == node)
{
while(1){}
}
//记录任务信息
node->run = 0; //注册后不执行
node->timer = period; //设置计时器
node->period = period; //设置预装载值
node->taskHook = taskHook; //记录任务回调函数
node->next = NULL; //下一项默认为空
node->taskName = taskName; //记录任务名
//禁止定时器更新
s_iTimerPollFlag++;
//直接添加到表头
node->next = s_pTaskHeader;
s_pTaskHeader = node;
//恢复定时器更新
s_iTimerPollFlag--;
//标记任务链表又被修改过
s_iLinkUpdateFag = 1;
}
/*********************************************************************************************************
* 函数名称: TaskDelete
* 函数功能: 删除任务
* 输入参数: taskHook:任务回调函数
* 输出参数: void
* 返 回 值: void
* 创建日期: 2023年09月04日
* 注 意:
*********************************************************************************************************/
void TaskDelete(void (*taskHook)(void))
{
StructTaskCtr* node;
StructTaskCtr* next;
//尚未初始化
if(0 == s_iHasInitFlag)
{
InitTaskProc();
}
//禁止定时器更新
s_iTimerPollFlag++;
//获取表头指针
node = s_pTaskHeader;
//需要删除的任务就在表头
if(taskHook == s_pTaskHeader->taskHook)
{
s_pTaskHeader = s_pTaskHeader->next;
node->run = 0;
node->timer = 0;
node->period = 0;
node->taskHook = 0;
node->next = 0;
}
//递归查找任务所在位置并删除
else
{
while(NULL != node->next)
{
next = node->next;
if(taskHook == next->taskHook)
{
node->next = next->next;
next->run = 0;
next->timer = 0;
next->period = 0;
next->taskHook = 0;
next->next = 0;
break;
}
node = node->next;
}
}
//恢复定时器更新
s_iTimerPollFlag--;
//标记任务链表又被修改过
s_iLinkUpdateFag = 1;
}
/*********************************************************************************************************
* 函数名称: TaskModifyPeriod
* 函数功能: 修改任务周期
* 输入参数: taskHook:任务回调函数,period:任务执行周期(ms)
* 输出参数: void
* 返 回 值: void
* 创建日期: 2023年09月04日
* 注 意: 禁止将任务周期设定为 0
*********************************************************************************************************/
void TaskModifyPeriod(void (*taskHook)(void), unsigned int period)
{
int i;
//尚未初始化
if(0 == s_iHasInitFlag)
{
InitTaskProc();
}
//周期为 0,直接返回
if(0 == period)
{
return;
}
//禁止定时器更新
s_iTimerPollFlag++;
//查找目标位置并更新时间
for(i = 0; i < MAX_TASK_NUM; i++)
{
if(taskHook == s_arrTaskItem[i].taskHook)
{
s_arrTaskItem[i].run = 0;
s_arrTaskItem[i].timer = period;
s_arrTaskItem[i].period = period;
break;
}
}
//恢复定时器更新
s_iTimerPollFlag--;
//标记任务链表又被修改过
s_iLinkUpdateFag = 1;
}
/*********************************************************************************************************
* 函数名称: TaskSuspend
* 函数功能: 挂起任务
* 输入参数: taskHook:任务回调函数
* 输出参数: void
* 返 回 值: void
* 创建日期: 2023年09月04日
* 注 意: TaskSuspend 与 TaskResume 配对使用
*********************************************************************************************************/
void TaskSuspend(void (*taskHook)(void))
{
StructTaskCtr* node;
StructTaskCtr* next;
//尚未初始化
if(0 == s_iHasInitFlag)
{
InitTaskProc();
}
//禁止定时器更新
s_iTimerPollFlag++;
//获取表头指针
node = s_pTaskHeader;
//需要挂起的任务就在表头
if(taskHook == s_pTaskHeader->taskHook)
{
s_pTaskHeader = s_pTaskHeader->next;
node->next = 0;
}
//递归查找任务所在位置并移除
else
{
while(NULL != node->next)
{
next = node->next;
if(taskHook == next->taskHook)
{
node->next = next->next;
next->next = 0;
break;
}
node = node->next;
}
}
//恢复定时器更新
s_iTimerPollFlag--;
//标记任务链表又被修改过
s_iLinkUpdateFag = 1;
}
/*********************************************************************************************************
* 函数名称: TaskResume
* 函数功能: 唤醒任务
* 输入参数: taskHook:任务回调函数
* 输出参数: void
* 返 回 值: void
* 创建日期: 2023年09月04日
* 注 意:
* 1、TaskSuspend 与 TaskResume 配对使用
* 2、任务唤醒后将会被立即运行
*********************************************************************************************************/
void TaskResume(void (*taskHook)(void))
{
int i;
StructTaskCtr* node;
//尚未初始化
if(0 == s_iHasInitFlag)
{
InitTaskProc();
}
//校验该任务是否已经处在活动链表,若已经已存在过则直接返回
node = s_pTaskHeader;
while(NULL != node)
{
if(taskHook == node->taskHook)
{
return;
}
node = node->next;
}
//寻找对应的任务项
node = NULL;
for(i = 0; i < MAX_TASK_NUM; i++)
{
if(taskHook == s_arrTaskItem[i].taskHook)
{
node = &s_arrTaskItem[i];
break;
}
}
//查找失败,说明该任务尚未注册,直接返回
if(NULL == node)
{
return;
}
//记录任务信息
node->run = 1; //唤醒后立即执行
node->timer = node->period; //设置计时器
node->next = NULL; //下一项默认为空
//禁止定时器更新
s_iTimerPollFlag++;
//直接添加到表头
node->next = s_pTaskHeader;
s_pTaskHeader = node;
//恢复定时器更新
s_iTimerPollFlag--;
//标记任务链表又被修改过
s_iLinkUpdateFag = 1;
}
/*********************************************************************************************************
* 函数名称: TaskPrintParam
* 函数功能: 打印所有任务参数
* 输入参数: void
* 输出参数: void
* 返 回 值: void
* 创建日期: 2023年09月04日
* 注 意: 空的任务项不会被打印
*********************************************************************************************************/
#if TASK_PROC_DEBUG_ENABLE
void TaskPrintParam(void)
{
int i;
StructTaskCtr* next;
StructTaskCtr* node;
//打印所有任务的参数
printf("\r\nTaskPrintParam: All Task Param:\r\n");
for(i = 0; i < MAX_TASK_NUM; i++)
{
if(NULL != s_arrTaskItem[i].taskHook)
{
printf("\r\n");
if(NULL != s_arrTaskItem[i].taskName){printf("Task name: %s\r\n", s_arrTaskItem[i].taskName);}
else{printf("Task name: ?\r\n");}
printf("Task time: %d, %d, %d\r\n", s_arrTaskItem[i].period, s_arrTaskItem[i].timer, s_arrTaskItem[i].run);
printf("Task addr: 0x%08X\r\n", (unsigned int)s_arrTaskItem[i].taskHook);
if(NULL == s_arrTaskItem[i].next)
{
printf("Task next: NULL\r\n");
}
else
{
next = s_arrTaskItem[i].next;
if(NULL != next->taskName){printf("Task next: %s\r\n", next->taskName);}
else{printf("Task next: ?\r\n");}
}
}
}
//打印活动状态下的任务参数
printf("\r\nTaskPrintParam: Task Link\r\n");
node = s_pTaskHeader;
while(NULL != node)
{
if(NULL != node->next)
{
if(NULL != node->taskName){printf("%s->", node->taskName);}
else{printf("?->");}
}
else
{
if(NULL != node->taskName){printf("%s\r\n", node->taskName);}
else{printf("\r\n");}
}
node = node->next;
}
printf("\r\n");
}
#endif
移植
时钟源方面可以使用 SysTick 驱动,也可以使用任一定时器。移植代码如下所示。TaskTimerProc 函数的参数用于告知 TaskProc 模块定时器的周期,单位是 ms。此处 SysTick 中断服务函数执行周期为 1ms,因此 TaskTimerProc 参数为 1。如果 SysTick 中断服务函数执行周期为 10ms,那么 TaskTimerProc 的参数要修改为 10,以此类推。
#include "TaskProc.h"
/*********************************************************************************************************
* 函数名称: SysTick_Handler
* 函数功能: SysTick中断服务函数
* 输入参数: void
* 输出参数: void
* 返 回 值: void
* 创建日期: 2023年09月04日
* 注 意:
*********************************************************************************************************/
void SysTick_Handler(void)
{
TaskTimerProc(1);
}
主函数中要需要先初始化 TaskProc 组件,再注册相关任务,最后再不停的调用 TaskProcess 函数即可,如下所示。
#include "TaskProc.h"
#include "LED.h"
int main(void)
{
//初始化软硬件
InitHardware();
InitSoftware();
//初始化 TaskProc 组件
InitTaskProc();
//注册任务
TaskRegister(LED1Task, 500, "LED1 Task");
TaskRegister(LED2Task, 750, "LED2 Task");
//主循环
while(1)
{
TaskProcess();
}
}
任务注册与删除
任务注册与删除通过 TaskProc 组件提供的 TaskRegister 和 TaskDelete 函数实现。
任务的注册如下所示。任务函数的形参固定为 void (*)(void),与 FreeRTOS 和 μC/OS-III 等操作系统的任务函数不同,它并非一个死循环,且任务函数不能复用。注册函数时,需要提供任务函数入口地址,任务周期(以 ms 为单位)和任务名(字符串)。任务名仅用于调试输出,实际使用时可以简单的使用 NULL 空指针代替字符串。任务本身需要的控制块(控制结构体)已经在 TaskProc 组件中内置了,所以用户无需额外定义一个任务控制块。
注意:如果注册的任务超过了规定的最大任务数量,TaskRegister 函数会直接卡死。
#include "TaskProc.h"
//任务函数
void LED1Task(void)
{
GPIO_WriteBit(GPIOC, GPIO_Pin_4, (BitAction)(1 - GPIO_ReadOutputDataBit(GPIOC, GPIO_Pin_4)));
}
//主程序
int main(void)
{
//注册任务
TaskRegister(LED1Task, 500, "LED1 Task");
...
}
任务删除可以通过 TaskDelete 函数轻松实现,如下所示。
//删除 LED1 任务
TaskDelete(LED1Task);
任务的挂起和恢复
任务挂起与恢复通过 TaskProc 组件提供的 TaskSuspend 和 TaskResume 函数实现,具体使用如下所示。挂起任务时,TaskProc 组件会将该任务从活动链表中删除,但任务周期、任务名等信息会被保存下来。唤醒任务后,任务将按照之前设定的周期运行。
注意:通过 TaskResume 函数唤醒任务后,任务将会被立即执行。
//挂起 LED1 任务
TaskSuspend(LED1Task);
//恢复 LED1 任务
TaskResume(LED1Task);
修改任务周期
修改任务周期可以通过 TaskProc 组件提供的 TaskModifyPeriod 函数实现,具体如下所示。处在挂起状态的任务也可以通过 TaskModifyPeriod 修改周期。
//修改 LED1 任务的周期为 1s
TaskModifyPeriod(LED1Task, 1000);
中断相关
若要在中断里执行 TaskProc 组件的 API 函数,则要确保该中断的优先级小于等于定时器中断的优先级。最好是系统初始化时,直接将定时器中断优先级设定为最高。因为定时器中断服务程序中需要轮询所有任务,并更新时间,比较耗资源,因此不宜在系统中部署太多的任务。