【嵌入式开源库】MultiTimer 的使用,一款可无限扩展的软件定时器

2023-11-15

简介

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

本章使用环境:

正点原子stm32F4探索者
代码工程使用正点原子HAL库 实验8 定时器中断实验

下载

GIthub地址:https://github.com/0x1abin/MultiTimer

在这里插入图片描述

配有git环境可以使用以下命令进行下载

git clone https://github.com/0x1abin/MultiTimer.git

使用介绍

配置系统时间基准接口,安装定时器驱动;

uint64_t PlatformTicksGetFunc(void)
{
    /* Platform implementation */
}

MultiTimerInstall(PlatformTicksGetFunc);

实例化一个定时器对象;

MultiTimer timer1;

设置定时时间,超时回调处理函数, 用户上下指针,启动定时器;

int MultiTimerStart(&timer1, uint64_t timing, MultiTimerCallback_t callback, void* userData);

在主循环调用定时器后台处理函数

int main(int argc, char *argv[])
{
    ...
    while (1) {
        ...
        MultiTimerYield();
    }
}

1.定时器的时钟频率直接影响定时器的精确度,尽可能采用1ms/5ms/10ms这几个精度较高的tick;

2.定时器的回调函数内不应执行耗时操作,否则可能因占用过长的时间,导致其他定时器无法正常超时;

3.由于定时器的回调函数是在 MultiTimerYield 内执行的,需要注意栈空间的使用不能过大,否则可能会导致栈溢出。

工程移植

我们将下载好的MultiTimer源码中的multi_timer.c和multi_timer.h文件拷贝到stm32工程目录下的handware中的timer文件夹下
在这里插入图片描述
然后我们keil打开工程将multitimer添加到工程中来,然后编译发现有一个报错
在这里插入图片描述

…\HARDWARE\TIMER\MultiTimer.c(21): error: #268: declaration may not appear after executable statement in block

”错误:#268:声明可能不会出现在可执行语句块后“ 即变量应在主函数开头声明,不能出现在可执行语句后面。

解决办法就是打开C99 mode支持就可以了,在C89标准中是不支持变量随处定义的,再次编译成功
在这里插入图片描述

代码分析

还是先找到链表的结构体

struct MultiTimerHandle {
    MultiTimer* next; // 指向下一个节点
    uint64_t deadline;	// 定时器超时时间
    MultiTimerCallback_t callback;	// 超时回调函数
    void* userData;	// 节点数据,可以作为name使用
};

然后我们看需要轮询的函数

int MultiTimerYield(void)
{
    MultiTimer* entry = timerList;
    for (; entry; entry = entry->next) {
        /* Sorted list, just process with the front part. */
        if (platformTicksFunction() < entry->deadline) {
            return (int)(entry->deadline - platformTicksFunction());
        }
        /* remove expired timer from list */
        timerList = entry->next;

        /* call callback */
        if (entry->callback) {
            entry->callback(entry, entry->userData);
        }
    }
    return 0;
}

platformTicksFunction通过该函数和超时时间做比较,如果该函数返回的时间小于设定的时间直接返回,时钟这个函数需要我们自己编写,如果是使用hal库的朋友可以直接使用HAL_GetTick这个函数来产生1ms的定时计数,当然我们也可以使用定时器的方法来自己写一个计数函数,然后通过MultiTimerInstall(PlatformTicksGetFunc);注册到MultiTimer中的指针函数。

int MultiTimerInstall(PlatformTicksFunction_t ticksFunc)
{
    platformTicksFunction = ticksFunc;
    return 0;
}

在这里插入图片描述
然后这里就会出现一个问题,这个计数的变量这样一直加肯定会出现溢出问题,如果是uint32_t 的最大数也就是232次方,当加到这个数的时候就会将该变量置为0,我们可以通过将该变量改成uint8_t来测试,到达28次方时就会重新计数,然后multitimer的超时设计是调用一次entry->deadline就会刷新该超时数值的值,加入我们的计时变量溢出了,那超时时间还会停留在我们计时变量达不到的数值,然后整个系统就会瘫痪了,效果如下;
在这里插入图片描述
如果我们是做项目的话这样肯定是不行的,所以我们需要简单修改一下(HAL_GetTick也不行,该计数的变量类型也是uint32_t),首先我们需要记录以下我们的超时时间,重新给结构体添加一个timing变量,然后再轮询的这个函数中我们添加一句溢出时重新给deadline赋值的函数就可以解决该问题了;

struct MultiTimerHandle {
    MultiTimer* next;
    uint64_t deadline;
    uint16_t timing;
    MultiTimerCallback_t callback;
    void* userData;
};

