此定时器链表底层使用的是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 )