ST 适配的 LoRaWAN 协议栈

由于是 ST 适配过,所以和 Semtech 开源的 LoRaMac-node 协议栈还是有差别的

一、OTAA

1. 参数

OTAA 主要参数如下:

  1. DevAddr
  2. AppSKey
  3. NwkSKey
  4. NetworkActivation

2. 入网保存参数的流程

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
/* Mac/LoRaMac.c */

static void ProcessRadioRxDone( void )
{
    ...
    switch( macHdr.Bits.MType )
    {
        case FRAME_TYPE_JOIN_ACCEPT:
        {
            ...
            SecureElementGetJoinEui( joinEui );
            /* 解密 */
            macCryptoStatus = LoRaMacCryptoHandleJoinAccept( JOIN_REQ, joinEui, &macMsgJoinAccept );
            ...
                ...
                /**
                 * 更新设备地址 DevAddr
                 * 1. Nvm.MacGroup2.DevAddr
                 * 2. Nvm.SecureElement.SeNvmDevJoinKey.DevAddrOTAA
                 */
                Nvm.MacGroup2.DevAddr = macMsgJoinAccept.DevAddr;
                SecureElementSetDevAddr( ACTIVATION_TYPE_OTAA, Nvm.MacGroup2.DevAddr );
                ...

                /* 更新激活状态 NetworkActivation */
                Nvm.MacGroup2.NetworkActivation = ACTIVATION_TYPE_OTAA;
                ...
            ...

            break;
        }
        ...
    }
    ...
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
/* Mac/LoRaMacCrypto.c */

LoRaMacCryptoStatus_t LoRaMacCryptoHandleJoinAccept( JoinReqIdentifier_t joinReqType, uint8_t* joinEUI, LoRaMacMessageJoinAccept_t* macMsg )
{
    ...
    {
        ...
        /**
         * 生成密钥 AppSKey
         * 1. Nvm.SecureElement.KeyList[], 对应的 key id 是 APP_S_KEY
         */
        retval = DeriveSessionKey10x( APP_S_KEY, currentJoinNonce, netID, nonce );
        ...

#if (defined( LORAMAC_VERSION ) && ( LORAMAC_VERSION == 0x01010100 ))
        ...
#else
        /**
         * 生成密钥 NwKSKey
         * 1. Nvm.SecureElement.KeyList[], 对应的 key id 是 NWK_S_KEY
         */
        retval = DeriveSessionKey10x( NWK_S_KEY, currentJoinNonce, netID, nonce );
#endif
        ...
    }
    ...
    
    /**
     * FCnt 相关统计清零
     * CryptoNvm 指向的是 Nvm.Crypto, 实际是
     *  1. Nvm.Crypto.FCntList.FCntUp
     *  2. Nvm.Crypto.FCntList.FCntDown
     *  3. Nvm.Crypto.FCntList.NFCntDown
     *  4. Nvm.Crypto.FCntList.AFCntDown
     */
    CryptoNvm->FCntList.FCntUp = 0;
    CryptoNvm->FCntList.FCntDown = FCNT_DOWN_INITIAL_VALUE;
    CryptoNvm->FCntList.NFCntDown = FCNT_DOWN_INITIAL_VALUE;
    CryptoNvm->FCntList.AFCntDown = FCNT_DOWN_INITIAL_VALUE;

    return LORAMAC_CRYPTO_SUCCESS;
}

二、ABP

1. 参数

ABP 要预先配置的参数如下:

  1. DevAddr
  2. AppKey
  3. AppSKey
  4. NwkSKey

2. 参数的配置

DevAddr:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
/* LmHandler/LmHandler.c */

LmHandlerErrorStatus_t LmHandlerSetDevAddr( uint32_t devAddr )
{
    MibRequestConfirm_t mibReq;

    if( LmHandlerJoinStatus() != LORAMAC_HANDLER_SET )
    {
        mibReq.Type = MIB_DEV_ADDR;
        mibReq.Param.DevAddr = devAddr;
        if( LoRaMacMibSetRequestConfirm( &mibReq ) != LORAMAC_STATUS_OK )
        {
            return LORAMAC_HANDLER_ERROR;
        }
        return LORAMAC_HANDLER_SUCCESS;
    }
    else
    {
        return LORAMAC_HANDLER_ERROR;
    }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/* Mac/LoRaMac.c */

LoRaMacStatus_t LoRaMacMibSetRequestConfirm( MibRequestConfirm_t* mibSet )
{
    ...
    switch( mibSet->Type )
    {
        ...
        case MIB_DEV_ADDR:
        {
            if(SecureElementSetDevAddr( Nvm.MacGroup2.NetworkActivation, mibSet->Param.DevAddr ) != SECURE_ELEMENT_SUCCESS )
            {
                status = LORAMAC_STATUS_PARAMETER_INVALID;
            }
            else
            {
                Nvm.MacGroup2.DevAddr = mibSet->Param.DevAddr;
            }
            break;
        }
        ...
    }
    ...
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/* Crypto/soft-se.c */

SecureElementStatus_t SecureElementSetDevAddr( ActivationType_t mode, uint32_t devAddr )
{
#if (LORAWAN_KMS == 0)
    if( mode == ACTIVATION_TYPE_OTAA )
    {
        SeNvm->SeNvmDevJoinKey.DevAddrOTAA = devAddr;
    }
    else
    {
        /**
         * ABP 走这里
         * 实际是 Nvm.SecureElement.SeNvmDevJoinKey.DevAddrABP
         */
        SeNvm->SeNvmDevJoinKey.DevAddrABP = devAddr;
    }

    return SECURE_ELEMENT_SUCCESS;
#else
    ...
#endif /* LORAWAN_KMS */
}

AppKey / AppSKey / NwkSKey:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
/* LmHandler/LmHandler.c */

LmHandlerErrorStatus_t LmHandlerSetKey( KeyIdentifier_t keyID, uint8_t *key )
{
    if( LmHandlerJoinStatus( ) != LORAMAC_HANDLER_SET )
    {
        if( keyID == APP_KEY )
        {
            MibRequestConfirm_t mibReq;
            mibReq.Type = MIB_APP_KEY;
            mibReq.Param.AppKey = key;
            if( LoRaMacMibSetRequestConfirm( &mibReq ) != LORAMAC_STATUS_OK )
            {
                return LORAMAC_HANDLER_ERROR;
            }
            return LORAMAC_HANDLER_SUCCESS;
        }
        else if( SECURE_ELEMENT_SUCCESS != SecureElementSetKey( keyID, key ) )
        {
            return LORAMAC_HANDLER_ERROR;
        }
    }
    else
    {
        return LORAMAC_HANDLER_ERROR;
    }

    return LORAMAC_HANDLER_SUCCESS;
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
/* Mac/LoRaMac.c */

LoRaMacStatus_t LoRaMacMibSetRequestConfirm( MibRequestConfirm_t* mibSet )
{
    ...
    switch( mibSet->Type )
    {
        ...
        case MIB_APP_KEY:
        {
            if( mibSet->Param.AppKey != NULL )
            {
                if( LORAMAC_CRYPTO_SUCCESS != LoRaMacCryptoSetKey( APP_KEY, mibSet->Param.AppKey ) )
                {
                    return LORAMAC_STATUS_CRYPTO_ERROR;
                }
            }
            else
            {
                status = LORAMAC_STATUS_PARAMETER_INVALID;
            }
            break;
        }
        ...
    }
    ...
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/* Mac/LoRaMacCrypto.c */

LoRaMacCryptoStatus_t LoRaMacCryptoSetKey( KeyIdentifier_t keyID, uint8_t* key )
{
    if( SecureElementSetKey( keyID, key ) != SECURE_ELEMENT_SUCCESS )
    {
        return LORAMAC_CRYPTO_ERROR_SECURE_ELEMENT_FUNC;
    }
    if( keyID == APP_KEY )
    {
        if( LoRaMacCryptoDeriveLifeTimeKey( CryptoNvm->LrWanVersion.Fields.Minor, MC_ROOT_KEY ) != LORAMAC_CRYPTO_SUCCESS )
        {
            return LORAMAC_CRYPTO_ERROR_SECURE_ELEMENT_FUNC;
        }
        if( LoRaMacCryptoDeriveLifeTimeKey( 0, MC_KE_KEY ) != LORAMAC_CRYPTO_SUCCESS )
        {
            return LORAMAC_CRYPTO_ERROR_SECURE_ELEMENT_FUNC;
        }
        if( LoRaMacCryptoDeriveLifeTimeKey( 0, DATABLOCK_INT_KEY ) != LORAMAC_CRYPTO_SUCCESS )
        {
            return LORAMAC_CRYPTO_ERROR_SECURE_ELEMENT_FUNC;
        }
    }
    return LORAMAC_CRYPTO_SUCCESS;
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
/* Crypto/soft-se.c */

SecureElementStatus_t SecureElementSetKey( KeyIdentifier_t keyID, uint8_t *key )
{
    if( key == NULL )
    {
        return SECURE_ELEMENT_ERROR_NPE;
    }

#if (LORAWAN_KMS == 0)
    for( uint8_t i = 0; i < NUM_OF_KEYS; i++ )
    {
        if( SeNvm->KeyList[i].KeyID == keyID )
        {
#if ( LORAMAC_MAX_MC_CTX == 1 )
            if( keyID == MC_KEY_0 )
#else /* LORAMAC_MAX_MC_CTX > 1 */
            ...
#endif /* LORAMAC_MAX_MC_CTX */
            {
                SecureElementStatus_t retval = SECURE_ELEMENT_ERROR;
                uint8_t decryptedKey[SE_KEY_SIZE] = { 0 };

                retval = SecureElementAesEncrypt( key, SE_KEY_SIZE, MC_KE_KEY, decryptedKey );

                memcpy1( SeNvm->KeyList[i].KeyValue, decryptedKey, SE_KEY_SIZE );
                return retval;
            }
            else
            {
                memcpy1( SeNvm->KeyList[i].KeyValue, key, SE_KEY_SIZE );
                return SECURE_ELEMENT_SUCCESS;
            }
        }
    }

    return SECURE_ELEMENT_ERROR_INVALID_KEY_ID;
#else /* LORAWAN_KMS == 1 */
    ...
#endif /* LORAWAN_KMS */
}

3. 入网保存参数的流程

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
/* LmHandler/LmHandler.c */

void LmHandlerJoin( ActivationType_t mode, bool forceRejoin )
{
    MlmeReq_t mlmeReq;

    mlmeReq.Type = MLME_JOIN;
    mlmeReq.Req.Join.Datarate = LmHandlerParams.TxDatarate;
    mlmeReq.Req.Join.TxPower = LmHandlerParams.TxPower;

    if( mode == ACTIVATION_TYPE_OTAA )
    {
        ...
    }
    else
    {
        ...
        JoinParams.Mode = ACTIVATION_TYPE_ABP;
        ...

        if( CtxRestoreDone == false )
        {
            /* 如果没有保存过入网参数, 也就是未入网的设备上电后进行入网 */
            ...
        }

        LoRaMacStart();
        /* Nvm.MacGroup2.NetworkActivation 赋值为 ACTIVATION_TYPE_ABP */
        mibReq.Type = MIB_NETWORK_ACTIVATION;
        mibReq.Param.NetworkActivation = ACTIVATION_TYPE_ABP;
        LoRaMacMibSetRequestConfirm( &mibReq );
        ...
    }

#if (defined( LORAMAC_VERSION ) && (( LORAMAC_VERSION == 0x01000400 ) || ( LORAMAC_VERSION == 0x01010100 )))
    if( ( CtxRestoreDone == false ) || ( forceRejoin == true ) )
    {
        /* 强制重新入网 或 未入网的设备上电后进行入网 */
        LoRaMacMlmeRequest( &mlmeReq );
    }
    DutyCycleWaitTime = mlmeReq.ReqReturn.DutyCycleWaitTime;
#endif /* LORAMAC_VERSION */
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
/* Mac/LoRaMac.c */

LoRaMacStatus_t LoRaMacMlmeRequest( MlmeReq_t* mlmeRequest )
{
    ...
    switch( mlmeRequest->Type )
    {
        case MLME_JOIN:
        {
            ...
#if (defined( LORAMAC_VERSION ) && ( LORAMAC_VERSION == 0x01000300 ))
            ...
#elif (defined( LORAMAC_VERSION ) && (( LORAMAC_VERSION == 0x01000400 ) || ( LORAMAC_VERSION == 0x01010100 )))
            if( mlmeRequest->Req.Join.NetworkActivation == ACTIVATION_TYPE_OTAA )
            {
                ...
            }
            else if( mlmeRequest->Req.Join.NetworkActivation == ACTIVATION_TYPE_ABP )
            {
                ...
                Nvm.MacGroup2.NetworkActivation = mlmeRequest->Req.Join.NetworkActivation;
                ...
            }
#endif /* LORAMAC_VERSION */
            break;
        }
        ...
    }
    ...
}

三、FCnt

1. 帧计数的使用

调用流程:

1
2
3
4
5
6
7
8
9
# Mac/LoRaMac.c

Send
  -> PrepareFrame
    -> LoRaMacCryptoGetFCntUp
  -> ScheduleTx
    -> SendFrameOnChannel
      -> SecureFrame
        -> LoRaMacCryptoGetFCntUp
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
/* Mac/LoRaMacCrypto.c */

LoRaMacCryptoStatus_t LoRaMacCryptoGetFCntUp( uint32_t* currentUp )
{
    if( currentUp == NULL )
    {
        return LORAMAC_CRYPTO_ERROR_NPE;
    }

    /* CryptoNvm 指向的是 Nvm.Crypto, 实际是 Nvm.Crypto.FCntList.FCntUp */
    *currentUp = CryptoNvm->FCntList.FCntUp + 1;

    return LORAMAC_CRYPTO_SUCCESS;
}

2. 帧计数的更新

调用流程:

1
2
3
4
ScheduleTx
  -> SendFrameOnChannel
    -> SecureFrame
      -> LoRaMacCryptoSecureMessage
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
/* Mac/LoRaMacCrypto.c */

LoRaMacCryptoStatus_t LoRaMacCryptoSecureMessage( uint32_t fCntUp, uint8_t txDr, uint8_t txCh, LoRaMacMessageData_t* macMsg )
{
    ...
    /* CryptoNvm 指向的是 Nvm.Crypto, 实际是 Nvm.Crypto.FCntList.FCntUp */
    CryptoNvm->FCntList.FCntUp = fCntUp;

    return LORAMAC_CRYPTO_SUCCESS;
}

四、NVM

1. 类型和定义

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
/* Mac/LoRaMacInterfaces.h */

/*!
 * LoRaMAC data structure for non-volatile memory (NVM).
 * This structure contains data which must be stored in NVM.
 */
typedef struct sLoRaMacNvmData
{
    /*!
     * Parameters related to the crypto layer. Change with every TX/RX
     * procedure.
     */
    LoRaMacCryptoNvmData_t Crypto;
    /*!
     * Parameters related to the MAC which change with high probability after
     * every TX/RX procedure.
     */
    LoRaMacNvmDataGroup1_t MacGroup1;
    /*!
     * Parameters related to the MAC which do not change very likely with every
     * TX/RX procedure.
     */
    LoRaMacNvmDataGroup2_t MacGroup2;
    /*!
     * Parameters related to the secure-element.
     */
    SecureElementNvmData_t SecureElement;
    /*!
     * Parameters related to the regional implementation which change with high
     * probability after every TX/RX procedure.
     */
    RegionNvmDataGroup1_t RegionGroup1;
    /*!
     * Parameters related to the regional implementation which do not change
     * very likely with every TX/RX procedure.
     */
    RegionNvmDataGroup2_t RegionGroup2;
    /*!
     * Parameters related to class b.
     */
    LoRaMacClassBNvmData_t ClassB;
}LoRaMacNvmData_t;
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
/* Mac/LoRaMac.c */

/**
 * 在 LoRaWAN 1.0.4 及以后的版本, 都需要在 lorawan_conf.h 头文件中定义 CONTEXT_MANAGEMENT_ENABLED
 * 下面是整理后的代码, 方便查看
 */
#if (defined( CONTEXT_MANAGEMENT_ENABLED ) && ( CONTEXT_MANAGEMENT_ENABLED == 1 ))
    #if defined(__ICCARM__)
        __NO_INIT __ROOT static LoRaMacNvmData_t Nvm @ ".LW_NVM_RAM";
    #elif defined(__GNUC__)
        __attribute__((section(".bss.LW_NVM_RAM")))
        __NO_INIT __ROOT static LoRaMacNvmData_t Nvm;
    #else
        #warning NVM RAM placement not defined
        __NO_INIT __ROOT static LoRaMacNvmData_t Nvm;
    #endif

    #if defined(__ICCARM__)
        __NO_INIT __ROOT static LoRaMacNvmData_t NvmBackup @ ".LW_NVM_BACKUP_RAM";
    #elif defined(__GNUC__)
        __attribute__((section(".bss.LW_NVM_BACKUP_RAM")))
        __NO_INIT __ROOT static LoRaMacNvmData_t NvmBackup;
    #else
        #warning NVM RAM placement not defined
        __NO_INIT __ROOT static LoRaMacNvmData_t NvmBackup;
    #endif
#else
    static LoRaMacNvmData_t Nvm;
#endif 

2. 保存

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
/* LmHandler/LmHandler.c */

LmHandlerErrorStatus_t LmHandlerNvmDataStore( void )
{
#if (defined( CONTEXT_MANAGEMENT_ENABLED ) && ( CONTEXT_MANAGEMENT_ENABLED == 1 ))
    LoRaMacNvmData_t *nvm;
    uint32_t nvm_size;
    LmHandlerErrorStatus_t lmhStatus = LORAMAC_HANDLER_SUCCESS;
    int32_t status = NVM_DATA_OK;

    lmhStatus = LmHandlerHalt();

    if( lmhStatus == LORAMAC_HANDLER_SUCCESS )
    {
        status = NvmDataMgmtStoreBegin();

        if( status == NVM_DATA_NO_UPDATED_DATA )
        {
            lmhStatus = LORAMAC_HANDLER_NVM_DATA_UP_TO_DATE;
        }
        else if( ( status != NVM_DATA_OK ) || ( LmHandlerCallbacks->OnStoreContextRequest == NULL ) )
        {
            lmhStatus = LORAMAC_HANDLER_ERROR;
        }
        else
        {
            MibRequestConfirm_t mibReq;
            mibReq.Type = MIB_NVM_CTXS;
            LoRaMacMibGetRequestConfirm( &mibReq );
            /* 获取到在 LoRaMac.c 里面定义的静态全局变量 static LoRaMacNvmData_t Nvm 的指针 */
            nvm = ( LoRaMacNvmData_t * )mibReq.Param.Contexts;
            /* 向上进行 8 取整, 因为 flash 操作的内存大小要求是 8 字节对齐 */
            nvm_size = ( ( sizeof( LoRaMacNvmData_t ) + 7 ) & ~0x07 );
            /**
             * 调用回调函数保存 nvm
             * 这个回调函数是调用 LmHandlerInit 初始化时提供的
             */
            LmHandlerCallbacks->OnStoreContextRequest( nvm, nvm_size );
        }

        if( NvmDataMgmtStoreEnd() != NVM_DATA_OK )
        {
            lmhStatus = LORAMAC_HANDLER_ERROR;
        }
    }

    if( ( lmhStatus == LORAMAC_HANDLER_SUCCESS ) && ( LmHandlerCallbacks->OnNvmDataChange != NULL ) )
    {
        LmHandlerCallbacks->OnNvmDataChange( LORAMAC_HANDLER_NVM_STORE );
    }

    return lmhStatus;
#else
    return LORAMAC_HANDLER_ERROR;
#endif /* CONTEXT_MANAGEMENT_ENABLED */
}

假设回调函数如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
#include "flash_if.h"

#define LORAWAN_NVM_BASE_ADDRESS    ((void *)0x0803F000UL)

static void OnStoreContextRequest(void *nvm, uint32_t nvm_size)
{
    /**
     * 调用 ST 提供的 FLASH 操作函数进行保存操作
     * 将整个 nvm (LoRaMacNvmData_t) 保存到 FLASH 里面
     */
    if (FLASH_IF_Erase(LORAWAN_NVM_BASE_ADDRESS, FLASH_PAGE_SIZE) == FLASH_IF_OK) {
        FLASH_IF_Write(LORAWAN_NVM_BASE_ADDRESS, (const void *)nvm, nvm_size);
    }
}

3. 上电后读取

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
/* LmHandler/LmHandler.c */

LmHandlerErrorStatus_t LmHandlerConfigure( LmHandlerParams_t *handlerParams )
{
    ...
    /**
     * 对于 LoRaMac.c 里面定义的静态全局变量 static LoRaMacNvmData_t Nvm 来说
     *  1. 使用 memset1 将其置 0
     *  2. 配置默认值
     *  3. 调用 SecureElementInit 函数对 Nvm.SecureElement 进行初始化
     *  4. 调用 LoRaMacCryptoInit 函数对 Nvm.Crypto 进行清零和初始化
     */
    if( LoRaMacInitialization( &LoRaMacPrimitives, &LoRaMacCallbacks, LmHandlerParams.ActiveRegion ) != LORAMAC_STATUS_OK )
    {
        return LORAMAC_HANDLER_ERROR;
    }

#if (defined( CONTEXT_MANAGEMENT_ENABLED ) && ( CONTEXT_MANAGEMENT_ENABLED == 1 ))
    /**
     * 调用 RestoreNvmData 函数
     *  1. 对 NvmBackup 进行 CRC32 校验
     *  2. 调用 memcpy1 函数将 NvmBackup 赋值给 Nvm
     *  3. 调用 memset1 函数将 NvmBackup 清零
     */
    mibReq.Type = MIB_NVM_CTXS;
    if( LoRaMacMibSetRequestConfirm( &mibReq ) == LORAMAC_STATUS_OK )
    {
        CtxRestoreDone = true;
    }
    else
    {
        /* 由于是重新上电, 所以是走这里 */
        mibReq.Type = MIB_NVM_BKP_CTXS;
        if( LmHandlerCallbacks->OnRestoreContextRequest != NULL )
        {
            /**
             * 获取 LoRaMac.c 里面定义的静态全局变量 static LoRaMacNvmData_t NvmBackup
             * 读取 FLASH 保存的数据, 并将其赋值给 NvmBackup
             */
            LoRaMacMibGetRequestConfirm( &mibReq );
            LmHandlerCallbacks->OnRestoreContextRequest( mibReq.Param.BackupContexts, sizeof( LoRaMacNvmData_t ) );
        }
        /* 重新调用 RestoreNvmData 函数 */
        mibReq.Type = MIB_NVM_CTXS;
        if( LoRaMacMibSetRequestConfirm( &mibReq ) == LORAMAC_STATUS_OK )
        {
            mibReq.Type = MIB_NETWORK_ACTIVATION;
            LoRaMacMibGetRequestConfirm( &mibReq );
            /* 检测设备是否已经入网, 如果已经入网, 则需要恢复上下文 */
            if( mibReq.Param.NetworkActivation != ACTIVATION_TYPE_NONE )
            {
                CtxRestoreDone = true;
            }
        }
    }

    /* 恢复上下文 */
    if( CtxRestoreDone == true )
    {
        if( LmHandlerCallbacks->OnNvmDataChange != NULL )
        {
            LmHandlerCallbacks->OnNvmDataChange( LORAMAC_HANDLER_NVM_RESTORE );
        }

        if(( LmHandlerJoinStatus() == LORAMAC_HANDLER_SET) && LoRaMacIsStopped())
        {
            /**
             * 由于设备上一次运行期间是已入网的, 所以需要在这里调用 LoRaMacStart 函数启动 LoRaMac
             * 如果是未入网的情况, 则是通过 LmHandlerJoin 函数来调用 LoRaMacStart 函数
             */
            LoRaMacStart();
        }

        /* 获取 LoRaMac.c 里面定义的静态全局变量 static LoRaMacNvmData_t Nvm */
        mibReq.Type = MIB_NVM_CTXS;
        LoRaMacMibGetRequestConfirm( &mibReq );
        LoRaMacNvmData_t *current_nvm = mibReq.Param.Contexts;

        LmHandlerParams.ActiveRegion = current_nvm->MacGroup2.Region;
        LmHandlerParams.DefaultClass = current_nvm->MacGroup2.DeviceClass;
        LmHandlerParams.AdrEnable = current_nvm->MacGroup2.AdrCtrlOn;
    }
    else
#endif /* CONTEXT_MANAGEMENT_ENABLED == 1 */
}

假设回调函数如下:

1
2
3
4
5
6
7
8
#include "flash_if.h"

#define LORAWAN_NVM_BASE_ADDRESS    ((void *)0x0803F000UL)

static void OnRestoreContextRequest(void *nvm, uint32_t nvm_size)
{
    FLASH_IF_Read(nvm, LORAWAN_NVM_BASE_ADDRESS, nvm_size);
}

五、总结

  1. 设备 OTAA 入网后,会将服务器下发的关键参数更新到 Nvm 变量里面。

    设备 ABP 入网前,也会手动将关键参数更新到 Nvm 变量里面。

  2. 设备发送一笔数据帧,也会将 FCnt 更新到 Nvm 变量里面。

  3. 我们只需要将 Nvm 变量保存到 Flash 里面,上电后从 Flash 重新加载到 Nvm 变量,那么设备就不用再次入网,而是直接发送数据了。

版权声明

本文为「Zeepunt 日常随笔」的原创文章,遵循 CC BY-NC-ND 4.0 许可协议。允许在署名作者、注明原文链接且不作任何更改的前提下非商业性地分享本文。

原文链接:https://zeepunt.github.io/article/lora/device%E5%85%A5%E7%BD%91%E5%8F%82%E6%95%B0%E7%9A%84%E4%BF%9D%E5%AD%98/