[代码分享]一个简单好用的轮询延时器模块

2019-12-16 22:38发布

本帖最后由 Jmhh247 于 2017-5-23 10:08 编辑

关键字:延时器;非阻塞自由延时


在一些按键或其它形式的命令响应中,要求严格的话通常会需要队列缓存命令,以防丢失。

但是我通常还会遇到一些要求非常不严格的场景,这些场景甚至要限制响应的次数或频率(这里指不能响应太快)。
比如(只是个例子),我的产品是一个根据接收到的串口命令来工作的。串口命令发送的速度是不确定的,有时候1秒
会发送10帧命令过来,有的时候1秒确能发送100帧,但是这个产品每秒只打算处理20帧命令。而且由于命令有多个,
对于有些命令码可能每秒响应40帧或30帧等,这种场景用多个延时器就很合适了。


延时器模块的接口函数一共3个,如下:
  1. /* Exported functions ------------------------------------------------------- */
  2. bool zl_poll_delay_set(uint8_t chUserId, uint16_t hwTime);
  3. bool zl_poll_delay_timeout(uint8_t chUserId);
  4. void zl_poll_delay_tick(void);
复制代码

看下接口函数的实现,很简单的
  1. /*   Z L _ P O L L _ D E L A Y _ S E T   */
  2. /*-------------------------------------------------------------------------
  3. 功能描述  : 设定延时时间,同时启动延时。
  4. 输入参数  : uint8_t chUserId  延时器ID
  5.              uint16_t hwTime   延时时间
  6. 输出参数  : 无
  7. 返 回 值  :

  8. 修改历史      
  9.   1.日    期   : 2017年2月6日
  10.     作    者   : zys
  11.     修改内容   : 新生成函数,OK。

  12. -------------------------------------------------------------------------*/
  13. bool zl_poll_delay_set(uint8_t chUserId, uint16_t hwTime)
  14. {
  15.     if (chUserId >= ZL_POLL_DELAY_NUM) {
  16.         return false;
  17.     }

  18.     if (0 == hwTime) {
  19.         return false;
  20.     }

  21.     s_tPollBuf[chUserId].Time = hwTime;

  22.     return true;

  23. }


  24. /*   Z L _ P O L L _ D E L A Y _ T I M E O U T   */
  25. /*-------------------------------------------------------------------------
  26. 功能描述  : 查询给定ID的延时器是否超时。
  27. 输入参数  : uint8_t chUserId  
  28. 输出参数  : 无
  29. 返 回 值  : true 超时;false 未超时。

  30. 修改历史      
  31.   1.日    期   : 2017年5月22日
  32.     作    者   : zys
  33.     修改内容   : 新生成函数。

  34. -------------------------------------------------------------------------*/
  35. bool zl_poll_delay_timeout(uint8_t chUserId)
  36. {
  37.     if (chUserId >= ZL_POLL_DELAY_NUM) {
  38.         return false;
  39.     }

  40.     if ( !s_tPollBuf[chUserId].Time) {
  41.         return true;
  42.     }

  43.     return false;
  44. }


  45. /*   Z L _ P O L L _ D E L A Y _ T I C K   */
  46. /*-------------------------------------------------------------------------
  47. 功能描述  : 延时模块的驱动任务,在心跳中断里运行。
  48. 输入参数  : void  
  49. 输出参数  : 无
  50. 返 回 值  :

  51. 修改历史      
  52.   1.日    期   : 2017年5月22日
  53.     作    者   : zys
  54.     修改内容   : 新生成函数。

  55. -------------------------------------------------------------------------*/
  56. void zl_poll_delay_tick(void)
  57. {
  58.     uint8_t i;


  59.     for ( i = 0 ; i < ZL_POLL_DELAY_NUM; i++ ) {
  60.         if (s_tPollBuf[i].Time) {
  61.             s_tPollBuf[i].Time--;
  62.         }
  63.     }
  64. }
复制代码

模块的使用
使用很简单,首先把模块源文件添加到工程中,然后把
  1. void zl_poll_delay_tick(void)
复制代码
放到心跳中断里,我一般用1ms的心跳中断。


