搜索
bottom↓
回复: 81

【MultiTimer】简易小巧的软件定时器扩展模块

  [复制链接]

出0入0汤圆

发表于 2016-9-23 16:16:42 | 显示全部楼层 |阅读模式
本帖最后由 半导体 于 2016-9-23 22:44 编辑

简介:
        前几天在坛里分享的MultiButton按键模块看到大家对此感兴趣,这让我备受鼓舞!今天分享的是一个简易的软件定时器模块,跟MultiButton一样使用单向链表串连管理同样的套路。
        这种类似的软件定时器模块其实在很多SOC的SDK上都已经有自带的API接口了,但很多工程师在裸机上开发还是习惯使用判断定时变量计数或标志位的方式去传递执行一些定时任务,
        虽然这种方式简单高效,但随着工程增大会出现满天飞的定时全局变量和标志位,加大了各代码段之间的耦合程度,并且很不优雅。
        MultiTimer 是一个软件定时器扩展模块,可无限扩展你所需的定时器任务,取代传统的标志位判断方式, 更优雅更便捷地管理程序的时间触发时序。

使用方法:
        1.先申请一个定时器管理handle:
                struct Timer timer1;

        2.初始化定时器对象,注册定时器回调处理函数,设置定时时间(ms),循环定时触发时间:
                timer_init(struct Timer* handle, void(*timeout_cb)(), uint32_t timeout, uint32_t repeat);

        3.启动定时器
                timer_start(&timer1);

        4.设置1ms的硬件定时器循环调用 timer_ticks() 以提供时间基准
                void HAL_SYSTICK_Callback(void)
                {
                        timer_ticks();
                }

        5.在主循环调用定时器后台处理函数
                int main()
                {
                            while(1) {
                                ...
                                timer_loop();
                            }
                }

例程:
  1. #include "multi_timer.h"

  2. struct Timer timer1;
  3. struct Timer timer2;

  4. void timer1_callback()
  5. {
  6.     printf("timer1 timeout!\r\n");
  7. }

  8. void timer2_callback()
  9. {
  10.     printf("timer2 timeout!\r\n");
  11. }

  12. int main()
  13. {
  14.         timer_init(&timer1, timer1_callback, 1000, 0); //1s delay
  15.         timer_start(&timer1);
  16.        
  17.         timer_init(&timer2, timer2_callback, 50, 50); //50ms loop
  18.         timer_start(&timer2);
  19.        
  20.         while(1) {
  21.             
  22.             timer_loop();
  23.         }
  24. }

  25. void HAL_SYSTICK_Callback(void)
  26. {
  27.     timer_ticks(); //1ms ticks
  28. }
复制代码


模块下载地址:
https://github.com/0x1abin/MultiTimer
(注:使用github的原因是我后面的一些改动能够及时更新,附件没法及时更新)

出0入0汤圆

发表于 2016-9-23 16:44:46 | 显示全部楼层
刚才还是特斯拉,一下变成了秋田

出0入0汤圆

 楼主| 发表于 2016-9-23 16:46:18 | 显示全部楼层
3DA502 发表于 2016-9-23 16:44
刚才还是特斯拉,一下变成了秋田

哈哈,要低调点。

出0入17汤圆

发表于 2016-9-23 17:29:48 | 显示全部楼层
谢谢分享,楼主的头像好晃眼啊

出0入0汤圆

发表于 2016-9-23 18:39:45 | 显示全部楼层
很不错,之前坛里还有一个smarttimer也不错

出0入0汤圆

发表于 2016-9-23 20:12:46 | 显示全部楼层
可以用在低功耗应用吗

出0入0汤圆

发表于 2016-9-23 20:21:47 | 显示全部楼层
谢谢!还有没消息队列学习下?能LIFO,FIFO的。

出0入0汤圆

发表于 2016-9-23 21:27:28 来自手机 | 显示全部楼层
楼主这一系列写的很好!

出140入8汤圆

发表于 2016-9-24 06:08:51 | 显示全部楼层
楼主可以出一本书

出0入0汤圆

发表于 2016-9-24 07:55:45 来自手机 | 显示全部楼层
非常感谢 最近整在研究菜单按键和框架问题

出0入0汤圆

发表于 2016-9-24 08:14:26 | 显示全部楼层
链表,回调函数不能传参数吗

出0入0汤圆

发表于 2016-9-24 09:36:58 | 显示全部楼层
看着不错,改一改用到自己项目上正好。
多谢楼主分享。

出0入0汤圆

