LoRaWAN节点RTC定时器链表分析

在LoRaWAN子机节点的官方代码中,整个系统的定时器采用RTC定时器链表的形式完成。

今天参考了这篇文档,试着分析了下子机节点的RTC定时器链表。http://www.cnblogs.com/answerinthewind/p/6206521.html

定时器链表的底层是采用STM32的内部RTC作为基准时间,但是这里的基准时钟不是以1S为基准。官方源码中定义基准时间如下所示,从源码中可以得知,RTC的基准时间为0.48828125ms。

--------rtc-board.c--------
/*!
 * 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

在配置RTC的时候,配置了RTC的时钟为32.768kHz晶振的16分频,即2.048kHz。所以,RTC时钟累计每增加1,实际时间不是1S,而是0.48828125ms。这样设计的目的是实现定时器毫秒级别的定时。如果要让RTC时钟每增加1,时间时间为1S,32.768kHz的时钟要进行32768分频,即得到1Hz的时钟脉冲供RTC模块工作。

void RtcInit( void )
{
    RtcCalendar_t rtcInit;

    if( RtcInitialized == false )
    {
        .....

        //32.768k/(3+1)/(3+1)=2.048kHz
        RtcHandle.Init.AsynchPrediv = 3;
        RtcHandle.Init.SynchPrediv = 3;

        RtcHandle.Init.OutPut = RTC_OUTPUT_DISABLE;
        RtcHandle.Init.OutPutPolarity = RTC_OUTPUT_POLARITY_HIGH;
        RtcHandle.Init.OutPutType = RTC_OUTPUT_TYPE_OPENDRAIN;
        HAL_RTC_Init( &RtcHandle );
        .....
    }
}

在源码中,RTC时钟模块对输入的2.048kHz脉冲进行计数,因此调用RtcGetCalendar()函数实际获取的是脉冲个数,而不是实际的时间值。

//从内部RTC里获取脉冲数
static RtcCalendar_t RtcGetCalendar( void )
{
    RtcCalendar_t calendar;
    HAL_RTC_GetTime( &RtcHandle, &calendar.CalendarTime, RTC_FORMAT_BIN );  
    HAL_RTC_GetDate( &RtcHandle, &calendar.CalendarDate, RTC_FORMAT_BIN );  
    calendar.CalendarCentury = Century;
    RtcCheckCalendarRollOver( calendar.CalendarDate.Year );
    return calendar;
}

从内部RTC获取到脉冲数之后,要把它转换成时间戳,使用的是RtcConvertCalendarTickToTimerTime()函数。

//将内部RTC时钟脉冲数转换成时间戳,即 RTC脉冲数-->时间戳
static TimerTime_t RtcConvertCalendarTickToTimerTime( RtcCalendar_t *calendar )
{
    TimerTime_t timeCounter = 0;
    RtcCalendar_t now;
    double timeCounterTemp = 0.0;

    // Passing a NULL pointer will compute from "now" else,
    // compute from the given calendar value
    if( calendar == NULL )
    {
        now = RtcGetCalendar( );    //获取当前日历进行计算
    }
    else
    {
        now = *calendar;
    }

    // Years (calculation valid up to year 2099)
    for( int16_t i = 0; i < ( now.CalendarDate.Year + now.CalendarCentury ); i++ )  //年数转换成时钟脉冲数
    {
        if( ( i == 0 ) || ( i % 4 ) == 0 )
        {
            timeCounterTemp += ( double )SecondsInLeapYear;
        }
        else
        {
            timeCounterTemp += ( double )SecondsInYear;
        }
    }

    // Months (calculation valid up to year 2099)*/
    if( ( now.CalendarDate.Year == 0 ) || ( ( now.CalendarDate.Year + now.CalendarCentury ) % 4 ) == 0 )    //月数转换成时钟脉冲数
    {
        for( uint8_t i = 0; i < ( now.CalendarDate.Month - 1 ); i++ )
        {
            timeCounterTemp += ( double )( DaysInMonthLeapYear[i] * SecondsInDay );
        }
    }
    else
    {
        for( uint8_t i = 0;  i < ( now.CalendarDate.Month - 1 ); i++ )
        {
            timeCounterTemp += ( double )( DaysInMonth[i] * SecondsInDay );
        }
    }

    //时分秒日转换成秒数
    timeCounterTemp += ( double )( ( uint32_t )now.CalendarTime.Seconds +
                     ( ( uint32_t )now.CalendarTime.Minutes * SecondsInMinute ) +
                     ( ( uint32_t )now.CalendarTime.Hours * SecondsInHour ) +
                     ( ( uint32_t )( now.CalendarDate.Date * SecondsInDay ) ) );

    timeCounterTemp = ( double )timeCounterTemp * RTC_ALARM_TICK_DURATION;  //将内部RTC时钟脉冲数转换成时间戳,单位ms

    timeCounter = round( timeCounterTemp ); //时间戳四舍五入求值
    return ( timeCounter );
}

