您的位置:首页 > 其它

OSAL之时间管理,软件定时器链表管理

2015-05-18 10:23 405 查看

读源码写作,尊重原创;

本博文根据蓝牙4.0, 协议族版本是1.3.2

本博文分两大块。一部分是时间管理,另一部分是软件定时器管理。

OSAL的时钟实现在OSAL_CockBLE.c与OSAL_Clock.h两个文件中。OSAL支持完整的UTC(世界统一时间),以2000年1月1日00:00:00为时间起点,可以精确到年、月、日、时、分、秒的时间值。

背景知识

[code]// number of seconds since 0 hrs, 0 minutes, 0 seconds, on the 1st of January 2000 UTC存储自2000年1月1日开始的**秒数**,uint32可存储大约135年的数据,就是到2135年左右。
typedef uint32 UTCTime;
UTCTime OSAL_timeSeconds = 0;//定义一个存储秒数的变量

// To be used with
typedef struct
{
  uint8 seconds;  // 0-59
  uint8 minutes;  // 0-59
  uint8 hour;     // 0-23
  uint8 day;      // 0-30
  uint8 month;    // 0-11
  uint16 year;    // 2000+
} UTCTimeStruct;//这是将UTCTime转化为UTCTimeStruct的结构

//几个宏定义,判断闰年
#define IsLeapYear(yr)  (!((yr) % 400) || (((yr) % 100) && !((yr) % 4)))
//每年多少天
#define YearLength(yr)  (IsLeapYear(yr) ? 366 : 365)
// (MAXCALCTICKS * 5) + (max remainder) must be <= (uint16 max),
// so: (13105 * 5) + 7 <= 65535这是防止微妙转化为毫秒是uint16溢出
#define MAXCALCTICKS  ((uint16)(13105))

#define BEGYEAR         2000     // UTC started at 00:00:00 January 1, 2000   UTC时间是自2000年1月1日开始

#define DAY             86400UL  // 24 hours * 60 minutes * 60 seconds一天的秒数

//全局变量
static uint16 previousLLTimerTick = 0;   //存储的是上次调用osalTimeUpdate获得的625us反转次数,也即上次调用的节拍数量
static uint16 remUsTicks = 0;   //存储的是每次更新us转ms *5/8的余数
static uint16 timeMSec = 0;     //存储的是ms转s   的余数


osalTimeUpdata

OSAL里面所有时间的根源都来自于硬件层一个定时器的更新,它是625us自动反转重新计时的定时器,只要计算前后两次获得它反转的次数想减那么就可以计算出之间花费了多少时间。

这个定时器由osalTimeUpdata调用。

[code]void osalTimeUpdate( void )
{
  uint16 tmp;  //记录这次获得625us逝去的次数
  uint16 ticks625us;   //记录前后两次625us逝去的次数
  uint16 elapsedMSec = 0;  //记录前后两次625 us逝去的毫秒数

  // Get the free-running count of 625us timer ticks
 tmp = ll_McuPrecisionCount();   //计算当前过去了多少个625us次数

  if ( tmp != previousLLTimerTick )
  {
    // Calculate the elapsed ticks of the free-running timer.
    ticks625us = tmp - previousLLTimerTick;

    // Store the LL Timer tick count for the next time through this function.存储这次过去的625us的次数
    previousLLTimerTick = tmp;

    /* It is necessary to loop to convert the usecs to msecs in increments so as
     * not to overflow the 16-bit variables.
     */
    while ( ticks625us > MAXCALCTICKS ) //主要为了数据过大转换为ms是溢出了。MAXCALCTICKS >13105就会溢出从零开始,对时间来说是不行的
    {
      ticks625us -= MAXCALCTICKS;
      elapsedMSec += MAXCALCTICKS * 5 / 8;  //计算逝去的毫秒;整除,MAXCALCTICKS =13105个节拍转换为ms是8190ms
      remUsTicks += MAXCALCTICKS * 5 % 8;   //计算当前循环的余数,余数为5,时间0.625ms;5除8 =0.625
    }
    }

    // update converted number with remaining ticks from loop and the
    // accumulated remainder from loop把余数加进去,组合成剩余的滴答数*5;这里限制了必须使得((ticks625us *5) + 7 < = 65535)
    tmp = (ticks625us * 5) + remUsTicks;

    // Convert the 625 us ticks into milliseconds and a remainder
    elapsedMSec += tmp / 8;   //将剩余的滴答数转化成ms
   remUsTicks = tmp % 8;      //将余数留到下次使用

    // Update OSAL Clock and Timers更新时钟和软件定时器
    if ( elapsedMSec )//
    {
      osalClockUpdate( elapsedMSec );//更新整个系统的时间,UTCTime以秒为单位记录  
      osalTimerUpdate( elapsedMSec );//传入过去流逝的时间值,进一步更新每个软件定时器,减去一定的时间  
    }
  }
}