现在就能使用
  1. bool zl_poll_delay_set(uint8_t chUserId, uint16_t hwTime)
复制代码
来设置延时器了,入参分别是延时器的ID和延时时间,延时时间单位和心跳中断有关,看自己需求了。。。


然后用
  1. bool zl_poll_delay_timeout(uint8_t chUserId)
复制代码
来查询延时器是否超时,入参是要查询的延时器ID号。


延时器的可用最大数量通过宏定义配置
  1. /* 定义可用的延时数量 */
  2. #define ZL_POLL_DELAY_NUM   5
复制代码



模拟一个应用场景演示代码


  1. /* 定义延时器信息,便于阅读修改 */
  2. #define DELAY_ID0           0
  3. #define DELAY_TIME0         50

  4. #define DELAY_ID1           1
  5. #define DELAY_TIME1         900



  6. /* 模拟一个应用场景 */
  7. void xxx_cmd_process(uint8_t chCmd)
  8. {
  9.     switch (chCmd) {
  10.     case CMD_LEFT:

  11.         /* 该命令执行间隔不能小于50ms */
  12.         if (zl_poll_delay_timeout(DELAY_ID0)) {
  13.             zl_poll_delay_set(DELAY_ID0, DELAY_TIME0);

  14.             cmd_do_left();
  15.         }
  16.         break;

  17.     case CMD_RIGHT:

  18.         /* 该命令执行间隔不能小于900ms */
  19.         if (zl_poll_delay_timeout(DELAY_ID1)) {
  20.             zl_poll_delay_set(DELAY_ID1, DELAY_TIME1);

  21.             cmd_do_left();
  22.         }
  23.         break;

  24.     case CMD_UP:

  25.         /* 该命令无限制,来就执行 */
  26.         cmd_do_up();
  27.         break;

  28.     case CMD_DOWN:
  29.         break;

  30.     default:
  31.         break;
  32.     }
  33. }


  34. /* 在1ms心跳(时基)中断里运行 */
  35. void st_systick_1ms_irq(void)
  36. {
  37.     zl_poll_delay_tick()
  38. }
复制代码


延时器的用处还有很多,可以灵活。。。

zlib poll delay模块源码.rar (2.02 KB, 下载次数: 108) 2017-5-23 10:07 上传 点击文件名下载附件

友情提示: 此问题已得到解决,问题已经关闭,关闭后问题禁止继续编辑,回答。
该问题目前已经被作者或者管理员关闭, 无法添加新回复
38条回答
Jmhh247
1楼-- · 2019-12-19 15:15
jiangyimfs1 发表于 2017-8-22 08:23
楼主的非阻塞延时器很实用,在工程中使用了一下,出了点问题,请教一下楼主是不是哪个步骤使用错误了,贴上 ...

呃,大哥,你得告诉我出了啥问题吧,光贴代码不知道你想表达啥问题。。。另,你的代码缩进好难受



只能让我猜猜你所谓的问题吧

你想要实现的是不是:
>状态0:两个LED全灭,延时4S
>状态1:LED1亮,延时4S
>状态2:LED2亮,延时4S,LED2亮起的同时切换到状态0

如果是这样,你的写法确实存在些问题,用一个延时器就可以实现了。。。
Jmhh247
2楼-- · 2019-12-19 17:01
jiangyimfs1 发表于 2017-8-22 08:23
楼主的非阻塞延时器很实用,在工程中使用了一下,出了点问题,请教一下楼主是不是哪个步骤使用错误了,贴上 ...