RtcConvertCalendarTickToTimerTime()函数相反的是将时间戳转换成脉冲数函数RtcConvertTimerTimeToCalendarTick()

//将时间戳转换成内部RTC时钟脉冲数,即 时间戳-->RTC脉冲数
RtcCalendar_t RtcConvertTimerTimeToCalendarTick( TimerTime_t timeCounter )
{
    RtcCalendar_t calendar = { 0 };

    uint16_t seconds = 0;
    uint16_t minutes = 0;
    uint16_t hours = 0;
    uint16_t days = 0;
    uint8_t months = 1; // Start at 1, month 0 does not exist
    uint16_t years = 0;
    uint16_t century = 0;
    double timeCounterTemp = 0.0;

    timeCounterTemp = ( double )timeCounter * RTC_ALARM_TICK_PER_MS;    //将时间戳转成脉冲数

    // Convert milliseconds to RTC format and add to now
    // 将脉冲数转成RTC格式
    while( timeCounterTemp >= SecondsInLeapYear )
    {
        if( ( years == 0 ) || ( years % 4 ) == 0 )
        {
            timeCounterTemp -= SecondsInLeapYear;
        }
        else
        {
            timeCounterTemp -= SecondsInYear;
        }
        years++;
        if( years == 100 )
        {
            century = century + 100;
            years = 0;
        }
    }

    if( timeCounterTemp >= SecondsInYear )
    {
        if( ( years == 0 ) || ( years % 4 ) == 0 )
        {
            // Nothing to be done
        }
        else
        {
            timeCounterTemp -= SecondsInYear;
            years++;
        }
    }

    if( ( years == 0 ) || ( years % 4 ) == 0 )
    {
        while( timeCounterTemp >= ( DaysInMonthLeapYear[ months - 1 ] * SecondsInDay ) )
        {
            timeCounterTemp -= DaysInMonthLeapYear[ months - 1 ] * SecondsInDay;
            months++;
        }
    }
    else
    {
        while( timeCounterTemp >= ( DaysInMonth[ months - 1 ] * SecondsInDay ) )
        {
            timeCounterTemp -= DaysInMonth[ months - 1 ] * SecondsInDay;
            months++;
        }
    }

    // Convert milliseconds to RTC format and add to now
    while( timeCounterTemp >= SecondsInDay )
    {
        timeCounterTemp -= SecondsInDay;
        days++;
    }

    // Calculate hours
    while( timeCounterTemp >= SecondsInHour )
    {
        timeCounterTemp -= SecondsInHour;
        hours++;
    }

    // Calculate minutes
    while( timeCounterTemp >= SecondsInMinute )
    {
        timeCounterTemp -= SecondsInMinute;
        minutes++;
    }

    // Calculate seconds
    seconds = round( timeCounterTemp );

    calendar.CalendarTime.Seconds = seconds;
    calendar.CalendarTime.Minutes = minutes;
    calendar.CalendarTime.Hours = hours;
    calendar.CalendarDate.Date = days;
    calendar.CalendarDate.Month = months;
    calendar.CalendarDate.Year = years;
    calendar.CalendarCentury = century;

    return calendar;
}

官方源码中整个系统的定时器链表实现在\src\system\timer.c文件下。定时器链表的使用方法:

1.新建一个TimerEvent_t类型的全局结构体变量,例如,TimerEvent_t Led1Timer;
2.调用TimerInit()函数对结构体变量进行初始化,并且注册定时器超时回调函数,例如:TimerInit( &Led1Timer, OnLed1TimerEvent );
3.调用TimerSetValue()函数设置定时器超时时间,例如:TimerSetValue( &Led1Timer, 50 );
4.调用TimerStart()函数启动定时器,例如:TimerStart( &Led1Timer );
5.编写定时器超时回调函数OnLed1TimerEvent(),在超时回调函数中,关闭定时器TimerStop( &Led1Timer );

