近日在閱讀semtech的Lora-net/LoRaMac-node。此代碼是LoRaWAN MAC層的node段的代碼。node
此代碼中構建了一個定時器鏈表,此鏈表構建得很是的巧妙,如今和你們分享。git
此定時器鏈表底層使用的是RTC的鬧鐘(Alarm)機制(將日曆時間轉換成時間戳時間),而非使用一個定時器產生一個固定的定時(好比1ms),而後定時刷新整個鏈表。github
也就是說此RTC定時器並不是產生一個嘀嗒定時器來定時檢查定時器鏈表,而是直接根據鏈表你的表頭來直接定時,一步到位。函數
用RTC的方法相比較嘀嗒定時器定時的方法,工做效率會明顯提高,並不會由於鏈表中定時器數目的增長使得花費在刷新定時器上的時間增長,由於不須要遍歷整個鏈表。但代碼的實現難度會較高ui
假如程序剛開始執行,並且定時器鏈表爲空,此時有4個定時事件須要放入鏈表,分別爲 A 10ms B 30ms C 20ms D 40ms,指針
RTC鬧鐘鏈表:
其存儲的結果會是這樣:code
事件名稱 | 定時時間 |
---|---|
A | 10 |
C | 10 |
B | 10 |
D | 10 |
而嘀嗒定時鏈表:
其存儲的結果會是這樣:事件
事件名稱 | 定時時間 |
---|---|
A | 10 |
B | 30 |
C | 20 |
D | 40 |
當時間過了5ms,RTC鬧鐘鏈表中存儲的數據並不會發生任何變化,由於它是以RTC的鬧鐘來做爲刷新依據的,而嘀嗒定時鏈表中的數據就全發生了變化
嘀嗒定時鏈表 變化獲得狀況以下:rem
事件名稱 | 定時時間 |
---|---|
A | 5 |
B | 25 |
C | 15 |
D | 35 |
再過5ms,此時A事件的定時時間就到了,須要被執行,在RTC鬧鐘鏈表中的表現是RTC Alarm中斷觸發,在嘀嗒定時鏈表中的表現是A事件的定時時間逐漸減小至0。當A事件被執行以後兩種定時器鏈表中的存儲都發生了變化,都是原先的鏈表的頭指針指向原先的第二個節點,而原先的頭節點被釋放。get
仍是上述的例子,在定時器執行了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 )