基于链表的软件定时器实现
1. 应用需求
1.1 背景
- 主要是针对单片机的开发,一般单片机的硬件定时器是有限的,不太可能有一个定时任务就用硬件定时器实现。
1.2 前提
- 有一个已经实现的硬件定时器在进行计数。(本文的实现是基于time.h的库进行获取的时间)
1.3 功能
- 支持根据参数设置的优先级进行顺序插入,优先级的值越小,则轮询定时器的时候会更快执行。
- 支持配置软件定时器的定时时间,可以有两种模式配置,一种是持续性的自动装载,另外一种是设置重复运行的次数。
2. 功能实现
2.1 头文件定义
#include "stdio.h"
#include "stdlib.h"
#include "string.h"
#include "time.h"
//所用的参数类型定义
typedef unsigned char uint8_t;
typedef unsigned short uint16_t;
typedef unsigned long uint32_t;
//超时回调函数,无参数无返回值
typedef void (*TimeoutCb)(void);
//软件定时器属性
typedef struct
{
uint8_t id; //软件定时器ID
uint8_t priority; //软件定时器优先级
uint16_t mode; //BIT15:1表示无限制连续重载,0表示启用重载次数,此时BIT[14:0]表示重载的次数
uint16_t timeout; //定时时间
uint32_t startTimer; //设置软件定时器的起始时间
TimeoutCb timeoutFun; //软件定时的超时回调
} SoftTimer;
//软件定时器节点
typedef struct _SoftTimerNode
{
SoftTimer property;
struct _SoftTimerNode *next;
} SoftTimerNode;
2.2 源文件功能
2.2.1 链表头的定义
//初始化一个软件定时器链表默认指向空
static SoftTimerNode *s_softTimerHead = NULL;
2.2.2 基准定时器的参考
- 在单片机的应用中,单片机中应该有一个基准定时器进行计数,可供外部文件获取计数值。本文中直接从系统中获取一个utc时间,模拟计数值的产生。
- 检测软件定时器是否超时,这里要特别注意溢出的情况,本文中实现没有考虑这个问题,实际应用中根据情况而定。
//获取系统当前UTC时间
uint32_t GetTimerCount(void)
{
time_t utcTimer = 0;
time(&utcTimer);
return utcTimer;
}
//软件定时器是否超时
uint8_t IsSoftTimerTimeout(SoftTimer *softTimer)
{
return ((softTimer->startTimer + softTimer->timeout) <= GetTimerCount());
}
2.2.3 获取软件定时器的个数
- 如果链表是空的话,直接返回0,表示没有定时器。
- 如果链表存在,则返回实际的软件定时器个数。
//获取软件定时器链表的长度
uint32_t GetSoftTimerCount(void)
{
SoftTimerNode *cur = NULL;
uint32_t cnt = 0;
if (s_softTimerHead == NULL)
{
return 0;
}
cur = s_softTimerHead;
//下一个数据不为空
while (cur->next != NULL)
{
cnt++;
cur = cur->next;
}
return 1 + cnt;
}
2.2.4 查找软件定时器的位置
- 主要用于查找固定的定时器ID是否存在于当前链表的位置,如果不存在返回一个-1。
- 如果链表中存在这个名称为ID的软件定时器,则返回当前存在的位置。这个位置从0开始表示,即链表头指向的是第一个数据,此时位置为0。
//查找定时器ID的位置
int SearchSoftTimerById(uint8_t id)
{
SoftTimerNode *cur = NULL;
int pos = 0;
if (s_softTimerHead == NULL)
{
return -1;
}
cur = s_softTimerHead;
for (;;)
{
if (cur->property.id == id)
{
break;
}
if (cur->next == NULL)
{
pos = -1; //找不到位置
break;
}
cur = cur->next;
pos++;
}
return pos;
}
2.2.5 根据优先级判断应该插入的位置
- 主要用于判断当前优先级应该位于链表的哪个位置,这个位置从0开始表示,即链表头指向的是第一个数据,此时位置为0。
- 链表的排放顺序是按照优先级进行顺序排放,优先级的参数值越小,则插入的位置应该越靠前。
- 如果优先级是相同的,则放在相同优先级结点的最后一个位置。
//获取插入的位置
uint32_t GetInsertPosByPriority(uint16_t priority)
{
SoftTimerNode *cur = NULL;
uint32_t pos = 0;
if (s_softTimerHead == NULL)
{
return pos; //插入在头部
}
cur = s_softTimerHead;
for (;;)
{
//优先级比当前的结点要高
if (priority < cur->property.priority)
{
break;
}
pos++;
if (cur->next == NULL)
{
break;
}
cur = cur->next;
}
return pos;
}
2.2.6 删除一个软件定时器