LoRaWAN end device MAC layer for SX1272 and SX1276. Supports LoRaWAN-1.0 and LoRaWAN-1.1

Dependencies:   sx12xx_hal

Dependents:   LoRaWAN-SanJose_Bootcamp LoRaWAN-grove-cayenne LoRaWAN-classC-demo LoRaWAN-grove-cayenne ... more

radio chip selection

Radio chip driver is not included, because two options are available.
If you're using SX1272 or SX1276, then import sx127x driver into your program.
if you're using SX1261 or SX1262, then import sx126x driver into your program.
If you're using NAmote72 or Murata discovery, then you must import only sx127x driver.

application project requirements

This library requires mbed TLS to be enabled.
The file mbed_app.json must be present in the project using this library:

{
    "macros": [ "MBEDTLS_CMAC_C" ]
}

regional PHY selection

All end device configuration is done in Commissioning.h, define desired radio frequency band of operation in this header file.
Commissioning.h is located in the application using this library.

end device provisioning

End device is provisioned by editing Commissioning.h in the application which is using this library
To use LoRaWAN-1.0 OTA: make sure LORAWAN_ROOT_APPKEY is undefined.
To use LoRaWAN-1.1 OTA, define LORAWAN_ROOT_APPKEY.
To select OTA operation, define LORAWAN_JOIN_EUI, then LORAWAN_DEVICE_EUI must be defined, along with root key(s).
To select ABP operation, undefine LORAWAN_JOIN_EUI: then define session keys

LoRaWAN 1.0 nameLoRaWAN 1.1 nameComissioning.h defnedescription
OTADevEUIDevEUILORAWAN_DEVICE_EUIuniquely identifies end device
OTAAppEUIJoinEUILORAWAN_JOIN_EUI
OTAAppKeyNwkKeyLORAWAN_ROOT_NWKKEYroot key for network server
OTA(note 1)AppKeyLORAWAN_ROOT_APPKEYroot key for application server
ABPNwkSKey(note 3)LORAWAN_FNwkSIntKeynetwork session key
ABP(note 2)SNwkSIntKeyLORAWAN_SNwkSIntKeymac layer network integrity key
ABP(note 2)NwkSEncKeyLORAWAN_NwkSEncKeynetwork session encryption key
ABP(note 2)FNwkSIntKeyLORAWAN_FNwkSIntKeyforwarding network session integrity key
ABPAppSKeyAppSKeyLORAWAN_APPSKEYapplication session encryption key

(note 1): LoRaWAN-1.0 OTA uses a single root key for both network server and application server.

In LoRaWAN-1.0 OTA: the single root AppKey is used to generate NwkSkey and AppSKey.
(note 2): In LoRaWAN-1.0 (both OTA and ABP) SNwkSIntKey, NwkSEncKey. FNwkSIntKey are of same value and are collectively known as NwkSKey.
(note 3): LoRaWAN-1.0 uses single network session key, LoRaWAN-1.1 uses 3 network session keys. Both use a unique application session key.


In LoRaWAN-1.1 OTA: the root NwkKey is used to generate SNwkSIntKey, NwkSEncKey, FNwkSIntKey
In LoRaWAN-1.1 OTA: the root AppKey is used to generate AppSKey


in ABP mode, the DevAddr, and session keys are fixed (never change), and frame counters never reset to zero.
ABP operation has no concept of: root keys, or DevEUI or JoinEUI/AppEUI.
in OTA mode, the DevAddr and session keys are assigned at join procedure, and frame counters reset at join.

eeprom

This library includes eeprom driver to support non-volatile storage required by LoRaWAN specification.
Currently eeprom is implemented for STM32L1 family and STM32L0 family.
Writing of values are wear-leveled to increase endurance; each write operation circulates across several memory locations. A read operation returns the highest value found. This simple method is used for sequence numbers which only increase.

value nameused in
DevNonceOTAfor Join request (note 1)
RJcount1OTAfor ReJoin Type 1 request
FCntUpABPuplink frame counter
NFCntDownABPdownlink frame counter
AFCntDownABPdownlink frame counter