个人觉得上面对MAXCALCTICKS处理有点问题,详情可以看下面OSAL时钟优化

osalClockUpdate( elapsedMSec );

用于更新系统时钟时间,保存到一个全局变量OSAL_timeSeconds中。参数是毫秒数据,

[code]static void osalClockUpdate( uint16 elapsedMSec )
{
  // Add elapsed milliseconds to the saved millisecond portion of time
  timeMSec += elapsedMSec;

  // Roll up milliseconds to the number of seconds
  if ( timeMSec >= 1000 )
  {
    OSAL_timeSeconds += timeMSec / 1000;
    timeMSec = timeMSec % 1000;   //存储ms转s余数
  }
}


时间管理的其他API

void osal_setClock函数是更新系统时间,以UTCTime为参数,秒级别

[code]void osal_setClock( UTCTime newTime )
{
  OSAL_timeSeconds = newTime;
}


osal_getClock函数用于获得当前的系统时钟时间

[code]UTCTime osal_getClock( void )
{
  return ( OSAL_timeSeconds );
}


osal_ConvertUTCTime函数是将时间从UTCTime转化成UTCTimeStruct

[code]void osal_ConvertUTCTime( UTCTimeStruct *tm, UTCTime secTime )
{
  // calculate the time less than a day - hours, minutes, seconds计算时间不足一天
  {
    uint32 day = secTime % DAY;//不足一天的秒数
    tm->seconds = day % 60UL;  //秒
    tm->minutes = (day % 3600UL) / 60UL;  //分
    tm->hour = day / 3600UL;   //小时
  }

  // Fill in the calendar - day, month, year填充日,月,年
  {
    uint16 numDays = secTime / DAY;//总的天数
    tm->year = BEGYEAR;
    while ( numDays >= YearLength( tm->year ) )
    {
      numDays -= YearLength( tm->year );//根据YearLength判定是否是闰年?返回366/365
      tm->year++;
    }

    tm->month = 0;
    while ( numDays >= monthLength( IsLeapYear( tm->year ), tm->month ) )//这里判定月份天数的算法比较独特
    {
      numDays -= monthLength( IsLeapYear( tm->year ), tm->month );
      tm->month++;
    }

    tm->day = numDays;
  }
}
//下面是对月份判断的代码
static uint8 monthLength( uint8 lpyr, uint8 mon )
{
  uint8 days = 31;

  if ( mon == 1 ) // feb
  {
    days = ( 28 + lpyr );
  }
  else
  {
    if ( mon > 6 ) // aug-dec
    {
      mon--;
    }

    if ( mon & 1 )
    {
      days = 30;
    }
  }

  return ( days );
}



月份的计算小算法

osal_ConvertUTCSecs 函数

//下面这里是将UTCStruct时间转化为uint32的秒数