发表于 2016-9-24 10:25:08 | 显示全部楼层
谢谢分享,看着不错

出0入0汤圆

发表于 2016-9-24 11:54:43 | 显示全部楼层
帮顶,集思广益....

出20入0汤圆

发表于 2016-9-24 15:21:32 | 显示全部楼层
  高质量的分享,谢谢。

出0入0汤圆

发表于 2016-9-24 16:03:22 | 显示全部楼层
顶贴,受益匪浅

出0入0汤圆

发表于 2016-9-24 16:11:11 | 显示全部楼层
楼主timer_loop(); 函数里面做什么了呢

出0入0汤圆

 楼主| 发表于 2016-9-24 16:21:44 | 显示全部楼层
磊磊映画 发表于 2016-9-24 16:11
楼主timer_loop(); 函数里面做什么了呢

检测定时器超时和执行定时器回调啊

出0入0汤圆

 楼主| 发表于 2016-9-24 16:23:10 | 显示全部楼层
first_blood 发表于 2016-9-24 08:14
链表,回调函数不能传参数吗

觉得这个没必要回调没必要传参就没留了。

出0入0汤圆

 楼主| 发表于 2016-9-24 16:26:21 | 显示全部楼层
qwert1213131 发表于 2016-9-23 20:12
可以用在低功耗应用吗

可以啊,但各mcu的低功耗差异不同,这个根据你的需求确定吧

出0入0汤圆

 楼主| 发表于 2016-9-24 16:27:52 | 显示全部楼层
leiyitan 发表于 2016-9-24 06:08
楼主可以出一本书

我的水平还没到那么地步啊,大家一起学习进步吧

出0入0汤圆

发表于 2016-9-24 16:49:32 | 显示全部楼层
半导体 发表于 2016-9-24 16:21
检测定时器超时和执行定时器回调啊

去github看了下源代码
  1. /**
  2.   * @brief  main loop.
  3.   * @param  None.
  4.   * @retval None
  5.   */
  6. void timer_loop()
  7. {
  8.         struct Timer* target;
  9.         for(target=head_handle; target; target=target->next) {
  10.                 if(_timer_ticks >= target->timeout) {
  11.                         if(target->repeat == 0) {
  12.                                 timer_stop(target);
  13.                         } else {
  14.                                 target->timeout = _timer_ticks + target->repeat;
  15.                         }
  16.                         target->timeout_cb();
  17.                 }
  18.         }
  19. }
复制代码
就明白了
楼主的定时器模块很灵活
1.可以实现程序延时
2.又可以实现定时
3.用一个硬件systick,模拟出了多个软件定时器,很节约资源
缺点:就是定时有一定的误差,不过一般应用还是可以满足的。


出0入8汤圆

发表于 2016-9-24 16:54:00 | 显示全部楼层
半导体 发表于 2016-9-24 16:23
觉得这个没必要回调没必要传参就没留了。

回调函数一般至少都要有一个参数,或者是 parameter,或者是 context,这是工程的较好实践之一,
这也是用来减少全局变量传递的方法,
我的建议是:要想做通用的话,不管暂时有没有用,最好是遵循这一设计模式,加上这个。

出0入8汤圆

发表于 2016-9-24 17:12:39 | 显示全部楼层
单看了 @磊磊映画 在这边的贴出代码逻辑,
我简单说一下:
1、LZ 这个没有考虑 tick 环绕溢出的问题
2、每次都要遍历整个链表

你可以去看下开源项目的 timer 组件,或者论坛上搜一下,看下前辈们有没有给出类似的解决方案。

出0入0汤圆

发表于 2016-9-24 17:24:52 | 显示全部楼层
思想和 “基于事件触发的嵌入式系统”里面提到的差不多, 基本上定时器,合作式调度器都是这么干的

出0入0汤圆

 楼主| 发表于 2016-9-24 17:44:26 | 显示全部楼层
security 发表于 2016-9-24 17:12
单看了 @磊磊映画 在这边的贴出代码逻辑,
我简单说一下:
1、LZ 这个没有考虑 tick 环绕溢出的问题

1. 原本应该是64bit的tick,但考虑到占用内存和实际的需求默认选了32bit,32bit的tick可以计数49.7天,基本可以满足大部分产品的需求。
2. 我这个组件本来就在libuv库上的思路简化过来,主要是就让它更小巧简单,在一般项目中整个链表是很短的,遍历整个链表占用的开销很小。

出0入0汤圆

发表于 2016-9-24 18:01:42 | 显示全部楼层
好东西,鼓励一下,继续开源哈