int MultiTimerYield(void)
{
    MultiTimer* entry = timerList;
    
	if(platformTicksFunction() == 0) 
    {
        for (; entry; entry = entry->next)
        {
        	// 遍历全部扩展的定时器清零
            entry->deadline = 0 + entry->timing;
        }
    }

    for (; entry; entry = entry->next) {
        /* Sorted list, just process with the front part. */
        if (platformTicksFunction() < entry->deadline) {
            return (int)(entry->deadline - platformTicksFunction());
        }
        /* remove expired timer from list */
        timerList = entry->next;
        /* call callback */
        if (entry->callback) {
            entry->callback(entry, entry->userData);
        }
    }
    return 0;
}

该库代码量并不多,这里还有两个接口时start和stop也就是链表的插入和删除;

int MultiTimerStart(MultiTimer* timer, uint64_t timing, MultiTimerCallback_t callback, void* userData); // 第一个参数也就是我们的定时器,第二个参数为我们的超时时间,第三个参数为超时回调函数,第四个参数时超时后需要传入的参数

int MultiTimerStop(MultiTimer* timer);

核心代码

main.c

#include "timer.h"
#include "multitimer.h"

************************************************/

MultiTimer timer1;
MultiTimer timer2;

void timer1_callback(MultiTimer* timer, void* userData)
{
    printf("timer1 timeout!\r\n");
    LED0 = !LED0;
    MultiTimerStart(&timer1, 100, timer1_callback, NULL);
}

void timer2_callback(MultiTimer* timer, void* userData)
{
    printf("timer2 timeout!\r\n");
    LED1 = !LED1;
    MultiTimerStart(&timer2, 50, timer2_callback, NULL);
}

int main(void)
{
    HAL_Init();                   	//初始化HAL库    
    Stm32_Clock_Init(336,8,2,7);  	//设置时钟,168Mhz
	delay_init(168);               	//初始化延时函数
	uart_init(115200);             	//初始化USART
	LED_Init();						//初始化LED	
    TIM3_Init(10-1,8400-1);       //定时器3初始化,定时器时钟为84M,分频系数为8400-1,
                                    //所以定时器3的频率为84M/8400=10K,自动重装载为5000-1,那么定时器周期就是500ms
    
    MultiTimerInstall(PlatformTicksGetFunc);
    MultiTimerStart(&timer1, 100, timer1_callback, NULL);
    MultiTimerStart(&timer2, 50, timer2_callback, NULL);
    
    while(1)
    {
//        printf("PlatformTicksGetFunc:%d\r\n",PlatformTicksGetFunc());
        MultiTimerYield();
    }
}

timer.c中添加修改

static uint64_t time3_tick = 0;

uint64_t PlatformTicksGetFunc(void)
{
  // HAL_GetTick();
  return time3_tick;
}

//回调函数,定时器中断服务函数调用
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
    if(htim==(&TIM3_Handler))
    {
//        LED1=!LED1;        //LED1反转
          time3_tick++;
    }
}

multitimer.c修改

int MultiTimerStart(MultiTimer* timer, uint64_t timing, MultiTimerCallback_t callback, void* userData)
{
    if (!timer || !callback ) {
        return -1;
    }
    MultiTimer** nextTimer = &timerList;
    /* Remove the existing target timer. */
    for (; *nextTimer; nextTimer = &(*nextTimer)->next) {
        if (timer == *nextTimer) {
            *nextTimer = timer->next; /* remove from list */
            break;
        }
    }

    /* Init timer. */
    timer->deadline = platformTicksFunction() + timing;
    timer->callback = callback;
    timer->userData = userData;
    /* 新添加的记录超时时间 */
    timer->timing = timing;

    /* Insert timer. */
    for (nextTimer = &timerList;; nextTimer = &(*nextTimer)->next) {
        if (!*nextTimer) {
            timer->next = NULL;
            *nextTimer = timer;
            break;
        }
        if (timer->deadline < (*nextTimer)->deadline) {
            timer->next = *nextTimer;
            *nextTimer = timer;
            break;
        }
    }
    return 0;
}

int MultiTimerYield(void)
{
    MultiTimer* entry = timerList;
    
    if(platformTicksFunction() == 0) 
    {
        for (; entry; entry = entry->next)
        {
            entry->deadline = 0 + entry->timing;
        }
    }
    
    for (; entry; entry = entry->next) {
        /* Sorted list, just process with the front part. */
        if (platformTicksFunction() < entry->deadline) {
            return (int)(entry->deadline - platformTicksFunction());
        }
        /* remove expired timer from list */
        timerList = entry->next;
        /* call callback */
        if (entry->callback) {
            entry->callback(entry, entry->userData);
        }
    }
    return 0;
}

