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 );
        }
    }
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章