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

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

简介

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;  
    }
}


相关文章
|
自然语言处理 JavaScript 前端开发
Duktape:一个新的小巧的超精简可嵌入式JavaScript引擎
Duktape是一个可嵌入的Javascript引擎,主要关注便携性和精简及紧凑性。 Duktape很容易集成到C/C++项目: 添加duktape.c和duktape.h到您的build中,并使用Duktape API从C代码中,调用ECMAScript代码的功能,反之亦然。
1086 0
|
2天前
|
开发工具 C语言 git
【嵌入式开源库】MultiButton的使用,简单易用的事件驱动型按键驱动模块
【嵌入式开源库】MultiButton的使用,简单易用的事件驱动型按键驱动模块
|
2天前
|
Linux 编译器 程序员
嵌入式中编写可移植 C/C++ 程序的要点方法
嵌入式中编写可移植 C/C++ 程序的要点方法
28 0
|
2天前
嵌入式中利用软件实现定时器的两种方法分析
嵌入式中利用软件实现定时器的两种方法分析
42 0
|
6月前
|
传感器 数据采集 物联网
Golang硬件控制:将软件力量扩展到物理世界
Golang硬件控制:将软件力量扩展到物理世界
|
6月前
|
Web App开发 存储 Linux
Linux C/C++开发(后端/音视频/游戏/嵌入式/高性能网络/存储/基础架构/安全)(下)
Linux C/C++开发(后端/音视频/游戏/嵌入式/高性能网络/存储/基础架构/安全)
|
6月前
|
存储 Linux 调度
Linux C/C++开发(后端/音视频/游戏/嵌入式/高性能网络/存储/基础架构/安全)(上)
Linux C/C++开发(后端/音视频/游戏/嵌入式/高性能网络/存储/基础架构/安全)
|
6月前
|
设计模式 算法 开发者
嵌入式框架设计中的四种常用模式
嵌入式框架设计中的四种常用模式
|
8月前
|
JSON 算法 物联网
第1期 | MultiButton,一个小巧简单易用的事件驱动型按键驱动模块
第1期 | MultiButton,一个小巧简单易用的事件驱动型按键驱动模块
161 0
|
11月前
|
缓存 API C语言