当前位置: 首页 > news >正文

嵌入式裸机模块——软定时器

仓库地址: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。

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 */}

然后启动时基

HAL_TIM_Base_Start(&htim4);

TimTask.h文件的顶部修改BASE来源
修改
下面的ratio根据你的分频系数来修改,我的软定时器是毫秒级别的。

实验效果

主函数代码

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的任务循环定时输出信息。
效果1

效果2 暂停和启动任务

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

总结和观望

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

附录代码

TimTask.c

#include "TimTask.h"/****************************************************************** @brief 初始化任务头* @param pstTT * @author lize* @date 2025-05-08
*****************************************************************/
void TimTask_Init(TIM_TASK * pstTT)
{pstTT->id = 0;pstTT->pTaskFun = NULL;pstTT->next = NULL;pstTT->Last_ID = 1;STimInit(&pstTT->stTimer);
}/****************************************************************** @brief 查找ID任务是否存在* @param pTask  头* @param ulID   ID* @return INT8U 1 存在* @author lize* @date 2025-05-08
*****************************************************************/
INT8U TimTaskFindID(const TIM_TASK * pTask,INT32U ID)
{TIM_TASK * pTemp = pTask->next;while (pTemp != NULL){if(pTemp->id == ID){return 1;}pTemp = pTemp->next;}return 0;
}/****************************************************************** @brief 启动指定ID的软定时器* @param pTask * @param id * @return INT8U 1:成功* @author lize* @date 2025-05-09
*****************************************************************/
INT8U TimTaskStartID(TIM_TASK * pTask,INT32U id)
{TIM_TASK * pTemp = pTask->next;while (pTemp != NULL){if(pTemp->id == id){Start(&pTemp->stTimer);return 1;}pTemp = pTemp->next;}return 0;
}/****************************************************************** @brief 暂停指定ID的软定时器* @param pTask * @param id * @return INT8U 1:成功* @author lize* @date 2025-05-09
*****************************************************************/
INT8U TimTaskStopID(TIM_TASK * pTask,INT32U id)
{TIM_TASK * pTemp = pTask->next;while (pTemp != NULL){if(pTemp->id == id){Stop(&pTemp->stTimer);return 1;}pTemp = pTemp->next;}return 0;
}/****************************************************************** @brief 添加任务* @param pTask     头* @param uiTime    时间* @param pfun      待执行函数* @return INT32U   0 : Err ; other : 任务的id* @author lize* @date 2025-05-08
*****************************************************************/
INT32U TimTaskAddTask(TIM_TASK * pTask,INT32U ulTime,void (*pfun)(void))
{TIM_TASK *pnew;if (!pTask || !pfun) return 0;pnew = (TIM_TASK *)malloc(sizeof(TIM_TASK));if (pnew == NULL)    return 0;pnew->next = NULL;pnew->pTaskFun = pfun;pnew->id = pTask->Last_ID ++;STimInit(&pnew->stTimer);set(&pnew->stTimer, ulTime);TIM_TASK *scan = pTask;while (scan->next != NULL){scan = scan->next;}scan->next = pnew;return pnew->id;
}/****************************************************************** @brief 删除任务* @param pTask     头* @param id        任务ID* @return INT8U    1 : 找到对应ID任务并删除* @author lize* @date 2025-05-08
*****************************************************************/
INT8U TimTaskDelTask(TIM_TASK * pTask,INT32U id)
{TIM_TASK *pTemp = pTask->next;TIM_TASK *pPrev =  pTask;if (pTask == NULL)return 0;while (pTemp != NULL){if (pTemp->id == id){if (pTemp->next != NULL){pPrev->next = pTemp->next;}else{pPrev->next = NULL;}free(pTemp);return 1;}pPrev = pTemp;pTemp = pTemp->next;}return 0;
}/****************************************************************** @brief 定时任务主程序* @param pTask * @author lize* @date 2025-05-08
*****************************************************************/
void TimTaskHandle(TIM_TASK *pTask)
{TIM_TASK **ppCurrent = &pTask->next;while (*ppCurrent != NULL){TIM_TASK *current = *ppCurrent;if (Timout(&current->stTimer)){current->pTaskFun(); // 可能删除当前节点// 若当前节点未被删除,移动到下一个节点if (*ppCurrent == current){ppCurrent = &current->next;}// 若被删除,ppCurrent 已自动指向新节点}else{ppCurrent = &current->next;}}
}/*****************************************************************
函数名称:
函数功能:
输入参数:
返回参数:
修改记录:
日      期		版 本		修改人      内容
2023.9.21		 1.0		 lzj	    初版
2023.11.3        1.1         lzj        结构体版
*****************************************************************/
static void ReadTimBase(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;		        // 记录本次变化
}/******************** 初始化 ****************************/
static void STimInit(TIM_TASK_SOFTTIM *pstTimVal)
{//ulTmr = 0;pstTimVal->uchStop = 1;			// 0:启动 1停止pstTimVal->uchReadState = 1;
}/******************** 设置时间 ****************************/
static void set(TIM_TASK_SOFTTIM *pstTimVal,INT32U ulTime)
{pstTimVal->ulInterval = ulTime * TASK_Tim_ratio;Start(pstTimVal);
}/******************** 启动 ****************************/
static void Start(TIM_TASK_SOFTTIM *pstTimVal)
{ReadTimBase(pstTimVal);pstTimVal->ulTmr = pstTimVal->ulSoftTim;pstTimVal->uchStop = 0;
}/******************** 暂停 ****************************/
static void Stop(TIM_TASK_SOFTTIM *pstTimVal)
{pstTimVal->uchStop = 1;
}/******************** 超时判断 ***************************/
static INT8S Timout(TIM_TASK_SOFTTIM *pstTimVal)
{if(pstTimVal->uchStop == 1)return 0;ReadTimBase(pstTimVal);INT32U diff = pstTimVal->ulSoftTim - pstTimVal->ulTmr;if (diff >= pstTimVal->ulInterval) {pstTimVal->ulTmr = pstTimVal->ulSoftTim;return 1;}return 0;
}

TimTask.h

#ifndef __TIMTASK_H
#define __TIMTASK_H
#include "Pub_typedef.h"
#include "stdlib.h"
#include "tim.h"#define TASK_GET_TIM_BASE	    (htim4.Instance->CNT)
//定时器时钟除预分频值为定时器频率,周期为100us。软定时为毫秒级
//		TIM:		        主频     预分频
#define TASK_Tim_ratio		    (( 72000000 / 7200 ) / 1000 )typedef struct
{INT16U uinTimerRec;INT32U ulSoftTim;INT32U ulInterval;INT32U 	ulTmr;INT8U	uchStop;INT8U	uchReadState;
}TIM_TASK_SOFTTIM;typedef struct t_TIM_TASK
{INT32U id;TIM_TASK_SOFTTIM stTimer;void (*pTaskFun)(void);INT32U Last_ID;             // 头节点使用,用于记录最大idstruct t_TIM_TASK * next;
}TIM_TASK;void  TimTask_Init(TIM_TASK *pstTT);
INT8U TimTaskFindID(const TIM_TASK *pTask, INT32U ID);
INT8U TimTaskStartID(TIM_TASK *pTask, INT32U id);
INT8U TimTaskStopID(TIM_TASK *pTask, INT32U id);
INT32U TimTaskAddTask(TIM_TASK *pTask, INT32U ulTime, void (*pfun)(void));
INT8U TimTaskDelTask(TIM_TASK *pTask, INT32U id);
void  TimTaskHandle(TIM_TASK *pTask);static void ReadTimBase(TIM_TASK_SOFTTIM *pstTimVal);
static void STimInit(TIM_TASK_SOFTTIM *pstSoftTimClass);
static void set(TIM_TASK_SOFTTIM *pstTimVal,INT32U ulTime);
static void Start(TIM_TASK_SOFTTIM *pstTimVal);
static void Stop(TIM_TASK_SOFTTIM *pstTimVal);
static INT8S Timout(TIM_TASK_SOFTTIM *pstTimVal);#endif

相关文章:

  • 数据结构-堆
  • AWS之数据分析类产品
  • Mac 3大好用的复制粘贴管理工具对比
  • Android RxJava框架分析:它的执行流程是如何的?它的线程是如何切换的?如何自定义RxJava操作符?
  • 第十七节:图像梯度与边缘检测-Sobel 算子
  • Uskin阵列式三轴力触觉传感器:驱动机器人智能的触觉数据专家
  • 深入理解 Java 代理模式:从基础到实战​
  • MiM: Mask in Mask Self-SupervisedPre-Training for 3D Medical Image Analysis
  • Docker宿主机IP获取
  • 智慧工会服务平台建设方案Word(23页)
  • 机器学习-无量纲化与特征降维(一)
  • 爬虫学习————开始
  • AI服务器通常会运用在哪些场景当中?
  • vue dev-tools插件
  • 电动汽车充换电设施可调能力聚合评估与预测 - 使用说明文档
  • 亚马逊跨境新蓝海:解码爱尔兰电商市场的凯尔特密码
  • HDLC(High-Level Data Link Control,高级数据链路控制协议)
  • uniapp-商城-47-后台 分类数据的生成(通过数据)
  • uniapp 不同路由之间的区别
  • 高频数据结构面试题总结
  • 第四轮伊美核谈判将于11日在阿曼举行
  • 图忆|红场阅兵:俄罗斯30年来的卫国战争胜利日阅兵式
  • “爱鸟周”为何不能像FI和花展那样“市区联动”
  • 人民日报评“组团退演出服”:市场经济诚信原则需全社会维护
  • 85后清华博士黄佐财任湖北咸宁市咸安区委副书记、代区长
  • A股低开高走全线上涨:军工股再度领涨,两市成交12934亿元