整个系统的定时器时钟基准源来自于STM32内部的RTC时钟,为了让RTC时钟更好地完成定时器功能,在源码中对RTC时钟函数进行了一次封装操作。

/****************************************************************************************
以下四个函数对内部RTC时钟时间进行了转换,进行封装后以适用定时器使用。
将脉冲数转换成时间戳
****************************************************************************************/
定时器获取距离最近一次闹钟唤醒之后的时间戳差值
TimerTime_t TimerGetValue( void )
{
    return RtcGetElapsedAlarmTime( );
}

//定时器从内部RTC获取当前时间戳,实际是系统启动运行的时间。
TimerTime_t TimerGetCurrentTime( void )
{
    return RtcGetTimerValue( );
}

//定时器获取距离savedTime的时间戳差值
TimerTime_t TimerGetElapsedTime( TimerTime_t savedTime )
{
    return RtcComputeElapsedTime( savedTime );
}

//定时器获取将来事件的时间,当前时间距离eventInFuture的时间戳差值
TimerTime_t TimerGetFutureTime( TimerTime_t eventInFuture )
{
    return RtcComputeFutureEventTime( eventInFuture );
}

//定时器设置超时时间
static void TimerSetTimeout( TimerEvent_t *obj )
{
    HasLoopedThroughMain = 0;
    obj->Timestamp = RtcGetAdjustedTimeoutValue( obj->Timestamp );  
    RtcSetTimeout( obj->Timestamp );
}
/****************************************************************************************/

函数TimerGetValue()对底层RTC函数RtcGetElapsedAlarmTime()进行了简单的封装。RtcGetElapsedAlarmTime()函数是获取最近一次闹钟中断唤醒之后,到目前时刻的时间戳值。

//获取最近一次闹钟中断唤醒之后,到目前时刻的时间戳值
TimerTime_t RtcGetElapsedAlarmTime( void )
{
    TimerTime_t currentTime = 0;
    TimerTime_t contextTime = 0;

    currentTime = RtcConvertCalendarTickToTimerTime( NULL );                //当前时间戳
    contextTime = RtcConvertCalendarTickToTimerTime( &RtcCalendarContext ); //RtcCalendarContext记录最近一次闹钟唤醒时刻的脉冲数

    //计算两者的时间戳差值
    if( currentTime < contextTime ) //脉冲数溢出情况
    {
        return( currentTime + ( 0xFFFFFFFF - contextTime ) );
    }
    else        //脉冲数未溢出情况
    {
        return( currentTime - contextTime );
    }
}

函数TimerGetCurrentTime()对底层RTC函数RtcGetTimerValue()简单封装,而RtcGetTimerValue()函数又是对RtcConvertCalendarTickToTimerTime()的封装,所以TimerGetCurrentTime()实际是通过获取当前RTC时钟的脉冲数,转换成时间戳,这个时间值是距离系统自启动运行到当前的值。因为系统在每次启动的时候,都把RTC的时钟设定到一个固定的时间之上,所以这里获取的当前时间值是相对值而不是实际的时间值。

void RtcInit( void )
{
    RtcCalendar_t rtcInit;

    if( RtcInitialized == false )
    {
        __HAL_RCC_RTC_ENABLE( );

        //将RTC的时间值设定为2000-01-01 00:00:00
        // Set Date: Friday 1st of January 2000
        rtcInit.CalendarDate.Year = 0;
        rtcInit.CalendarDate.Month = 1;
        rtcInit.CalendarDate.Date = 1;
        rtcInit.CalendarDate.WeekDay = RTC_WEEKDAY_SATURDAY;
        HAL_RTC_SetDate( &RtcHandle, &rtcInit.CalendarDate, RTC_FORMAT_BIN );

        // Set Time: 00:00:00
        rtcInit.CalendarTime.Hours = 0;
        rtcInit.CalendarTime.Minutes = 0;
        rtcInit.CalendarTime.Seconds = 0;
        rtcInit.CalendarTime.TimeFormat = RTC_HOURFORMAT12_AM;
        rtcInit.CalendarTime.DayLightSaving = RTC_DAYLIGHTSAVING_NONE;
        rtcInit.CalendarTime.StoreOperation = RTC_STOREOPERATION_RESET;
        HAL_RTC_SetTime( &RtcHandle, &rtcInit.CalendarTime, RTC_FORMAT_BIN );

        RtcInitialized = true;
    }
}