AFCntDown is only used in LoRaWAN-1.1 when application payload is present in downlink and FPort > 0.
NFCntDown is used in LoRaWAN-1.1 when FPort is zero in downlink or application payload not present.
NFCntDown is the only downlink frame counter used in LoRaWAN-1.0
(note 1) OTA DevNonce is random number in LoRaWAN-1.0, therefore not stored in eeprom. DevNonce in LoRaWAN-1.1 is forever increasing (non-volatile) number upon each join request,.
RJcount0 is only stored in RAM because the value resets upon new session from JoinAccept, therefore not stored in eeprom.
Frame counters in OTA mode reset upon new session in join request, therefore are stored in RAM instead of eeprom for OTA.

radio driver support

When SX127x driver is used, both SX1272 and SX1276 are supported without defining at compile time. The chip is detected at start-up.
Supported radio platforms:


Alternately, when SX126x driver is imported, the SX126xDVK1xAS board is used.

low-speed clock oscillator selection

LoRaWAN uses 32768Hz crystal to permit low-power operation.
However, some mbed targets might revert to low-speed internal oscillator, which is not accurate enough for LoRaWAN operation.
An oscillator check is performed at initialization; program will not start if internal oscillator is used.
To force LSE watch crystal, add to mbed_app.json

{
    "macros": [ "MBEDTLS_CMAC_C" ],
    "target_overrides": {
        "<your-target>": {
            "target.lse_available": true
        }
    }
}

mac/region_us915.cpp

Committer:
Wayne Roberts
Date:
2018-07-23
Revision:
11:ce1317758488
Parent:
10:9a7a8b8d0ac2
Child:
12:0f28f2e7c35e

File content as of revision 11:ce1317758488:

#include "lorawan_board.h"
#if defined(USE_BAND_915_HYBRID) || defined(USE_BAND_915)
#include <stdint.h>
#include "LoRaMacPrivate.h"

ChannelParams_t Channels[LORA_MAX_NB_CHANNELS];
uint16_t ChannelsMaskRemaining[6];
const int8_t TxPowers[]    = { 30, 28, 26, 24, 22, 20, 18, 16, 14, 12, 10 };

//                        DR:   0  1  2  3  4  5  6  7   8   9  10  11  12  13  14  15
const uint8_t Datarates[]  = { 10, 9, 8, 7, 8, 0, 0, 0, 12, 11, 10,  9,  8,  7,  0,  0};

const uint8_t MaxPayloadOfDatarate[] = { 11, 53, 125, 242, 242, 0, 0, 0, 53, 129, 242, 242, 242, 242, 0, 0 };

bool DisableChannelInMask( uint8_t id, uint16_t* mask )
{
    uint8_t index = 0;
    index = id / 16;

    if( ( index > 4 ) || ( id >= LORA_MAX_NB_CHANNELS ) )
    {
        return false;
    }

    // Deactivate channel
    mask[index] &= ~( 1 << ( id % 16 ) );

    return true;
}

/*!
 * Up/Down link data rates offset definition
 */
const int8_t datarateOffsets[5][4] =
{
    { DR_10, DR_9 , DR_8 , DR_8  }, // DR_0
    { DR_11, DR_10, DR_9 , DR_8  }, // DR_1
    { DR_12, DR_11, DR_10, DR_9  }, // DR_2
    { DR_13, DR_12, DR_11, DR_10 }, // DR_3
    { DR_13, DR_13, DR_12, DR_11 }, // DR_4
};

uint32_t region_GetRxBandwidth( int8_t datarate )
{
    if( datarate >= DR_4 )
    {// LoRa 500 kHz
        return 2;
    }
    return 0; // LoRa 125 kHz
}

uint16_t region_GetRxSymbolTimeout( int8_t datarate )
{
    switch( datarate )
    {
        case DR_0:       // SF10 - BW125
            return 5;

        case DR_1:       // SF9  - BW125
        case DR_2:       // SF8  - BW125
        case DR_8:       // SF12 - BW500
        case DR_9:       // SF11 - BW500
        case DR_10:      // SF10 - BW500
            return 8;

        case DR_3:       // SF7  - BW125
        case DR_11:     // SF9  - BW500
            return 10;

        case DR_4:       // SF8  - BW500
        case DR_12:      // SF8  - BW500
            return 14;

        case DR_13:      // SF7  - BW500
            return 16;

        default:
            return 0;   // LoRa 125 kHz
    }
}

