Lora无线通信关键技术特点及网关、终端代码分析记录

Lora技术特点

请添加图片描述

速率请添加图片描述

请添加图片描述

同时接收的特点

  1. LoRa采用分频技术,基站核心模块SX1301能同时开通八个带宽为125kHz的频道和终端进行通信。SX1301通过两片SX125x进行无线信号的接收和发送,SX125x接收到无线信号后,通过采样总线传给八个通信信道(总共有十个信道,但用于和终端通信的只有八个信道),每个信道都能独立的对其对应的频率进行采样。所以不同信道之间的通信不会产生通信冲突

  2. 相同信道上使用不同通信速率通信不会产生通信冲突
    每个信道将采样到的数据通过交换设备传给一个能对所有信道、所有传输速率的前导码搜索引擎进行处理,这个前导码搜索引擎能对八个信道上的六种传输频率进行同时搜索。前导码搜索引擎在搜索到前导码后,就将相应的报文交给八个 LoRa解调器中的一个进行信号的解调。每个解调器可以解调任何信道上任何传输速率的报文,但最多只能八个解调器同时工作(数量可以通过配置调整) [26。

  3. 扩频技术的作用有两个。一是将发送的数据与扩频码相乘,增加传输的字节量从而降低误码率;二是设备之问是有正交的扩频因子发送数据,实现同一网络中多个设备能同时传输数据,从而增加网络的吞吐量

    LoRa 作为低功耗、远距离传输技术的代表,使用扩频技术主要是为了降低误码率,通过扩频技术来增强低功率发送数据过程对干扰的抵抗能力。在 LoRa 技术中为了使基站能同时检测多种传输速率的前导码。除了 SF7-12 这六种扩频因子正交之外,同一传输速率下的终端都采用相同的扩频因子。因此,同一信道,同一传输速率的终端同时传输数据,一定会产生通信冲突。针对使用相同信道、使用同一传输速率的终端和基站之间的通信冲突,
    请添加图片描述
    请添加图片描述

终端程序中的关键词

  • MCPS : MAC Common Part Sublayer ,MCPS services for data transmissions and data receptions

  • MLME : MAC layer management entity, MLME services to manage the LoRaWAN network.

  • MIB : MAC information base,The MIB is responsible
    to store important runtime information and holds the configuration of the LoRaMAC layer.

网关代码分析

协议:GWMP :“Gateway Message Protocol”,
请添加图片描述
分为三类

消息类型消息作用消息方向应答消息
PUSH_DATA上行数据GW -> NSPUSH_ACK
PULL_DATA周期心跳GW -> NSPULL_ACK
PULL_RESP下行数据NS -> GWTX_ACK

疑问: TX_ACK是本地rf发送后才发送还是gw接收就发送?

**答案:**是后者,具体:The TX_ACK message is sent by a gateway to the network server as an acknowledgement to a received PULL_RESP message.

sever、网关信息流

网关与服务器之间的信息流
请添加图片描述
请添加图片描述

上行去重如何实现?

问:如果同一个环境中有多个网关多个节点,节点是怎么确定和哪个网关进行通信的呢?是选择信号好的那个还是与上行数据会发送给每一个网关呢? 同样的NS下行数据时是与上行数据的那个网关进行通信吗?还是说有可能会选择其他网关呢?

答:如下图所示,在 LoRaWAN 协议中,一个节点的上行数据包会被信号覆盖范围内的所有 LoRa 基站(实例为 3 台)接收,这些基站会将这个数据包转发给 LoRa Server
LoRa Server 接收这些数据包时,它会发现节点 ID(EUI) 和 数据包 FCnt 是一致的,它意识到这是重复的数据包,因此,它选择一个信号质量(SNR 和 RSSI)最强的数据包。
这称之为 LoRa Server 去重
如果有下行的数据包,服务器也会选择信号质量(SNR 和 RSSI)最强的基站下发给节点,本例为中间的基站。请添加图片描述

ADR

LoRa网络允许终端采用任何可能的数据速率。LoRaWAN协议利用该特性来优化固定终端的 数据速率。这就是自适应数据速率(Adaptive Data Rate (ADR))。当这个使能时,网络会优化 使得尽可能使用最快的数据速率。 移动的终端由于射频环境的快速变化,数据速率管理就不再适用了,应当使用固定的数据速 率。 如果ADR的位字段有置位,网络就会通过相应的MAC命令来控制终端设备的数据速率。如果 ADR位没设置,网络则无视终端的接收信号强度,不再控制终端设备的数据速率。ADR位可 以根据需要通过终端及网络来设置或取消。不管怎样,ADR机制都应该尽可能使能,帮助终 端延长电池寿命和扩大网络容量。

下行同步网络的原理

对于一个支持ClassB的网络,所有网关必须同步广播一个信标,以给所有终端提供一个参考 时间。基于这个时间参考,终端可以周期性地打开接收窗口,下文称之为“ping slot”,这 个“ping slot”被网络建设者用于发起下行通信。网络使用ping slots其中之一来发起下行通讯的 行为,称之为“ping”。用来发起下行通讯的网关,是network server根据终端最近一次上行包 的信号传输质量来选择的。基于此,如果终端根据广播的信标帧发现网络发生了切换(通信的 网关发生了变化),它必须发出上行帧给network server,以使server端更新下行路径的数据 库。

classB时间同步精度

在从 Class A 切换到 Class B 之前,终端必须首先接收一个网络的信标来将它自身的时间基 准与网络时间进行校准。 一旦处于 Class B 模式,终端必须定期地去搜索并且接收一个网络信标,以消除自身内部基 准时间相对于网络时间的任何漂移。 Class B 模式的设备也许会短暂性地无法接收信标(超出与网关的通信范围,存在干扰,…)在 这种情况之下,终端必须考虑它内部时钟可能产生的漂移,逐步地加大信标和ping时隙的接收 窗口时间。 例如,一个设备精度为 10ppm 的内部时钟每个信标周期(128s)就会有+/-1.3ms的漂移。

classB下行可以用时间

为了使 Class B模式能够正常运行,终端必须以信标规定的精准时刻打开接收时隙窗口。这章 节定义了所需的时序操作。 两个连续的信标起始点之间的间隔称为信标周期。信标帧的传输以 BEACON_RESERVED 时 间间隔的起始端对齐。每个信标都有一个保护时间间隔,在该时间间隔之内是没有 ping 时隙 的。保护间隔的长度对应于允许帧在空中的最长时间。这样就能保证在保护时间之前的一个 ping 时隙内发起的下行数据帧总是有时间去完成传输而不与信标的传输发生冲突。因此用于 ping时隙的时间间隔是从 BEACON_RESERVED 时间间隔的末尾节点到下一 BEACON_GUARD 时间间隔的起始节点。
请添加图片描述
信标帧在空中的时间实际上是远小于 BEACON_RESERVED 时间间隔的,目的是将来用于添 加网络管理广播帧。

BEACON_WINDOW 时间间隔被划分为2^12=4096个小时段,每一段时长为 30ms,所有时 段的编号从0~4095

每个使用时隙号N的终端必须在Beacon start开始之后的 Ton 秒打开它的接收窗口,Ton的 计算公式如下: Ton = beacon_reserved + N * 30ms

N 称为时隙编号。 最后一个 ping 时隙时段(编号为4095)的开始时间是在 beacon start 后的beacon_reserved + 4095 * 30 ms = 124970ms或者下一个信标开始前的3030ms/请添加图片描述

  • pingNb在一个beacon period(128s)内终端会打开多少次接收窗口,范围是 0-128
  • pingPeriod 在一个beacon period(128s)对应 2的12次方即4096个beacon windows中,单个接收窗口占用多少个ping windows(单个ping window耗时30ms)
  • pingOffset 指终端在beacon period开始后随机延时N个 slotLen(30ms),N的范围为 0-pingPeriod

信标下发

疑问:信标下发也是利用多信道实现吗??

网关发送道netserver的状态数据

请添加图片描述

ppm含义

1 ppm指的是每隔1million clock会产生一个clock的偏移。 即100万误差1

假设有一个50HZ的时钟,总有5ppm的频率误差,那么当它用于实时时钟时,每日引起的走时误差为:5*((24*60*60)/1000000)=0.432s,即每日的走时误差不超过0.5s

lora 1301是1M晶振 20ppm误差,那么1小时最多误差时间为:20* ((60*60)/1000000)= 0.072 s即72ms

1分钟误差为20* ((60)/1000000)= 0.00012s 即12us

cat   ptl_debug_2022-04-07_20\:17\:34 | grep -A 25 "*node_addr_g
et begin"

tail -f ptl_debug_2022-04-07_20\:17\:34 | grep "join"

tail -f ptl_debug_2022-04-07_20\:17\:34 | grep -A 4 "scan_decode

时钟同步流程

1 GPS线程

thread_gps是一个线程,内部循环读取gps串口输出的ubx和nmea数据(连续执行,只要串口有数据就执行)

读到数据后,

lgw_parse_ubx解析ubx数据 (提供GPS时间),写入全局变量gps_iTOW,gps_fTOW,解析成功后调用 gps_process_sync

lgw_parse_nmea解析nmea数据(提供UTC时间和定位经纬度),写入全局变量

static short gps_yea = 0; /* year (2 or 4 digits) */
static short gps_mon = 0; /* month (1-12) */
static short gps_day = 0; /* day of the month (1-31) */
static short gps_hou = 0; /* hours (0-23) */
static short gps_min = 0; /* minutes (0-59) */
static short gps_sec = 0; /* seconds (0-60)(60 is for leap second) */

static short gps_dla = 0; /* degrees of latitude */
static double gps_mla = 0.0; /* minutes of latitude */

解析成功后调用 gps_process_coords

在gps_process_sync中

调用lgw_gps_get得到gps同步后的utc和gps时间

lgw_get_trigcnt得到1301的cnt

执行 lgw_gps_sync 利用上边的utc gps cnt得到 time_reference_gps也就是参考时间,参考时间包含linux系统时间,从gps得到的utc时间gps时间和从1301读取的count_us以及一个时钟误差

time_reference_gps->systime = time(NULL);
time_reference_gps->count_us = count_us;
time_reference_gps->utc.tv_sec = utc.tv_sec;
time_reference_gps->utc.tv_nsec = utc.tv_nsec;
time_reference_gps->gps.tv_sec = gps_time.tv_sec;
time_reference_gps->gps.tv_nsec = gps_time.tv_nsec;
time_reference_gps->xtal_err = slope;

注意slope的计算

cnt_diff = (double)(count_us - ref->count_us) / (double)(TS_CPS); /* uncorrected by xtal_err 未被xtal错误纠正*/
utc_diff = (double)(utc.tv_sec - (ref->utc).tv_sec) + (1E-9 * (double)(utc.tv_nsec - (ref->utc).tv_nsec));

/* detect aberrant points by measuring if slope limits are exceeded 
    检测异常点通过测量如果坡度限制超过  
    */
if (utc_diff != 0) { // prevent divide by zero
    slope = cnt_diff/utc_diff;
    if ((slope > PLUS_10PPM) || (slope < MINUS_10PPM)) {
        DEBUG_MSG("Warning: correction range exceeded\n");//校正范围超过了
        aber_n0 = true;
    } else {
        aber_n0 = false;
    }
} else {
    DEBUG_MSG("Warning: aberrant UTC value for synchronization\n");
    aber_n0 = true;
}

lgw_parse_nmea函数中解析得到UTC时间 &gps_hou, &gps_min, &gps_sec, &gps_fra &gps_day, &gps_mon, &gps_yea

和经纬度信息 gps_sat, gps_dla, gps_mla, gps_ola, gps_dlo, gps_mlo, gps_olo, gps_alt);

