ZigBee系统定时器

时间:2021-11-18 20:35:24

我使用的协议栈版本及例子信息:

ZigBee2006\Texas Instruments\ZStack-1.4.3-1.2.1\Projects\zstack\Samples\SampleApp

 ……ZigBee系统定时器OOXX……ZigBee系统定时器~~~~

个人觉得,在协议栈里面涉及到两类定时器:
一类是硬件定时器,对应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系统定时器
本文作者所记录,以上基本为个人见解,错误之处还请高手指点,本人随时更新,转载请注明出处,谢谢!

参考资料:《ZigBee学习之35—按键部分及系统调用时钟的分析》

                   《OSAL系统框架专题》