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