在gps_process_coords中

设置经纬度到全局变量 meas_gps_coord 中

2 GPS有效线程

thread_valid

注意: time_reference_gps 变量是在linux收到gps发来的数据后才会进行更新,如果没有gps,那么不更新。

本线程就是为了防止gps时间不更新出现问题

线程中首先 求linux系统当前时间和time_reference_gps 的插值,如果插值大于0且小于 GPS_REF_MAX_AGE(30s)也就是linux系统中时间有30秒没有更新了,设置

 gps_ref_valid = true;
 ref_valid_local = true;

否则将这两个变量设置为false

接下来判断,如果超过30秒没有收到gps时间,

3.时间同步线程thread_timersync

每个60秒执行一次,执行流程:

  1. 先获取系统时间 gettimeofday(&unix_timeval, NULL);

  2. 获取1301的cnt lgw_get_trigcnt(&sx1301_timecount);

  3. 将刚才得到的cnt转换为 *struct* timeval格式的集中器时间

  concentrator_timeval.tv_sec = sx1301_timecount / 1000000UL;
  concentrator_timeval.tv_usec = sx1301_timecount - (concentrator_timeval.tv_sec * 1000000UL);
  1. 备份之前linux系统时间和1301系统时间的差值

    offset_previous.tv_sec = offset_unix_concent.tv_sec;
    offset_previous.tv_usec = offset_unix_concent.tv_usec;
    
  2. 计算新的linux系统时间和1301时间的插值:offset_unix_concent

