嵌入式裸机模块——软定时器
2026/8/13 17:58:47 网站建设 项目流程

仓库地址:Gitee - TimTask

模块概述

在嵌入式领域中,比如单片机,尤其STM32这种32位ARM架构的MCU(当然这个模块也可以用在51单片机上,不过51的资源太少了,建议用精简版的)上裸机运行业务代码的,规模较大且逻辑复杂的裸机框架一般用的都是队列、状态机、软定时器,行业内隐形规则呢就是少用阻塞式的延时,比如HAL库自带的HAL_Delay(),目前我接触到的就这些,有更多的好技术也还请朋友评论区留言。

针对软定时器在以前复刻了我之前工作的公司的软定时器,是面向对象设计的,就是cpp里的class(类)这么一个技术。后来我用C99的模式重写了一遍,也就是用结构体+指针的方式。用了很久一段时间,发现每次使用前,场景比如某个模块的结构体里包含了这个软定时器,那么使用之前需要初始化(init),设置时间(set),判断超时(timeout)这么几个操作,当项目渐渐变大的时候这些代码全部堆在里面阅读起来也渐渐比较的烦心了。

那么有没有一种更方便的操作方式呢,比如初始化自动完成、设置时间或者单次/循环运行、一个函数里判断所有的超时判断然后执行函数呢?
答案肯定是有的,不然也不会有这篇文章了,我使用了链表+函数指针的方式来新增任务或者删除任务,并在主任务执行函数中对链表遍历判断软定时器是否超时,然后执行对应任务里的函数指针指向的待执行的函数。
本模块只需要定义好硬件定时器时基来源,然后声明定义任务链表的头指针TIM_TASK TimMain,并在主函数中执行TimTaskHandle(&TimMain);就可以自动完成软定时器的超时判断了。
需要添加或者删除定时任务呢也只用两条函数即可,分别是
INT32U TimTaskAddTask(TIM_TASK * pTask,INT32U ulTime,void (*pfun)(void))
其中ulTime是超时时间,pfun超时执行的函数。添加任务成功后会返回任务id。
INT8U TimTaskDelTask(TIM_TASK * pTask,INT32U id)
删除指定id任务。
INT8U TimTaskStartID(TIM_TASK * pTask,INT32U id)
指定ID的软定时器启动,注:从当前时间线开始判断超时
INT8U TimTaskStopID(TIM_TASK * pTask,INT32U id)
指定ID的软定时器停止

💡 该模块需要的资源非常简单,仅需片上资源有硬件TIM定时器或者计数器就行。

移值

在keil 或者iar工程中添加.c文件不必细说了,初始化定时器。
我用的TIM4作为时基来源,分频系数7199,也就是定时器内部计数器是每10us 就 +1。

voidMX_TIM4_Init(void){/* USER CODE BEGIN TIM4_Init 0 *//* USER CODE END TIM4_Init 0 */TIM_ClockConfigTypeDef sClockSourceConfig={0};TIM_MasterConfigTypeDef sMasterConfig={0};/* USER CODE BEGIN TIM4_Init 1 *//* USER CODE END TIM4_Init 1 */htim4.Instance=TIM4;htim4.Init.Prescaler=7199;htim4.Init.CounterMode=TIM_COUNTERMODE_UP;htim4.Init.Period=65535;htim4.Init.ClockDivision=TIM_CLOCKDIVISION_DIV1;htim4.Init.AutoReloadPreload=TIM_AUTORELOAD_PRELOAD_ENABLE;if(HAL_TIM_Base_Init(&htim4)!=HAL_OK){Error_Handler();}sClockSourceConfig.ClockSource=TIM_CLOCKSOURCE_INTERNAL;if(HAL_TIM_ConfigClockSource(&htim4,&sClockSourceConfig)!=HAL_OK){Error_Handler();}sMasterConfig.MasterOutputTrigger=TIM_TRGO_RESET;sMasterConfig.MasterSlaveMode=TIM_MASTERSLAVEMODE_DISABLE;if(HAL_TIMEx_MasterConfigSynchronization(&htim4,&sMasterConfig)!=HAL_OK){Error_Handler();}/* USER CODE BEGIN TIM4_Init 2 *//* USER CODE END TIM4_Init 2 */}

然后启动时基

HAL_TIM_Base_Start(&htim4);

