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
        }
    }
}

radio/radio_sx126x.cpp

Committer:
Wayne Roberts
Date:
2018-06-11
Revision:
9:fe8e08792ae9
Parent:
8:5a5ea7cc946f

File content as of revision 9:fe8e08792ae9:

#include "radio.h"
#ifdef SX126x_H 
#include "board.h"
#include "SPIu.h"

LowPowerTimer Radio::lpt;
volatile us_timestamp_t dio1at;

#ifdef TARGET_FF_ARDUINO
    SPIu spi(D11, D12, D13); // mosi, miso, sclk
                   //spi, nss, busy, dio1
    SX126x Radio::radio(spi, D7, D3, D5);

    DigitalOut antswPower(D8);
    AnalogIn xtalSel(A3);

    DigitalIn chipType(A2);
    #define CHIP_TYPE_SX1262        0
    #define CHIP_TYPE_SX1261        1

    #define PINNAME_NRST            A0
#endif /* TARGET_FF_ARDUINO */

const RadioEvents_t* RadioEvents;
PacketParams_t Radio::pp;
RadioModems_t Radio::_m_;

#ifdef TARGET_FF_MORPHO
    DigitalOut pc3(PC_3);   // debug RX indication, for nucleo boards
#endif /* TARGET_FF_MORPHO */

void Radio::Rx(unsigned timeout)
{
    antswPower = 1;

    {
        uint8_t buf[8];
        IrqFlags_t irqEnable;
        irqEnable.word = 0;
        irqEnable.bits.RxDone = 1;
        irqEnable.bits.Timeout = 1;

        buf[0] = irqEnable.word >> 8;    // enable bits
        buf[1] = irqEnable.word; // enable bits
        buf[2] = irqEnable.word >> 8;     // dio1
        buf[3] = irqEnable.word;  // dio1
        buf[4] = 0; // dio2
        buf[5] = 0; // dio2
        buf[6] = 0; // dio3
        buf[7] = 0; // dio3
        radio.xfer(OPCODE_SET_DIO_IRQ_PARAMS, 8, 0, buf);
    }

#ifdef TARGET_FF_MORPHO
    pc3 = 1;
#endif /* TARGET_FF_MORPHO */
    if (timeout == 0)
        radio.start_rx(RX_TIMEOUT_CONTINUOUS);
    else
        radio.start_rx(timeout * RC_TICKS_PER_US);
}

void Radio::Standby()
{
    radio.setStandby(STBY_RC);  // STBY_XOSC

    antswPower = 0;
}

void Radio::Sleep()
{
    radio.setSleep(true, false);

    antswPower = 0;
}

void Radio::SetTxContinuousWave(unsigned hz, int8_t dbm, unsigned timeout_us)
{
    SetChannel(hz);
    radio.set_tx_dbm(chipType == CHIP_TYPE_SX1262, dbm);
    radio.xfer(OPCODE_SET_TX_CONTINUOUS, 0, 0, NULL);
}

uint32_t Radio::Random(void)
{
    uint32_t ret;

    radio.start_rx(RX_TIMEOUT_CONTINUOUS);

    ret = radio.readReg(REG_ADDR_RANDOM, 4);

    Standby();

    return ret;
}

bool Radio::CheckRfFrequency(unsigned hz)
{
    return true;
}

void Radio::SetChannel(unsigned hz)
{
    radio.setMHz(hz / 1000000.0);
}

