最近有网友咨询LoRaWAN定时器的移植,我重新整理下了资料
首先参考代码是 LoRaMAC-node 4.4.7版本
这个代码大部分都是基于STM32的定时器移植,如果大家基于STM32移植协议栈,那么这部分代码机会可以直接使用,STM32 每个系列的RTC都相同,ST是以闹钟实现的,但是一般其他平台和ST的RTC底层是不一样的,他们普遍没有闹钟,只有一个常规的32位或者24位的定时器,其实采用普通的硬件定时器也能做,但是无法实现低功耗情况下的协议使用
这里如果是其他平台,那么就要手动移植一个定时器链表的底层实现,在协议中中有SAM平台,可以参考他的实现
首先我们分析一下顶层的timer代码 ,我在代码中加了完整的注解
/*!
* \file timer.c
*
* \brief Timer objects and scheduling management implementation
*
* \copyright Revised BSD License, see section \ref LICENSE.
*
* \code
* ______ _
* / _____) _ | |
* ( (____ _____ ____ _| |_ _____ ____| |__
* \____ \| ___ | (_ _) ___ |/ ___) _ \
* _____) ) ____| | | || |_| ____( (___| | | |
* (______/|_____)_|_|_| \__)_____)\____)_| |_|
* (C)2013-2017 Semtech
*
* \endcode
*
* \author Miguel Luis ( Semtech )
*
* \author Gregory Cristian ( Semtech )
*/
#include "utilities.h"
#include "board.h"
#include "rtc-board.h"
#include "timer.h"
/*!
* Safely execute call back
*/
#define ExecuteCallBack( _callback_, context ) \
do \
{ \
if( _callback_ == NULL ) \
{ \
while( 1 ); \
} \
else \
{ \
_callback_( context ); \
} \
}while( 0 );
/*!
* Timers list head pointer
*/
static TimerEvent_t *TimerListHead = NULL;
/*!
* \brief Adds or replace the head timer of the list.
*
* \remark The list is automatically sorted. The list head always contains the
* next timer to expire.
*
* \param [IN] obj Timer object to be become the new head
* \param [IN] remainingTime Remaining time of the previous head to be replaced
*/
static void TimerInsertNewHeadTimer( TimerEvent_t *obj );
/*!
* \brief Adds a timer to the list.
*
* \remark The list is automatically sorted. The list head always contains the
* next timer to expire.
*
* \param [IN] obj Timer object to be added to the list
* \param [IN] remainingTime Remaining time of the running head after which the object may be added
*/
static void TimerInsertTimer( TimerEvent_t *obj );
/*!
* \brief Sets a timeout with the duration "timestamp"
*
* \param [IN] timestamp Delay duration
*/
static void TimerSetTimeout( TimerEvent_t *obj );
/*!
* \brief Check if the Object to be added is not already in the list
*
* \param [IN] timestamp Delay duration
* \retval true (the object is already in the list) or false
*/
static bool TimerExists( TimerEvent_t *obj );
/*定时器初始化,这个时候定时器还没加入到链表 */
void TimerInit( TimerEvent_t *obj, void ( *callback )( void *context ) )
{
obj->Timestamp = 0;
obj->ReloadValue = 0;
obj->IsStarted = false;
obj->IsNext2Expire = false;
obj->Callback = callback;
obj->Context = NULL;
obj->Next = NULL;
}
/*设置上下文内容 目前我注意到协议栈并没有使用*/
void TimerSetContext( TimerEvent_t *obj, void* context )
{
obj->Context = context;
}
/*开启定时器 ,开启定时器后 定时器会加入到链表*/
void TimerStart( TimerEvent_t *obj )
{
uint32_t elapsedTime = 0;
CRITICAL_SECTION_BEGIN( );
if( ( obj == NULL ) || ( TimerExists( obj ) == true ) )
{
CRITICAL_SECTION_END( );
return;
}
/*obj->Timestamp 这个是要实际设置进去的时间戳 obj->ReloadValue 这个是用户设置的超时tick*/
obj->Timestamp = obj->ReloadValue;
/*标记为开启定时器*/
obj->IsStarted = true;
/*标记为没有超时*/
obj->IsNext2Expire = false;
/*链表中没有定时器*/
if( TimerListHead == NULL )
{
/*这里是和底层相关了,获取RTC定时器中的tick 这个函数只出现在这里和中断中*/
RtcSetTimerContext( );
// Inserts a timer at time now + obj->Timestamp
/*开启一个新的定时器 相当于链表的第一个节点定时器*/
TimerInsertNewHeadTimer( obj );
}
else
{
//获取已经消耗的时间
/*也就是获取目前到上次中断的时间 */
elapsedTime = RtcGetTimerElapsedTime( );
/*设置的时间戳 时间基准是之前中断那个基准 所以要加上消耗掉的时间*/
obj->Timestamp += elapsedTime;
/*判断这个时间戳 和 目前已经开启的时间戳做比较
如果这个obj先到,那么他要放在头指针的位置,否则进入TimerInsertTimer*/
if( obj->Timestamp < TimerListHead->Timestamp )
{
TimerInsertNewHeadTimer( obj );
}
else
{
TimerInsertTimer( obj );
}
}
CRITICAL_SECTION_END( );
}
/*插入定时器 非头指针插入 这里是一个常规单向链表的插入操作*/
static void TimerInsertTimer( TimerEvent_t *obj )
{
TimerEvent_t* cur = TimerListHead; //辅助指针
TimerEvent_t* next = TimerListHead->Next; //辅助指针
while( cur->Next != NULL )
{
//根据时间戳插入 从小到大 一直往下找
if( obj->Timestamp > next->Timestamp )
{
cur = next;
next = next->Next;
}
else //找到位置 插入
{
cur->Next = obj;
obj->Next = next;
return;
}
}
//查到尾 说明这个定时器时间是链表里最长的
cur->Next = obj;
obj->Next = NULL;
}
static void TimerInsertNewHeadTimer( TimerEvent_t *obj )
{
TimerEvent_t* cur = TimerListHead; //辅助指针指向头指针
if( cur != NULL )
{
cur->IsNext2Expire = false;
}
/*新的定时器 插入到头指针 然后更新头指针*/
obj->Next = cur;
TimerListHead = obj;
//设置超时时间
TimerSetTimeout( TimerListHead );
}
bool TimerIsStarted( TimerEvent_t *obj )
{
return obj->IsStarted;
}
void TimerIrqHandler( void )
{
TimerEvent_t* cur;
TimerEvent_t* next;
uint32_t old = RtcGetTimerContext( ); //获取上次上下文更新时的时间戳
uint32_t now = RtcSetTimerContext( ); //从RTC底层拿到时间戳 再次强调这里的都是tick 不是ms
uint32_t deltaContext = now - old; // intentional wrap around //如果是32位的定时器 ,这个其实是不存在溢出的 是由于二进制补码存放规则
//但是我之前移植过一个 24位定时器 就需要溢出的考虑 例如就要考虑 old 和 now的大小关系
// Update timeStamp based upon new Time Reference
// because delta context should never exceed 2^32
if( TimerListHead != NULL )
{
//遍历链表 所有的定时器 都要减去一个消耗的值
for( cur = TimerListHead; cur->Next != NULL; cur = cur->Next )
{
next = cur->Next;
if( next->Timestamp > deltaContext )
{
next->Timestamp -= deltaContext;
}
else //定时器到了
{
next->Timestamp = 0;
}
}
}
// Execute immediately the alarm callback
if ( TimerListHead != NULL )
{
cur = TimerListHead; //cur 指向头指针 也就是到时的定时器
TimerListHead = TimerListHead->Next; //头指针指向下一个定时器
cur->IsStarted = false;
ExecuteCallBack( cur->Callback, cur->Context ); //执行回调函数
}
// Remove all the expired object from the list //理论上是不会进到这里的 就是清除掉已经过时的定时器
while( ( TimerListHead != NULL ) && ( TimerListHead->Timestamp < RtcGetTimerElapsedTime( ) ) )
{
cur = TimerListHead;
TimerListHead = TimerListHead->Next;
cur->IsStarted = false;
ExecuteCallBack( cur->Callback, cur->Context );
}
// Start the next TimerListHead if it exists AND NOT running //开启新的定时器
if( ( TimerListHead != NULL ) && ( TimerListHead->IsNext2Expire == false ) )
{
TimerSetTimeout( TimerListHead );
}
}
void TimerStop( TimerEvent_t *obj )
{
CRITICAL_SECTION_BEGIN( );
TimerEvent_t* prev = TimerListHead;
TimerEvent_t* cur = TimerListHead;
// List is empty or the obj to stop does not exist
if( ( TimerListHead == NULL ) || ( obj == NULL ) )
{
CRITICAL_SECTION_END( );
return;
}
obj->IsStarted = false;
if( TimerListHead == obj ) // Stop the Head
{
if( TimerListHead->IsNext2Expire == true ) // The head is already running
{
TimerListHead->IsNext2Expire = false;
if( TimerListHead->Next != NULL )
{
TimerListHead = TimerListHead->Next;
TimerSetTimeout( TimerListHead );
}
else
{
RtcStopAlarm( );
TimerListHead = NULL;
}
}
else // Stop the head before it is started
{
if( TimerListHead->Next != NULL )
{
TimerListHead = TimerListHead->Next;
}
else
{
TimerListHead = NULL;
}
}
}
else // Stop an object within the list
{
while( cur != NULL )
{
if( cur == obj )
{
if( cur->Next != NULL )
{
cur = cur->Next;
prev->Next = cur;
}
else
{
cur = NULL;
prev->Next = cur;
}
break;
}
else
{
prev = cur;
cur = cur->Next;
}
}
}
CRITICAL_SECTION_END( );
}
static bool TimerExists( TimerEvent_t *obj )
{
TimerEvent_t* cur = TimerListHead;
while( cur != NULL )
{
if( cur == obj )
{
return true;
}
cur = cur->Next;
}
return false;
}
void TimerReset( TimerEvent_t *obj )
{
TimerStop( obj );
TimerStart( obj );
}
/*设置定时器的超时值 这个代码将ms的单位直接转为tick ,后面代码处理全部是tick*/
void TimerSetValue( TimerEvent_t *obj, uint32_t value )
{
uint32_t minValue = 0;
uint32_t ticks = RtcMs2Tick( value );
TimerStop( obj );
minValue = RtcGetMinimumTimeout( );
if( ticks < minValue )
{
ticks = minValue;
}
obj->Timestamp = ticks; //这个是要根据实际基准后面要设置的相对时间戳
obj->ReloadValue = ticks; //这个是用户要设置的超时 ticks
}
TimerTime_t TimerGetCurrentTime( void )
{
uint32_t now = RtcGetTimerValue( );
return RtcTick2Ms( now );
}
TimerTime_t TimerGetElapsedTime( TimerTime_t past )
{
if ( past == 0 )
{
return 0;
}
uint32_t nowInTicks = RtcGetTimerValue( );
uint32_t pastInTicks = RtcMs2Tick( past );
// Intentional wrap around. Works Ok if tick duration below 1ms
return RtcTick2Ms( nowInTicks - pastInTicks );
}
static void TimerSetTimeout( TimerEvent_t *obj )
{
int32_t minTicks= RtcGetMinimumTimeout( );
obj->IsNext2Expire = true;
// In case deadline too soon /*RtcGetTimerElapsedTime( ) 这是一个补偿相当于,已经走过的时间,如果小于最小定时则等于最小定时*/
if( obj->Timestamp < ( RtcGetTimerElapsedTime( ) + minTicks ) )
{
obj->Timestamp = RtcGetTimerElapsedTime( ) + minTicks;
}
/*设置时间戳 ticks 底层就是设置了一个超时值*/
RtcSetAlarm( obj->Timestamp );
}
TimerTime_t TimerTempCompensation( TimerTime_t period, float temperature )
{
return RtcTempCompensation( period, temperature );
}
//这个函数其实也可以不实现 这个一般是用在while轮询的 我们是中断处理比较多
void TimerProcess( void )
{
RtcProcess( );
}
下一篇文章我们看下底层的处理