在一般的嵌入式產品設計中,介于成本、功耗等,所選型的MCU基本都是資源受限的,而里面的定時器的數量更是有限。在我們軟件設計中往往有多種定時需求,例如脈沖輸出、按鍵檢測、LCD切屏延時等等 ,我們不可能讓每一個定時業務都去開一個硬件定時器,一來硬件資源可能不足,二來會使軟件過度依賴于硬件平臺,從而導致較差的可移植性。
而若我們有一個軟件定時器,所有定時業務都依賴于軟件定時器,不僅節省硬件資源,以后在移植的時候也只需要將軟件定時器和硬件相關的部分修改就行了,其他部分都不用動。
軟件定時器實現方式:
一、用結構體數組的方式實現軟件定時器
用結構體數組的方式實現起來較簡單,也容易理解,除此之外和之后的鏈表實現方式比起來沒有其他優點。但還是介紹一下實現方法:在結構體數組內定義一個start標志和定時時長duration,還有一個為計數值count,這3個變量為最基本的3個變量,其他的可以自己補充,比如運行模式、回調函數指針等。還有就是每一個結構體數組就是一個定時器,需要我們提前定義好這個結構體數組有多大。
定義好之后,在開啟定時器的時候我們將對應的數組內start標志置位,在硬件tick中斷服務函數里面我們去查所有結構體數組內的start標志是否置位,當查到當前start被置位時,將此數組內的duration和count做比較,如果相等就說明此定時器定時時間到了,如果不等就將count++,然后接著查其他數組的start標志,以此無限循環。
此種方式缺點非常明顯,那就是在硬件tick中斷服務函數內,我們得輪詢所有數組,如果我們軟件業務需求是20個定時任務,那我們就得在軟件定時器的實現里定義20個數組,空間浪費倒是其次的,關鍵是硬件tick輪詢的數組越多,執行到某個數組的時間就越長,若以后有50個、100個定時需求時,將會導致定時時間極不精準。
二、用鏈表實現軟件定時器
介于以上用結構體數組實現軟件定時器的種種缺點,我們提出改進方案。經過分析,在大多數定時業務中,往往只需要在某個時間段定時一次,也就是說定時器會開啟定時和結束定時,當然,用數組的實現的定時器也可以開啟定時和關閉定時,只需要用start標志去決定就行了,但是用數組實現的方式中,即使你關閉了定時器,也就是去掉了start標志,此定時器雖然不運行了,但是數組的空間不會減少,硬件 tick依然要輪詢所有數組。
所以我們需要用鏈表來實現軟件定時器,在硬件tick中輪詢所有節點,開啟一個定時器就加入一個節點,關閉定時器就刪除一個節點,可以保證在當前時刻只輪詢需要定時的節點,可以極大的保證定時準確性。
在加上可以讓用戶選擇定時時間到了直接在硬件tick內執行或者在硬件tick內置標志,然后在while循環內排隊執行,可以非常有效的解決關鍵業務定時不精準的問題,比如脈沖輸出這種需要定時準確的業務。
鏈表實現方式
H文件:
/**
* sfor_timer_list.h
* 鏈表實現的軟件定時器庫
*/
#ifndef __SOFT_TIMER_LIST_H
#define __SOFT_TIMER_LIST_H
/**
* 硬件中斷tick
*/
#define TIMER_HARD_TICK 100U //ms,硬件tick取決于硬件定時中斷時間
#define TIMER_200MS_TICK (200U/TIMER_HARD_TICK) //TIMER_HARD_TICK * (2) = 100mS
#define TIMER_SEC_TICK (1000U/TIMER_HARD_TICK) //TIMER_HARD_TICK * (20) = 1S
/**
* 定時模式選擇
*/
typedefenum
{
ONCE_MODE, /* 單次定時模式,即超時后自動關閉定時器 */
CONTINUE_MODE, /* 持續定時模式,只要開啟除非手動關閉否則永不停歇 */
DEFINE_NUM_MODE, /* 定義次數的模式,運行指定的次數后關閉定時器 */
}TimerTimingModeType;
/**
* 定時超時后運行的回調函數可以選擇在中斷直接運行或者掛起任務輪詢執行
* 只要在定時需求準確的時候才建議選擇中斷模式執行,類似無磁傳感器脈沖測量
* 像一些超時判斷類的應用以輪詢的方式進行執行
* 中斷執行模式越多,其他定時器越不準,畢竟中斷允許占時間,查詢其他定時器時
* 會有延時
*/
typedefenum
{
RUN_IN_LOOP_MODE, /* 輪詢執行模式 */
RUN_IN_INTERRUPT_MODE, /* 中斷實時執行模式 */
}TimerRunModeType;
/**
* 軟件定時器基本類型
*/
typedefstructSoftTimer
{
unsignedlong counter; /* 計數 */
unsignedlong duration; /* 定時時長 */
unsignedlong run_num; /* 自定義的定時次數 */
BOOL start_flag; /* 啟動標志 */
BOOL loop_flag; /* 輪詢標志 */
TimerRunModeType run_mode;
TimerTimingModeType timing_mode;
void (*callback_function)(void); /* 回調函數 */
structSoftTimer *next;
}SoftTimer;
/*
* 初始化軟件定時器的硬件tick
*/
externvoidsoft_timer_tick_init(void);
/*
* 創建一個只運行一次的軟件定時器并立刻開始計時
* 參數表:p: 定時器結構體指針,由用戶創建
* mode: 選擇運行模式,可選定時器到了之后是直接在tick中斷內執行還是置起標志在while循環內輪詢執行
* duration: 要計時的時長,單位為硬件中斷的tick
* timeout_handler: 定時到了之后要執行的函數指針
* return:無
*/
externvoidcreat_single_soft_timer(SoftTimer *p, TimerRunModeType mode, unsignedlong duration, void(*timeout_handler)(void));
/*
* 創建永遠運行的軟件定時器并立刻開始計時
* 參數表:p: 定時器結構體指針,由用戶創建
* mode: 選擇運行模式,可選定時器到了之后是直接在tick中斷內執行還是置起標志在while循環內輪詢執行
* duration: 要計時的時長,單位為硬件中斷的tick
* timeout_handler: 定時到了之后要執行的函數指針
* return:無
*/
externvoidcreat_continue_soft_timer(SoftTimer *p, TimerRunModeType mode, unsignedlong duration, void(*timeout_handler)(void));
/*
* 創建指定次數運行的軟件定時器并立刻開始計時
* 參數表:p: 定時器結構體指針,由用戶創建
* mode: 選擇運行模式,可選定時器到了之后是直接在tick中斷內執行還是置起標志在while循環內輪詢執行
* run_num:要定時的次數,比如1就是定時1次,5就是定時5次以后自動關閉定時器
* duration: 要計時的時長,單位為硬件中斷的tick
* timeout_handler: 定時到了之后要執行的函數指針
* return:無
*/
externvoidcreat_limit_num_soft_timer(SoftTimer *p, TimerRunModeType mode,unsignedlong run_num, unsignedlong duration, void(*timeout_handler)(void));
/*
* 重啟指定的單次軟件定時器
* 參數表:p: 定時器結構體指針,由用戶創建
* mode: 選擇運行模式,可選定時器到了之后是直接在tick中斷內執行還是置起標志在while循環內輪詢執行
* duration: 要計時的時長,單位為硬件中斷的tick
* timeout_handler: 定時到了之后要執行的函數指針
* return:無
*/
externvoidrestart_single_soft_timer(SoftTimer *p, TimerRunModeType mode, unsignedlong duration, void(*timeout_handler)(void));
/**
* 刪除一個軟件定時器
*/
externvoidstop_timer(SoftTimer *p);
/**
* 系統main循環進程,用于執行輪詢模式的回調函數
*/
externvoidsoft_timer_main_loop(void);
/**
* 此函數為tick中斷服務函數,需要掛載在外部硬件定時器上
* 因此軟件定時器的定時精度由此函數掛載的硬件定時時間決定,
* 比如此函數掛載在定時50ms的外部定時器上,那么定時dutation
* 為20時定時時間就是20*50ms=1S
*/
externvoidsystem_tick_IrqHandler(void);
#endif/* !1__SOFT_TIMER_LIST_H */C文件:
/**
* sfor_timer_list.c
* 鏈表實現的軟件定時器庫
*/
#define NULL ((void *)0)
typedefenum {FALSE = 0, TRUE = !FALSE} BOOL;
#include"meter_include.h"//包含用戶的硬件定時器初始化函數
#include"soft_timer_list.h"
/**
* 軟件定時器內部變量
*/
static SoftTimer *head_point = NULL;
staticstruct SoftTimer *creat_node(SoftTimer *node);
staticchardelete_node(SoftTimer *node);
static BOOL is_node_already_creat(SoftTimer *node);
/**
* 初始化軟件定時器的硬件tick
*/
voidsoft_timer_tick_init(void)
{
R_IT_Create(); /* 由用戶初始化一個硬件定時器,當前tick 100ms */
R_IT_Start();
}
/**
* 系統main循環進程,用于執行輪詢模式的回調函數
*/
voidsoft_timer_main_loop(void)
{
structSoftTimer *p1 = head_point;
while (p1 != NULL) //下一個節點如果不為空
{
if(p1->loop_flag == TRUE)
{
p1->loop_flag= FALSE;
p1->callback_function();
if(p1->start_flag != TRUE)
delete_node(p1); /* 如果定時器被刪除就刪除節點 */
}
/* 尋找下一個有意義的節點 */
p1 = p1->next;
}
}
/*
* 創建一個只運行一次的軟件定時器并立刻開始計時
* 參數表:p: 定時器結構體指針,由用戶創建
* mode: 選擇運行模式,可選定時器到了之后是直接在tick中斷內執行還是置起標志在while循環內輪詢執行
* duration: 要計時的時長,單位為硬件中斷的tick
* timeout_handler: 定時到了之后要執行的函數指針
* return:無
*/
voidcreat_single_soft_timer(SoftTimer *p, TimerRunModeType mode, unsignedlong duration, void(*timeout_handler)(void))
{
if ((p == NULL)||(timeout_handler == NULL) || duration == 0) return;
p->start_flag = TRUE;
p->counter = 0;
p->loop_flag = FALSE;
p->duration = duration;
if(mode == RUN_IN_LOOP_MODE)
p->run_mode = RUN_IN_LOOP_MODE;
else
p->run_mode = RUN_IN_INTERRUPT_MODE;
p->callback_function = timeout_handler;
p->timing_mode = ONCE_MODE;
p->run_num = 0; /* 只有在自定義運行次數的情況下此值才有效 */
head_point = creat_node(p);
}
/*
* 創建永遠運行的軟件定時器并立刻開始計時
* 參數表:p: 定時器結構體指針,由用戶創建
* mode: 選擇運行模式,可選定時器到了之后是直接在tick中斷內執行(除非實在必要)還是置起標志在while循環內輪詢執行
* duration: 要計時的時長,單位為硬件中斷的tick
* timeout_handler: 定時到了之后要執行的函數指針
* return:無
*/
voidcreat_continue_soft_timer(SoftTimer *p, TimerRunModeType mode, unsignedlong duration, void(*timeout_handler)(void))
{
if ((p == NULL)||(timeout_handler == NULL) || duration == 0) return;
p->start_flag = TRUE;
p->counter = 0;
p->loop_flag = FALSE;
p->duration = duration;
if(mode == RUN_IN_LOOP_MODE)
p->run_mode = RUN_IN_LOOP_MODE;
else
p->run_mode = RUN_IN_INTERRUPT_MODE;
p->callback_function = timeout_handler;
p->timing_mode = CONTINUE_MODE;
p->run_num = 0; /* 只有在自定義運行次數的情況下此值才有效 */
head_point = creat_node(p);
}
/*
* 創建指定次數運行的軟件定時器并立刻開始計時
* 參數表:p: 定時器結構體指針,由用戶創建
* mode: 選擇運行模式,可選定時器到了之后是直接在tick中斷內執行還是置起標志在while循環內輪詢執行
* run_num:要定時的次數,比如1就是定時1次,5就是定時5次以后自動關閉定時器
* duration: 要計時的時長,單位為硬件中斷的tick
* timeout_handler: 定時到了之后要執行的函數指針
* return:無
*/
voidcreat_limit_num_soft_timer(SoftTimer *p, TimerRunModeType mode,unsignedlong run_num, unsignedlong duration, void(*timeout_handler)(void))
{
if ((p == NULL)||(timeout_handler == NULL) || duration == 0) return;
p->start_flag = TRUE;
p->counter = 0;
p->loop_flag = FALSE;
p->duration = duration;
if(mode == RUN_IN_LOOP_MODE)
p->run_mode = RUN_IN_LOOP_MODE;
else
p->run_mode = RUN_IN_INTERRUPT_MODE;
p->callback_function = timeout_handler;
p->timing_mode = DEFINE_NUM_MODE;
p->run_num = run_num; /* 只有在自定義運行次數的情況下此值才有效 */
head_point = creat_node(p);
}
/*
* 重啟指定的單次軟件定時器
* 參數表:p: 定時器結構體指針,由用戶創建
* mode: 選擇運行模式,可選定時器到了之后是直接在tick中斷內執行還是置起標志在while循環內輪詢執行
* duration: 要計時的時長,單位為硬件中斷的tick
* timeout_handler: 定時到了之后要執行的函數指針
* return:無
*/
voidrestart_single_soft_timer(SoftTimer *p, TimerRunModeType mode, unsignedlong duration, void(*timeout_handler)(void))
{
if ((p == NULL)||(timeout_handler == NULL) || duration == 0) return;
p->start_flag = TRUE;
p->counter = 0;
p->loop_flag = FALSE;
p->duration = duration;
if(mode == RUN_IN_LOOP_MODE)
p->run_mode = RUN_IN_LOOP_MODE;
else
p->run_mode = RUN_IN_INTERRUPT_MODE;
p->callback_function = timeout_handler;
p->timing_mode = ONCE_MODE;
p->run_num = 0; /* 只有在自定義運行次數的情況下此值才有效 */
if (is_node_already_creat(p) != TRUE) /* 若之前的節點已被刪除就重新創建 */
head_point = creat_node(p);
}
/**
* 封裝后給用戶使用
*/
voidstop_timer(SoftTimer *p)
{
if (p != NULL) {
p->counter = 0;
p->start_flag = FALSE;
delete_node(p);
}
}
staticstruct SoftTimer *creat_node(SoftTimer *node)
{
structSoftTimer *p1; //p1保存當前需要檢查的節點的地址
if(node == NULL)
return head_point;
if(is_node_already_creat(node)!=FALSE) {
delete_node(node); /* 當節點已經存在的時候在這里選擇退出還是刪除后重新創建,目前重新創建 */
}
//當頭節點為空時,將傳入的節點作為頭節點,返回頭節點
if (head_point == NULL) {
head_point = node;
node->next = NULL;
return head_point;
}
p1 = head_point;
while(p1->next != NULL)
{
p1 = p1->next; //后移一個節點
}
if(p1->next == NULL) //將該節點插入鏈表的末尾
{
p1->next = node;
node->next = NULL;
}
else
{
}
return head_point;
}
staticchardelete_node(SoftTimer *node)
{
structSoftTimer *p1; //p1保存當前需要檢查的節點的地址
structSoftTimer *temp;
if(node == NULL)
return1;
p1 = head_point;
if(node == head_point) {
head_point = head_point->next; /* 如果要刪除頭指針,就將頭指針后移 */
} else {
while (p1 != NULL) /*頭節點如果不為空 */
{
temp = p1; /* 記錄當前節點 */
p1 = p1->next; /* 檢索的是下一個節點 */
if (p1 == NULL) {
return1;
}
if (p1 == node) {
temp->next = p1->next; /* 刪除此節點 */
return0;
}
}
}
return0;
}
static BOOL is_node_already_creat(SoftTimer *node)
{
structSoftTimer *p1; //p1保存當前需要檢查的節點的地址
if(node == NULL)
return FALSE;
p1 = head_point;
while(p1 != NULL)
{
if(p1 == node)
return TRUE;
p1 = p1->next; //后移一個節點
}
return FALSE;
}
/**
* 此函數為tick中斷服務函數,需要掛載在外部硬件定時器上
* 因此軟件定時器的定時精度由此函數掛載的硬件定時時間決定,
* 比如此函數掛載在定時50ms的外部定時器上,那么定時dutation
* 為20時定時時間就是20*50ms=1S
*/
voidsystem_tick_IrqHandler(void)
{
structSoftTimer *p1 = head_point;
close_global_ir(); //關閉中斷,根據硬件平臺修改
while (p1 != NULL) //下一個節點如果不為空
{
if(p1->start_flag != FALSE) /* 判斷當前定時器是否被開啟 */
{
if(++p1->counter >= p1->duration) /* 判斷當前計時有沒有到達 */
{
switch (p1->timing_mode)
{
case ONCE_MODE:
p1->start_flag = FALSE;
break;
case CONTINUE_MODE:
break;
case DEFINE_NUM_MODE:
if (p1->run_num > 0)
{
if (--p1->run_num == 0)
{
p1->start_flag = FALSE;
}
}
default:
break;
}
if(p1->run_mode == RUN_IN_INTERRUPT_MODE)
{
p1->callback_function(); /* 中斷內直接運行回調函數,用于實時性比較高的程序 */
if(p1->start_flag != TRUE)
delete_node(p1);
}
else
p1->loop_flag = TRUE;
p1->counter = 0;
}
}
/* 尋找下一個有意義的節點 */
p1 = p1->next;
}
open_global_ir(); //打開中斷,根據硬件平臺修改
}結構體實現方式
最后在附上用結構體數組實現的軟件定時器以作參考。
H文件:
/**
* sfor_timer_array.h
* 數組實現的軟件定時器庫
* 一個軟件定時器解決整個項目中所有的定時需求,回調函數可根據應用
* 自動切換中斷實時操作或者不實時的輪詢操作,可以有效解決硬件資源
* 不足或者軟件定時器定時不準的問題,定時誤差就幾個C語言語句,倘若
* 配置最大10個軟件定時器,誤差就是最多10個for循環的時間
*/
#ifndef __SOFT_TIMER_ARRAY_H
#define __SOFT_TIMER_ARRAY_H
/**
* 定義最大的可用的軟件定時器數量
* 理論上可以無限大,但是數量越大定時誤差越大,所以用幾個開幾個
* 誤差在于輪詢檢測所有定時器,定時器越多輪詢到自己的定時器就越慢,
* 此外,數量增多亦會帶來空間增大
*/
#define MAX_TIMER_NUM 10
/**
* 定時模式選擇
*/
typedefenum
{
ONCE_MODE, /* 單次定時模式,即超時后自動關閉定時器 */
CONTINUE_MODE, /* 持續定時模式,只要開啟除非手動關閉否則永不停歇 */
DEFINE_NUM_MODE, /* 定義次數的模式,運行指定的次數后關閉定時器 */
}TimerTimingModeType;
/**
* 定時超時后運行的回調函數可以選擇在中斷直接運行或者掛起任務輪詢執行
* 只要在定時需求準確的時候才建議選擇中斷模式執行,類似無磁傳感器脈沖測量
* 像一些超時判斷類的應用以輪詢的方式進行執行
* 中斷執行模式越多,其他定時器越不準,畢竟中斷允許占時間,查詢其他定時器時
* 會有延時
*/
typedefenum
{
RUN_IN_LOOP_MODE, /* 輪詢執行模式 */
RUN_IN_INTERRUPT_MODE, /* 中斷實時執行模式 */
}TimerRunModeType;
/**
* 軟件定時器基本類型
*/
typedefstruct
{
unsignedlong counter; /* 計數 */
unsignedlong duration; /* 定時時長 */
unsignedlong run_num; /* 自定義的定時次數 */
unsignedchar start_flag; /* 啟動標志 */
unsignedchar loop_flag; /* 輪詢標志 */
TimerRunModeType run_mode;
TimerTimingModeType timing_mode;
void (*callback_function)(void); /* 回調函數 */
}SoftTimer;
/**
* 刪除一個軟件定時器
*/
externvoidstop_timer(void (*callback_function)(void));
/*
* 創建一個軟件定時器并立刻開始計時
* return:沒有空閑定時器時返回1,創建成功時返回0
*/
externcharsoft_timer_start(TimerTimingModeType timing_mode,
TimerRunModeType run_mode,
unsignedlong run_num,
unsignedlong duration,
void (*callback_function)(void));
/**
* 系統main循環進程,用于執行輪詢模式的回調函數
*/
externvoidsoft_timer_main_loop(void);
/**
* 此函數為tick中斷服務函數,需要掛載在外部硬件定時器上
* 因此軟件定時器的定時精度由此函數掛載的硬件定時時間決定,
* 比如此函數掛載在定時50ms的外部定時器上,那么定時dutation
* 為20時定時時間就是20*50ms=1S
*/
externvoidsystem_tick_IrqHandler(void);
#endif/* !1__SOFT_TIMER_LIB_H */C文件:
/**
* sfor_timer_array.c
* 數組實現的軟件定時器庫
* 一個軟件定時器解決整個項目中所有的定時需求,回調函數可根據應用
* 自動切換中斷實時操作或者不實時的輪詢操作,可以解決硬件資源
* 不足或者軟件定時器定時不準的問題
*/
#include"soft_timer_array.h"
/**
* 軟件定時器內部變量
*/
static SoftTimer soft_timer[MAX_TIMER_NUM];
/*
* 創建一個軟件定時器并立刻開始計時
* return:沒有空閑定時器時返回1,創建成功時返回0
*/
charsoft_timer_start(TimerTimingModeType timing_mode, TimerRunModeType run_mode, unsignedlong run_num, unsignedlong duration,
void (*callback_function)(void))
{
unsignedchar i = 0;
if(!callback_function) return1;
stop_timer(callback_function); /* 先判斷有沒有一樣的定時器,有的話先刪除 */
for(i =0; i<MAX_TIMER_NUM;i++)
{
if(soft_timer[i].start_flag == 0) /* 查詢空閑的定時器 */
{
soft_timer[i].start_flag = 1;
soft_timer[i].counter = 0;
soft_timer[i].loop_flag = 0;
soft_timer[i].duration = duration;
soft_timer[i].run_mode = run_mode;
soft_timer[i].callback_function = callback_function;
if ((soft_timer[i].timing_mode = timing_mode) == DEFINE_NUM_MODE)
soft_timer[i].run_num = run_num; /* 只有在自定義運行次數的情況下此值才有效 */
break;
}
if (i == MAX_TIMER_NUM ) /* 沒有空閑定時器 */
return1;
}
return0;
}
/**
* 刪除一個軟件定時器
*/
voidstop_timer(void (*callback_function)(void))
{
unsignedchar i;
for(i = 0; i <MAX_TIMER_NUM; i++)
{
if(soft_timer[i].callback_function == callback_function)
{
soft_timer[i].start_flag = 0;
soft_timer[i].loop_flag = 0;
}
}
}
/**
* 系統main循環進程,用于執行輪詢模式的回調函數
*/
voidsoft_timer_main_loop(void)
{
unsignedchar i;
for(i = 0; i <MAX_TIMER_NUM; i++) /* 輪詢執行回調函數 */
{
if(soft_timer[i].loop_flag == 1)
{
soft_timer[i].loop_flag= 0;
soft_timer[i].callback_function();
// return; /* 運行一次任務后就退出去等跑一輪后運行下個程序,防止任務堆積在一起運行 */
}
}
}
/**
* 此函數為tick中斷服務函數,需要掛載在外部硬件定時器上
* 因此軟件定時器的定時精度由此函數掛載的硬件定時時間決定,
* 比如此函數掛載在定時50ms的外部定時器上,那么定時dutation
* 為20時定時時間就是20*50ms=1S
*/
voidsystem_tick_IrqHandler(void)
{
unsignedchar i = 0;
for(i =0 ;i <MAX_TIMER_NUM; i++)
{
if(soft_timer[i].start_flag !=0 ) /* 判斷當前定時器是否被開啟 */
{
if(++soft_timer[i].counter >= soft_timer[i].duration) /* 判斷當前計時有沒有到達 */
{
switch (soft_timer[i].timing_mode)
{
case ONCE_MODE:
soft_timer[i].start_flag = 0;
break;
case CONTINUE_MODE:
break;
case DEFINE_NUM_MODE:
if (soft_timer[i].run_num > 0)
{
if (--soft_timer[i].run_num == 0)
{
soft_timer[i].start_flag = 0;
}
}
default:
break;
}
if(soft_timer[i].run_mode == RUN_IN_INTERRUPT_MODE)
soft_timer[i].callback_function(); /* 中斷內直接運行回調函數,用于實時性比較高的程序 */
else
soft_timer[i].loop_flag = 1;
soft_timer[i].counter = 0;
}
// else
// {
// soft_timer[i].counter++;
// }
}
}
}聲明:本文素材來源網絡,版權歸原作者所有。如涉及作品版權問題,請與我聯系刪除。