void Radio::SetRxConfig(
    RadioModems_t modem,
    uint32_t bandwidth,
    uint32_t datarate,
    uint8_t coderate,
    uint32_t bandwidthAfc,
    uint16_t preambleLen,
    uint16_t symbTimeout,
    bool fixLen,
    uint8_t payloadLen,
    bool crcOn,
    bool iqInverted)
{
    uint8_t buf[8];

    if (modem == MODEM_FSK)
        buf[0] = PACKET_TYPE_GFSK;
    else
        buf[0] = PACKET_TYPE_LORA;

    radio.xfer(OPCODE_SET_PACKET_TYPE, 1, 0, buf);

    buf[0] = 0; // TX base address
    buf[1] = 0; // RX base address
    radio.xfer(OPCODE_SET_BUFFER_BASE_ADDR, 2, 0, buf);

    if (modem == MODEM_FSK) {
        FSKConfig(bandwidth, datarate, 0, preambleLen, fixLen, crcOn);

        pp.gfsk.PayloadLength = payloadLen;

        memcpy(buf, pp.buf, 8);
        radio.xfer(OPCODE_SET_PACKET_PARAMS, 8, 0, buf);
    } else if (modem == MODEM_LORA) {
        LoRaConfig(bandwidth, datarate, coderate, preambleLen, fixLen, crcOn, iqInverted);

        pp.lora.PayloadLength = payloadLen;

        memcpy(buf, pp.buf, 6);
        radio.xfer(OPCODE_SET_PACKET_PARAMS, 6, 0, buf);

        buf[0] = symbTimeout;
        radio.xfer(OPCODE_SET_LORA_SYMBOL_TIMEOUT, 1, 0, buf);
    }

    {
        IrqFlags_t irqEnable;
        irqEnable.word = 0;
        irqEnable.bits.RxDone = 1;
        irqEnable.bits.Timeout = 1;

        buf[0] = irqEnable.word >> 8;    // enable bits
        buf[1] = irqEnable.word; // enable bits
        buf[2] = irqEnable.word >> 8;     // dio1
        buf[3] = irqEnable.word;  // dio1
        buf[4] = 0; // dio2
        buf[5] = 0; // dio2
        buf[6] = 0; // dio3
        buf[7] = 0; // dio3
        radio.xfer(OPCODE_SET_DIO_IRQ_PARAMS, 8, 0, buf);
    }

} // ..SetRxConfig()

void Radio::FSKConfig(
    uint32_t bandwidth,
    uint32_t datarate,
    uint32_t fdev,
    uint32_t preambleLen,
    bool fixLen,
    bool crcOn)
{
    ModulationParams_t mp;
    uint32_t u32;

    u32  = 32 * (XTAL_FREQ_HZ / datarate);
    mp.gfsk.bitrateHi = u32 >> 16; // param1
    mp.gfsk.bitrateMid = u32 >> 8; // param2
    mp.gfsk.bitrateLo = u32;       // param3
    mp.gfsk.PulseShape = GFSK_SHAPE_BT1_0; // param4
    // param5:
    if (bandwidth < 5800)
        mp.gfsk.bandwidth = GFSK_RX_BW_4800;
    else if (bandwidth < 7300)
        mp.gfsk.bandwidth = GFSK_RX_BW_5800;
    else if (bandwidth < 9700)
        mp.gfsk.bandwidth = GFSK_RX_BW_7300;
    else if (bandwidth < 11700)
        mp.gfsk.bandwidth = GFSK_RX_BW_9700;
    else if (bandwidth < 14600)
        mp.gfsk.bandwidth = GFSK_RX_BW_11700;
    else if (bandwidth < 19500)
        mp.gfsk.bandwidth = GFSK_RX_BW_14600;
    else if (bandwidth < 23400)
        mp.gfsk.bandwidth = GFSK_RX_BW_19500;
    else if (bandwidth < 29300)
        mp.gfsk.bandwidth = GFSK_RX_BW_23400;
    else if (bandwidth < 39000)
        mp.gfsk.bandwidth = GFSK_RX_BW_29300;
    else if (bandwidth < 46900)
        mp.gfsk.bandwidth = GFSK_RX_BW_39000;
    else if (bandwidth < 58600)
        mp.gfsk.bandwidth = GFSK_RX_BW_46900;
    else if (bandwidth < 78200)
        mp.gfsk.bandwidth = GFSK_RX_BW_58600;
    else if (bandwidth < 93800)
        mp.gfsk.bandwidth = GFSK_RX_BW_78200;
    else if (bandwidth < 117300)
        mp.gfsk.bandwidth = GFSK_RX_BW_93800;
    else if (bandwidth < 156200)
        mp.gfsk.bandwidth = GFSK_RX_BW_117300;
    else if (bandwidth < 187200)
        mp.gfsk.bandwidth = GFSK_RX_BW_156200;
    else if (bandwidth < 234300)
        mp.gfsk.bandwidth = GFSK_RX_BW_187200;
    else if (bandwidth < 312000)
        mp.gfsk.bandwidth = GFSK_RX_BW_234300;
    else if (bandwidth < 373600)
        mp.gfsk.bandwidth = GFSK_RX_BW_312000;
    else if (bandwidth < 467000)
        mp.gfsk.bandwidth = GFSK_RX_BW_373600;
    else
        mp.gfsk.bandwidth = GFSK_RX_BW_467000;

    if (fdev > 0) {
        u32 = fdev / FREQ_STEP;
        mp.gfsk.fdevHi = u32 >> 16; // param6
        mp.gfsk.fdevMid = u32 >> 8;    // param7
        mp.gfsk.fdevLo = u32; // param8
    }

    radio.xfer(OPCODE_SET_MODULATION_PARAMS, 8, 0, mp.buf);


    pp.gfsk.PreambleLengthHi = preambleLen >> 8;
    pp.gfsk.PreambleLengthLo = preambleLen;
    pp.gfsk.PreambleDetectorLength = GFSK_PREAMBLE_DETECTOR_LENGTH_16BITS;
    pp.gfsk.SyncWordLength = 24; // 0xC194C1
    pp.gfsk.AddrComp = 0;
    pp.gfsk.PacketType = fixLen;
    if (crcOn)
        pp.gfsk.CRCType = GFSK_CRC_2_BYTE;
    else
        pp.gfsk.CRCType = GFSK_CRC_OFF;
}