函数TimerGetElapsedTime( TimerTime_t savedTime )是获取当前距离传进去的已过时间戳savedTime的时间戳差值,其实对RTC底层函数RtcComputeElapsedTime()的封装。

//获取距离已过时间戳eventInTime的差值
TimerTime_t RtcComputeElapsedTime( TimerTime_t eventInTime )
{
    TimerTime_t elapsedTime = 0;

    // Needed at boot, cannot compute with 0 or elapsed time will be equal to current time
    if( eventInTime == 0 )
    {
        return 0;
    }

    elapsedTime = RtcConvertCalendarTickToTimerTime( NULL );        //获取当前时间戳

    //计算距离eventInTime的时间戳差值
    if( elapsedTime < eventInTime )     //时间戳溢出情况
    { // roll over of the counter   
        return( elapsedTime + ( 0xFFFFFFFF - eventInTime ) );
    }
    else            //时间戳未溢出情况
    {
        return( elapsedTime - eventInTime );
    }
}

函数TimerGetFutureTime()是对RtcComputeFutureEventTime()进行了封装,RtcComputeFutureEventTime( eventInFuture )函数是获取当前时间距离未来时间eventInFuture的时间戳差值。

//获取将来事件的时间戳
TimerTime_t RtcComputeFutureEventTime( TimerTime_t futureEventInTime )
{
    return( RtcGetTimerValue( ) + futureEventInTime );  //将来事件的时间=当前时间+将来时间
}

最后一个使用到底层RTC接口的是TimerSetTimeout()函数,该函数是利用RTC闹钟功能,设定定时器的超时时间,在RTC闹钟中调用定时器初始化时注册的回调函数,处理超时任务。

//定时器设置超时时间
static void TimerSetTimeout( TimerEvent_t *obj )
{
    HasLoopedThroughMain = 0;
    obj->Timestamp = RtcGetAdjustedTimeoutValue( obj->Timestamp );  
    RtcSetTimeout( obj->Timestamp );
}

由于系统在运行McuWakeUpTime后会进入休眠,因此在设定定时器超时时间的时候,要考虑所设定的时间长度是否超过一个McuWakeUpTime周期。如果超过一个McuWakeUpTime周期的话,要对设定的超时时间进行调整,使之在下一个(或则更后面)的保持唤醒周期内完成定时器任务。源代码中,调用RtcGetAdjustedTimeoutValue()函数根据McuWakeUpTime调整超时时间。

//RTC获取调整后的超时时间值
TimerTime_t RtcGetAdjustedTimeoutValue( uint32_t timeout )
{
    //如果设置定时器超时时间比一个保持唤醒周期还大,需要放到下一个保持唤醒周期进行定时处理
    if( timeout > McuWakeUpTime )
    {   // we have waken up from a GPIO and we have lost "McuWakeUpTime" that we need to compensate on next event
        if( NonScheduledWakeUp == true )
        {
            NonScheduledWakeUp = false;
            timeout -= McuWakeUpTime;
        }
    }

    if( timeout > McuWakeUpTime )
    {   // we don't go in Low Power mode for delay below 50ms (needed for LEDs)
        // 不允许低于50ms进入低功耗模式
        if( timeout < 50 ) // 50 ms
        {
            RtcTimerEventAllowsLowPower = false;
        }
        else
        {
            RtcTimerEventAllowsLowPower = true;
            timeout -= McuWakeUpTime;
        }
    }
    return  timeout;
}

经过时间调整之后,调用RtcSetTimeout()函数,把需要设定的超时时间设置到RTC中,并启动闹钟中断。

void RtcSetTimeout( uint32_t timeout )
{
    RtcStartWakeUpAlarm( timeout );
}

