0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

一款可无限扩展的软件定时器MultiTimer

strongerHuang 来源:Mculover666 作者:Mculover666 2021-11-16 09:23 次阅读
加入交流群
微信小助手二维码

扫码添加小助手

加入工程师交流群

1. MultiTimer

今天给大家带来的开源项目是 MultiTimer,一款可无限扩展的软件定时器,作者0x1abin,目前收获 95 个 star,遵循 MIT 开源许可协议。

MultiTimer 是一个软件定时器扩展模块,可无限扩展你所需的定时器任务,取代传统的标志位判断方式, 更优雅更便捷地管理程序的时间触发时序。

项目地址:https://github.com/0x1abin/MultiTimer

2. 移植MultiTimer

2.1. 移植思路

开源项目在移植过程中主要参考项目的readme文档,一般只需两步:

  • ① 添加源码到裸机工程中;
  • ② 实现需要的接口

2.2. 准备裸机工程

本文中我使用的是小熊派IoT开发套件,主控芯片为STM32L431RCT6:

移植之前需要准备一份裸机工程,我使用STM32CubeMX生成,需要初始化以下配置:

  • 配置一个串口用于打印信息
  • printf重定向

2.3. 添加MultiTimer到工程中

① 复制MultiTimer源码到工程中

② 在keil中添加 MultiTimer的源码文件

③ 将MultiTimer头文件路径添加到keil中

3. 使用MultiTimer

使用时包含头文件:

#include "multi_timer.h"

如果遇到multi_timer.c文件中NULL宏定义报错,则在multi_timer.h中添加头文件即可。

3.1. 创建Timer对象

/* USER CODE BEGIN PV */struct Timer timer1;struct Timer timer2;
/* USER CODE END PV */

3.2. Timer回调函数

