定时器触发事件流程

时间:2022-04-17 00:15:30

定时器触发事件流程

参考文章:
http://bbs.feibit.com/thread-3420-1-1.html
https://e2e.ti.com/support/wireless_connectivity/f/158/t/189014

以osal_start_timerEx(uint8 taskID, uint16 event_id, uint16 timeout_value)函数为出发点,看看时间到了,事件是怎么被置位的。

OSAL.Timers.c:
osal_start_timerEx(uint8 taskID, uint16 event_id, uint16 timeout_value)
{
.......
newTimer = osalAddTimer( taskID, event_id, timeout_value );
......
}

函数最核心的就是上面这条语句,猜想z-stack中应该也有一个定时器队列(类似消息队列),这个队列应该是按心跳计时?
——————————————————————————————

OSAL.Timers.c:
osalAddTimer( uint8 task_id, uint16 event_flag, uint16 timeout )
{
newTimer = osalFindTimer( task_id, event_flag );

if ( newTimer )
{
// Timer is found - update it.
newTimer->timeout = timeout;
return ( newTimer );
}

else
{
newTimer = osal_mem_alloc( sizeof( osalTimerRec_t ) );
if ( newTimer )
{
newTimer->task_id = task_id;
newTimer->event_flag = event_flag;
newTimer->timeout = timeout;
newTimer->next = (void *)NULL;
newTimer->reloadTimeout = 0;

if ( timerHead == NULL )
{
timerHead = newTimer;
}

else
{
srchTimer = timerHead;
while ( srchTimer->next )
srchTimer = srchTimer->next;

srchTimer->next = newTimer;
}

return ( newTimer );
}
else
return ( (osalTimerRec_t *)NULL );
}

函数开始先寻找这个定时任务是不是已经存在了,如果存在进入if条件,更新剩余时间值,返回。如果不存在进入else,生成新的定时任务,并填充osalTimerRec_t结构体。
这里还有一个判断定时队列不是空的,如果空的就把这个新生成的定时任务设为头结点;如果队列不空,则遍历队列,找到最后一个结点,把newTimer插到队尾。

——————————————————————————————————

OSAL.Timers.c:
osalFindTimer( uint8 task_id, uint16 event_flag )
{
.......
while ( srchTimer )
{
if ( srchTimer->event_flag == event_flag &&srchTimer->task_id == task_id )
break;


srchTimer = srchTimer->next;
}

return ( srchTimer );
}

由于采用独热码编码,事件编码类型又是uint16的,所以每个任务下可以定义最多15个事件(有一个0x8000是强制事件),“srchTimer->event_flag == event_flag &&srchTimer->task_id == task_id”这一句也能看出定时队列是以事件为节点的。

——————————————————————————————————
说了这么多,还只是说了一个定时事件是怎么插入到定时队列的。对于插入之后时间的更新还没说,时间到了怎么设置事件标志也没说。

我想,既然是定时事件,那么事件的时间肯定要倒计时的,倒计时应该出现在一个循环中,自然而然想到了osal_run_system( void )。进入找找看。

—————————————————————————————————

OSAL_Clock.c:
在osal_run_system()中有osalTimeUpdate( )
{
......
tmp = macMcuPrecisionCount();

if ( tmp != previousMacTimerTick )
{
ticks320us = (tmp - previousMacTimerTick) & 0xffffffffu; //两次运行间隔

previousMacTimerTick = tmp;

tmp = (ticks320us * 8) + remUsTicks;

CONVERT_320US_TO_MS_ELAPSED_REMAINDER( tmp, elapsedMSec, remUsTicks );

if ( elapsedMSec )
{
osalClockUpdate( elapsedMSec );
osalTimerUpdate( elapsedMSec );
}
}
}

tmp为计数器,函数返回的是溢出次数,每次溢出的单位是320us,就是说tmp值记录了每一次调用这个函数时候timer2计数器经过了多少次320us。
只要系统不是刚上电运行,if的判断语句就为真,因为tmp随着系统运行不断变大的(没搞懂什么时候溢出?)两值相减得出了两次运行到这里的心跳差。
把tmp赋值给previousMacTimerTick ,用来进行下一次运行到这里的比较。
接下来代码的解释参考http://bbs.feibit.com/thread-3420-1-1.html的解释,里面非常清楚。
经过CONVERT_320US_TO_MS_ELAPSED_REMAINDER这个宏定义之后,将上次运行之后经过的时间保存到elapsedMSec中(单位是ms)。

——————————————————————————————————

OSAL_Timers.c:
osalTimerUpdate( uint16 updateTime )
{
......
if ( timerHead != NULL ) //如果定时队列不空
{
srchTimer = timerHead; //把队列头结点赋值
prevTimer = (void *)NULL;

while ( srchTimer )
{
osalTimerRec_t *freeTimer = NULL; //生成一个*定时器,用来保存剩余时间为0的节点

......

if (srchTimer->timeout <= updateTime) //剩余时间小于已经经过的时间
{
srchTimer->timeout = 0;
}
else //剩余时间大于经过时间的则减去经过时间
{
srchTimer->timeout = srchTimer->timeout - updateTime;
}

if ( (srchTimer->timeout == 0) && (srchTimer->reloadTimeout) && (srchTimer->event_flag) )
{
osal_set_event( srchTimer->task_id, srchTimer->event_flag );

srchTimer->timeout = srchTimer->reloadTimeout;
}

// 这个函数很有意思,前面被置0的任务不是在这里被osal_set_event的,因为osal_start_timerEx()里面的osalAddTimer()的函数体里面有newTimer->reloadTimeout = 0。意思就是说每个加到定时器队列的节点的reloadTimeout值都是0,那么肯定进不去这个if语句,那这个语句要来干嘛?(估计是有其他层,里面会改动这个值,但我还没看到)
```


if ( srchTimer->timeout == 0 || srchTimer->event_flag == 0 )
{
if ( prevTimer == NULL ) //如果删除的是头结点
timerHead = srchTimer->next;
else //删除的不是头结点
prevTimer->next = srchTimer->next;

freeTimer = srchTimer; //保存“定时时间到”的节点

srchTimer = srchTimer->next; //往下一个节点走,这样才能遍历完整个队列
}

else //更新了之后剩余时间不为0
{
prevTimer = srchTimer;
srchTimer = srchTimer->next;
}

if ( freeTimer ) //这里才是osal_set_event()的地方
{
if ( freeTimer->timeout == 0 )
{
osal_set_event( freeTimer->task_id, freeTimer->event_flag );
}//为什么要加这个if呢,因为如果一轮遍历下来都没有时间为0的事件,那么开头生成的结构体就要释放,否则内存会挤满
osal_mem_free( freeTimer );
}
}
}

这样子的话,主循环每次进入osalTimeUpdate( )就是更新队列里面的节点的时间,实现了定时任务的倒计时。