static void RtcStartWakeUpAlarm( uint32_t timeoutValue )
{
    RtcCalendar_t now;
    RtcCalendar_t alarmTimer;
    RTC_AlarmTypeDef alarmStructure;

    HAL_RTC_DeactivateAlarm( &RtcHandle, RTC_ALARM_A );
    HAL_RTCEx_DeactivateWakeUpTimer( &RtcHandle );

    // Load the RTC calendar
    now = RtcGetCalendar( );

    // Save the calendar into RtcCalendarContext to be able to calculate the elapsed time
    RtcCalendarContext = now;

    // timeoutValue is in ms
    alarmTimer = RtcComputeTimerTimeToAlarmTick( timeoutValue, now );

    alarmStructure.Alarm = RTC_ALARM_A;
    alarmStructure.AlarmDateWeekDaySel = RTC_ALARMDATEWEEKDAYSEL_DATE;
    alarmStructure.AlarmMask = RTC_ALARMMASK_NONE;
    alarmStructure.AlarmTime.TimeFormat = RTC_HOURFORMAT12_AM;

    alarmStructure.AlarmTime.Seconds = alarmTimer.CalendarTime.Seconds;
    alarmStructure.AlarmTime.Minutes = alarmTimer.CalendarTime.Minutes;
    alarmStructure.AlarmTime.Hours = alarmTimer.CalendarTime.Hours;
    alarmStructure.AlarmDateWeekDay = alarmTimer.CalendarDate.Date;

    //设置RTC定时器闹钟
    if( HAL_RTC_SetAlarm_IT( &RtcHandle, &alarmStructure, RTC_FORMAT_BIN ) != HAL_OK )
    {
        assert_param( FAIL );
    }
}

在完成对底层RTC接口的封装之后,接下来就是使用这些接口完成定时器链表。

链表是由静态全局变量定义的链表头开始的,该变量在源码中的定义如下:

static TimerEvent_t *TimerListHead = NULL;

typedef struct TimerEvent_s
{
    uint32_t Timestamp;         //! Current timer value
    uint32_t ReloadValue;       //! Timer delay value
    bool IsRunning;             //! Is the timer currently running
    void ( *Callback )( void ); //! Timer IRQ callback function
    struct TimerEvent_s *Next;  //! Pointer to the next Timer object.
}TimerEvent_t;

接下来的使用过程中,主要就是对该链表的操作。比如,现在要新增一个定时器。

第一步:定义一个定时器实体。

/*!
 * Timer to handle the state of LED1
 */
TimerEvent_t Led1Timer;

第二步:初始化定时器实体,并注册回调函数。

TimerInit( &Led1Timer, OnLed1TimerEvent );

//定时器初始化
void TimerInit( TimerEvent_t *obj, void ( *callback )( void ) )
{
    obj->Timestamp = 0;
    obj->ReloadValue = 0;
    obj->IsRunning = false;
    obj->Callback = callback;
    obj->Next = NULL;
}

第三步:设定超时时间。

TimerSetValue( &Led1Timer, 50 );

//指定定时器设定超时值
void TimerSetValue( TimerEvent_t *obj, uint32_t value )
{
    TimerStop( obj );
    obj->Timestamp = value;
    obj->ReloadValue = value;
}   

第四步:启动定时器。

TimerStart( &Led1Timer );

//启动一个指定定时器
void TimerStart( TimerEvent_t *obj )
{
    uint32_t elapsedTime = 0;
    uint32_t remainingTime = 0;     //剩余时间=链表头定时器时间戳-自上次闹钟事件之后已过的时间戳

    BoardDisableIrq( );

    if( ( obj == NULL ) || ( TimerExists( obj ) == true ) )
    {
        BoardEnableIrq( );
        return;
    }

    obj->Timestamp = obj->ReloadValue;
    obj->IsRunning = false;

    if( TimerListHead == NULL ) //定时器链表头为空,则直接在链表头插入指定定时器
    {
        TimerInsertNewHeadTimer( obj, obj->Timestamp );
    }
    else
    {
        //获取距离下次闹钟中断的剩余时间
        if( TimerListHead->IsRunning == true )
        {
            elapsedTime = TimerGetValue( );
            if( elapsedTime > TimerListHead->Timestamp )
            {
                elapsedTime = TimerListHead->Timestamp; // security but should never occur
            }
            remainingTime = TimerListHead->Timestamp - elapsedTime;
        }
        else
        {
            remainingTime = TimerListHead->Timestamp;
        }

        if( obj->Timestamp < remainingTime )    //如果插入定时器对象的时间比当前链表头定时器对象剩余时间还短,
                                                //则把该定时器对象插入到链表头
        {
            TimerInsertNewHeadTimer( obj, remainingTime );
        }
        else
        {
             TimerInsertTimer( obj, remainingTime );    //否则,把该定时器对象插入到链表的其它位置
        }
    }
    BoardEnableIrq( );
}