timersub(&unix_timeval, &concentrator_timeval, &offset_unix_concent);
  1. 计算两次偏差值的偏差值
  timersub(&offset_unix_concent, &offset_previous, &offset_drift);

主要目的是得到 linux 系统时间和1301计数器之间的偏差时间offset_unix_concent

疑问点

  1. A中tmst的作用
    请添加图片描述
    2.lora beacon接收到beacon后还会调频等待下次接收,如下
    请添加图片描述
  2. sticky是?请添加图片描述

lora_node代码分析

重要函数

帧数据拼接函数 PrepareFrame

mac commond发送后等待回复检测流程

发送流程LmHandlerSend->LoRaMacMcpsRequest->Send->PrepareFrameScheduleTx->SerializeTxFrame(内部调用了LoRaMacSerializerData)and SendFrameOnChannel->Radio.Send

Indication

当收到数据后在MacCtx.MacFlags.Bits.McpsInd = 1,那么在LoRaMacHandleIndicationEvents函数中判断 该值为1 后 调用McpsIndication然后调用LmHandlerCallbacks->OnTxData( &TxParams );执行应用层的接收回调函数比如显示在屏幕中,

之后调用LmHandlerPackagesNotify函数遍历执行各个package的LmHandlerPackages[i]->OnMcpsIndicationProcess( ( McpsIndication_t* )params );