void Radio::LoRaConfig(
    uint32_t bandwidth,
    uint8_t datarate,
    uint8_t coderate,
    uint16_t preambleLen,
    bool fixLen,
    bool crcOn,
    bool iqInverted)
{
    ModulationParams_t mp;
    float sp, khz;

    mp.lora.spreadingFactor = datarate; // param1
    mp.lora.bandwidth = bandwidth + 4; // param2
    mp.lora.codingRate = coderate; // param3

    switch (mp.lora.bandwidth) {
        case LORA_BW_7: khz = 7.81; break;
        case LORA_BW_10: khz = 10.42; break;
        case LORA_BW_15: khz = 15.625; break;
        case LORA_BW_20: khz = 20.83; break;
        case LORA_BW_31: khz = 31.25; break;
        case LORA_BW_41: khz = 41.67; break;
        case LORA_BW_62: khz = 62.5; break;
        case LORA_BW_125: khz = 125; break;
        case LORA_BW_250: khz = 250; break;
        case LORA_BW_500: khz = 500; break;
        default: khz = 0; break;
    }
    sp = (1 << mp.lora.spreadingFactor) / khz;
    /* TCXO dependent */
    if (sp > 16)
        mp.lora.LowDatarateOptimize = 1; // param4
    else
        mp.lora.LowDatarateOptimize = 0; // param4

    radio.xfer(OPCODE_SET_MODULATION_PARAMS, 4, 0, mp.buf);

    pp.lora.PreambleLengthHi = preambleLen >> 8;
    pp.lora.PreambleLengthLo = preambleLen;
    pp.lora.HeaderType = fixLen;
    pp.lora.CRCType = crcOn;
    pp.lora.InvertIQ = iqInverted;
}

void Radio::SetTxConfig(
        RadioModems_t modem,
        int8_t dbm,
        uint32_t fdev, 
        uint32_t bandwidth,
        uint32_t datarate,
        uint8_t coderate,
        uint16_t preambleLen,
        bool fixLen,
        bool crcOn,
        bool iqInverted)
{
    uint8_t buf[8];

    radio.set_tx_dbm(chipType == CHIP_TYPE_SX1262, dbm);

    if (modem == MODEM_FSK)
        buf[0] = PACKET_TYPE_GFSK;
    else
        buf[0] = PACKET_TYPE_LORA;

    radio.xfer(OPCODE_SET_PACKET_TYPE, 1, 0, buf);


    buf[0] = 0; // TX base address
    buf[1] = 0; // RX base address
    radio.xfer(OPCODE_SET_BUFFER_BASE_ADDR, 2, 0, buf);

    if (modem == MODEM_FSK) {
        FSKConfig(bandwidth, datarate, fdev, preambleLen, fixLen, crcOn);
    } else if (modem == MODEM_LORA) {
        LoRaConfig(bandwidth, datarate, coderate, preambleLen, fixLen, crcOn, iqInverted);
    }

    {
        IrqFlags_t irqEnable;
        irqEnable.word = 0;
        irqEnable.bits.TxDone = 1;
        irqEnable.bits.Timeout = 1;

        buf[0] = irqEnable.word >> 8;    // enable bits
        buf[1] = irqEnable.word; // enable bits
        buf[2] = irqEnable.word >> 8;     // dio1
        buf[3] = irqEnable.word;  // dio1
        buf[4] = 0; // dio2
        buf[5] = 0; // dio2
        buf[6] = 0; // dio3
        buf[7] = 0; // dio3
        radio.xfer(OPCODE_SET_DIO_IRQ_PARAMS, 8, 0, buf);
    }

    _m_ = modem;
    /* SetPacketParams written at Send() where payloadLength provided */

    antswPower = 1;
} // ..SetTxConfig()