multitimer.h修改

/*
 * Copyright (c) 2021 0x1abin
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to
 * deal in the Software without restriction, including without limitation the
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
 * sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
 * IN THE SOFTWARE.
 */

#ifndef _MULTI_TIMER_H_
#define _MULTI_TIMER_H_
#include <stdint.h>
#ifdef __cplusplus  
extern "C" {  
#endif

typedef uint64_t (*PlatformTicksFunction_t)(void);
typedef struct MultiTimerHandle MultiTimer;
typedef void (*MultiTimerCallback_t)(MultiTimer* timer, void* userData);

#define MAXTIME 250 // 这里做测试节省时间使用的是250,实际尽可能的设置为计数变量能接受的最大值
struct MultiTimerHandle {
    MultiTimer* next;
    uint64_t deadline;
    uint64_t timing;
    MultiTimerCallback_t callback;
    void* userData; 
};

/**
 * @brief Platform ticks function.
 * 
 * @param ticksFunc ticks function.
 * @return int 0 on success, -1 on error.
 */
int MultiTimerInstall(PlatformTicksFunction_t ticksFunc);

/**
 * @brief Start the timer work, add the handle into work list.
 * 
 * @param timer target handle strcut.
 * @param timing Set the start time.
 * @param callback deadline callback.
 * @param userData user data.
 * @return int 0: success, -1: fail.
 */
int MultiTimerStart(MultiTimer* timer, uint64_t timing, MultiTimerCallback_t callback, void* userData);

/**
 * @brief Stop the timer work, remove the handle off work list.
 * 
 * @param timer target handle strcut.
 * @return int 0: success, -1: fail.
 */
int MultiTimerStop(MultiTimer* timer);

/**
 * @brief Check the timer expried and call callback.
 * 
 * @return int The next timer expires.
 */
int MultiTimerYield(void);

#ifdef __cplusplus
} 
#endif
#endif

实验效果

在这里插入图片描述

总结

考虑到环境问题溢出后变量的值并不一定是等于0的所以所添加的代码不一定生效,如果时钟源是可靠的情况下我们可以规定时钟计数的最大值,当时钟计数值到某个大小就重新开始计数,例如

static uint64_t time3_tick = 0;

uint64_t PlatformTicksGetFunc(void)
{
  return time3_tick;
}

//回调函数,定时器中断服务函数调用
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
    if(htim==(&TIM3_Handler))
    {
    if( time3_tick++>= MAX_TIME)
    		time3_tick = 0;  
    }
}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

【嵌入式开源库】MultiTimer 的使用,一款可无限扩展的软件定时器 的相关文章