出0入0汤圆

 楼主| 发表于 2016-9-26 09:36:20 | 显示全部楼层
lsx007 发表于 2016-9-24 17:24
思想和 “基于事件触发的嵌入式系统”里面提到的差不多, 基本上定时器,合作式调度器都是这么干的
...

是的,思路都是那样的。

出0入0汤圆

 楼主| 发表于 2016-9-26 09:36:36 | 显示全部楼层
bad_fpga 发表于 2016-9-24 18:01
好东西,鼓励一下,继续开源哈

哈哈,谢谢!

出20入0汤圆

发表于 2016-9-28 11:00:24 | 显示全部楼层
半导体 发表于 2016-9-24 16:27
我的水平还没到那么地步啊,大家一起学习进步吧

  楼主,loop和tick为什么不合并为tick,在tick中检查各个计数器的参数,这样结构好一点。

出20入0汤圆

发表于 2016-9-28 11:04:32 | 显示全部楼层

另外一个,楼主你这个软件定时器怎么实现反转一个io  n次的需求,不用额外变量。

出0入0汤圆

发表于 2016-9-28 11:05:57 | 显示全部楼层
收藏,学习

出0入0汤圆

 楼主| 发表于 2016-9-28 11:43:19 | 显示全部楼层
talkingbeast 发表于 2016-9-28 11:00
楼主,loop和tick为什么不合并为tick,在tick中检查各个计数器的参数,这样结构好一点。 ...

loop中除了检查计数还要执行回调,执行时间不恒定,不适合在中断中调用。

出0入0汤圆

 楼主| 发表于 2016-9-28 11:46:23 | 显示全部楼层
talkingbeast 发表于 2016-9-28 11:04
另外一个,楼主你这个软件定时器怎么实现反转一个io  n次的需求,不用额外变量。 ...

100hz频率翻转:
timer_init(&timer1, timer1_callback, 10, 10);

void timer1_callback()
{
        PIN_A1 = !PIN_A1;
        //stm32 :HAL_GPIO_TogglePin(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin)
}

出20入0汤圆

发表于 2016-9-28 12:07:38 | 显示全部楼层
半导体 发表于 2016-9-28 11:46
100hz频率翻转:
timer_init(&timer1, timer1_callback, 10, 10);

  我的意思是,我只需要反转一个IO 100次,100次以后停止运行此定时器。

出0入0汤圆

 楼主| 发表于 2016-9-28 13:51:48 | 显示全部楼层
talkingbeast 发表于 2016-9-28 12:07
我的意思是,我只需要反转一个IO 100次,100次以后停止运行此定时器。

这模块没有执行次数的参数接口,修改一下就可以了

出0入0汤圆

发表于 2016-9-28 16:38:57 | 显示全部楼层
本帖最后由 heianshaonian 于 2016-9-28 16:53 编辑

楼主的代码已经移植过来了 ,time_loop我暂时放到中断里面了 因为while(1)有个oled要跑 放while(1) 巡不过来  只有把中断服务程序弄短一点了

出20入70汤圆

发表于 2016-9-30 13:55:51 | 显示全部楼层
看了LZ的代码,有两处问题说一下!

1.
时钟计数值_timer_ticks是32位无符号数,在51类单片机下读取时不是原子操作,读取时可能中断发生,导致读取到的数值出错,此时运行timer_loop()函数肯定会出错!
建议加入_timer_ticks读取保护措施!

2.
timer_loop()函数中判断超时部分可能出错:
由程序中判断语句 if(_timer_ticks >= target->timeout)可知,超时与否仅仅根据_timer_ticks 值的大小比较,当_timer_ticks 发生溢出时可能出错。
你能保证单个定时器模块的回调函数运行时间在1个ticks内,但如果多个回调函数刚好同时运行在单次调用timer_loop()时,总运行时间可能超出1个ticks,
甚至达到2~3个ticks。比如我们经常延时5ms,10ms,20ms,50ms等,当ticks=100ms时,可能要运行4个回调函数。
如果此时某个target->timeout=0xFFFFFFFF,而在_timer_ticks = 0xFFFFFFFF时恰好因为延误未调用到此模块,当_timer_ticks = 0 反转后,此回调函数再想运行就要等到猴年马月了!
        

出20入70汤圆

发表于 2016-9-30 14:09:44 | 显示全部楼层
第2处问题说的有点啰嗦!

