一种软件定时器/超时机制的实现方法

概述

在程序设计中,常会遇到需要循环等待的问题,比如在while中等待串口中断接收完一帧数据。这时为了避免一直接收不到,程序进入死循环,我们会选择增加超时退出机制。
本文主要是记录一下超时机制的实现。

简单方法

这种实现方式相对比较简单,理解起来容易的多。比较适用于超时机制较少的程序。
首先需要三个变量,超时开始标志、超时计数值、超时时间到标志:

volatile uint8_t  g_xxx_Time_On_Flg=0;
volatile uint16_t  g_xxx_TimeCnt=0;
volatile uint8_t  g_xxx_Time_Out_Flg=0;

芯片资源受限,为了节省空间,常会开启优化功能。所以此处加了volatile关键字,避免被优化。实现上分为三个步骤:

  1. 开启超时。
    void Xxx_TimeOutSet(uint16_t TimeValue)
    {
    	g_xxx_Time_On_Flg = 1;
    	g_xxx_TimeCnt = TimeValue;
    	g_xxx_Time_Out_Flg = 0;	
    }
    
  2. 超时计数。放在系统定时器中运行:
    void Xxx_TimeOutISR(void)
    {
    	if(g_xxx_Time_On_Flg==1) {
    		g_xxx_TimeCnt--;
    		if(!g_xxx_TimeCnt) {
    			g_xxx_Time_On_Flg = 0;
    			g_xxx_Time_Out_Flg = 1;
    		}	
    	}
    }
    
  3. 关闭超时。当等待的条件满足而退出循环时,则需要关闭超时:
    void Xxx_TimeOutClr(void)
    {
    	g_xxx_Time_On_Flg = 0;
     	g_xxx_TimeCnt = 0;
     	g_xxx_Time_Out_Flg = 0;	
    }
    

可以想到,随着逻辑功能增加,会需要很多超时机制或定时器。这样频繁定义三个变量和三个函数,程序会变得很臃肿,且代码复用率很低。明显并不是很适用。

优化方案

考虑优化,可以将变量变为数组,通过索引来标识不同的超时,比如0是串口协议帧超时、1是SPI协议帧超时、2是系统运行时间等。

首先想到的是通过#define宏定义来实现,最近正好在学习RT-Thread,这里借鉴下,使用枚举的形式:

typedef enum __TIMER_T__
{
	TIMER_UART_FRAME = 0, //串口协议帧超时
	TIMER_SPI_FRAME, 	  //SPI协议帧超时
    TIMER_SYSTEM_RUN, 	  //系统运行时间
	TIMER_CNT
}timer_t;

TIMER_CNT即是允许的定时器的总个数,我们定义一下:

static uint32_t timer[TIMER_CNT] = {0};

不再需要g_xxx_Time_On_Flgg_xxx_TimeCntg_xxx_Time_Out_Flg三个变量,以串口帧超时为例,我们通过timer[TIMER_UART_FRAME]的值来判断。

//没有开启超时,也就是g_xxx_Time_On_Flg = 0
timer[TIMER_UART_FRAME] == 0	
//超时时间到,也就是g_xxx_Time_Out_Flg = 1
timer[TIMER_UART_FRAME] == 1	
//超时技术值自减1,此时timer[TIMER_UART_FRAME]等同于g_xxx_TimeCnt,
//且暗含了g_xxx_Time_On_Flg = 1,g_xxx_Time_Out_Flg = 0
timer[TIMER_UART_FRAME] == other

可以看出,给timer[TIMER_UART_FRAME]赋值时,也就同步开启了定时器。检测timer[TIMER_UART_FRAME]的值,也就等同于检测超时结果。这里就得到三个关键问题的实现:计数值自减、开启超时、检测超时。

计数值自减

将上面的语句封装成一个函数:

static int timer_run(uint8_t num)
{
    if (num >= TIMER_CNT) {
		return 0xFF;
	}	
    if (timer[num] == 0) {	//没有开启超时
        return 0;
    }
    else if (timer[num] == 1) {	//超时时间到
        timer[num] = 0;
        return -1;
    }
    else {	//超时计数值自减1
        /* if (timer[num] > 1) */
        timer[num]--;
        return 1;
    }
}

从第一部分已经知道,自减1是在系统定时器中进行的,所以需要在系统定时器中调用timer_run函数。

开启超时

开启超时函数修改为:

static void timer_start (uint8_t num)
{
    if (num >= TIMER_CNT) return;
    
    if (num == TIMER_UART_FRAME)
        nt_timer[TIMER_UART_FRAME] = TIMEOUT_CNT_UART_FRAME;
    if (num == TIMER_SPI_FRAME)
        nt_timer[TIMER_SPI_FRAME] = TIMEOUT_CNT_SPI_FRAME;
    if (num == TIMER_SYSTEM_RUN)
        nt_timer[TIMER_SYSTEM_RUN] = TIMEOUT_CNT_SYSTEM_RUN;
}

函数内的超时时间是一个宏定义

#define TIMEOUT_CNT_SYSTEM_RUN 	100
#define TIMEOUT_CNT_SPI_FRAME	150
#define TIMEOUT_CNT_SYSTEM_RUN	200

这里可能会想到:因为超时时间直接写为常量,如果超时值需要根据程序上下文进行调整,则比较麻烦。不过可以通过增加形参来传入,或者增加一个定时器,反正就是增加一个index嘛。

超时检测

取消了g_xxx_Time_Out_Flg 标志,超时检测使用函数调用来实现:

static int timer_chk (uint8_t num)
{
    if (num >= TIMER_CNT) {
		return 0;
	}
	
    if (timer[num] > 0) {
        timer[num] = 0; /* stop timer */
        return 1;
    }
    else {
        timer[num] = 0; /* stop timer */
        return 0;
    }
}
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值