int Radio::Send(uint8_t size, timestamp_t maxListenTime, timestamp_t channelFreeTime, int rssiThresh)
{
    uint8_t buf[8];

    if (_m_ == MODEM_FSK) {
        pp.gfsk.PayloadLength = size;

        memcpy(buf, pp.buf, 8);
        radio.xfer(OPCODE_SET_PACKET_PARAMS, 8, 0, buf);
    } else if (_m_ == MODEM_LORA) {
        pp.lora.PayloadLength = size;

        memcpy(buf, pp.buf, 6);
        radio.xfer(OPCODE_SET_PACKET_PARAMS, 6, 0, buf);
    }

    if (maxListenTime > 0) {
        int rssi;
        us_timestamp_t startAt, chFreeAt, now;
        radio.start_rx(RX_TIMEOUT_CONTINUOUS);
        startAt = lpt.read_us();
Lstart:
        do {
            now = lpt.read_us();
            if ((now - startAt) > maxListenTime) {
                return -1;
            }
            radio.xfer(OPCODE_GET_RSSIINST, 0, 2, buf);
            rssi = buf[1] / -2;
        } while (rssi > rssiThresh);
        chFreeAt = lpt.read_us();
        do {
            now = lpt.read_us();
            radio.xfer(OPCODE_GET_RSSIINST, 0, 2, buf);
            rssi = buf[1] / -2;
            if (rssi > rssiThresh) {
                goto Lstart;
            }
        } while ((now - chFreeAt) < channelFreeTime);
    }

    radio.start_tx(size);

    return 0;
} // ..Send()

void Radio::SetRxMaxPayloadLength(RadioModems_t modem, uint8_t max)
{
    uint8_t buf[8];

    if (modem == MODEM_FSK) {
        pp.gfsk.PayloadLength = max;
        memcpy(buf, pp.buf, 8);
        radio.xfer(OPCODE_SET_PACKET_PARAMS, 8, 0, buf);
    } else if (modem == MODEM_LORA) {
        pp.lora.PayloadLength = max;
        memcpy(buf, pp.buf, 6);
        radio.xfer(OPCODE_SET_PACKET_PARAMS, 6, 0, buf);
    }
}

void Radio::dio1_top_half()
{
    dio1at = lpt.read_us();

    if (radio.chipMode == CHIPMODE_TX) {
        /* TxDone handling requires low latency */
        if (RadioEvents->TxDone) {
            RadioEvents->TxDone(dio1at);
        }
    }
#ifdef TARGET_FF_MORPHO
    else
        pc3 = 0;
#endif /* TARGET_FF_MORPHO */
}

void Radio::timeout_callback(bool tx)
{
    if (!tx) {
        if (RadioEvents->RxTimeout)
            RadioEvents->RxTimeout();
#ifdef TARGET_FF_MORPHO
        pc3 = 0;
#endif /* TARGET_FF_MORPHO */
    } // else TODO tx timeout
}

void Radio::rx_done(uint8_t size, float rssi, float snr)
{
    RadioEvents->RxDone(radio.rx_buf, size, rssi, snr, dio1at);
}

void Radio::Init(const RadioEvents_t* e)
{
    radio.txDone = NULL;
    radio.rxDone = rx_done;
    radio.timeout = timeout_callback;
    radio.dio1_topHalf = dio1_top_half;

    RadioEvents = e;
    lpt.start();

    radio.SetDIO2AsRfSwitchCtrl(1);
}

void Radio::UserContext()
{
    radio.service();
}

void Radio::SetPublicNetwork(bool en)
{
    uint16_t ppg;

    if (en)
        ppg = 0x3444;
    else
        ppg = 0x1424;

    radio.writeReg(REG_ADDR_LORA_SYNC, ppg, 2);
}

void Radio::PrintStatus()
{
    uint8_t buf[4];
    status_t status;
    IrqFlags_t irqFlags;
    radio.xfer(OPCODE_GET_IRQ_STATUS, 0, 3, buf);
    irqFlags.word = buf[1] << 8;
    irqFlags.word |= buf[2];

    printf("dio1:%u  irqFlags:%04x\r\n", radio.getDIO1(), irqFlags.word);
    radio.xfer(OPCODE_GET_STATUS, 0, 1, &status.octet);
    radio.PrintChipStatus(status);
}

#endif /* ..SX126x_H */