简言之,就是:
_timer_ticks 溢出反转前,必须保证所有定时器模块的target->timeout都是溢出反转后的值,否则此定时器模块想再次执行回调函数要等将近一个_timer_ticks 溢出周期!

出0入0汤圆

 楼主| 发表于 2016-9-30 16:10:23 | 显示全部楼层
本帖最后由 半导体 于 2016-9-30 16:14 编辑
techbaby 发表于 2016-9-30 13:55
看了LZ的代码,有两处问题说一下!

1.


1. MultiTimer属于中间层模块,timer_ticks()只提供时基累加服务,是不考虑平台环境的,所以由平台自行处理:
viod hardware_timer_ISR()
{
        CPU_INT_DIS();
        timer_ticks();
        CPU_INT_EN();
}

2. 我明白你的意思,这个问题我在27楼解释过了,32bit的tick可以计数49.7天,根据你的需求而定可以用64bit计数(350亿年后溢出)。

出0入0汤圆

发表于 2016-9-30 16:11:28 | 显示全部楼层
很不错,又可以替换了。

出20入70汤圆

发表于 2016-9-30 16:26:00 | 显示全部楼层
半导体 发表于 2016-9-30 16:10
1. MultiTimer属于中间层模块,timer_ticks()只提供时基累加服务,是不考虑平台环境的,所以由平台自行处 ...

出0入0汤圆

 楼主| 发表于 2016-9-30 16:29:50 | 显示全部楼层

谢谢前辈指出问题

出0入0汤圆

发表于 2016-10-3 17:48:36 | 显示全部楼层
LZ,你的编程思想很好啊,受益匪浅,大赞!

出0入0汤圆

发表于 2016-10-3 19:24:24 | 显示全部楼层
谢谢分享,看着不错

出0入0汤圆

发表于 2016-10-24 14:04:56 | 显示全部楼层
timer_init(&timer1, timer1_callback, 1000, 0); //1s delay

timer_init(&timer2, timer2_callback, 50, 50); //50ms loop

这个是不是可以这样理解
当repeat为0时,此时为延时函数,延时时间为timeout ms,比如第一个延时为1000ms

当repeat不为0时,此时为定时调用timer2_callback函数,调用时间为repeat ms,前面的timeout 与调用时间无关。
我自己试了,无论timeout 改为多少都没改变定时时间

出0入0汤圆

发表于 2016-10-24 14:26:13 | 显示全部楼层
这个好用,谢谢

出0入0汤圆

发表于 2016-11-17 23:44:17 | 显示全部楼层
mark,定时模块。谢谢楼主!

出0入0汤圆

发表于 2016-11-18 07:25:09 来自手机 | 显示全部楼层
谢谢楼主。希望能多发1些相似不同模块的贴子

出0入8汤圆

发表于 2016-11-18 08:17:46 来自手机 | 显示全部楼层
楼主这个系列非常不错都是精品啊

出0入8汤圆

发表于 2016-11-18 22:35:20 | 显示全部楼层
希望楼主继续分享,今天移植到我的开发板上,简直太好用了,感觉和用C#编程一样爽

出0入0汤圆

发表于 2016-11-19 09:11:22 | 显示全部楼层
感谢楼主分享经验

出0入0汤圆

发表于 2016-11-19 16:13:55 来自手机 | 显示全部楼层
学习擂主

出0入0汤圆

发表于 2016-11-19 22:25:03 | 显示全部楼层
谢谢分享,学习下

出0入0汤圆

发表于 2016-11-20 10:08:06 | 显示全部楼层
嗯,很受益,谢谢

出0入0汤圆

发表于 2016-11-20 10:17:37 | 显示全部楼层
atmel zigbee内核中定时器就是用与此相同的原理做成的

出0入0汤圆

发表于 2016-11-21 15:14:22 | 显示全部楼层
恩,好好学习,天天向上!

出0入0汤圆

发表于 2016-11-21 15:37:22 | 显示全部楼层
mark 一下

出0入0汤圆

发表于 2016-11-21 22:15:28 | 显示全部楼层
多谢楼主分享,先拿来用用测试一下

出0入0汤圆

发表于 2016-11-22 15:53:58 | 显示全部楼层
Mark 简易小巧的软件定时器扩展模块

出0入0汤圆

发表于 2016-11-23 14:54:28 | 显示全部楼层
谢谢分享

出0入0汤圆

发表于 2016-11-23 22:56:25 | 显示全部楼层
谢谢分享了!

出0入0汤圆

发表于 2016-11-28 15:48:41 | 显示全部楼层
按照楼主的思路重新改写了下

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?注册

