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