按我猜测的功能写的,不过代码未测试,只是展示延时器的使用。。。


  1. #define LED_DELAY_TIME                                                4000

  2. /*   L E D _ D E M O _ T A S K   */
  3. /*-------------------------------------------------------------------------
  4.     *功能:延时器测试闪灯,在超级循环中运行。
  5.     *参数:无
  6.     *返回:无
  7.     *备注:未测试。
  8. -------------------------------------------------------------------------*/
  9. void led_demo_task(void)
  10. {
  11.         static uint8_t s_chStep = 0;


  12.         switch (s_chStep)
  13.         {
  14.                 case 0:
  15.                         /* 全灭 */
  16.                         /* 第一次用直接就是超时状态,因为还未设置超时时间 */
  17.                         if (zl_poll_delay_timeout(0)) {
  18.                                 zl_poll_delay_set(0, LED_DELAY_TIME);

  19.                                 LED_1 = 1;
  20.                                 LED_2 = 1;

  21.                                 s_chStep = 1;
  22.                         }

  23.                         break;

  24.                 case 1:
  25.                         /* LED1 亮 */
  26.                         if (zl_poll_delay_timeout(0)) {
  27.                                 zl_poll_delay_set(0, LED_DELAY_TIME);

  28.                                 LED_1 = 0;

  29.                                 s_chStep = 2;
  30.                         }
  31.                        
  32.                         break;

  33.                 case 2:
  34.                         /* LED2 亮 */
  35.                         if (zl_poll_delay_timeout(0)) {
  36.                                 zl_poll_delay_set(0, LED_DELAY_TIME);

  37.                                 LED_2 = 0;

  38.                                 s_chStep = 0;
  39.                         }
  40.                         break;

  41.                 default:
  42.                         break;
  43.         }
  44. }
复制代码

在每个case里设置的延时,是为下个case服务的。


Jmhh247
3楼-- · 2019-12-19 18:03
我去,我这个代码的缩进也不对,居然变长了。。。
gtnr
4楼-- · 2019-12-19 21:55
 精彩回答 2  元偷偷看……
Jmhh247
5楼-- · 2019-12-19 22:02
gtnr 发表于 2017-8-22 10:05
@楼主: 假如有大量的定时要处理,中断处理会消耗不少时间,会降低系统实时性。
建议设计成一个查询函数, ...

你说的对,量大消耗的时间也多

就是设计查询函数,我不太明白,是基于心跳计数查询吗

18楼的方法我觉得也不错。
jiangyimfs1
6楼-- · 2019-12-20 01:18
本帖最后由 jiangyimfs1 于 2017-8-22 16:02 编辑
Jmhh247 发表于 2017-8-22 09:08
呃,大哥,你得告诉我出了啥问题吧,光贴代码不知道你想表达啥问题。。。另,你的代码缩进好难受


对的,代码就是实现这样的效果了,代码我直接复制黏贴了,没注意看显示的效果 了,不好意思,看了一下楼主的代码,是我自己理解错误了,按楼主的代码试了一下,效果很好,非常感谢了。。。


再问一下楼主, 我case0 延时3s,case1 延时2s,case2 延时1s,是下面这么写吗,现象是LED1,LED2同时亮了,我是不是又哪里用错了



#define DELAY_ID0           0
#define DELAY_TIME0         1000

#define DELAY_ID1           1
#define DELAY_TIME1         2000

#define DELAY_ID2           2
#define DELAY_TIME2         3000

/*   L E D _ D E M O _ T A S K   */
/*-------------------------------------------------------------------------
    *功能:延时器测试闪灯,在超级循环中运行。
    *参数:无
    *返回:无
    *备注:未测试。
-------------------------------------------------------------------------*/
void led_demo_task(void)
{
        static uint8_t s_chStep = 0;


        switch (s_chStep)
        {
                case 0:
                        /* 全灭 */
                        /* 第一次用直接就是超时状态,因为还未设置超时时间 */
                        if (zl_poll_delay_timeout(DELAY_ID2)) {
                                zl_poll_delay_set(DELAY_ID2, DELAY_TIME2);  //延时3s

                                LED1 = 1;
                                LED2 = 1;

                                s_chStep = 1;
                        }

                        break;

                case 1:
                        /* LED1 亮 */
                        if (zl_poll_delay_timeout(DELAY_ID1)) {
                                zl_poll_delay_set(DELAY_ID1,DELAY_TIME1);   //延时2s

                                LED1 = 0;

                                s_chStep = 2;
                        }
                        
                        break;

                case 2:
                        /* LED2 亮 */
                        if (zl_poll_delay_timeout(DELAY_ID0)) {
                                zl_poll_delay_set(DELAY_ID0, DELAY_TIME0);  //1s

                                LED2 = 0;

                                s_chStep = 0;
                        }
                        break;

                default:
                        break;
        }
}

一周热门 更多>