void region_rx1_setup(uint8_t ch)
{
    int8_t datarate = datarateOffsets[LoRaMacParams.ChannelsDatarate][LoRaMacParams.Rx1DrOffset];
    if( datarate < 0 )
    {
        datarate = DR_0;
    }

    RxWindowSetup(
        LORAMAC_FIRST_RX1_CHANNEL + ( ch % 8 ) * LORAMAC_STEPWIDTH_RX1_CHANNEL,
        datarate,
        region_GetRxBandwidth(datarate),
        region_GetRxSymbolTimeout(datarate)
    );
}

bool ValidateChannelMask( uint16_t* channelsMask )
{
    bool chanMaskState = false;
    uint16_t block1 = 0;
    uint16_t block2 = 0;
    uint8_t index = 0;

    for( uint8_t i = 0; i < 4; i++ )
    {
        block1 = channelsMask[i] & 0x00FF;
        block2 = channelsMask[i] & 0xFF00;

        if( ( CountBits( block1, 16 ) > 5 ) && ( chanMaskState == false ) )
        {
            channelsMask[i] &= block1;
            channelsMask[4] = 1 << ( i * 2 );
            chanMaskState = true;
            index = i;
        }
        else if( ( CountBits( block2, 16 ) > 5 ) && ( chanMaskState == false ) )
        {
            channelsMask[i] &= block2;
            channelsMask[4] = 1 << ( i * 2 + 1 );
            chanMaskState = true;
            index = i;
        }
    }

    // Do only change the channel mask, if we have found a valid block.
    if( chanMaskState == true )
    {
        for( uint8_t i = 0; i < 4; i++ )
        {
            if( i != index )
            {
                channelsMask[i] = 0;
            }
        }
    }
    return chanMaskState;
}

void region_adr_request(adr_t* adr)
{
    if( adr->chMaskCntl == 6 )
    {
        // Enable all 125 kHz channels
        adr->channelsMask[0] = 0xFFFF;
        adr->channelsMask[1] = 0xFFFF;
        adr->channelsMask[2] = 0xFFFF;
        adr->channelsMask[3] = 0xFFFF;
        // Apply chMask to channels 64 to 71
        adr->channelsMask[4] = adr->chMask;
    }
    else if( adr->chMaskCntl == 7 )
    {
        // Disable all 125 kHz channels
        adr->channelsMask[0] = 0x0000;
        adr->channelsMask[1] = 0x0000;
        adr->channelsMask[2] = 0x0000;
        adr->channelsMask[3] = 0x0000;
        // Apply chMask to channels 64 to 71
        adr->channelsMask[4] = adr->chMask;
    }
    else if( adr->chMaskCntl == 5 )
    {
        // RFU
        adr->status &= 0xFE; // Channel mask KO
    }
    else
    {
        adr->channelsMask[adr->chMaskCntl] = adr->chMask;

        // FCC 15.247 paragraph F mandates to hop on at least 2 125 kHz channels
        if( ( adr->datarate < DR_4 ) && ( CountNbEnabled125kHzChannels( adr->channelsMask ) < 2 ) )
        {
            adr->status &= 0xFE; // Channel mask KO
        }

#if defined( USE_BAND_915_HYBRID )
        if( ValidateChannelMask( adr->channelsMask ) == false )
        {
            adr->status &= 0xFE; // Channel mask KO
        }
#endif
    }


    if ((adr->status & 0x07) == 0x07) {
        // Reset ChannelsMaskRemaining to the new ChannelsMask
        ChannelsMaskRemaining[0] &= adr->channelsMask[0];
        ChannelsMaskRemaining[1] &= adr->channelsMask[1];
        ChannelsMaskRemaining[2] &= adr->channelsMask[2];
        ChannelsMaskRemaining[3] &= adr->channelsMask[3];
        ChannelsMaskRemaining[4] = adr->channelsMask[4];
        ChannelsMaskRemaining[5] = adr->channelsMask[5];
    }
}

uint8_t region_CountNbEnabledChannels()
{
    return CountNbEnabled125kHzChannels(LoRaMacParams.ChannelsMask) + CountBits(LoRaMacParams.ChannelsMask[4], 16);
}

