LoRaMAC-node LoRaWAN 定时器的移植 (一)

最近有网友咨询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( );
}

下一篇文章我们看下底层的处理

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值