/* Private user code ---------------------------------------------------------*//* USER CODE BEGIN 0 */void timer1_callback(){    printf("timer1 timeout!
");}
void timer2_callback(){    printf("timer2 timeout!
");}/* USER CODE END 0 */

3.3. 初始化并启动Timer

始化定时器对象,注册定时器回调处理函数,设置定时时间(ms),循环定时触发时间:

/* USER CODE BEGIN 2 */printf("multi timer test...
");
//重复计时,周期为1000次,即1000ms=1stimer_init(&timer1, timer1_callback, 1000, 1000);timer_start(&timer1);
//单次计时,周期为50次,即50mstimer_init(&timer2, timer2_callback, 50, 0);timer_start(&timer2);
/* USER CODE END 2 */

3.4. Timer对象处理

在循环中调用Timer对象处理函数,处理函数会判断链表上的每个定时器是否超时,如果超过,则拉起注册的回调函数:

/* Infinite loop *//* USER CODE BEGIN WHILE */while (1){  /* USER CODE END WHILE */
  /* USER CODE BEGIN 3 */  timer_loop();} /* USER CODE END 3 */

3.5. 提供Timer时基信号

MultiTimer中所有的定时器都是通过一个32位的计数值_timer_ticks来判断的,所以需要一个硬件定时器提供时基信号,递增该值。

本文中使用的是STM32HAL库,所以通过Systick来提供,无需设置额外的定时器。

main.c文件的最后编写Systick回调函数:

/* USER CODE BEGIN 4 */void HAL_SYSTICK_Callback(void){    //给multitimer提供时基信号    timer_ticks(); //1ms ticks}
/* USER CODE END 4 */

然后在stm32l4xx_it.c中调用该回调函数:

/**  * @brief This function handles System tick timer.  */void SysTick_Handler(void){  /* USER CODE BEGIN SysTick_IRQn 0 */  HAL_SYSTICK_IRQHandler();
  /* USER CODE END SysTick_IRQn 0 */  HAL_IncTick();  /* USER CODE BEGIN SysTick_IRQn 1 */
  /* USER CODE END SysTick_IRQn 1 */}

接下来编译下载,看在串口助手中看到打印的日志:b416dbb8-4441-11ec-b939-dac502259ad0.png

4. MultiTimer设计思想解读

4.1. 软件定时器设计思想

MultiTimer的设计比较简洁。

设置一个计数值_timer_ticks不断递增,由定时器提供的中断驱动,只计次数,不计时间,有了很大的自由度,一般时基信号设置为1ms一次:

/**  * @brief  background ticks, timer repeat invoking interval 1ms.  * @param  None.  * @retval None.  */void timer_ticks(){  _timer_ticks++;}

在程序运行时循环比较定时器设置的超时值是否大于当前_timer_ticks的计数值,如果是则再次判断是否重复计数值是否为0,是则停止定时器,完成单次计时效果,否则修改计数值,最后拉起注册到该定时器的回调函数执行:

/**  * @brief  main loop.  * @param  None.  * @retval None  */void timer_loop(){  struct Timer* target;  for(target=head_handle; target; target=target->next) {    if(_timer_ticks >= target->timeout) {      if(target->repeat == 0) {        timer_stop(target);      } else {        target->timeout = _timer_ticks + target->repeat;      }      target->timeout_cb();    }  }}

4.2. 单链表操作

MultiTimer的代码少,非常适合拿来学习单链表的操作,学习数据结构的过程是乏味的,不如直接来个实例看看是如何操作的。

① 链表的节点设计为一个软件定时器,所以理论上支持的定时器数量只受内存限制。

typedef struct Timer {    uint32_t timeout;    uint32_t repeat;    void (*timeout_cb)(void);    struct Timer* next;}Timer;

定时器初始化函数timer_init就是初始化一个链表节点:

void timer_init(struct Timer* handle, void(*timeout_cb)(), uint32_t timeout, uint32_t repeat){  // memset(handle, sizeof(struct Timer), 0);  handle->timeout_cb = timeout_cb;  handle->timeout = _timer_ticks + timeout;  handle->repeat = repeat;}

② 设置链表头指针,只需知道头指针就能完成对整个单链表的操作:

//timer handle list head.static struct Timer* head_handle = NULL;

③ 向单链表增加一个节点

向单链表增加一个节点有三种方式:

  • 在单链表尾部增加一个节点
  • 在单链表头部增加一个节点
  • 在单链表中间增加一个节点

MultiTimer中所有的结点都是定时器,每个定时器之间相互独立,不存在先后次序关系,所以无论加到中间,还是加到尾部,还是加到头部,最后的功能都是一样的,但是在插入算法上有优劣性能之分。

先来看看再单链表尾部增加一个节点的算法:b4a1d22c-4441-11ec-b939-dac502259ad0.gif( 我会动哦 )

int timer_start(struct Timer* handle){  /**    * 算法1 —— 向单链表尾部添加节点   * 时间复杂度O(n)   * Mculover666   */  struct Timer* target = head_handle;  if(head_handle == NULL)  {    /* 链表为空 */    head_handle = handle;    handle->next = NULL;  }  else  {    /* 链表中存在节点,遍历找最后一个节点 */    while(target->next != NULL)    {      if(target == handle)        return -1;      target = target->next;    }    target->next = handle;    handle->next = NULL;  }
  return 0;}

这种算法理解简单,实现简单,但是算法时间复杂度秒变为O(n),当n很大时,插入一个节点的时间就会非常久。

再来看看在链表头部插入一个新节点的情况:

(我会动哦)

int timer_start(struct Timer* handle){  /**    * 算法2 —— 向单链表头部添加节点   * 时间复杂度O(n),如果去掉判断重复,则时间复杂度O(1)   * 0x1abin   */   struct Timer *target = head_handle;
   //判断是否有重复的定时器   while(target)   {    if(target == handle)    {      return -1;    }    target = target->next;   }   handle->next = head_handle;   head_handle = handle;   return 0;}

这里第二种头部插入节点的算法时间复杂度依然是O(n),emmm?

其实,这里因为单链表节点是定时器,在插入的时候需要对整个链表进行判断,避免重复添加同样的定时器节点,所以无论任何一种算法,都需要对单链表进行遍历。

如果在不需要判断重复的情况下,尾部插入算法仍然需要遍历,但是头部插入算法只需要插入就可以,时间复杂度为O(1),算法更优

④ 单链表删除其中一个节点

删除单链表的节点时,因为节点自身只保存有下一个节点的指针,并没有指向上一个节点的指针,所以不能直接入手删除节点,那么如何删除单链表的节点呢?

方法是:设置二级指针(指向Timer类型指针的指针),通过遍历链表的方式来寻找节点中next指针指向删除节点的那个节点,代码如下。

void timer_stop(struct Timer* handle){  struct Timer** curr;  for(curr = &head_handle; *curr; ) {    struct Timer* entry = *curr;    if (entry == handle) {      *curr = entry->next;//      free(entry);    } else      curr = &entry->next;  }}

责任编辑:haq
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 软件
    +关注

    关注

    69

    文章

    5297

    浏览量

    90874
  • 定时器
    +关注

    关注

    23

    文章

    3360

    浏览量

    121740

原文标题:MultiTimer,一款可无限扩展的软件定时器

文章出处:【微信号:strongerHuang,微信公众号:strongerHuang】欢迎添加关注!文章转载请注明出处。

收藏 人收藏
加入交流群
微信小助手二维码

扫码添加小助手

加入工程师交流群

    评论

    相关推荐
    热点推荐

    PWM、定时器、SysTick 区别及应用场景

    在单片机和嵌入式开发中,经常会遇到PWM(脉宽调制)、定时器、SysTick(系统滴答定时器)这几个词。很多初学者容易把它们混为谈,以为都是“定时相关的东西”。其实,它们在本质和应用
    的头像 发表于 11-17 10:53 219次阅读
    PWM、<b class='flag-5'>定时器</b>、SysTick 区别及应用场景

    看门狗定时器、复位源、异常处理机制科普

    的作用、原理与应用。、看门狗定时器(WatchdogTimer)1.原理看门狗定时器本质上是个独立定时器,持续倒计时:
    的头像 发表于 11-17 10:53 704次阅读
    看门狗<b class='flag-5'>定时器</b>、复位源、异常处理机制科普

    SysTick系统滴答定时器简介

    SysTick—系统定时器是属于CM33内核中的个外设,内嵌在NVIC中。系统定时器个24bit的向下递减的计数,计数
    的头像 发表于 09-23 09:50 1328次阅读
    SysTick系统滴答<b class='flag-5'>定时器</b>简介

    TPL5100 ACTIVE 具有电源门控功能和 MOS 驱动的 Nano 供电可编程定时器

    TPL5100 是一款针对低功耗应用优化的长期定时器 IC。该TPL5100可以替换微控制的内部定时器,使微控制保持完全关闭而不是运行
    的头像 发表于 09-15 09:45 474次阅读
    TPL5100 ACTIVE 具有电源门控功能和 MOS 驱动<b class='flag-5'>器</b>的 Nano 供电可编程<b class='flag-5'>定时器</b>

    ‌TPL5110 低功耗定时器技术文档总结

    TPL5110 Nano 定时器一款低功耗定时器,集成了 MOSFET 驱动,设计用于占空比或电池供电应用中的功率门控。该TPL5110仅消耗 35 nA,可以使能电源线并大幅降低
    的头像 发表于 09-14 10:50 986次阅读
    ‌TPL5110 低功耗<b class='flag-5'>定时器</b>技术文档总结

    TPL5111 超低功耗系统定时器技术手册

    TPL5111 Nano 定时器一款低功耗系统定时器,设计用于占空比或电池供电应用中的功率门控。该TPL5111仅消耗 35 nA,可用于使能和禁用微控制或其他系统设备的电源,从而
    的头像 发表于 09-14 10:07 972次阅读
    TPL5111 超低功耗系统<b class='flag-5'>定时器</b>技术手册

    ‌TPL5010-Q1 纳米功耗系统定时器(带看门狗功能)技术文档摘要

    TPL5010-Q1 Nano 定时器一款低功耗、符合 AEC-Q100 标准的定时器,带有看门狗 该功能非常适合占空比或电池供电应用中的系统唤醒。在这样的系统中 微控制
    的头像 发表于 09-13 10:01 1256次阅读
    ‌TPL5010-Q1 纳米功耗系统<b class='flag-5'>定时器</b>(带看门狗功能)技术文档摘要

    大彩讲堂:VisualHMI-LUA教程-定时器的使用指南

    定时器的使用
    的头像 发表于 08-31 16:59 848次阅读
    大彩讲堂:VisualHMI-LUA教程-<b class='flag-5'>定时器</b>的使用指南

    TPS3435 Nano IQ精密超时看门狗定时器技术解析与应用指南

    Texas Instruments TPS3435/TPS3435-Q1 Nano I~Q~ 精确超时看门狗定时器一款250nA(典型值)超低功耗器件,具有可编程超时看门狗定时器。该看门狗
    的头像 发表于 08-26 16:20 713次阅读
    TPS3435 Nano IQ精密超时看门狗<b class='flag-5'>定时器</b>技术解析与应用指南

    德州仪器TPS3436-Q1汽车级窗口看门狗定时器技术解析

    Texas Instruments TPS3436-Q1精密窗口看门狗定时器一款超低功耗(250nA典型值)器件,提供可编程窗口看门狗定时器。TPS3436-Q1可提供具有多种功能的高精度超时
    的头像 发表于 08-18 14:54 673次阅读
    德州仪器TPS3436-Q1汽车级窗口看门狗<b class='flag-5'>定时器</b>技术解析

    Texas Instruments DS160PT801X16EVM重定时器评估模块数据手册

    Texas Instruments DS160PT801X16EVM重定时器评估模块(EVM)是一款16通道PCIe转接卡板,用于评估DS160PT801 PCIe Gen4重定时器。该评估模块采用
    的头像 发表于 07-29 15:25 525次阅读
    Texas Instruments DS160PT801X16EVM重<b class='flag-5'>定时器</b>评估模块数据手册

    MCU定时器/计数

    RISC-V核低功耗MCU通过灵活的定时器架构、低功耗模式适配及硬件级中断优化,在工业控制、智能家居等场景中实现高精度计时与能耗控制的协同设计,满足复杂任务调度与实时响应的双重需求‌。 、‌硬件
    的头像 发表于 04-27 13:54 592次阅读

    TPS3435 纳米静态电流精密超时看门狗定时器数据手册

    TPS3435 是一款超低功耗 (典型值为 250nA) 器件,提供可编程超时看门狗定时器。 TPS3435 提供高精度超时看门狗定时器,具有适用于各种应用的系列功能。超时看门
    的头像 发表于 04-09 15:49 716次阅读
    TPS3435 纳米静态电流精密超时看门狗<b class='flag-5'>定时器</b>数据手册

    圣邦微电子SGM819SxQ车规级看门狗定时器电路特性与数据手册分享

    SGM819SxQ 是一款独立的看门狗定时器电路,它可以帮助防止因硬件故障(例如外围设备错误、总线占用)或软件故障(例如循环中无限执行的代码)导致的系统故障。 该器件配备了 WDI 输
    的头像 发表于 02-26 17:34 2147次阅读
    圣邦微电子SGM819SxQ车规级看门狗<b class='flag-5'>定时器</b>电路特性与数据手册分享

    圣邦微电子车规级看门狗定时器电路SGM819SxQ特性与典型应用电路

    圣邦微电子推出 SGM819SxQ,一款车规级看门狗定时器电路。 该器件适用于汽车应用、工业设备、电信、安全应用、网络、医疗设备和不间断电源系统。 SGM819SxQ 是一款独立的看门狗定时器
    的头像 发表于 02-26 09:13 1651次阅读
    圣邦微电子车规级看门狗<b class='flag-5'>定时器</b>电路SGM819SxQ特性与典型应用电路