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