第五步:编写超时处理函数。

void OnLed1TimerEvent( void )
{
    TimerStop( &Led1Timer );
    // Switch LED 1 OFF
    GpioWrite( &Led1, 1 );
}

//停止定时器
void TimerStop( TimerEvent_t *obj )
{
    BoardDisableIrq( );

    uint32_t elapsedTime = 0;
    uint32_t remainingTime = 0;

    TimerEvent_t* prev = TimerListHead;
    TimerEvent_t* cur = TimerListHead;

    // List is empty or the Obj to stop does not exist
    if( ( TimerListHead == NULL ) || ( obj == NULL ) )
    {
        BoardEnableIrq( );
        return;
    }

    if( TimerListHead == obj ) // Stop the Head     //停止的定时器对象处于链表头位置
    {
        if( TimerListHead->IsRunning == true ) // The head is already running   //链表头定时器对象处于运行状态
        {
            elapsedTime = TimerGetValue( );         //获取已过时间戳
            if( elapsedTime > obj->Timestamp )
            {
                elapsedTime = obj->Timestamp;
            }

            remainingTime = obj->Timestamp - elapsedTime;   //计算得到剩余时间

            if( TimerListHead->Next != NULL )   //定时器链表有两个及以上对象
            {
                TimerListHead->IsRunning = false;
                TimerListHead = TimerListHead->Next;        //调整链表头指针的位置
                TimerListHead->Timestamp += remainingTime; 
                TimerListHead->IsRunning = true;
                TimerSetTimeout( TimerListHead );           //启动定时器链表的下一个定时器
            }
            else
            {
                TimerListHead = NULL;   //定时器链表只有一个对象
            }
        }
        else // Stop the head before it is started  //链表头定时器对象处于停止状态
        {
            if( TimerListHead->Next != NULL )
            {
                remainingTime = obj->Timestamp;
                TimerListHead = TimerListHead->Next;
                TimerListHead->Timestamp += remainingTime;
            }
            else
            {
                TimerListHead = NULL;
            }
        }
    }
    else // Stop an object within the list  //停止的定时器对象在链表内
    {
        remainingTime = obj->Timestamp; 

        //循环查找需要停止的定时器对象
        while( cur != NULL )
        {
            if( cur == obj )
            {
                //直接调整链表的指针,没删除定时器对象
                if( cur->Next != NULL ) 
                {
                    cur = cur->Next;
                    prev->Next = cur;
                    cur->Timestamp += remainingTime;
                }
                else
                {
                    cur = NULL;
                    prev->Next = cur;
                }
                break;
            }
            else
            {
                prev = cur;             //调整prev指针指向的对象
                cur = cur->Next;        //调整cur指针指向的对象
            }
        }
    }
    BoardEnableIrq( );
}

启动一个定时器,其实就是向TimerListHead链表中插入一个定时器对象;停止一个定时器,其实就是从TimerListHead链表中出一个定时器对象;定时器超时,其实就是判断TimerListHead链表第一个定时器对象是否超时,若超时则执行其回调函数即可。

在向TimerListHead链表插入一个对象的时候,有三种情况:

1. TimerListHead链表为空时,直接把目标对象插入到TimerListHead链表后面;
2. TimerListHead链表不为空,并且链表只存在一个对象,该目标对象插到已存在对象后面即可;
3. TimerListHead链表不为空,并且链表存在两个及以上对象,这种情况下,按照超时时间从小到大的顺序排序插入到链表中,并调整相邻位置的超时时间。


//在定时器链表头中插入新的定时器
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 );       //设置定时器超时时间
}

