概述
在程序设计中,常会遇到需要循环等待的问题,比如在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
关键字,避免被优化。实现上分为三个步骤:
- 开启超时。
void Xxx_TimeOutSet(uint16_t TimeValue) { g_xxx_Time_On_Flg = 1; g_xxx_TimeCnt = TimeValue; g_xxx_Time_Out_Flg = 0; }
- 超时计数。放在系统定时器中运行:
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; } } }
- 关闭超时。当等待的条件满足而退出循环时,则需要关闭超时:
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_Flg
、g_xxx_TimeCnt
、g_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;
}
}