LmhpClockSyncpakcage为例,在他LmhpClockSyncOnMcpsIndication回调函数中首先判断 当前port,如果不是自身则返回,如果时自身,再判断命令号做不同的处理,如下:

static void LmhpClockSyncOnMcpsIndication( McpsIndication_t *mcpsIndication )
{
    uint8_t cmdIndex = 0;
    uint8_t dataBufferIndex = 0;

    //如果端口不对,则直接返回,原来如此
    if( mcpsIndication->Port != CLOCK_SYNC_PORT )
    {
        return;
    }

    while( cmdIndex < mcpsIndication->BufferSize )
    {
        switch( mcpsIndication->Buffer[cmdIndex++] )
        {
            case CLOCK_SYNC_PKG_VERSION_REQ:
            {
                LmhpClockSyncState.DataBuffer[dataBufferIndex++] = CLOCK_SYNC_PKG_VERSION_ANS;
                LmhpClockSyncState.DataBuffer[dataBufferIndex++] = CLOCK_SYNC_ID;
                LmhpClockSyncState.DataBuffer[dataBufferIndex++] = CLOCK_SYNC_VERSION;
                break;
            }
            case CLOCK_SYNC_APP_TIME_ANS:
            {
                LmhpClockSyncState.NbTransmissions = 0;

                // Check if a more precise time correction has been received.
                // If yes then don't process and ignore this answer.
                if( mcpsIndication->DeviceTimeAnsReceived == true )
                {
                    cmdIndex += 5;
                    break;
                }
                int32_t timeCorrection = 0;
                timeCorrection  = ( mcpsIndication->Buffer[cmdIndex++] << 0  ) & 0x000000FF;
                timeCorrection += ( mcpsIndication->Buffer[cmdIndex++] << 8  ) & 0x0000FF00;
                timeCorrection += ( mcpsIndication->Buffer[cmdIndex++] << 16 ) & 0x00FF0000;
                timeCorrection += ( mcpsIndication->Buffer[cmdIndex++] << 24 ) & 0xFF000000;
                if( ( mcpsIndication->Buffer[cmdIndex++] & 0x0F ) == LmhpClockSyncState.TimeReqParam.Fields.TokenReq )
                {
                    SysTime_t curTime = { .Seconds = 0, .SubSeconds = 0 };
                    curTime = SysTimeGet( );
                    curTime.Seconds += timeCorrection;
                    SysTimeSet( curTime );
                    LmhpClockSyncState.TimeReqParam.Fields.TokenReq = ( LmhpClockSyncState.TimeReqParam.Fields.TokenReq + 1 ) & 0x0F;
                    if( LmhpClockSyncPackage.OnSysTimeUpdate != NULL )
                    {
#if( LMH_SYS_TIME_UPDATE_NEW_API == 1 )
                        LmhpClockSyncPackage.OnSysTimeUpdate( 
                                        ( timeCorrection >= -1 ) && ( timeCorrection <= 1 ),
                                        timeCorrection );
#else
                        if( ( timeCorrection >= -1 ) && ( timeCorrection <= 1 ) )
                        {
                            LmhpClockSyncPackage.OnSysTimeUpdate( );
                        }
#endif
                    }
                }
                break;
            }
            case CLOCK_SYNC_APP_TIME_PERIOD_REQ:
            {
                // Increment index
                cmdIndex++;
                // TODO implement command prosessing and handling
                LmhpClockSyncState.DataBuffer[dataBufferIndex++] = CLOCK_SYNC_APP_TIME_PERIOD_ANS;
                // Answer status not supported.
                LmhpClockSyncState.DataBuffer[dataBufferIndex++] = 0x01;

                SysTime_t curTime = SysTimeGet( );
                // Substract Unix to Gps epcoh offset. The system time is based on Unix time.
                curTime.Seconds -= UNIX_GPS_EPOCH_OFFSET;
                LmhpClockSyncState.DataBuffer[dataBufferIndex++] = ( curTime.Seconds >> 0  ) & 0xFF;
                LmhpClockSyncState.DataBuffer[dataBufferIndex++] = ( curTime.Seconds >> 8  ) & 0xFF;
                LmhpClockSyncState.DataBuffer[dataBufferIndex++] = ( curTime.Seconds >> 16 ) & 0xFF;
                LmhpClockSyncState.DataBuffer[dataBufferIndex++] = ( curTime.Seconds >> 24 ) & 0xFF;
                break;
            }
            case CLOCK_SYNC_FORCE_RESYNC_REQ:
            {
                LmhpClockSyncState.NbTransmissions = mcpsIndication->Buffer[cmdIndex++] & 0X07;
                break;
            }
        }
    }

    if( dataBufferIndex != 0 )
    {
        // Answer commands
        LmHandlerAppData_t appData =
        {
            .Buffer = LmhpClockSyncState.DataBuffer,
            .BufferSize = dataBufferIndex,
            .Port = CLOCK_SYNC_PORT
        };
        LmHandlerSend( &appData, LORAMAC_HANDLER_UNCONFIRMED_MSG );
    }
}

