近日在閱讀semtech的Lora-net/LoRaMac-node。此代碼是LoRaWAN MAC層的node段的代碼。
此代碼中構建了一個定時器鏈表,此鏈表構建得非常的巧妙,現在和大家分享。
此定時器鏈表底層使用的是RTC的鬧鍾(Alarm)機制(將日歷時間轉換成時間戳時間),而非使用一個定時器產生一個固定的定時(比如1ms),然后定時刷新整個鏈表。
也就是說此RTC定時器並非產生一個嘀嗒定時器來定時檢查定時器鏈表,而是直接根據鏈表你的表頭來直接定時,一步到位。
用RTC的方法相比較嘀嗒定時器定時的方法,工作效率會明顯提升,並不會因為鏈表中定時器數目的增加使得花費在刷新定時器上的時間增加,因為不需要遍歷整個鏈表。但代碼的實現難度會較高
假如程序剛開始執行,而且定時器鏈表為空,此時有4個定時事件需要放入鏈表,分別為 A 10ms B 30ms C 20ms D 40ms,
RTC鬧鍾鏈表:
其存儲的結果會是這樣:
事件名稱 | 定時時間 |
---|---|
A | 10 |
C | 10 |
B | 10 |
D | 10 |
而嘀嗒定時鏈表:
其存儲的結果會是這樣:
事件名稱 | 定時時間 |
---|---|
A | 10 |
B | 30 |
C | 20 |
D | 40 |
當時間過了5ms,RTC鬧鍾鏈表中存儲的數據並不會發生任何變化,因為它是以RTC的鬧鍾來作為刷新依據的,而嘀嗒定時鏈表中的數據就全發生了變化
嘀嗒定時鏈表 變化得到情況如下:
事件名稱 | 定時時間 |
---|---|
A | 5 |
B | 25 |
C | 15 |
D | 35 |
再過5ms,此時A事件的定時時間就到了,需要被執行,在RTC鬧鍾鏈表中的表現是RTC Alarm中斷觸發,在嘀嗒定時鏈表中的表現是A事件的定時時間逐漸減少至0。當A事件被執行之后兩種定時器鏈表中的存儲都發生了變化,都是原先的鏈表的頭指針指向原先的第二個節點,而原先的頭節點被釋放。
還是上述的例子,在定時器執行了7ms的時候,這時有個事件需要插入,為E 24ms,此時,兩種鏈表對於此事件器的插入操作也會明顯不同。
RTC鬧鍾插入之后
事件名稱 | 定時時間 |
---|---|
A | 10 |
C | 10 |
B | 10 |
E | 1 |
D | 9 |
而嘀嗒定時器在插入之后為
事件名稱 | 定時時間 |
---|---|
A | 3 |
B | 13 |
C | 23 |
D | 33 |
E | 24 |
以下是RTC鬧鍾的部分插入代碼,其中可以看到他的定時器插入的邏輯
elapsedTime = TimerGetValue( );//獲取距離上一次設置鬧鍾的時間
remainingTime = TimerListHead->Timestamp - elapsedTime;//remainingTime表示剩余的頭節點中的事件剩余的定時事件,因為此鏈表是按順序存儲的,所以頭節點中的定時時間一定是最少的
static void TimerInsertNewHeadTimer( TimerEvent_t *obj, uint32_t remainingTime )
{
TimerEvent_t* cur = TimerListHead;
if( cur != NULL )//表頭不為空,將新的定時器插入之前,將原先表頭的定時器時間減去新定時器的定時時間,確保原先的定時器任務定時正常
{
cur->Timestamp = remainingTime - obj->Timestamp;
cur->IsRunning = false;
}
obj->Next = cur;
obj->IsRunning = true;
TimerListHead = obj;
TimerSetTimeout( TimerListHead );//設置超時,等時間到的時候,會發生RTC報警
}
另外還有一點,此RTC中的1s並非物理時間的1s,在此具體的時間基准如下:
此項目中,使用的RTC的時鍾源為32.768Khz的LSE,通過AsynchPrediv和SynchPrediv分頻得到2.048KHz的RTCtick,計算公式為32.768/(3+1)/(3+1) = 2.048;
相關的配置代碼如下:
void RtcInit( void )
{
...
RtcHandle.Init.AsynchPrediv = 3;
RtcHandle.Init.SynchPrediv = 3;
...
}
/*!
* RTC Time base in ms
*/
#define RTC_ALARM_TICK_DURATION 0.48828125 // 1 tick every 488us
#define RTC_ALARM_TICK_PER_MS 2.048 // 1/2.048 = tick duration in ms
由於原本每個tick相當於1s,而在這里,每個tick相當於0.48828125ms,小於1ms,所以在程序中能夠實現ms級的定時任務。
RTC定時器的用法主要分為三步:
1. 初始化,注冊回調函數
void TimerInit( TimerEvent_t *obj, void ( *callback )( void ) )//設置回調函數
2. 設置定時時間
void TimerSetValue( TimerEvent_t *obj, uint32_t value )
3. 開啟定時時間
void TimerStart( TimerEvent_t *obj )