[code]UTCTime osal_ConvertUTCSecs( UTCTimeStruct *tm )
{
  uint32 seconds;

  /* Seconds for the partial day */
  seconds = (((tm->hour * 60UL) + tm->minutes) * 60UL) + tm->seconds;//计算不足一天的秒数

  /* Account for previous complete days计算当前月已过去的天数 ,比如20,就等于20天*/
  {
    /* Start with complete days in current month计算已当前月过去的月份 */
    uint16 days = tm->day;

    /* Next, complete months in current year计算当年已过去的月,比如month = 2,三月,过了两月了,执行两次循环 */
    {
      int8 month = tm->month;
      while ( --month >= 0 )
      {
        days += monthLength( IsLeapYear( tm->year ), month );
      }
    }

    /* Next, complete years before current year计算自2000年到当前多了多少年 */
    {
      uint16 year = tm->year;
      while ( --year >= BEGYEAR )
      {
        days += YearLength( year );
      }
    }

    /* Add total seconds before partial day */
    seconds += (days * DAY);
  }

  return ( seconds );
}


//下面这个函数根据上面osalTimeUpdate函数更新系统时间osal_systemClock,更新软件定时器里面定时值。OSAL的定时器的相关代码实现OSAL_Timers.c与OSAL_Timers.h

osalTimerUpdate( elapsedMSec );

函数功能是用过去的毫秒值去更新软件定时器的值,减去时间;

软件定时器相关的背景知识

[code]typedef union {
  uint32 time32;
  uint16 time16[2];
  uint8 time8[4];
} osalTime_t;

typedef struct
{
  void   *next;
  osalTime_t timeout;
  uint16 event_flag;
  uint8  task_id;
  uint32 reloadTimeout;
} osalTimerRec_t;
//上面是一个软件定时器的结构体,OSAL的定时器主要为任务提供定时服务,所以定时器的结构需要包括任务的ID(task_id)以及它承载的事件ID(event_flag),以便告诉OSAL让哪个任务做什么事情;除此之外定时器还需要记录的定时时间(timeout)以及超时后需要重载的定时值(reloadTimeout)。
//一个用来记录系统上电的运行时间
static uint32 osal_systemClock;
osalTimerRec_t *timerHead;  //定义一个头指针,指向软件定时器链表


例程讲解