如果收到的命令类别是REQ且需要回复数据,那么接着调用 LmHandlerSend( &appData, LORAMAC_HANDLER_UNCONFIRMED_MSG );函数

所有package都是一样的套路

Confirm

当调用Send函数后会将 MacCtx.MacFlags.Bits.McpsReq = 1 ,那么在LoRaMacHandleRequestEvents函数中判断该值为1后会执行 MacCtx.MacPrimitives->MacMcpsConfirm( &MacCtx.McpsConfirm );McpsConfirm,内部执行LmHandlerCallbacks->OnTxData( &TxParams );

LmHandlerPackagesNotify( PACKAGE_MCPS_CONFIRM, mcpsConfirm );同上述Indication 前一个函数通知应用级别发送回调函数,后者调用了每个pacakage的 LmHandlerPackages[i]->OnMcpsConfirmProcess( ( *McpsConfirm_t** ) params );函数

LmhpClockSyncpakcage为例,在它内部的 OnMcpsConfirmProcess 调用了三次LoRaMacMibSetRequestConfirm来设置MAC层的参数(ADR、NbTrans、rate)疑问:为什么在发送之后再次设置mac参数呢?

另外 当McpsReq == 1时, LoRaMacHandleMcpsRequest函数也会执行,该函数内部主要是为了实现重发操作,判断是否重发根据类别来判断