uint8_t CountNbEnabled125kHzChannels( uint16_t *channelsMask )
{
    uint8_t nb125kHzChannels = 0;

    for( uint8_t i = 0, k = 0; i < LORA_MAX_NB_CHANNELS - 8; i += 16, k++ )
    {
        nb125kHzChannels += CountBits( channelsMask[k], 16 );
    }

    return nb125kHzChannels;
}

static bool SetNextChannel( )
{
    uint8_t nbEnabledChannels = 0;
    uint8_t enabledChannels[LORA_MAX_NB_CHANNELS];

    memset( enabledChannels, 0, LORA_MAX_NB_CHANNELS );

    if( CountNbEnabled125kHzChannels( ChannelsMaskRemaining ) == 0 )
    { // Restore default channels
        memcpy( ( uint8_t* ) ChannelsMaskRemaining, ( uint8_t* ) LoRaMacParams.ChannelsMask, 8 );
    }
    if( ( LoRaMacParams.ChannelsDatarate >= DR_4 ) && ( ( ChannelsMaskRemaining[4] & 0x00FF ) == 0 ) )
    { // Make sure, that the channels are activated
        ChannelsMaskRemaining[4] = LoRaMacParams.ChannelsMask[4];
    }

    // Search how many channels are enabled
    for( uint8_t i = 0, k = 0; i < LORA_MAX_NB_CHANNELS; i += 16, k++ )
    {
        for( uint8_t j = 0; j < 16; j++ )
        {
            if( ( ChannelsMaskRemaining[k] & ( 1 << j ) ) != 0 )
            {
                if( Channels[i + j].FreqHz == 0 )
                { // Check if the channel is enabled
                    continue;
                }
                if( ( ( Channels[i + j].DrRange.Fields.Min <= LoRaMacParams.ChannelsDatarate ) &&
                      ( LoRaMacParams.ChannelsDatarate <= Channels[i + j].DrRange.Fields.Max ) ) == false )
                { // Check if the current channel selection supports the given datarate
                    continue;
                }
                enabledChannels[nbEnabledChannels++] = i + j;
            }
        }
    }

    if( nbEnabledChannels > 0 )
    {
        Channel = enabledChannels[random_at_most( nbEnabledChannels - 1 )];
        if( Channel < ( LORA_MAX_NB_CHANNELS - 8 ) )
        {
            DisableChannelInMask( Channel, ChannelsMaskRemaining );
        }
        return true;
    }
    else
    {
        // Datarate not supported by any channel
        return false;
    }
}

void region_ScheduleTx( )
{
    // Select channel
    while( SetNextChannel() == false )
    {
        // Set the default datarate
        LoRaMacParams.ChannelsDatarate = LoRaMacParamsDefaults.ChannelsDatarate;
    }

    //MAC_PRINTF("ch%u ", Channel);
    SendFrameOnChannel( Channel );
}

void region_tx_setup(int8_t dbm, uint8_t pktLen)
{
    int8_t datarate = Datarates[LoRaMacParams.ChannelsDatarate];

    Radio::set_tx_dbm(dbm);

    //MAC_PRINTF("txsetup sf%d dr%u ", datarate, LoRaMacParams.ChannelsDatarate);

    if( LoRaMacParams.ChannelsDatarate >= DR_4 )
    { // High speed LoRa channel BW500 kHz
        //TxTimeOnAir_us = Radio.TimeOnAir_us( MODEM_LORA, pktLen );
        Radio::LoRaModemConfig(500, datarate, 1);
        Radio::LoRaPacketConfig(8, false, true, false);
    }
    else
    { // Normal LoRa channel
        //TxTimeOnAir_us = Radio.TimeOnAir_us( MODEM_LORA, pktLen );
        Radio::LoRaModemConfig(125, datarate, 1);
        Radio::LoRaPacketConfig(8, false, true, false);
    }
}