TimTask.h文件的顶部修改BASE来源

下面的ratio根据你的分频系数来修改,我的软定时器是毫秒级别的。

实验效果

主函数代码

TIM_TASK TimMain;voidTaskTest(void){SEGGER_RTT_printf(0,"id1 1000!%d\n",GET_TIM_BASE);}voidTaskTest1(void){TimTaskDelTask(&TimMain,2);TimTaskDelTask(&TimMain,3);TimTaskDelTask(&TimMain,1);SEGGER_RTT_printf(0,"id2 1001!%d\n",GET_TIM_BASE);}voidTaskTest2(void){SEGGER_RTT_printf(0,"id3 2000!%d\n",GET_TIM_BASE);}voidTaskTest3(void){SEGGER_RTT_printf(0,"id4 3000!%d\n",GET_TIM_BASE);}voidTaskTest4(void){SEGGER_RTT_printf(0,"id5 4000!%d\n",GET_TIM_BASE);}intmain(void){TimTask_Init(&TimMain);TimTaskAddTask(&TimMain,1000,TaskTest);TimTaskAddTask(&TimMain,1001,TaskTest1);TimTaskAddTask(&TimMain,2000,TaskTest2);TimTaskAddTask(&TimMain,3000,TaskTest3);TimTaskAddTask(&TimMain,4000,TaskTest4);MX_TIM4_Init();HAL_TIM_Base_Start(&htim4);while(1){TimTaskHandle(&TimMain);}}

效果1 删除任务

id1 1000!10000指的是id号为1的任务设置超时为1000ms的任务,10000是硬件定时器的计数器,为每10us自增。可以看出误差是小于10us的,至少两个id4任务的定时器计数器差值为0,即 (60000-30000)us-(3000x10)us=0x10(us)

现象效果如下,TaskTest1将id为 1 2 3的任务都删除了,所以先输出了id 为 1 2 的信息,id3由于超时时间未到并且在此之前就被id2 任务删除了所以没有输出,id 4 id 5的任务循环定时输出信息。

效果2 暂停和启动任务

这个效果是暂停任务,在输出id1 id2的任务后,暂停id为1 2 3的任务,并不是删除链表并释放内存空间,只是没有进超时判断函数中,在经过4000ms后在id5任务中重新启动id为3的任务,所以在6000ms时,id3的任务输出信息。

总结和观望

经过上诉的设计和自主测试,定时任务的时间误差是非常非常小的,自测也没发现什么重大BUG,如有广大开发者朋友在使用该模块时遇到BUG或者其他问题均可在本文或者github、gitee中提出或者修复并提交,非常感谢。

附录代码

TimTask.c