static void LoRaMacHandleMcpsRequest( void )
{
    // Handle MCPS uplinks
    if( MacCtx.MacFlags.Bits.McpsReq == 1 )
    {
        bool stopRetransmission = false;
        bool waitForRetransmission = false;

        if( ( MacCtx.McpsConfirm.McpsRequest == MCPS_UNCONFIRMED ) ||
            ( MacCtx.McpsConfirm.McpsRequest == MCPS_PROPRIETARY ) )
        {
            stopRetransmission = CheckRetransUnconfirmedUplink( );//判断是否应该停止重发
        }
        else if( MacCtx.McpsConfirm.McpsRequest == MCPS_CONFIRMED )
        {
            if( MacCtx.RetransmitTimeoutRetry == true )
            {
                stopRetransmission = CheckRetransConfirmedUplink( );//判断是否应该停止重发
            }
            else
            {
                waitForRetransmission = true;
            }
        }

        if( stopRetransmission == true )
        {// Stop retransmission
            TimerStop( &MacCtx.TxDelayedTimer );
            MacCtx.MacState &= ~LORAMAC_TX_DELAYED;
            StopRetransmission( );

            if( IsReJoin0Required( ) == true )
            {
                SendReJoinReq( REJOIN_REQ_0 );
            }
        }
        else if( waitForRetransmission == false )
        {// Arrange further retransmission 进一步安排重发
            MacCtx.MacFlags.Bits.MacDone = 0;
            // Reset the state of the AckTimeout
            MacCtx.RetransmitTimeoutRetry = false;
            // Sends the same frame again
            OnTxDelayedTimerEvent( NULL );//内部由调度下发
        }
    }
}

当请求是 MCPS_UNCONFIRMED是判断 if( MacCtx.MacFlags.Bits.McpsInd == 1 )且到了接收窗口则停止重发

当请求是 则判断 if( MacCtx.MacFlags.Bits.McpsInd == 1 ) 且if( MacCtx.McpsConfirm.AckReceived == true )停止重发

无论是上诉哪一种,只要到了最大重发次数都停止重发

另外 Confirm 应该指的是回复 回复是在哪里处理的呢?

Mlme

当调用LoRaMacMlmeRequest时,设置MacCtx.MacFlags.Bits.MlmeReq = 1;

LoRaMacHandleRequestEvents判断该值为1时 会调用LoRaMacConfirmQueueHandleCb( &MacCtx.MlmeConfirm );这个函数内部遍历 ConfirmQueueCtx.Nvm.MlmeConfirmQueueCnt找其中那个节点的 readyToHandle == true,一旦找到,执行ConfirmQueueCtx.Primitives->MacMlmeConfirm( *mlmeConfirm* );该回到函数调用MlmeConfirm这个函数内部判断 请求类别做不同处理,MLME类别主要是网络的管理,比如join等

readyToHandle 是什么时候变成true的呢?是被 LoRaMacConfirmQueueSetStatus 调用改变的,而LoRaMacConfirmQueueSetStatus则是在接收中断回调函数 ProcessRadioRxDone或者其内部的ProcessMacCommands中调用的!

ProcessMacCommands就是mac层的命令处理函数,当命令为XXX_ANS类别时,会执行