//向定时器链表插入一个指定定时器
static void TimerInsertTimer( TimerEvent_t *obj, uint32_t remainingTime )
{
    uint32_t aggregatedTimestamp = 0;      // hold the sum of timestamps                        //到prev对象的时间戳
    uint32_t aggregatedTimestampNext = 0;  // hold the sum of timestamps up to the next event   //到cur对象的时间戳

    TimerEvent_t* prev = TimerListHead;         //定时器链表头指针
    TimerEvent_t* cur = TimerListHead->Next;    //第二个定时器对象指针

    if( cur == NULL )   //定时器链表只有一个定时器对象,直接插入到链表头下一位置
    { // obj comes just after the head
        obj->Timestamp -= remainingTime;        //调整插入定时器时间戳,减去剩余时间
        prev->Next = obj;
        obj->Next = NULL;
    }
    else    //定时器链表有两个以上定时器对象,要进行剩余时间的对比。定时器链表是按照剩余时间长短排序的。
    {
        aggregatedTimestamp = remainingTime;
        aggregatedTimestampNext = remainingTime + cur->Timestamp;

        while( prev != NULL )
        {
            //通过循环对比链表中现有的每个定时器对象的时间戳,查找时间戳比即将插入定时器时间戳小的位置,
            //然后将定时器插入到该位置前面,并调整新插入位置之后每个定时器的时间戳。
            if( aggregatedTimestampNext > obj->Timestamp )  //找到插入定时器的位置
            {
                obj->Timestamp -= aggregatedTimestamp;  //计算得到obj对象相对于prev对象的时间戳
                if( cur != NULL )
                {
                    cur->Timestamp -= obj->Timestamp;   //计算得到cur对象相对于obj对象的时间戳
                }
                prev->Next = obj;                       //调整指针,插入定时器到prev对象的下一个位置      
                obj->Next = cur;                        //调整指针,使cur对象处于obj对象的下一位置     
                break;                                  //插入完成,退出
            }
            else                              
            {
                prev = cur;         //通过改变前一个指针和当前指针的位置,搜索新插入定时器的位置
                cur = cur->Next;
                if( cur == NULL )   //插入定时器链表的尾部
                { // obj comes at the end of the list
                    aggregatedTimestamp = aggregatedTimestampNext;
                    obj->Timestamp -= aggregatedTimestamp;              //计算得到obj对象相对于prev对象的时间戳
                    prev->Next = obj;                                   //调整指针,插入定时器到prev对象的下一个位置            
                    obj->Next = NULL;                                   //调整指针,obj对象的下一位置为空(链表尾部)
                    break;                                              //插入完成,退出
                }
                else
                {
                    aggregatedTimestamp = aggregatedTimestampNext;                      //调整到prev对象的时间戳
                    aggregatedTimestampNext = aggregatedTimestampNext + cur->Timestamp; //调整到cur对象的时间戳
                }
            }
        }
    }
}

停止一个指定的定时器,即把定时器对象从TimerListHead链表中删除。

在删除TimerListHead链表中的指定定时器时,有两种情况:

1. 停止的定时器刚好是链表头的对象。并且是定时器在运行的情况,则计算得到剩余时间,把链表头对象指针指向下一个对象,启动新链表头对象;若是定时器处于停止的情况,则直接把链表头指针指向下一个对象。
2. 停止的定时器在链表内部其它位置。需要在链表中循环查找需要停止的定时器对象,找到之后,调整相邻对象指针指向位置,即可把指定定时器从链表中删除。注意:这里仅是把定时器从链表中删除,但是没有删除定时器对象,因为定时器一般都是通过全局变量的形式静态分配的,所以不能动态删除。