[code]void osalTimerUpdate( uint32 updateTime )
{
  halIntState_t intState;
  osalTimerRec_t *srchTimer;  //遍历软件定时器链表
  osalTimerRec_t *prevTimer;

  osalTime_t timeUnion;
  timeUnion.time32 = updateTime;//共用体,下面可以看到这个共用体是为了可以字节操作timeout而设置的。这是共用体的好处吧

  HAL_ENTER_CRITICAL_SECTION( intState );  // Hold off interrupts.
  // Update the system time
  osal_systemClock += updateTime;//将逝去的时间更新到系统时钟,以毫秒级别
  HAL_EXIT_CRITICAL_SECTION( intState );   // Re-enable interrupts.

  // 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 )
    {
      osalTimerRec_t *freeTimer = NULL;//记录待释放的软件定时器

      HAL_ENTER_CRITICAL_SECTION( intState );  // Hold off interrupts.

      // To minimize time in this critical section, avoid 32-bit math为了避免32位匹配,计算最小时间
      if ((timeUnion.time16[1] == 0) && (timeUnion.time8[1] == 0))
      {
        // If upper 24 bits are zero, check lower 8 bits for roll over逝去的时间只有 1 byte
        if (srchTimer->timeout.time8[0] >= timeUnion.time8[0])
        {
          // 8-bit math减去逝去的时间
          srchTimer->timeout.time8[0] -= timeUnion.time8[0];
        }
        else
        {
          // 32-bit math
          if (srchTimer->timeout.time32 > timeUnion.time32)
          {
            srchTimer->timeout.time32 -= timeUnion.time32;
          }
          else
          {
            srchTimer->timeout.time32 = 0;
          }
        }
      }
      else
      {
          // 32-bit math
        if (srchTimer->timeout.time32 > timeUnion.time32)
        {
          srchTimer->timeout.time32 -= timeUnion.time32;//减去逝去的时间
        }
        else
        {
          srchTimer->timeout.time32 = 0;//定时时间到
        }
      }

      // Check for reloading对于自动重载的软件定时器处理
      if ( (srchTimer->timeout.time16[0] == 0) && (srchTimer->timeout.time16[1] == 0) &&
           (srchTimer->reloadTimeout) && (srchTimer->event_flag) )
      {
        // Notify the task of a timeout时间到,设定事件
        osal_set_event( srchTimer->task_id, srchTimer->event_flag );

        // Reload the timer timeout value重新设定时间
        srchTimer->timeout.time32 = srchTimer->reloadTimeout;
      }

      // When timeout or delete (event_flag == 0)对于只定时一次的软件定时器处理和要删除的软件定时器处理
      if ( ((srchTimer->timeout.time16[0] == 0) && (srchTimer->timeout.time16[1] == 0)) ||
            (srchTimer->event_flag == 0) )
      {
        // Take out of list
        if ( prevTimer == NULL )
        {
          timerHead = srchTimer->next;
        }
        else
        {
          prevTimer->next = srchTimer->next;
        }

        // Setup to free memory
        freeTimer = srchTimer;//记录当前待释放的定时器

        // Next
        srchTimer = srchTimer->next;
      }
      else
      {
        // Get next如果没有软件定时器到时,则遍历下一个软件定时器
        prevTimer = srchTimer;
        srchTimer = srchTimer->next;
      }

      HAL_EXIT_CRITICAL_SECTION( intState );   // Re-enable interrupts.

      if ( freeTimer )
      {
        if ( (freeTimer->timeout.time16[0] == 0) && (freeTimer->timeout.time16[1] == 0) )
        {
          osal_set_event( freeTimer->task_id, freeTimer->event_flag );
        }
        osal_mem_free( freeTimer );//释放软件定时器
      }
    }
  }
}


上面这个函数是对软件定时器链表的所有定时器时间进行更新。

OSAL维护着一个定时器全局链表timerHead,每当调用osal_start_timerEx或者osal_start_reload_timer实际再调用osalAddTimer,来创建一个新的软件Timer。

[code]osalTimerRec_t * osalAddTimer( uint8 task_id, uint16 event_flag, uint32 timeout )
{
  osalTimerRec_t *newTimer;
  osalTimerRec_t *srchTimer;

  // Look for an existing timer first
  newTimer = osalFindTimer( task_id, event_flag );查找是否已存在这个任务对应事件的的定时器;
  if ( newTimer )
  {
    // Timer is found - update it.如果存在更新超时时间
    newTimer->timeout.time32 = timeout;

    return ( newTimer );
  }
  else
  {
    // New Timer若没有,只要创建新的软件定时器
    newTimer = osal_mem_alloc( sizeof( osalTimerRec_t ) );

    if ( newTimer )
    {
      // Fill in new timer
      newTimer->task_id = task_id;
      newTimer->event_flag = event_flag;
      newTimer->timeout.time32 = timeout;
      newTimer->next = (void *)NULL;
      newTimer->reloadTimeout = 0;

      // Does the timer list already exist
      if ( timerHead == NULL )
      {
        // Start task list如果软件定时器为空,那么指向第一个软件定时器
        timerHead = newTimer;
      }
      else
      {
        // Add it to the end of the timer list不然加到线性表末尾
        srchTimer = timerHead;

        // Stop at the last record
        while ( srchTimer->next )
          srchTimer = srchTimer->next;

        // Add to the list
        srchTimer->next = newTimer;
      }

      return ( newTimer );
    }
    else
    {
      return ( (osalTimerRec_t *)NULL );
    }
  }
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: