简介
MultiTimer 是一个软件定时器扩展模块,可无限扩展你所需的定时器任务,取代传统的标志位判断方式, 更优雅更便捷地管理程序的时间触发时序,MultiTimer 的作者和MultiButton 的作者都是0x1abin。
本章使用环境:
正点原子stm32F4探索者
代码工程使用正点原子HAL库 实验8 定时器中断实验
下载
配有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; } }