#include"TimTask.h"typedefstruct{INT32U ulSoftTim;INT32U ulInterval;INT32U ulTmr;INT16U uinTimerRec;INT8U uchStop;INT8U uchReadState;}TIM_TASK_SOFTTIM;typedefstructt_TIM_TASK{INT32U id;TIM_TASK_SOFTTIM stTimer;void(*pTaskFun)(INT32U);INT32U Parameter;INT8U uchDeleteMark;// 统一回收,避免回调中修改出错structt_TIM_TASK*next;}TIM_TASK;staticTIM_TASK TaskMain={0};staticvolatileINT32U TaskLast_ID;// 记录最后的任务ID,用于创建staticvolatileINT8U g_isDelTask=0;staticvolatileINT32U g_currentTaskId=0;staticvoidReadTimBase(TIM_TASK_SOFTTIM*pstTimVal);staticvoidSTimInit(TIM_TASK_SOFTTIM*pstSoftTimClass);staticvoidset(TIM_TASK_SOFTTIM*pstTimVal,INT32U ulTime);staticvoidStart(TIM_TASK_SOFTTIM*pstTimVal);staticvoidStop(TIM_TASK_SOFTTIM*pstTimVal);staticINT8STimout(TIM_TASK_SOFTTIM*pstTimVal);/***************************************************************** * @brief 初始化任务管理器 * @param pstTT * @author lize * @date 2025-05-08 *****************************************************************/voidTimTask_Init(void){TaskMain.id=0;TaskMain.pTaskFun=NULL;TaskMain.next=NULL;TaskLast_ID=1;TaskMain.Parameter=0;STimInit(&TaskMain.stTimer);}/***************************************************************** * @brief 查找ID任务是否存在 * @param ulID ID * @return INT8U 1 存在 * @author lize * @date 2025-05-08 *****************************************************************/INT8UTimTaskFindID(INT32U ID){if(TaskMain.next==NULL)return0;TIM_TASK*pTemp=TaskMain.next;while(pTemp!=NULL){if(pTemp->id==ID){return1;}pTemp=pTemp->next;}return0;}/***************************************************************** * @brief 获取当前正在执行的回调任务ID *****************************************************************/INT32UTimTaskGetCurrentID(void){returng_currentTaskId;}/***************************************************************** * @brief 启动指定ID的软定时器 * @param id * @return INT8U 1:成功 * @author lize * @date 2025-05-09 *****************************************************************/INT8UTimTaskStartID(INT32U id){if(TaskMain.next==NULL)return0;TIM_TASK*pTemp=TaskMain.next;while(pTemp!=NULL){if(pTemp->id==id&&pTemp->uchDeleteMark==0){Start(&pTemp->stTimer);return1;}pTemp=pTemp->next;}return0;}/***************************************************************** * @brief 暂停指定ID的软定时器 * @param id * @return INT8U 1:成功 * @author lize * @date 2025-05-09 *****************************************************************/INT8UTimTaskStopID(INT32U id){if(TaskMain.next==NULL)return0;TIM_TASK*pTemp=TaskMain.next;while(pTemp!=NULL){if(pTemp->id==id&&pTemp->uchDeleteMark==0){Stop(&pTemp->stTimer);return1;}pTemp=pTemp->next;}return0;}/***************************************************************** * @brief 添加任务 * @param uiTime 时间 * @param pfun 待执行函数 * @return INT32U 0 : Err ; other : 任务的id * @author lize * @date 2025-05-08 *****************************************************************/INT32UTimTaskAddTask(INT32U ulTime,void(*pfun)(INT32U),INT32U ulParameter){TIM_TASK*pnew;INT32U startId;INT32U ret=0;// 默认失败返回0if(!pfun)return0;TT_inter_critical;pnew=(TIM_TASK*)malloc(sizeof(TIM_TASK));if(pnew==NULL)gotocleanup;memset(pnew,0,sizeof(TIM_TASK));startId=TaskLast_ID;do{if(0==TimTaskFindID(TaskLast_ID))break;TaskLast_ID++;if(TaskLast_ID==0)TaskLast_ID=1;}while(TaskLast_ID!=startId);if(TimTaskFindID(TaskLast_ID)!=0){// 兜底错误,如果都存在的情况free(pnew);gotocleanup;}pnew->id=TaskLast_ID;pnew->next=NULL;pnew->pTaskFun=pfun;pnew->Parameter=ulParameter;STimInit(&pnew->stTimer);set(&pnew->stTimer,ulTime);TIM_TASK*scan=&TaskMain;while(scan->next!=NULL){scan=scan->next;}scan->next=pnew;ret=pnew->id;cleanup:TT_exit_critical;returnret;}/***************************************************************** * @brief 删除任务 * @param id 任务ID * @return INT8U 1 : 找到对应ID任务并标记删除 * @author lize * @date 2025-05-08 * @note 仅置 uchDeleteMark=1 并立即停止定时器,不真正 free. * 节点由 TimTaskHandle 在一轮遍历结束后统一回收, * 这样回调执行期间删除任意节点都不会导致 未知错误. * TimTaskFindID/StartID/StopID 已做处理,删除立即生效. *****************************************************************/INT8UTimTaskDelTask(INT32U id){TIM_TASK*pTemp;INT8U ret=0;if(TaskMain.next==NULL)return0;TT_inter_critical;pTemp=TaskMain.next;while(pTemp!=NULL){if(pTemp->id==id&&pTemp->uchDeleteMark==0){pTemp->uchDeleteMark=1;pTemp->stTimer.uchStop=1;// 立即停止定时,不再触发回调g_isDelTask=1;ret=1;break;}pTemp=pTemp->next;}TT_exit_critical;returnret;}/***************************************************************** * @brief 定时任务主程序 * @author lize * @date 2025-05-08 *****************************************************************/voidTimTaskHandle(void){TIM_TASK*prev;TIM_TASK*current;if(TaskMain.next==NULL)return;if(g_isDelTask==1){prev=&TaskMain;current=TaskMain.next;while(current!=NULL){if(current->uchDeleteMark){prev->next=current->next;current->next=NULL;free(current);current=prev->next;}else{prev=current;current=current->next;}}g_isDelTask=0;}current=TaskMain.next;while(current!=NULL){if(current->uchDeleteMark){// 跳过已标记删除的节点current=current->next;continue;}if(Timout(&current->stTimer)){g_currentTaskId=current->id;current->pTaskFun(current->Parameter);g_currentTaskId=0;}current=current->next;}}/***************************************************************** 函数名称: 函数功能: 输入参数: 返回参数: 修改记录: 日 期 版 本 修改人 内容 2023.9.21 1.0 lzj 初版 2023.11.3 1.1 lzj 结构体版 *****************************************************************/staticvoidReadTimBase(TIM_TASK_SOFTTIM*pstTimVal){INT16U uinTemp;INT16U uinTimeStamp;if(pstTimVal->uchReadState){pstTimVal->uchReadState=0;pstTimVal->uinTimerRec=TASK_GET_TIM_BASE;// 进入时取时基初始时间}uinTimeStamp=TASK_GET_TIM_BASE;uinTemp=uinTimeStamp-pstTimVal->uinTimerRec;// 计算差值pstTimVal->ulSoftTim+=uinTemp;pstTimVal->uinTimerRec=uinTimeStamp;// 记录本次变化}/******************** 初始化 ****************************/staticvoidSTimInit(TIM_TASK_SOFTTIM*pstTimVal){//ulTmr = 0;pstTimVal->uchStop=1;// 0:启动 1停止pstTimVal->uchReadState=1;}/******************** 设置时间 ****************************/staticvoidset(TIM_TASK_SOFTTIM*pstTimVal,INT32U ulTime){pstTimVal->ulInterval=ulTime*TASK_Tim_ratio;Start(pstTimVal);}/******************** 启动 ****************************/staticvoidStart(TIM_TASK_SOFTTIM*pstTimVal){ReadTimBase(pstTimVal);pstTimVal->ulTmr=pstTimVal->ulSoftTim;pstTimVal->uchStop=0;}/******************** 暂停 ****************************/staticvoidStop(TIM_TASK_SOFTTIM*pstTimVal){pstTimVal->uchStop=1;}/******************** 超时判断 ***************************/staticINT8STimout(TIM_TASK_SOFTTIM*pstTimVal){if(pstTimVal==NULL||pstTimVal->uchStop==1)return0;ReadTimBase(pstTimVal);if(pstTimVal->ulSoftTim>=pstTimVal->ulTmr){if((pstTimVal->ulSoftTim-pstTimVal->ulTmr)>=pstTimVal->ulInterval){pstTimVal->ulTmr=pstTimVal->ulSoftTim;return1;}}else{if((0xFFFFFFFFu-pstTimVal->ulTmr+pstTimVal->ulSoftTim+1u)>=pstTimVal->ulInterval){pstTimVal->ulTmr=pstTimVal->ulSoftTim;return1;}}return0;}

TimTask.h

#ifndef__TIMTASK_H#define__TIMTASK_H#include"Pub_typedef.h"#include"stdlib.h"#include"Timer.h"#defineTASK_GET_TIM_BASE(TIM2->CNT)//定时器时钟除预分频值为定时器频率,周期为1ms。软定时为毫秒级// TIM: 主频 预分频#defineTASK_Tim_ratio((24000000/24000)/1000)/***************************************************************** * @todo 如果只在主程序中使用可以不用考虑中断安全 * 在中断中删除或者增加任务需要进入临界区 *****************************************************************/#ifdefTASK_INTERRUPT_SAFETY#defineTT_inter_critical__disable_irq()#defineTT_exit_critical__enable_irq()#else#defineTT_inter_critical#defineTT_exit_critical#endifvoidTimTask_Init(void);INT8UTimTaskFindID(INT32U ID);INT32UTimTaskGetCurrentID(void);INT8UTimTaskStartID(INT32U id);INT8UTimTaskStopID(INT32U id);INT32UTimTaskAddTask(INT32U ulTime,void(*pfun)(INT32U),INT32U ulParameter);INT8UTimTaskDelTask(INT32U id);voidTimTaskHandle(void);#endif

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询