随机推荐

  • 一起写一个 Web 服务器

    http my oschina net leejun2005 blog 486771 一起写一个 Web 服务器 2 2015 06 06 实践项目 9 评论 Web服务器 分享到 8 本文由 伯乐在线 高世界 翻译 艾凌风 校稿 未经许可
  • java实现评论功能_Java实现评论回复功能的完整步骤

    前言 使用递归循环开发评论回复功能 适用于大部分的简单单体应用 评论功能或许是大多数的单体应用之中会用到的功能 我们会在自己所开发的项目之中进行集成该功能 大多数时候我们会将评论功能划分成以下几种 单一型 嵌套型 两层型 一 分类方式 1
  • SAP B/P 初步研究(二)

    从开发人员角度来看 B P客户创建可以试用两种方法 第一种是使用BAPI FUNCTION 第二种是使用BAPI CALL METHOD 个人更倾向于使用METHOD 因为METHOD方法只需要填充一个嵌套结构就可以实现B P所有业务视图的
  • 【STM32】制作一个bootloader

    工作环境 STM32CubeMX Keil 相关环境准备这里就不介绍了 bootloader是什么 bootloader就是单片机启动时候运行的一段小程序 这段程序负责单片机固件的更新 也就是单片机选择性的自己给自己下载程序 可以更新 可以
  • Linux C 系统编程 2-1 进程管理 进程环境

    该系列文章总纲链接 专题分纲目录 LinuxC 系统编程 本章节思维导图如下所示 思维导图会持续迭代 第一层 第二层 1 进程的启动和退出 1 1 流程 程序启动 gt 程序加载 地址分配 gt 程序退出 1 程序启动 对于二进制文件 如果
  • 浅谈State状态模式

    一 前言 状态模式在某些场合中使用是非常方便的 什么叫做状态 如果大家学过 编译原理 就会明白DFA M和NFA M 在确定有限状态机和非确定有限状态机中 状态就是最小的单元 当满足某种条件的时候 状态就会发生改变 我们可以把时间中的一个时
  • OSPF的路由器角色

    IR internal router 区域内路由器 普通区域 BR backbone router 骨干区域路由器 位于骨干区域 至少一个接口在骨干区域 ABR area border rouder 区域边界路由器 作用是连接骨干区域和普通
  • Aixcode代码自动补全插件的安装和使用

    最近在技术公众号上看到大佬们说到一款代码自动补全的智能插件aixcode 官方是这样宣传的 智能代码提示 她用强大的深度学习引擎 能给出更加精确的代码提示 代码风格检查 她有代码风格智能检查能力 帮助开发者改善代码质量 编程模式学习 她能自
  • Verilog数据类型

    作者 anekin 原作网址 http blog sina com cn s blog 615047920100ih0k html Verilog HDL有下列四种基本的值 1 0 逻辑0或 假 状态 2 1 逻辑1或 真 状态 3 x X
  • 因果推断 - 反事实

    目录 基础知识 案例实战 版权 转载前请联系作者获得授权 声明 部分内容出自因果关系之梯 已获得原作者授权 参考书籍 The Book of Why Judea Pearl 基础知识 定义 对于包含外生变量 U U U和内生变量 X X
  • mysql 显示用户_在Mysql中如何显示所有用户?

    这是一个mysql初学者经常问到的一个问题 今天我们就带大家看看是如何在Mysql中显示所有用户的 通常我们在mysql中使用SHOW DATABASES可以显示所有的数据库 SHOW TABLES将会显示所有的数据表 那么你是不是会猜测显
  • 软件版本号讲解:什么是Alpha, Beta, RC,Release

    1 软件版本阶段说明 Alpha版 此版本表示该软件在此阶段主要是以实现软件功能为主 通常只在软件开发者内部交流 一般而言 该版本软件的Bug较多 需要继续修改 Beta版 该版本相对于 版已有了很大的改进 消除了严重的错误 但还是存在着一
  • LayUI图片上传接口

    前端样式 div class layui upload drag i class layui icon xe67c i p 点击上传 或将文件拖拽到此处 p div js var uploadInst upload render elem
  • 10种React组件之间通信的方法

    组件间通信方式总结 父组件 gt 子组件 1 Props 2 Instance Methods refs 子组件 gt 父组件 3 Callback Functions 回调函数 4 Event Bubbling 事件冒泡机制 兄弟组件之间
  • 成都瀚网科技:抖店怎么上精选联盟?

    在抖音电商平台上 选定的联盟是一个非常重要的入口 对于商家来说 能够进入选定的联盟意味着更多的曝光度和流量 从而获得更好的销售机会 那么 抖店是如何进入精选联盟的呢 1 抖店如何加入特色联盟 提供优质的商品和服务 首先 抖店想要入驻所选联盟
  • 如何判断两个IP地址是否在同一个网段?什么是子网掩码?

    子网的概念挺复杂的 日常只要知道 网络传输时 只要对方IP与自己不在同一个子网 数据就会转交给网关处理 也就是路由器转发的意思 一 什么是子网掩码 在了解ip地址的网段之前 我们先来了解子网掩码 很多对网络了解不深的朋友都对子网掩码有些迷惑
  • JS的动画

    d1 hide 隐藏 d1 toggle 隐藏的显示 显示的隐藏 d1 fadeToggle 淡入淡出 d1 fadeTo fast 0 4 透明化 使用后 淡入淡出都无效包括再次透明化 d1 fadeOut fast 淡出 逐渐消失 d1
  • 都说C++难,那是没有学习数据结构【单链表】

    单链表 可有可无的目录 前言 一 链表是什么 链表的分类 二 链表的实现 总结 前言 上篇顺序表结尾了解了顺序表的诸多缺点 链表的特性很好的解决了这些问题 本期我们来认识单链表 一 链表是什么 链表是一种物理存储结构上非连续 非顺序的存储结
  • Latex 表格内文字过长自动换行

    法一 plain view plain copy begin tabular m 5cm 法二 plain view plain copy begin tabular p 0 9 columnwidth 法三 multirow 宏包 pla
  • 【嵌入式开源库】MultiTimer 的使用,一款可无限扩展的软件定时器

    MultiTimer 简介 下载 使用介绍 工程移植 代码分析 核心代码 实验效果 总结 简介 MultiTimer 是一个软件定时器扩展模块 可无限扩展你所需的定时器任务 取代传统的标志位判断方式 更优雅更便捷地管理程序的时间触发时序 M