//停止定时器
void TimerStop( TimerEvent_t *obj )
{
    BoardDisableIrq( );

    uint32_t elapsedTime = 0;
    uint32_t remainingTime = 0;

    TimerEvent_t* prev = TimerListHead;
    TimerEvent_t* cur = TimerListHead;

    // List is empty or the Obj to stop does not exist
    if( ( TimerListHead == NULL ) || ( obj == NULL ) )
    {
        BoardEnableIrq( );
        return;
    }

    if( TimerListHead == obj ) // Stop the Head     //停止的定时器对象处于链表头位置
    {
        if( TimerListHead->IsRunning == true ) // The head is already running   //链表头定时器对象处于运行状态
        {
            elapsedTime = TimerGetValue( );         //获取已过时间戳
            if( elapsedTime > obj->Timestamp )
            {
                elapsedTime = obj->Timestamp;
            }

            remainingTime = obj->Timestamp - elapsedTime;   //计算得到剩余时间

            if( TimerListHead->Next != NULL )   //定时器链表有两个及以上对象
            {
                TimerListHead->IsRunning = false;
                TimerListHead = TimerListHead->Next;        //调整链表头指针的位置
                TimerListHead->Timestamp += remainingTime; 
                TimerListHead->IsRunning = true;
                TimerSetTimeout( TimerListHead );           //启动定时器链表的下一个定时器
            }
            else
            {
                TimerListHead = NULL;   //定时器链表只有一个对象
            }
        }
        else // Stop the head before it is started  //链表头定时器对象处于停止状态
        {
            if( TimerListHead->Next != NULL )
            {
                remainingTime = obj->Timestamp;
                TimerListHead = TimerListHead->Next;
                TimerListHead->Timestamp += remainingTime;
            }
            else
            {
                TimerListHead = NULL;
            }
        }
    }
    else // Stop an object within the list  //停止的定时器对象在链表内
    {
        remainingTime = obj->Timestamp; 

        //循环查找需要停止的定时器对象
        while( cur != NULL )
        {
            if( cur == obj )
            {
                //直接调整链表的指针,没删除定时器对象
                if( cur->Next != NULL ) 
                {
                    cur = cur->Next;
                    prev->Next = cur;
                    cur->Timestamp += remainingTime;
                }
                else
                {
                    cur = NULL;
                    prev->Next = cur;
                }
                break;
            }
            else
            {
                prev = cur;             //调整prev指针指向的对象
                cur = cur->Next;        //调整cur指针指向的对象
            }
        }
    }
    BoardEnableIrq( );
}

向定时器链表中插入了定时器之后,怎样才能让它运作起来,到时指定超时时间,执行回调函数呢?

这里需要借助底层的内部RTC闹钟功能来实现超时时间的计算,在RTC闹钟中断处理函数RTC_Alarm_IRQHandler()中,调用TimerIrqHandler()进行定时器中断处理。

void RTC_Alarm_IRQHandler( void )
{
    HAL_RTC_AlarmIRQHandler( &RtcHandle );                  //闹钟中断处理函数
    HAL_RTC_DeactivateAlarm( &RtcHandle, RTC_ALARM_A );     //失能闹钟
    RtcRecoverMcuStatus( );                                 //从休眠中唤醒MCU,进行状态恢复
    RtcComputeWakeUpTime( );                                //计算唤醒保持时间
    BlockLowPowerDuringTask( false );                       //阻塞进入低功耗
    TimerIrqHandler( );                                     //RTC定时器中断处理,处理定时器链表
}   

在定时器中断处理函数TimerIrqHandler()中,判断链表头定时器是否超时,如果超时则调用定时器注册的超时回调函数,链表头指针指向下一个定时器对象。

//定时器中断处理函数(在内部RTC闹钟中断处理函数中被调用)
void TimerIrqHandler( void )
{
    uint32_t elapsedTime = 0;

    // Early out when TimerListHead is null to prevent null pointer
    if ( TimerListHead == NULL )
    {
        return;
    }

    elapsedTime = TimerGetValue( );

    //判断链表头定时器是否超时
    if( elapsedTime >= TimerListHead->Timestamp )   //链表头定时器超时,则清零链表头定时器时间戳
    {
        TimerListHead->Timestamp = 0;
    }
    else
    {
        TimerListHead->Timestamp -= elapsedTime;    //链表头定时器未超时,则更新链表头定时器时间戳
    }

    TimerListHead->IsRunning = false;

    //若链表头定时器已超时,则调用定时器超时回调函数,处理定时任务
    while( ( TimerListHead != NULL ) && ( TimerListHead->Timestamp == 0 ) )
    {
        TimerEvent_t* elapsedTimer = TimerListHead; //当前链表头定时器为已超时
        TimerListHead = TimerListHead->Next;        //链表头指向下一个定时器对象

        if( elapsedTimer->Callback != NULL )
        {
            elapsedTimer->Callback( );      //调用已经超时定时器的回调函数
        }
    }

    // start the next TimerListHead if it exists
    // 如果链表头不为空,则启动链表头下一个定时器
    if( TimerListHead != NULL )
    {
        if( TimerListHead->IsRunning != true )
        {
            TimerListHead->IsRunning = true;
            TimerSetTimeout( TimerListHead );
        }
    }
}

  • 13
    点赞
  • 37
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值