#define RECEIVE_DELAY2_us                           2000000
#define JOIN_ACCEPT_DELAY1_us                          5000000
#define JOIN_ACCEPT_DELAY2_us                          6000000
const LoRaMacParams_t LoRaMacParamsDefaults = {
    /* int8_t ChannelsTxPower; */   LORAMAC_DEFAULT_TX_POWER,
    /* int8_t ChannelsDatarate;*/   LORAMAC_DEFAULT_DATARATE,
    /* uint32_t MaxRxWindow_us;*/   MAX_RX_WINDOW_us,
    /* uint32_t ReceiveDelay1_us;*/ RECEIVE_DELAY1_us,
    /* uint32_t ReceiveDelay2_us;*/ RECEIVE_DELAY2_us,
#ifdef LORAWAN_JOIN_EUI
    /* uint32_t JoinAcceptDelay1_us;*/  JOIN_ACCEPT_DELAY1_us,
    /* uint32_t JoinAcceptDelay2_us;*/  JOIN_ACCEPT_DELAY2_us,
#endif /* LORAWAN_JOIN_EUI  */
    /* uint8_t NbTrans;*/   1,
    /* uint8_t Rx1DrOffset;*/   0,
    /* Rx2ChannelParams_t Rx2Channel;*/ RX_WND_2_CHANNEL,
#if defined( USE_BAND_915 )
    /* uint16_t ChannelsMask[6];*/ { 0xffff, 0xffff, 0xffff, 0xffff, 0x00ff, 0x0000},
    /* uint8_t NbEnabledChannels;*/ 72,
#elif defined( USE_BAND_915_HYBRID )
    /* uint16_t ChannelsMask[6];*/ { 0x00ff, 0x0000, 0x0000, 0x0000, 0x0001, 0x0000},
    /* uint8_t NbEnabledChannels;*/ 9,
#endif
    /* us_timestamp_t MaxListenTime */ 0    /* no LBT in USA */
};

void region_mac_init()
{
    // 125 kHz channels
    for( uint8_t i = 0; i < LORA_MAX_NB_CHANNELS - 8; i++ )
    {
        Channels[i].FreqHz = 902.3e6 + i * 200e3;
        Channels[i].DrRange.Value = ( DR_3 << 4 ) | DR_0;
        Channels[i].Band = 0;
    }
    // 500 kHz channels
    for( uint8_t i = LORA_MAX_NB_CHANNELS - 8; i < LORA_MAX_NB_CHANNELS; i++ )
    {
        Channels[i].FreqHz = 903.0e6 + ( i - ( LORA_MAX_NB_CHANNELS - 8 ) ) * 1.6e6;
        Channels[i].DrRange.Value = ( DR_4 << 4 ) | DR_4;
        Channels[i].Band = 0;
    }
}

int8_t
region_LimitTxPower( int8_t txPower )
{
    int8_t resultTxPower = txPower;
 
    if( ( LoRaMacParams.ChannelsDatarate == DR_4 ) ||
        ( ( LoRaMacParams.ChannelsDatarate >= DR_8 ) && ( LoRaMacParams.ChannelsDatarate <= DR_13 ) ) )
    {// Limit tx power to max 26dBm
        resultTxPower =  MAX( txPower, TX_POWER_26_DBM );
    }
    else
    {
        if( CountNbEnabled125kHzChannels( LoRaMacParams.ChannelsMask ) < 50 )
        {// Limit tx power to max 21dBm
            resultTxPower = MAX( txPower, TX_POWER_20_DBM );
        }
    }
    return resultTxPower;
}

#ifdef LORAWAN_JOIN_EUI
int8_t
region_AlternateDatarate( uint16_t nbTrials )
{
    if (region_CountNbEnabledChannels() < LoRaMacParamsDefaults.NbEnabledChannels) {
        memcpy(LoRaMacParams.ChannelsMask, LoRaMacParamsDefaults.ChannelsMask, sizeof(LoRaMacParams.ChannelsMask));
        LoRaMacParams.NbEnabledChannels = LoRaMacParamsDefaults.NbEnabledChannels;
    }

    if( ( nbTrials & 0x01 ) == 0x01 )
    {
        return DR_4;
    }
    else
    {
        return DR_0;
    }
}
#endif /* LORAWAN_JOIN_EUI  */

void region_session_start(LoRaMacEventInfoStatus_t status) { }

#endif /* USE_BAND_915_HYBRID || USE_BAND_915 */