x

出0入0汤圆

发表于 2016-11-28 20:36:07 | 显示全部楼层
都是链表操作啊,

出0入0汤圆

 楼主| 发表于 2017-3-2 15:19:36 | 显示全部楼层
s1j2h3 发表于 2016-11-20 10:17
atmel zigbee内核中定时器就是用与此相同的原理做成的

是的,很多soc的sdk库都有类似的模块。

出0入0汤圆

发表于 2017-3-11 09:59:10 | 显示全部楼层
感谢分享

出0入0汤圆

发表于 2017-3-13 17:54:49 | 显示全部楼层
多谢,好资料

出0入0汤圆

发表于 2017-3-17 09:11:38 | 显示全部楼层
谢谢分享,

出0入0汤圆

发表于 2017-4-3 22:20:34 | 显示全部楼层
MARK很通用的代码

出140入8汤圆

发表于 2017-5-5 08:02:12 | 显示全部楼层
本来准备用操作系统的。楼主这个精简,已经用上了,跑的很开心。

出0入0汤圆

发表于 2017-5-5 08:44:16 | 显示全部楼层
学习一下.

出5入10汤圆

发表于 2017-5-5 08:56:55 来自手机 | 显示全部楼层
很不错。马上用上

出0入0汤圆

发表于 2017-5-5 09:03:25 | 显示全部楼层
思路一致,只是我没有使用链表管理。

出140入8汤圆

发表于 2017-5-9 06:56:45 | 显示全部楼层
https://github.com/lmooml/SmartTimer
github上↑,这个软定时器功能和楼主的类似,但是实现代码复杂不少,tick函数复杂。

出0入0汤圆

发表于 2018-4-20 10:10:00 | 显示全部楼层
中断中调用timer_start(&timer2); 要注意什么吗?会出问题吗?

出0入0汤圆

发表于 2019-2-9 22:35:02 | 显示全部楼层
git上看到过,没想到也在阿莫论坛

出0入0汤圆

发表于 2019-2-11 00:36:35 | 显示全部楼层
簡單應用上很棒,可以不用上RTOS。

出0入0汤圆

发表于 2020-3-6 11:50:05 | 显示全部楼层
qwert1213131 发表于 2016-9-23 20:12
可以用在低功耗应用吗

不能用在低功耗上,要一直调用

出100入0汤圆

发表于 2021-2-4 22:31:25 | 显示全部楼层
techbaby 发表于 2016-9-30 13:55
看了LZ的代码,有两处问题说一下!

1.

当时用了楼主的软件定时扩展模块,没注意到这个细节,开发的程序跑了50天后有的任务死机了,大批返厂,开发还是要多注意细节啊

出0入0汤圆

发表于 2021-2-4 22:35:31 | 显示全部楼层
pingqifa 发表于 2021-2-4 22:31
当时用了楼主的软件定时扩展模块,没注意到这个细节,开发的程序跑了50天后有的任务死机了,大批返厂,开 ...

这个悲剧了。。

出100入0汤圆

发表于 2021-2-4 22:44:05 | 显示全部楼层
bad_fpga 发表于 2021-2-4 22:35
这个悲剧了。。

哎,实验阶段怎么也测试不出来的,编程时就要考虑变量越界问题,提醒没注意到的坛友不要重复采坑

出0入0汤圆

发表于 2021-2-5 09:24:04 | 显示全部楼层
把定时变量改到64bit
回帖提示: 反政府言论将被立即封锁ID 在按“提交”前,请自问一下:我这样表达会给举报吗,会给自己惹麻烦吗? 另外:尽量不要使用Mark、顶等没有意义的回复。不得大量使用大字体和彩色字。【本论坛不允许直接上传手机拍摄图片,浪费大家下载带宽和论坛服务器空间,请压缩后(图片小于1兆)才上传。压缩方法可以在微信里面发给自己(不要勾选“原图),然后下载,就能得到压缩后的图片】。另外,手机版只能上传图片,要上传附件需要切换到电脑版(不需要使用电脑,手机上切换到电脑版就行,页面底部)。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

手机版|Archiver|amobbs.com 阿莫电子技术论坛 ( 粤ICP备2022115958号, 版权所有:东莞阿莫电子贸易商行 创办于2004年 (公安交互式论坛备案:44190002001997 ) )

GMT+8, 2024-5-5 12:51

© Since 2004 www.amobbs.com, 原www.ourdev.cn, 原www.ouravr.com

快速回复 返回顶部 返回列表