近日在阅读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 )