if( LoRaMacConfirmQueueIsCmdActive( MLME_DEVICE_TIME ) == true )
{
    LoRaMacConfirmQueueSetStatus( LORAMAC_EVENT_INFO_STATUS_OK, MLME_DEVICE_TIME );

即从ConfirmQueue找对应的网络管理请求,找到后调用LoRaMacConfirmQueueSetStatus设置readyToHandle = true后续处理同上

另外在 函数中ProcessMacCommands还处理了XXX__CONFXXX_REQ两种类别的MAC命令,前者会执行

if( LoRaMacCommandsGetCmd( MOTE_MAC_DEVICE_MODE_IND, &macCmd) == LORAMAC_COMMANDS_SUCCESS )
{
    LoRaMacCommandsRemoveCmd( macCmd );
}

即从comands中查找mote MAC commands命令,然后删除,这个mote MAC commands 没明白什么意思

LoRaMAC mote MAC commands 和 LoRaMAC server MAC commands 区别是什么

后者处理XXX_REQ时 执行了LoRaMacCommandsAddCmd

case SRV_MAC_DUTY_CYCLE_REQ:
{
    Nvm.MacGroup2.MaxDCycle = payload[macIndex++] & 0x0F;
    Nvm.MacGroup2.AggregatedDCycle = 1 << Nvm.MacGroup2.MaxDCycle;
    LoRaMacCommandsAddCmd( MOTE_MAC_DUTY_CYCLE_ANS, macCmdPayload, 0 );
    break;
}

这个函数 LoRaMacCommandsAddCmd执行了mac命令缓存,但是是在什么时候触发发送呢?

MAC command 的回复检测

在接收中断回调函数中调用了如下代码

RemoveMacCommands( MacCtx.McpsIndication.RxSlot, macMsgData.FHDR.FCtrl, MacCtx.McpsConfirm.McpsRequest );
static void RemoveMacCommands( LoRaMacRxSlot_t rxSlot, LoRaMacFrameCtrl_t fCtrl, Mcps_t request )
{
    if( rxSlot == RX_SLOT_WIN_1 || rxSlot == RX_SLOT_WIN_2  )
    {
        // Remove all sticky MAC commands answers since we can assume
        // that they have been received by the server.删除所有的sticky MAC命令答案,因为我们可以假设它们已经被服务器接收到。
        if( request == MCPS_CONFIRMED )
        {
            if( fCtrl.Bits.Ack == 1 )
            {  // For confirmed uplinks only if we have received an ACK.   对于已确认的上行链路,只有当我们收到了ACK。
                LoRaMacCommandsRemoveStickyAnsCmds( );
            }
        }
        else
        {
            LoRaMacCommandsRemoveStickyAnsCmds( );
        }
    }
}

注意这里删除的都是 Sticky 类的命令,非 Sticky命令在send之后就被从 commond缓存中删除了

LoRaMacStatus_t Send( LoRaMacHeader_t* macHdr, uint8_t fPort, void* fBuffer, uint16_t fBufferSize )
{
 ..............................
    status = PrepareFrame( macHdr, &fCtrl, fPort, fBuffer, fBufferSize );

    // Validate status
    if( ( status == LORAMAC_STATUS_OK ) || ( status == LORAMAC_STATUS_SKIPPED_APP_DATA ) )
    {
        // Schedule frame, do not allow delayed transmissions
        status = ScheduleTx( false );
    }

    // Post processing
    if( status != LORAMAC_STATUS_OK )
    {
        // Bad case - restore
        // Store local variables
        Nvm.MacGroup1.ChannelsDatarate = datarate;
        Nvm.MacGroup1.ChannelsTxPower = txPower;
    }
    else
    {
        // Good case
        Nvm.MacGroup1.SrvAckRequested = false;
        Nvm.MacGroup1.AdrAckCounter = adrAckCounter;
        // Remove all none sticky MAC commands
        if( LoRaMacCommandsRemoveNoneStickyCmds( ) != LORAMAC_COMMANDS_SUCCESS ) //这里执行了删除 为什么???
        {
            return LORAMAC_STATUS_MAC_COMMAD_ERROR;
        }
    }
    return status;
}

Sticky类命令由以下几种

static bool IsSticky( uint8_t cid )
{
    switch( cid )
    {
        case MOTE_MAC_RESET_IND:
        case MOTE_MAC_REKEY_IND:
        case MOTE_MAC_DEVICE_MODE_IND:
        case MOTE_MAC_DL_CHANNEL_ANS:
        case MOTE_MAC_RX_PARAM_SETUP_ANS:
        case MOTE_MAC_RX_TIMING_SETUP_ANS:
        case MOTE_MAC_TX_PARAM_SETUP_ANS:
        case MOTE_MAC_PING_SLOT_CHANNEL_ANS:
            return true;
        default:
            return false;
    }
}

那么 非 Sticky 和 Sticky的区别是啥呢?????

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值