我使用的协议栈版本及例子信息:
ZigBee2006\Texas Instruments\ZStack-1.4.3-1.2.1\Projects\zstack\Samples\SampleApp
……OOXX……~~~~
个人觉得,在协议栈里面涉及到两类定时器:
一类是硬件定时器,对应cc2430上的几个Timer。系统时钟定时器为硬件定时器
另一类是软件定时器,通过osal_start_timer()添加到软定时器链表,再由系统时钟进行统一减计数。
在"OSAL调试机制"中记录过这段话:
时间管理
对事件进行时间管理,OSAL采用了链表的方式进行,有时发生一个要被处理的事件,就启动一个逻辑上的定时器,并将此定时器添加到链表当中。利用硬件定时器作为时间操作的基本单元。设置时间操作的最小精度为1ms,每1ms硬件定时器便产生一个时间中断,在时间中断处理程序中去更新定时器链表。每次更新,就将链表中的每一项时间计数减1,如果发现定时器链表中有某一表项时间计数已减到0,则将这个定时器从链表中删除,并设置相应的事件标志。这样任务调度程序便可以根据事件标志进行相应的事件处理。
时间管理函数:
extern byte osal_start_timer(byte task_id, uint16 event_id, uint16 timeout_value);
这个函数为事件event_id设置超时等待时间timeout_value。一旦等待结束,便为task_id所对应的任务设置相应的事件发生标记,进行相应处理.
这段话中的硬件定时器,即为系统时钟定时器,定时时间为“TICK_TIME—系统时间片”。
1、何为系统时间片?
引用outman的话:每个操作系统都有一个“节拍”-tick,就像每一个“活人”都有心跳一样。
2、系统时间片是多少?
在OnBoard.h中定义:
/* OSAL timer defines */
#define TICK_TIME 1000 // Timer per tick - in micro-sec
1000us,即1ms,
3、系统时钟什么时候初始化?
在OnBoard.c中InitBoard():
/***************************************
void InitBoard( byte level )//在zmain.c中先OB_COLD启动,后OB_READY启动
{
if ( level == OB_COLD )//冷启动
{
…………
/* Timer2 for Osal timer
* This development board uses ATmega128 Timer/Counter3 to provide
* system clock ticks for the OSAL scheduler. These functions perform
* the hardware specific actions required by the OSAL_Timers module.
*/
OnboardTimerIntEnable = FALSE;
HalTimerConfig (OSAL_TIMER, // 8bit timer2
HAL_TIMER_MODE_CTC, // Clear Timer on Compare
HAL_TIMER_CHANNEL_SINGLE, // Channel 1 - default
HAL_TIMER_CH_MODE_OUTPUT_COMPARE, // Output Compare mode
OnboardTimerIntEnable, // FALSE
Onboard_TimerCallBack); // Channel Mode
}
…………
}
/***************************************
4、系统时钟对应的cc2430哪个定时器?
从上面的初始化中可以看到 Timer2 for Osal timer,但事实上就像outman说的,要跟halTimerRemap()函数对应起来:
/****************
* @fn halTimerRemap
*
* @brief Maps(映射) API HAL_TIMER_ID to HW Timer ID.
* HAL_TIMER_0 --> HW Timer 3
* HAL_TIMER_2 --> HW Timer 4
* HAL_TIMER_3 --> HW Timer 1
*
* @param timerId - ID of the timer
/****************
Timer2映射到硬件上去即为Timer4
5、系统时钟什么用?
系统时钟定时器由osal_timer_activate()开启,溢出时调用其回调函数(中间这一系列流程后面具体记录),回调函数在InitBoard()被初始化为Onboard_TimerCallBack(),而Onboard_TimerCallBack()调用了
osal_update_timers(),而osal_update_timers()调用osalTimerUpdate()对系统软定时器链表中的每一项定时器时间计数减1ms,如果有软定时器计数减到0,删除这个软定时器并调用osal_set_event()设置相应事件发生标志。总结起来就是每1ms系统时钟都会跑到软件定时器链表中去把各定时器的计数值减1.
6、系统时钟定时器溢出时会不会产生中断进入中断函数?
不会.
OnBoard.h中初始化
/* OSAL Timer define */
#define OSAL_TIMER HAL_TIMER_2
而通过上面的halTimerRemap()知道HAL_TIMER_2对应HW Timer 4
在InitBoard()函数中有如下定义:
OnboardTimerIntEnable = FALSE; //对系统时钟定时器来说,默认为非中断方式
HalTimerConfig (OSAL_TIMER, // 8bit timer2
HAL_TIMER_MODE_CTC, // Clear Timer on Compare
HAL_TIMER_CHANNEL_SINGLE, // Channel 1 - default
HAL_TIMER_CH_MODE_OUTPUT_COMPARE, // Output Compare mode
OnboardTimerIntEnable, // FALSE
Onboard_TimerCallBack); // Channel Mode
在 HalTimerConfig()函数中
hwtimerid = halTimerRemap (timerId)
halTimerRecord[hwtimerid].intEnable = intEnable;
因此把系统时钟定时器映射到这里,变为
halTimerRecord[HW_TIMER_4].intEnable = intEnable = FALSE
因此当系统定时器溢出时不会产生中断,判断有没有溢出是在HalTimerTick()里的halProcessTimer4()进行的,当溢出时则是调用halTimerSendCallBack()进行处理。
//########################################################################
看下 osalTimerUpdate()这个函数
/*********************************************************************
* @fn osalTimerUpdate
*
* @brief Update the timer structures for a timer tick.
*
* @param none
*
* @return none
*********************************************************************/
static void osalTimerUpdate( uint16 updateTime )
{
…………
// Update the system time
osal_systemClock += updateTime; //系统时间,1ms往上加
// Look for open timer slot
if ( timerHead != NULL )
{
// Add it to the end of the timer list
srchTimer = timerHead;
prevTimer = (void *)NULL;
// Look for open timer slot
while ( srchTimer )
{
// Decrease the correct amount of time
if (srchTimer->timeout <= updateTime) //小于等于
srchTimer->timeout = 0;
else //大于
srchTimer->timeout = srchTimer->timeout - updateTime; //减1ms
// When timeout, execute the task
if ( srchTimer->timeout == 0 )
{
osal_set_event( srchTimer->task_id, srchTimer->event_flag ); //设置事件发生标志
…………
}
…………
}
…………
}
/*********************************************************************
osalTimerUpdate()来以ms为单位对软定时器链表中的“软定时器”减计数,溢出时,即调用osal_set_event触发事件。
//########################################################################
下面具体讲下系统时钟运行流程,纯属个人理解!由上面分析可知系统时钟定时器默认采用非中断方式。
(首先假定系统时钟已经开启,因为觉得时钟什么时候开启也是个问题,后面再分析下)
在系统主循环函数osal_start_system()调用了Hal_ProcessPoll()
Hal_ProcessPoll()调用HalTimerTick()和HalUARTPoll(),其中HalTimerTick()就是系统对定时器的轮询.下面进入这个函数:
/****************
void HalTimerTick (void)
{
if (!halTimerRecord[HW_TIMER_1].intEnable) //判断定时器是否允许中断
{
halProcessTimer1 (); //不允许中断则调用此函数
}
if (!halTimerRecord[HW_TIMER_3].intEnable)
{
halProcessTimer3 ();
}
if (!halTimerRecord[HW_TIMER_4].intEnable) //系统时钟定时器 默认intEnable=FALSE
{
halProcessTimer4 ();
}
}
/****************
( 这里分析下如果定时器采用的是中断方式的情况。
Timer1/3/4如果采用中断,当溢出时就会跳到中断处理函数,各定时器的中断函数为(1/3/4):
HAL_ISR_FUNCTION( halTimer1/3/4Isr, T1/3/4_VECTOR )
{
halProcessTimer1/3/4 ();
}
可以看到进入中断函数后也是跳到halProcessTimer()执行,因此接下来的流程跟后面的非中断方式一致.)
HalTimerTick()函数判断定时器是否允许中断,不允许则调用halProcessTimer(),在halProcessTimer()里判断定时器是否溢出.溢出则调用halTimerSendCallBack()来发送消息给相应硬件定时器的回调函数并调用。看下halTimerSendCallBack()
/****************
void halTimerSendCallBack (uint8 timerId, uint8 channel, uint8 channelMode)
{
uint8 hwtimerid;
hwtimerid = halTimerRemap (timerId);
if (halTimerRecord[hwtimerid].callBackFunc)
(halTimerRecord[hwtimerid].callBackFunc) (timerId, channel, channelMode);
}
/****************
看到函数最终调用相应定时器的回调函数.系统时钟定时器的回调函数在InitBoard()初始化为Onboard_TimerCallBack()
/****************
void Onboard_TimerCallBack ( uint8 timerId, uint8 channel, uint8 channelMode)
{
//#define OSAL_TIMER HAL_TIMER_2
if ((timerId == OSAL_TIMER) && (channelMode == HAL_TIMER_CH_MODE_OUTPUT_COMPARE))
{
osal_update_timers();
}
}
/****************
可以看到回调函数中调用了osal_update_timers(),而osal_update_timers()调用osalTimerUpdate(),由osalTimerUpdate来以ms为单位对“软定时器”减计数,溢出时便调用osal_set_event()触发事件。
以上就是系统时钟定时器运行流程,具体参考了outman的《OSAL系统框架专题》这篇文章,已转载!
定时器非中断方式函数调用流程:
osal_timer_activate( TRUE )开启系统时钟定时器——系统主循环函数osal_start_system()——调用Hal_ProcessPoll()轮询硬件——调用HalTimerTick()轮询定时器——定时器采用非中断方式则调用halProcessTimer()判断定时器是否溢出——溢出则调用halTimerSendCallBack()来发送消息给相应定时器的回调函数——调用各定时器的回调函数,如系统时钟定时器的Onboard_TimerCallBack()——调用osal_update_timers()来更新软件定时器链表中各定时器的计数值(每次减1ms)——如有软件定时器溢出调用osal_set_event()触发事件。
回到刚有个问题:系统时钟定时器什么时候开启?
初始化定时器函数osalTimerInit()中:
/*********************************
* @fn osalTimerInit
*
* @brief Initialization for the OSAL Timer System.
*
* @param none
*
* @return
*/
void osalTimerInit( void )
{
// Initialize the rollover modulo
tmr_count = TICK_TIME; //1000us=1ms
tmr_decr_time = TIMER_DECR_TIME; //1ms
// Initialize the system timer
osal_timer_activate( false );
timerActive = false;
osal_systemClock = 0;
}
/*********************************
可以看到默认osal_timer_activate( false )是没有开启的。
我看到过一篇文章《ZigBee学习之35—按键部分及系统调用时钟的分析》,里面也讨论了系统时钟什么时候开启的问题,最终的最终结果是:系统时钟是在osal_start_timerEx()这个函数里面被开启的。具体请参考这篇文章。
/*********************************
byte osal_start_timerEx( byte taskID, UINT16 event_id, UINT16 timeout_value )
{
…………
// Does the timer need to be started?
if ( timerActive == FALSE )
{
osal_timer_activate( TRUE );
}
…………
}
/*********************************
osalTimerInit()函数中初始化timerActive = false,因此会在osal_start_timerEx()中执行osal_timer_activate( TRUE )。
终上述,个人认为,系统什么时候开始运行第一个osal_start_timerEx(),就什么时候开启系统时钟。即当系统的软定时器链表中添加第一个软件定时器时,就开启系统时钟。那什么时候开始运行第一个osal_start_timerEx()??全协议栈搜寻osal_start_timerEx()函数,暂时还没找到确切的答案.以后再看……
//########################################################################
说明:
本文作者所记录,以上基本为个人见解,错误之处还请高手指点,本人随时更新,转载请注明出处,谢谢!
参考资料:《ZigBee学习之35—按键部分及系统调用时钟的分析》
《OSAL系统框架专题》