LoRaWAN节点RTC定时器链表分析

LoRaWAN节点RTC定时器链表分析LoRaWAN 节点 RTC 定时器链表分析

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

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

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

--------rtc-board.c-------- /*! * RTC Time base in ms */ #define RTC_ALARM_TICK_DURATION 0. // 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.ms。这样设计的目的是实现定时器毫秒级别的定时。如果要让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 ); } } } 

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/178717.html原文链接:https://javaforall.net

(0)
上一篇 2026年3月26日 下午4:38
下一篇 2026年3月26日 下午4:39


相关推荐

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号