# TimTask **Repository Path**: Yuan-Prjs/TimTask ## Basic Information - **Project Name**: TimTask - **Description**: 嵌入式裸机 - 软定时器,采用链表+函数指针和软定时器结合的方式,使用单条语句就可以设置定时任务。 - **Primary Language**: C - **License**: MIT - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 3 - **Created**: 2025-05-09 - **Last Updated**: 2025-06-05 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README ## 模块概述 在嵌入式领域中,比如单片机,尤其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的软定时器停止 > :bulb: 该模块需要的资源非常简单,仅需片上资源有硬件TIM定时器或者计数器就行。 ## 移值 在keil 或者iar工程中添加 .c 文件不必细说了,初始化定时器。 我用的TIM4作为时基来源,分频系数7199,也就是定时器内部计数器是每10us 就 +1。 ```c void MX_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 */ } ``` 然后启动时基 ```c HAL_TIM_Base_Start(&htim4); ``` 在TimTask.h文件的顶部修改BASE来源 下面的ratio根据你的分频系数来修改,我的软定时器是毫秒级别的。 ![1](1.png) ## 实验效果 主函数代码 ```c TIM_TASK TimMain; void TaskTest(void) { SEGGER_RTT_printf(0,"id1 1000!%d\n",GET_TIM_BASE); } void TaskTest1(void) { TimTaskDelTask(&TimMain,2); TimTaskDelTask(&TimMain,3); TimTaskDelTask(&TimMain,1); SEGGER_RTT_printf(0,"id2 1001!%d\n",GET_TIM_BASE); } void TaskTest2(void) { SEGGER_RTT_printf(0,"id3 2000!%d\n",GET_TIM_BASE); } void TaskTest3(void) { SEGGER_RTT_printf(0,"id4 3000!%d\n",GET_TIM_BASE); } void TaskTest4(void) { SEGGER_RTT_printf(0,"id5 4000!%d\n",GET_TIM_BASE); } int main(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](2.png) ### 效果2 暂停和启动任务 这个效果是暂停任务,在输出id1 id2的任务后,暂停id为1 2 3的任务,并不是删除链表并释放内存空间,只是没有进超时判断函数中,在经过4000ms后在id5任务中重新启动id为3的任务,所以在6000ms时,id3的任务输出信息。 ![输入图片说明](3.png) ## 总结和观望 经过上诉的设计和自主测试,定时任务的时间误差是非常非常小的,自测也没发现什么重大BUG,如有广大开发者朋友在使用该模块时遇到BUG或者其他问题均可在本文或者github、gitee中提出或者修复并提交,非常感谢。