mirror of
https://github.com/manuelbl/ttn-esp32.git
synced 2024-12-21 17:09:45 +01:00
Upgrade to mcci-catena/arduino-lmic 4.0.1-pre
This commit is contained in:
parent
1913840679
commit
99bab17d4b
5
.vscode/c_cpp_properties.json
vendored
5
.vscode/c_cpp_properties.json
vendored
@ -22,14 +22,15 @@
|
||||
"${workspaceRoot}/examples/send_recv/build/include",
|
||||
"${workspaceRoot}/include",
|
||||
"${workspaceRoot}/src"
|
||||
],
|
||||
],
|
||||
"defines": [
|
||||
"_DEBUG"
|
||||
],
|
||||
"compilerPath": "/usr/bin/clang",
|
||||
"cStandard": "c11",
|
||||
"cppStandard": "c++11",
|
||||
"intelliSenseMode": "clang-x64"
|
||||
"intelliSenseMode": "clang-x64",
|
||||
"compileCommands": "${workspaceFolder}/examples/hello_world/build/compile_commands.json"
|
||||
}
|
||||
],
|
||||
"version": 4
|
||||
|
2
LICENSE
2
LICENSE
@ -1,6 +1,6 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2018 Manuel Bleichenbacher
|
||||
Copyright (c) 2018-2021 Manuel Bleichenbacher
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
@ -12,11 +12,10 @@ This ESP32 component provides LoRaWAN communication with [The Things Network](ht
|
||||
|
||||
The library is based on the LMIC library from IBM (specifically the well-maintained version by MCCI – see their [GitHub repository](https://github.com/mcci-catena/arduino-lmic)) and provides a high-level API specifically targeted at The Things Network.
|
||||
|
||||
## New in version 3.3
|
||||
## New in version 4.0
|
||||
|
||||
- Verified compatibility with ESP-IDF v4.2
|
||||
- Upgraded underlying library mcci-catena/arduino-lmic to v3.3.0 (no relevant changes)
|
||||
- Ensure interrupt code is in IRAM
|
||||
- Verified compatibility with ESP-IDF v4.3
|
||||
- Upgraded underlying library mcci-catena/arduino-lmic to v4.0.1 (improved channel shuffling)
|
||||
|
||||
|
||||
## Get Started
|
||||
|
@ -13,7 +13,7 @@
|
||||
"url": "https://github.com/manuelbl/ttn-esp32.git",
|
||||
"branch": "master"
|
||||
},
|
||||
"version": "3.3.0",
|
||||
"version": "4.0.0-pre",
|
||||
"license": "MIT License",
|
||||
"export": {
|
||||
"include": [
|
||||
|
@ -1476,7 +1476,12 @@ ostime_t LMICcore_adjustForDrift (ostime_t delay, ostime_t hsym, rxsyms_t rxsyms
|
||||
// a compile-time configuration. (In other words, assume that millis()
|
||||
// clock is accurate to 0.1%.) You should never use clockerror to
|
||||
// compensate for system-late problems.
|
||||
u2_t const maxError = LMIC_kMaxClockError_ppm * MAX_CLOCK_ERROR / (1000 * 1000);
|
||||
// note about compiler: The initializer for maxError is coded for
|
||||
// maximum portability. On 16-bit systems, some compilers complain
|
||||
// if we write x / (1000 * 1000). x / 1000 / 1000 uses constants,
|
||||
// is generally acceptable so it can be optimized in compiler's own
|
||||
// way.
|
||||
u2_t const maxError = LMIC_kMaxClockError_ppm * MAX_CLOCK_ERROR / 1000 / 1000;
|
||||
if (! LMIC_ENABLE_arbitrary_clock_error && clockerr > maxError)
|
||||
{
|
||||
clockerr = maxError;
|
||||
@ -1616,21 +1621,9 @@ static bit_t processJoinAccept (void) {
|
||||
// initDefaultChannels(0) for EU-like, nothing otherwise
|
||||
LMICbandplan_joinAcceptChannelClear();
|
||||
|
||||
if (!LMICbandplan_hasJoinCFlist() && dlen > LEN_JA) {
|
||||
// if no JoinCFList, we're supposed to continue
|
||||
// the join per 2.2.5 of LoRaWAN regional 2.2.4
|
||||
// https://github.com/mcci-catena/arduino-lmic/issues/19
|
||||
} else if ( LMICbandplan_hasJoinCFlist() && dlen > LEN_JA ) {
|
||||
dlen = OFF_CFLIST;
|
||||
for( u1_t chidx=3; chidx<8; chidx++, dlen+=3 ) {
|
||||
u4_t freq = LMICbandplan_convFreq(&LMIC.frame[dlen]);
|
||||
if( freq ) {
|
||||
LMIC_setupChannel(chidx, freq, 0, -1);
|
||||
#if LMIC_DEBUG_LEVEL > 1
|
||||
LMIC_DEBUG_PRINTF("%"LMIC_PRId_ostime_t": Setup channel, idx=%d, freq=%"PRIu32"\n", os_getTime(), chidx, freq);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
// process the CFList if present
|
||||
if (dlen == LEN_JAEXT) {
|
||||
LMICbandplan_processJoinAcceptCFList();
|
||||
}
|
||||
|
||||
// already incremented when JOIN REQ got sent off
|
||||
@ -2882,7 +2875,11 @@ dr_t LMIC_feasibleDataRateForFrame(dr_t dr, u1_t payloadSize) {
|
||||
}
|
||||
|
||||
static bit_t isTxPathBusy(void) {
|
||||
return (LMIC.opmode & (OP_TXDATA|OP_JOINING)) != 0;
|
||||
return (LMIC.opmode & (OP_POLL | OP_TXDATA | OP_JOINING | OP_TXRXPEND)) != 0;
|
||||
}
|
||||
|
||||
bit_t LMIC_queryTxReady (void) {
|
||||
return ! isTxPathBusy();
|
||||
}
|
||||
|
||||
static bit_t adjustDrForFrameIfNotBusy(u1_t len) {
|
||||
@ -2902,6 +2899,10 @@ void LMIC_setTxData (void) {
|
||||
}
|
||||
|
||||
void LMIC_setTxData_strict (void) {
|
||||
if (isTxPathBusy()) {
|
||||
return;
|
||||
}
|
||||
|
||||
LMICOS_logEventUint32(__func__, ((u4_t)LMIC.pendTxPort << 24u) | ((u4_t)LMIC.pendTxConf << 16u) | (LMIC.pendTxLen << 0u));
|
||||
LMIC.opmode |= OP_TXDATA;
|
||||
if( (LMIC.opmode & OP_JOINING) == 0 ) {
|
||||
@ -2920,7 +2921,7 @@ lmic_tx_error_t LMIC_setTxData2 (u1_t port, xref2u1_t data, u1_t dlen, u1_t conf
|
||||
|
||||
// send a message w/o callback; do not adjust data rate
|
||||
lmic_tx_error_t LMIC_setTxData2_strict (u1_t port, xref2u1_t data, u1_t dlen, u1_t confirmed) {
|
||||
if ( LMIC.opmode & OP_TXDATA ) {
|
||||
if (isTxPathBusy()) {
|
||||
// already have a message queued
|
||||
return LMIC_ERROR_TX_BUSY;
|
||||
}
|
||||
@ -2940,7 +2941,7 @@ lmic_tx_error_t LMIC_setTxData2_strict (u1_t port, xref2u1_t data, u1_t dlen, u1
|
||||
return LMIC_ERROR_TX_FAILED;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
return LMIC_ERROR_SUCCESS;
|
||||
}
|
||||
|
||||
// send a message with callback; try to adjust data rate
|
||||
|
@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Copyright (c) 2014-2016 IBM Corporation.
|
||||
* Copyright (c) 2016 Matthijs Kooijman.
|
||||
* Copyright (c) 2016-2020 MCCI Corporation.
|
||||
* Copyright (c) 2016-2021 MCCI Corporation.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
@ -96,7 +96,7 @@
|
||||
extern "C"{
|
||||
#endif
|
||||
|
||||
// LMIC version -- this is ths IBM LMIC version
|
||||
// LMIC version -- this is the IBM LMIC version
|
||||
#define LMIC_VERSION_MAJOR 1
|
||||
#define LMIC_VERSION_MINOR 6
|
||||
#define LMIC_VERSION_BUILD 1468577746
|
||||
@ -105,7 +105,8 @@ extern "C"{
|
||||
#define ARDUINO_LMIC_VERSION_CALC(major, minor, patch, local) \
|
||||
((((major)*UINT32_C(1)) << 24) | (((minor)*UINT32_C(1)) << 16) | (((patch)*UINT32_C(1)) << 8) | (((local)*UINT32_C(1)) << 0))
|
||||
|
||||
#define ARDUINO_LMIC_VERSION ARDUINO_LMIC_VERSION_CALC(3, 3, 0, 0) /* v3.3.0 */
|
||||
#define ARDUINO_LMIC_VERSION \
|
||||
ARDUINO_LMIC_VERSION_CALC(4, 0, 1, 1) /* 4.0.1-pre1 */
|
||||
|
||||
#define ARDUINO_LMIC_VERSION_GET_MAJOR(v) \
|
||||
((((v)*UINT32_C(1)) >> 24u) & 0xFFu)
|
||||
@ -119,10 +120,35 @@ extern "C"{
|
||||
#define ARDUINO_LMIC_VERSION_GET_LOCAL(v) \
|
||||
((v) & 0xFFu)
|
||||
|
||||
/// \brief convert a semantic version to an ordinal integer.
|
||||
#define ARDUINO_LMIC_VERSION_TO_ORDINAL(v) \
|
||||
(((v) & 0xFFFFFF00u) | (((v) - 1) & 0xFFu))
|
||||
|
||||
/// \brief compare two semantic versions
|
||||
/// \return \c true if \p a is less than \p b (as a semantic version).
|
||||
#define ARDUINO_LMIC_VERSION_COMPARE_LT(a, b) \
|
||||
(ARDUINO_LMIC_VERSION_TO_ORDINAL(a) < ARDUINO_LMIC_VERSION_TO_ORDINAL(b))
|
||||
|
||||
/// \brief compare two semantic versions
|
||||
/// \return \c true if \p a is less than or equal to \p b (as a semantic version).
|
||||
#define ARDUINO_LMIC_VERSION_COMPARE_LE(a, b) \
|
||||
(ARDUINO_LMIC_VERSION_TO_ORDINAL(a) <= ARDUINO_LMIC_VERSION_TO_ORDINAL(b))
|
||||
|
||||
/// \brief compare two semantic versions
|
||||
/// \return \c true if \p a is greater than \p b (as a semantic version).
|
||||
#define ARDUINO_LMIC_VERSION_COMPARE_GT(a, b) \
|
||||
(ARDUINO_LMIC_VERSION_TO_ORDINAL(a) > ARDUINO_LMIC_VERSION_TO_ORDINAL(b))
|
||||
|
||||
/// \brief compare two semantic versions
|
||||
/// \return \c true if \p a is greater than or equal to \p b (as a semantic version).
|
||||
#define ARDUINO_LMIC_VERSION_COMPARE_GE(a, b) \
|
||||
(ARDUINO_LMIC_VERSION_TO_ORDINAL(a) >= ARDUINO_LMIC_VERSION_TO_ORDINAL(b))
|
||||
|
||||
|
||||
//! Only For Antenna Tuning Tests !
|
||||
//#define CFG_TxContinuousMode 1
|
||||
|
||||
// since this was annouunced as the API variable, we keep it. But it's not used,
|
||||
// since this was announced as the API variable, we keep it. But it's not used,
|
||||
// MAX_LEN_FRAME is what the code uses.
|
||||
enum { MAX_FRAME_LEN = MAX_LEN_FRAME }; //!< Library cap on max frame length
|
||||
|
||||
@ -131,10 +157,10 @@ enum { MAX_MISSED_BCNS = (2 * 60 * 60 + 127) / 128 }; //!< threshold for d
|
||||
// note that we need 100 ppm timing accuracy for
|
||||
// this, to keep the timing error to +/- 700ms.
|
||||
enum { MAX_RXSYMS = 350 }; // Stop tracking beacon if sync error grows beyond this. A 0.4% clock error
|
||||
// at SF9.125k means 512 ms; one sybol is 4.096 ms,
|
||||
// at SF9.125k means 512 ms; one symbol is 4.096 ms,
|
||||
// so this needs to be at least 125 for an STM32L0.
|
||||
// And for 100ppm clocks and 2 hours of beacon misses,
|
||||
// this needs to accomodate 1.4 seconds of error at
|
||||
// this needs to accommodate 1.4 seconds of error at
|
||||
// 4.096 ms/sym or at least 342 symbols.
|
||||
|
||||
enum { LINK_CHECK_CONT = 0 , // continue with this after reported dead link
|
||||
@ -161,7 +187,7 @@ struct band_t {
|
||||
u2_t txcap; // duty cycle limitation: 1/txcap
|
||||
s1_t txpow; // maximum TX power
|
||||
u1_t lastchnl; // last used channel
|
||||
ostime_t avail; // channel is blocked until this time
|
||||
ostime_t avail; // band is blocked until this time
|
||||
};
|
||||
TYPEDEF_xref2band_t; //!< \internal
|
||||
|
||||
@ -172,10 +198,8 @@ struct lmic_saved_adr_state_s {
|
||||
|
||||
#elif CFG_LMIC_US_like // US915 spectrum =================================================
|
||||
|
||||
enum { MAX_XCHANNELS = 2 }; // extra channels in RAM, channels 0-71 are immutable
|
||||
|
||||
struct lmic_saved_adr_state_s {
|
||||
u2_t channelMap[(72+MAX_XCHANNELS+15)/16]; // enabled bits
|
||||
u2_t channelMap[(72+15)/16]; // enabled bits
|
||||
u2_t activeChannels125khz;
|
||||
u2_t activeChannels500khz;
|
||||
};
|
||||
@ -531,10 +555,10 @@ struct lmic_t {
|
||||
// bit map of enabled datarates for each channel
|
||||
u2_t channelDrMap[MAX_CHANNELS];
|
||||
u2_t channelMap;
|
||||
u2_t channelShuffleMap;
|
||||
#elif CFG_LMIC_US_like
|
||||
u4_t xchFreq[MAX_XCHANNELS]; // extra channel frequencies (if device is behind a repeater)
|
||||
u2_t xchDrMap[MAX_XCHANNELS]; // extra channel datarate ranges ---XXX: ditto
|
||||
u2_t channelMap[(72+MAX_XCHANNELS+15)/16]; // enabled bits
|
||||
u2_t channelMap[(72+15)/16]; // enabled bits
|
||||
u2_t channelShuffleMap[(72+15)/16]; // enabled bits
|
||||
u2_t activeChannels125khz;
|
||||
u2_t activeChannels500khz;
|
||||
#endif
|
||||
@ -569,7 +593,10 @@ struct lmic_t {
|
||||
|
||||
u1_t txChnl; // channel for next TX
|
||||
u1_t globalDutyRate; // max rate: 1/2^k
|
||||
|
||||
#if CFG_LMIC_US_like
|
||||
u1_t txChnl_125kHz; ///< during joins on 500 kHz, the 125 kHz channel
|
||||
/// that was last used.
|
||||
#endif
|
||||
u1_t upRepeat; // configured up repeat
|
||||
s1_t adrTxPow; // ADR adjusted TX power
|
||||
u1_t datarate; // current data rate
|
||||
@ -659,6 +686,12 @@ bit_t LMIC_enableChannel(u1_t channel);
|
||||
bit_t LMIC_disableSubBand(u1_t band);
|
||||
bit_t LMIC_selectSubBand(u1_t band);
|
||||
|
||||
//! \brief get the number of (fixed) default channels before the programmable channels.
|
||||
u1_t LMIC_queryNumDefaultChannels(void);
|
||||
|
||||
//! \brief check whether the LMIC is ready for a transmit packet
|
||||
bit_t LMIC_queryTxReady(void);
|
||||
|
||||
void LMIC_setDrTxpow (dr_t dr, s1_t txpow); // set default/start DR/txpow
|
||||
void LMIC_setAdrMode (bit_t enabled); // set ADR mode (if mobile turn off)
|
||||
|
||||
@ -705,6 +738,8 @@ int LMIC_getNetworkTimeReference(lmic_time_reference_t *pReference);
|
||||
int LMIC_registerRxMessageCb(lmic_rxmessage_cb_t *pRxMessageCb, void *pUserData);
|
||||
int LMIC_registerEventCb(lmic_event_cb_t *pEventCb, void *pUserData);
|
||||
|
||||
int LMIC_findNextChannel(uint16_t *, const uint16_t *, uint16_t, int);
|
||||
|
||||
// APIs for client half of compliance.
|
||||
typedef u1_t lmic_compliance_rx_action_t;
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Copyright (c) 2014-2016 IBM Corporation.
|
||||
* Copyright (c) 2017, 2019 MCCI Corporation.
|
||||
* Copyright (c) 2017, 2019-2021 MCCI Corporation.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
@ -50,6 +50,14 @@ CONST_TABLE(u1_t, _DR2RPS_CRC)[] = {
|
||||
ILLEGAL_RPS
|
||||
};
|
||||
|
||||
bit_t
|
||||
LMICas923_validDR(dr_t dr) {
|
||||
// use subtract here to avoid overflow
|
||||
if (dr >= LENOF_TABLE(_DR2RPS_CRC) - 2)
|
||||
return 0;
|
||||
return TABLE_GET_U1(_DR2RPS_CRC, dr+1)!=ILLEGAL_RPS;
|
||||
}
|
||||
|
||||
// see table in 2.7.6 -- this assumes UplinkDwellTime = 0.
|
||||
static CONST_TABLE(u1_t, maxFrameLens_dwell0)[] = {
|
||||
59+5, // [0]
|
||||
@ -226,17 +234,29 @@ bit_t LMIC_setupBand(u1_t bandidx, s1_t txpow, u2_t txcap) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
///
|
||||
/// \brief query number of default channels.
|
||||
///
|
||||
u1_t LMIC_queryNumDefaultChannels() {
|
||||
return NUM_DEFAULT_CHANNELS;
|
||||
}
|
||||
|
||||
///
|
||||
/// \brief LMIC_setupChannel for EU 868
|
||||
///
|
||||
/// \note according to LoRaWAN 1.3 section 5.6, "the acceptable range
|
||||
/// for **ChIndex** is N to 16", where N is our \c NUM_DEFAULT_CHANNELS.
|
||||
/// This routine is used internally for MAC commands, so we enforce
|
||||
/// this for the extenal API as well.
|
||||
///
|
||||
bit_t LMIC_setupChannel(u1_t chidx, u4_t freq, u2_t drmap, s1_t band) {
|
||||
// zero the band bits in freq, just in case.
|
||||
freq &= ~3;
|
||||
|
||||
if (chidx < NUM_DEFAULT_CHANNELS) {
|
||||
// can't disable a default channel.
|
||||
if (freq == 0)
|
||||
return 0;
|
||||
// can't change a default channel.
|
||||
else if (freq != (LMIC.channelFreq[chidx] & ~3))
|
||||
return 0;
|
||||
// can't do anything to a default channel.
|
||||
return 0;
|
||||
}
|
||||
bit_t fEnable = (freq != 0);
|
||||
if (chidx >= MAX_CHANNELS)
|
||||
@ -315,36 +335,109 @@ void LMICas923_setRx1Params(void) {
|
||||
LMIC.rps = dndr2rps(LMIC.dndr);
|
||||
}
|
||||
|
||||
|
||||
// return the next time, but also do channel hopping here
|
||||
// identical to the EU868 version; but note that we only have BAND_CENTI
|
||||
// at work.
|
||||
///
|
||||
/// \brief change the TX channel given the desired tx time.
|
||||
///
|
||||
/// \param [in] now is the time at which we want to transmit. In fact, it's always
|
||||
/// the current time.
|
||||
///
|
||||
/// \returns the actual time at which we can transmit. \c LMIC.txChnl is set to the
|
||||
/// selected channel.
|
||||
///
|
||||
/// \details
|
||||
/// We scan all the bands, creating a mask of all enabled channels that are
|
||||
/// feasible at the earliest possible time. We then randomly choose one from
|
||||
/// that, updating the shuffle mask.
|
||||
///
|
||||
/// \note
|
||||
/// identical to the EU868 version; but note that we only have BAND_CENTI
|
||||
/// in AS923.
|
||||
///
|
||||
ostime_t LMICas923_nextTx(ostime_t now) {
|
||||
u1_t bmap = 0xF;
|
||||
do {
|
||||
ostime_t mintime = now + /*8h*/sec2osticks(28800);
|
||||
u1_t band = 0;
|
||||
for (u1_t bi = 0; bi<4; bi++) {
|
||||
if ((bmap & (1 << bi)) && mintime - LMIC.bands[bi].avail > 0)
|
||||
mintime = LMIC.bands[band = bi].avail;
|
||||
}
|
||||
// Find next channel in given band
|
||||
u1_t chnl = LMIC.bands[band].lastchnl;
|
||||
for (u1_t ci = 0; ci<MAX_CHANNELS; ci++) {
|
||||
if ((chnl = (chnl + 1)) >= MAX_CHANNELS)
|
||||
chnl -= MAX_CHANNELS;
|
||||
if ((LMIC.channelMap & (1 << chnl)) != 0 && // channel enabled
|
||||
(LMIC.channelDrMap[chnl] & (1 << (LMIC.datarate & 0xF))) != 0 &&
|
||||
band == (LMIC.channelFreq[chnl] & 0x3)) { // in selected band
|
||||
LMIC.txChnl = LMIC.bands[band].lastchnl = chnl;
|
||||
return mintime;
|
||||
}
|
||||
}
|
||||
if ((bmap &= ~(1 << band)) == 0) {
|
||||
// No feasible channel found!
|
||||
return mintime;
|
||||
}
|
||||
} while (1);
|
||||
ostime_t mintime = now + /*8h*/sec2osticks(28800);
|
||||
u2_t availMap;
|
||||
u2_t feasibleMap;
|
||||
u1_t bandMap;
|
||||
|
||||
// set mintime to the earliest time of all enabled channels
|
||||
// (can't just look at bands); and for a given channel, we
|
||||
// can't tell if we're ready till we've checked all possible
|
||||
// avail times.
|
||||
bandMap = 0;
|
||||
for (u1_t chnl = 0; chnl < MAX_CHANNELS; ++chnl) {
|
||||
u2_t chnlBit = 1 << chnl;
|
||||
|
||||
// none at any higher numbers?
|
||||
if (LMIC.channelMap < chnlBit)
|
||||
break;
|
||||
|
||||
// not enabled?
|
||||
if ((LMIC.channelMap & chnlBit) == 0)
|
||||
continue;
|
||||
|
||||
// not feasible?
|
||||
if ((LMIC.channelDrMap[chnl] & (1 << (LMIC.datarate & 0xF))) == 0)
|
||||
continue;
|
||||
|
||||
u1_t const band = LMIC.channelFreq[chnl] & 0x3;
|
||||
u1_t const thisBandBit = 1 << band;
|
||||
// already considered?
|
||||
if ((bandMap & thisBandBit) != 0)
|
||||
continue;
|
||||
|
||||
// consider this band.
|
||||
bandMap |= thisBandBit;
|
||||
|
||||
// enabled, not considered, feasible: adjust the min time.
|
||||
if ((s4_t)(mintime - LMIC.bands[band].avail) > 0)
|
||||
mintime = LMIC.bands[band].avail;
|
||||
}
|
||||
|
||||
// make a mask of candidates available for use
|
||||
availMap = 0;
|
||||
feasibleMap = 0;
|
||||
for (u1_t chnl = 0; chnl < MAX_CHANNELS; ++chnl) {
|
||||
u2_t chnlBit = 1 << chnl;
|
||||
|
||||
// none at any higher numbers?
|
||||
if (LMIC.channelMap < chnlBit)
|
||||
break;
|
||||
|
||||
// not enabled?
|
||||
if ((LMIC.channelMap & chnlBit) == 0)
|
||||
continue;
|
||||
|
||||
// not feasible?
|
||||
if ((LMIC.channelDrMap[chnl] & (1 << (LMIC.datarate & 0xF))) == 0)
|
||||
continue;
|
||||
|
||||
// This channel is feasible. But might not be available.
|
||||
feasibleMap |= chnlBit;
|
||||
|
||||
// not available yet?
|
||||
u1_t const band = LMIC.channelFreq[chnl] & 0x3;
|
||||
if ((s4_t)(LMIC.bands[band].avail - mintime) > 0)
|
||||
continue;
|
||||
|
||||
// ok: this is a candidate.
|
||||
availMap |= chnlBit;
|
||||
}
|
||||
|
||||
// find the next available chennel.
|
||||
u2_t saveShuffleMap = LMIC.channelShuffleMap;
|
||||
int candidateCh = LMIC_findNextChannel(&LMIC.channelShuffleMap, &availMap, 1, LMIC.txChnl == 0xFF ? -1 : LMIC.txChnl);
|
||||
|
||||
// restore bits in the shuffleMap that were on, but might have reset
|
||||
// if availMap was used to refresh shuffleMap. These are channels that
|
||||
// are feasble but not yet candidates due to band saturation
|
||||
LMIC.channelShuffleMap |= saveShuffleMap & feasibleMap & ~availMap;
|
||||
|
||||
if (candidateCh >= 0) {
|
||||
// update the channel; otherwise we'll just use the
|
||||
// most recent one.
|
||||
LMIC.txChnl = candidateCh;
|
||||
}
|
||||
return mintime;
|
||||
}
|
||||
|
||||
#if !defined(DISABLE_BEACONS)
|
||||
|
@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Copyright (c) 2014-2016 IBM Corporation.
|
||||
* Copyright (c) 2017, 2019 MCCI Corporation.
|
||||
* Copyright (c) 2017, 2019-2021 MCCI Corporation.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
@ -55,6 +55,14 @@ CONST_TABLE(u1_t, _DR2RPS_CRC)[] = {
|
||||
ILLEGAL_RPS
|
||||
};
|
||||
|
||||
bit_t
|
||||
LMICau915_validDR(dr_t dr) {
|
||||
// use subtract here to avoid overflow
|
||||
if (dr >= LENOF_TABLE(_DR2RPS_CRC) - 2)
|
||||
return 0;
|
||||
return TABLE_GET_U1(_DR2RPS_CRC, dr+1)!=ILLEGAL_RPS;
|
||||
}
|
||||
|
||||
static CONST_TABLE(u1_t, maxFrameLens_dwell0)[] = {
|
||||
59+5, 59+5, 59+5, 123+5, 250+5, 250+5, 250+5, 0,
|
||||
61+5, 137+5, 250+5, 250+5, 250+5, 250+5 };
|
||||
@ -144,7 +152,24 @@ u4_t LMICau915_convFreq(xref2cu1_t ptr) {
|
||||
return freq;
|
||||
}
|
||||
|
||||
// au915: no support for xchannels.
|
||||
///
|
||||
/// \brief query number of default channels.
|
||||
///
|
||||
/// \note
|
||||
/// For AU, we have no programmable channels; all channels
|
||||
/// are fixed. Return the total channel count.
|
||||
///
|
||||
u1_t LMIC_queryNumDefaultChannels() {
|
||||
return 64 + 8;
|
||||
}
|
||||
|
||||
|
||||
///
|
||||
/// \brief LMIC_setupChannel for AU915
|
||||
///
|
||||
/// \note there are no progammable channels for US915, so this API
|
||||
/// always returns FALSE.
|
||||
///
|
||||
bit_t LMIC_setupChannel(u1_t chidx, u4_t freq, u2_t drmap, s1_t band) {
|
||||
LMIC_API_PARAMETER(chidx);
|
||||
LMIC_API_PARAMETER(freq);
|
||||
|
@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Copyright (c) 2014-2016 IBM Corporation.
|
||||
* Copyright (c) 2017, 2019 MCCI Corporation.
|
||||
* Copyright (c) 2017, 2019-2021 MCCI Corporation.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
@ -166,6 +166,14 @@
|
||||
# error "LMICbandplan_isDataRateFeasible() not defined by bandplan"
|
||||
#endif
|
||||
|
||||
#if !defined(LMICbandplan_validDR)
|
||||
# error "LMICbandplan_validDR() not defined by bandplan"
|
||||
#endif
|
||||
|
||||
#if !defined(LMICbandplan_processJoinAcceptCFList)
|
||||
# error "LMICbandplan_processJoinAcceptCFList() not defined by bandplan"
|
||||
#endif
|
||||
|
||||
//
|
||||
// Things common to lmic.c code
|
||||
//
|
||||
@ -228,4 +236,22 @@ ostime_t LMICcore_rndDelay(u1_t secSpan);
|
||||
void LMICcore_setDrJoin(u1_t reason, u1_t dr);
|
||||
ostime_t LMICcore_adjustForDrift(ostime_t delay, ostime_t hsym, rxsyms_t rxsyms_in);
|
||||
|
||||
// this has been exported to clients forever by lmic.h. including lorabase.h;
|
||||
// but with multiband lorabase can't really safely do this; it's really an LMIC-ism.
|
||||
// As are the rest of the static inlines..
|
||||
|
||||
///< \brief return non-zero if given DR is valid for this region.
|
||||
static inline bit_t validDR (dr_t dr) { return LMICbandplan_validDR(dr); } // in range
|
||||
|
||||
///< \brief region-specific table mapping DR to RPS/CRC bits; index by dr+1
|
||||
extern CONST_TABLE(u1_t, _DR2RPS_CRC)[];
|
||||
|
||||
static inline rps_t updr2rps (dr_t dr) { return (rps_t)TABLE_GET_U1(_DR2RPS_CRC, dr+1); }
|
||||
static inline rps_t dndr2rps (dr_t dr) { return setNocrc(updr2rps(dr),1); }
|
||||
static inline dr_t incDR (dr_t dr) { return TABLE_GET_U1(_DR2RPS_CRC, dr+2)==ILLEGAL_RPS ? dr : (dr_t)(dr+1); } // increase data rate
|
||||
static inline dr_t decDR (dr_t dr) { return TABLE_GET_U1(_DR2RPS_CRC, dr )==ILLEGAL_RPS ? dr : (dr_t)(dr-1); } // decrease data rate
|
||||
static inline dr_t assertDR (dr_t dr) { return TABLE_GET_U1(_DR2RPS_CRC, dr+1)==ILLEGAL_RPS ? (dr_t)DR_DFLTMIN : dr; } // force into a valid DR
|
||||
static inline dr_t lowerDR (dr_t dr, u1_t n) { while(n--){dr=decDR(dr);} return dr; } // decrease data rate by n steps
|
||||
|
||||
|
||||
#endif // _lmic_bandplan_h_
|
||||
|
@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Copyright (c) 2014-2016 IBM Corporation.
|
||||
* Copyright (c) 2017, 2019 MCCI Corporation.
|
||||
* Copyright (c) 2017, 2019-2021 MCCI Corporation.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
@ -108,4 +108,8 @@ void LMICas923_updateTx(ostime_t txbeg);
|
||||
ostime_t LMICas923_nextJoinTime(ostime_t now);
|
||||
#define LMICbandplan_nextJoinTime(now) LMICas923_nextJoinTime(now)
|
||||
|
||||
#undef LMICbandplan_validDR
|
||||
bit_t LMICas923_validDR(dr_t dr);
|
||||
#define LMICbandplan_validDR(dr) LMICas923_validDR(dr)
|
||||
|
||||
#endif // _lmic_bandplan_as923_h_
|
||||
|
@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Copyright (c) 2014-2016 IBM Corporation.
|
||||
* Copyright (c) 2017, 2019 MCCI Corporation.
|
||||
* Copyright (c) 2017, 2019-2021 MCCI Corporation.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
@ -66,4 +66,8 @@ void LMICau915_setRx1Params(void);
|
||||
void LMICau915_updateTx(ostime_t txbeg);
|
||||
#define LMICbandplan_updateTx(txbeg) LMICau915_updateTx(txbeg)
|
||||
|
||||
#undef LMICbandplan_validDR
|
||||
bit_t LMICau915_validDR(dr_t dr);
|
||||
#define LMICbandplan_validDR(dr) LMICau915_validDR(dr)
|
||||
|
||||
#endif // _lmic_bandplan_au915_h_
|
||||
|
@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Copyright (c) 2014-2016 IBM Corporation.
|
||||
* Copyright (c) 2017, 2019 MCCI Corporation.
|
||||
* Copyright (c) 2017, 2019-2021 MCCI Corporation.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
@ -88,4 +88,8 @@ ostime_t LMICeu868_nextJoinTime(ostime_t now);
|
||||
void LMICeu868_setRx1Params(void);
|
||||
#define LMICbandplan_setRx1Params() LMICeu868_setRx1Params()
|
||||
|
||||
#undef LMICbandplan_validDR
|
||||
bit_t LMICeu868_validDR(dr_t dr);
|
||||
#define LMICbandplan_validDR(dr) LMICeu868_validDR(dr)
|
||||
|
||||
#endif // _lmic_eu868_h_
|
||||
|
@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Copyright (c) 2014-2016 IBM Corporation.
|
||||
* Copyright (c) 2017, 2019 MCCI Corporation.
|
||||
* Copyright (c) 2017, 2019-2021 MCCI Corporation.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
@ -81,4 +81,8 @@ void LMICin866_initDefaultChannels(bit_t join);
|
||||
void LMICin866_setRx1Params(void);
|
||||
#define LMICbandplan_setRx1Params() LMICin866_setRx1Params()
|
||||
|
||||
#undef LMICbandplan_validDR
|
||||
bit_t LMICin866_validDR(dr_t dr);
|
||||
#define LMICbandplan_validDR(dr) LMICin866_validDR(dr)
|
||||
|
||||
#endif // _lmic_bandplan_in866_h_
|
||||
|
@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Copyright (c) 2014-2016 IBM Corporation.
|
||||
* Copyright (c) 2017, 2019 MCCI Corporation.
|
||||
* Copyright (c) 2017, 2019-2021 MCCI Corporation.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
@ -88,4 +88,8 @@ void LMICkr920_setRx1Params(void);
|
||||
void LMICkr920_updateTx(ostime_t txbeg);
|
||||
#define LMICbandplan_updateTx(t) LMICkr920_updateTx(t)
|
||||
|
||||
#undef LMICbandplan_validDR
|
||||
bit_t LMICkr920_validDR(dr_t dr);
|
||||
#define LMICbandplan_validDR(dr) LMICkr920_validDR(dr)
|
||||
|
||||
#endif // _lmic_kr920_h_
|
||||
|
@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Copyright (c) 2014-2016 IBM Corporation.
|
||||
* Copyright (c) 2017, 2019 MCCI Corporation.
|
||||
* Copyright (c) 2017, 2019-2021 MCCI Corporation.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
@ -66,4 +66,8 @@ void LMICus915_setRx1Params(void);
|
||||
void LMICus915_updateTx(ostime_t txbeg);
|
||||
#define LMICbandplan_updateTx(txbeg) LMICus915_updateTx(txbeg)
|
||||
|
||||
#undef LMICbandplan_validDR
|
||||
bit_t LMICus915_validDR(dr_t dr);
|
||||
#define LMICbandplan_validDR(dr) LMICus915_validDR(dr)
|
||||
|
||||
#endif // _lmic_bandplan_us915_h_
|
||||
|
217
src/lmic/lmic_channelshuffle.c
Normal file
217
src/lmic/lmic_channelshuffle.c
Normal file
@ -0,0 +1,217 @@
|
||||
/*
|
||||
|
||||
Module: lmic_channelshuffle.c
|
||||
|
||||
Function:
|
||||
Channel scheduling without replacement.
|
||||
|
||||
Copyright and License:
|
||||
This file copyright (C) 2021 by
|
||||
|
||||
MCCI Corporation
|
||||
3520 Krums Corners Road
|
||||
Ithaca, NY 14850
|
||||
|
||||
See accompanying LICENSE file for copyright and license information.
|
||||
|
||||
Author:
|
||||
Terry Moore, MCCI Corporation April 2021
|
||||
|
||||
*/
|
||||
|
||||
#include "lmic.h"
|
||||
#include <string.h>
|
||||
|
||||
/****************************************************************************\
|
||||
|
|
||||
| Manifest constants and local declarations.
|
||||
|
|
||||
\****************************************************************************/
|
||||
|
||||
static unsigned sidewaysSum16(const uint16_t *pMask, uint16_t nEntries);
|
||||
static unsigned findNthSetBit(const uint16_t *pMask, uint16_t bitnum);
|
||||
|
||||
/****************************************************************************\
|
||||
|
|
||||
| Read-only data.
|
||||
|
|
||||
\****************************************************************************/
|
||||
|
||||
/****************************************************************************\
|
||||
|
|
||||
| Variables.
|
||||
|
|
||||
\****************************************************************************/
|
||||
|
||||
/*
|
||||
|
||||
Name: LMIC_findNextChannel()
|
||||
|
||||
Function:
|
||||
Scan a shuffle mask, and select a channel (without replacement).
|
||||
|
||||
Definition:
|
||||
int LMIC_findNextChannel(
|
||||
uint16_t *pShuffleMask,
|
||||
const uint16_t *pEnableMask,
|
||||
uint16_t nEntries,
|
||||
int lastChannel
|
||||
);
|
||||
|
||||
Description:
|
||||
pShuffleMask and pEnableMask are bit vectors. Channels correspond to
|
||||
bits in little-endian order; entry [0] has channels 0 through 15, entry
|
||||
[1] channels 16 through 31, and so forth. nEntries specifies the number
|
||||
of entries in the mask vectors. The enable mask is 1 for a given channel
|
||||
if that channel is eligible for selection, 0 otherwise.
|
||||
|
||||
This routine selects channels from the shuffle mask until all entries
|
||||
are exhausted; it then refreshes the shuffle mask from the enable mask.
|
||||
|
||||
If it refreshes the channel mask, lastChannel is taken as a channel number
|
||||
that is to be avoided in the next selection. (This is to avoid back-to-back
|
||||
use of a channel across a refresh boundary.) Otherwise lastChannel is
|
||||
ignored. This avoidance can be suppresed by setting lastChannel to -1.
|
||||
If only one channel is enabled, lastChannel is also ignored. If lastChannel
|
||||
is actually disabled, lastChannel is also ignored.
|
||||
|
||||
Returns:
|
||||
A channel number, in 0 .. nEntries-1, or -1 if the enable mask is
|
||||
identically zero.
|
||||
|
||||
Notes:
|
||||
This routine is somewhat optimized for AVR processors, which don't have
|
||||
multi-bit shifts.
|
||||
|
||||
*/
|
||||
|
||||
int LMIC_findNextChannel(
|
||||
uint16_t *pShuffleMask,
|
||||
const uint16_t *pEnableMask,
|
||||
uint16_t nEntries,
|
||||
int lastChannel
|
||||
) {
|
||||
unsigned nSet16;
|
||||
uint16_t saveLastChannelVal;
|
||||
|
||||
// in case someone has changed the enable mask, update
|
||||
// the shuffle mask so there are no disable bits set.
|
||||
for (unsigned i = 0; i < nEntries; ++i) {
|
||||
pShuffleMask[i] &= pEnableMask[i];
|
||||
}
|
||||
|
||||
// count the set bits in the shuffle mask (with a factor of 16 for speed)
|
||||
nSet16 = sidewaysSum16(pShuffleMask, nEntries);
|
||||
|
||||
// if zero, copy the enable mask to the shuffle mask, and recount
|
||||
if (nSet16 == 0) {
|
||||
memcpy(pShuffleMask, pEnableMask, nEntries * sizeof(*pShuffleMask));
|
||||
nSet16 = sidewaysSum16(pShuffleMask, nEntries);
|
||||
} else {
|
||||
// don't try to skip the last channel becuase it can't be chosen.
|
||||
lastChannel = -1;
|
||||
}
|
||||
|
||||
// if still zero, return -1.
|
||||
if (nSet16 == 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// if we have to skip a channel, and we have more than one choice, turn off
|
||||
// the last channel bit. Post condition: if we really clered a bit,
|
||||
// saveLastChannelVal will be non-zero.
|
||||
saveLastChannelVal = 0;
|
||||
if (nSet16 > 16 && lastChannel >= 0 && lastChannel <= nEntries * 16) {
|
||||
uint16_t const saveLastChannelMask = (1 << (lastChannel & 0xF));
|
||||
|
||||
saveLastChannelVal = pShuffleMask[lastChannel >> 4] & saveLastChannelMask;
|
||||
pShuffleMask[lastChannel >> 4] &= ~saveLastChannelMask;
|
||||
|
||||
// if we cleared a bit, reduce the count.
|
||||
if (saveLastChannelVal > 0)
|
||||
nSet16 -= 16;
|
||||
}
|
||||
|
||||
if (saveLastChannelVal == 0) {
|
||||
// We didn't eliminate a channel, so we don't have to worry.
|
||||
lastChannel = -1;
|
||||
}
|
||||
|
||||
// get a random number
|
||||
unsigned choice = os_getRndU2() % ((uint16_t)nSet16 >> 4);
|
||||
|
||||
// choose a bit based on set bit
|
||||
unsigned channel = findNthSetBit(pShuffleMask, choice);
|
||||
pShuffleMask[channel / 16] ^= (1 << (channel & 0xF));
|
||||
|
||||
// handle channel skip
|
||||
if (lastChannel >= 0) {
|
||||
pShuffleMask[lastChannel >> 4] |= saveLastChannelVal;
|
||||
}
|
||||
return channel;
|
||||
}
|
||||
|
||||
static unsigned sidewaysSum16(const uint16_t *pMask, uint16_t nEntries) {
|
||||
unsigned result;
|
||||
|
||||
result = 0;
|
||||
for (; nEntries > 0; --nEntries, ++pMask)
|
||||
{
|
||||
uint16_t v = *pMask;
|
||||
|
||||
// the following is an adaptation of Knuth 7.1.3 (62). To avoid
|
||||
// lots of shifts (slow on AVR, and code intensive) and table lookups,
|
||||
// we sum popc * 16, then divide by 16.
|
||||
|
||||
// sum adjacent bits, making a series of 2-bit sums
|
||||
v = v - ((v >> 1) & 0x5555u);
|
||||
v = (v & 0x3333u) + ((v >> 2) & 0x3333u);
|
||||
// this assumes multiplies are essentialy free;
|
||||
v = (v & 0xF0F0u) + ((v & 0x0F0Fu) * 16u);
|
||||
// Accumulate result, but note it's times 16.
|
||||
// AVR compiler should optimize the x8 shift.
|
||||
result += (v & 0xFF) + (v >> 8);
|
||||
}
|
||||
|
||||
//
|
||||
return result;
|
||||
}
|
||||
|
||||
static unsigned findNthSetBit(const uint16_t *pMask, uint16_t bitnum) {
|
||||
unsigned result;
|
||||
result = 0;
|
||||
bitnum = bitnum * 16;
|
||||
for (;; result += 16) {
|
||||
uint16_t m = *pMask++;
|
||||
if (m == 0)
|
||||
continue;
|
||||
uint16_t v = m - ((m >> 1) & 0x5555u);
|
||||
v = (v & 0x3333u) + ((v >> 2) & 0x3333u);
|
||||
// this assumes multiplies are essentialy free;
|
||||
v = (v & 0xF0F0u) + ((v & 0x0F0Fu) * 16u);
|
||||
// Accumulate result, but note it's times 16.
|
||||
// AVR compiler should optimize the x8 shift.
|
||||
v = (v & 0xFF) + (v >> 8);
|
||||
if (v <= bitnum)
|
||||
bitnum -= v;
|
||||
else {
|
||||
// the selected bit is in this word. We need to count.
|
||||
while (bitnum > 0) {
|
||||
m &= m - 1;
|
||||
bitnum -= 16;
|
||||
}
|
||||
// now the lsb of m is our choice.
|
||||
// get a mask, then use Knuth 7.1.3 (59) to find the
|
||||
// bit number.
|
||||
m &= -m;
|
||||
result += ((m & 0x5555u) ? 0 : 1)
|
||||
+ ((m & 0x3333u) ? 0 : 2)
|
||||
+ ((m & 0x0F0Fu) ? 0 : 4)
|
||||
+ ((m & 0x00FFu) ? 0 : 8)
|
||||
;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
@ -284,7 +284,7 @@ static void evMessage(
|
||||
break;
|
||||
}
|
||||
case LORAWAN_COMPLIANCE_CMD_LINK: {
|
||||
// not clear what this request does.
|
||||
// we are required to initiate a Link
|
||||
break;
|
||||
}
|
||||
case LORAWAN_COMPLIANCE_CMD_JOIN: {
|
||||
|
@ -118,30 +118,6 @@ Revision history:
|
||||
(1 << LMIC_REGION_in866) | \
|
||||
0)
|
||||
|
||||
//
|
||||
// Our input is a -D of one of CFG_eu868, CFG_us915, CFG_as923, CFG_au915, CFG_in866
|
||||
// More will be added in the the future. So at this point we create CFG_region with
|
||||
// following values. These are in order of the sections in the manual. Not all of the
|
||||
// below are supported yet.
|
||||
//
|
||||
// CFG_as923jp is treated as a special case of CFG_as923, so it's not included in
|
||||
// the below.
|
||||
//
|
||||
// TODO(tmm@mcci.com) consider moving this block to a central file as it's not
|
||||
// user-editable.
|
||||
//
|
||||
# define CFG_LMIC_REGION_MASK \
|
||||
((defined(CFG_eu868) << LMIC_REGION_eu868) | \
|
||||
(defined(CFG_us915) << LMIC_REGION_us915) | \
|
||||
(defined(CFG_cn783) << LMIC_REGION_cn783) | \
|
||||
(defined(CFG_eu433) << LMIC_REGION_eu433) | \
|
||||
(defined(CFG_au915) << LMIC_REGION_au915) | \
|
||||
(defined(CFG_cn490) << LMIC_REGION_cn490) | \
|
||||
(defined(CFG_as923) << LMIC_REGION_as923) | \
|
||||
(defined(CFG_kr920) << LMIC_REGION_kr920) | \
|
||||
(defined(CFG_in866) << LMIC_REGION_in866) | \
|
||||
0)
|
||||
|
||||
// the selected region.
|
||||
// TODO(tmm@mcci.com) consider moving this block to a central file as it's not
|
||||
// user-editable.
|
||||
@ -171,11 +147,94 @@ Revision history:
|
||||
# define CFG_region 0
|
||||
#endif
|
||||
|
||||
// a bitmask of EU-like regions -- these are regions which have up to 16
|
||||
// channels indidually programmable via downloink.
|
||||
//
|
||||
// TODO(tmm@mcci.com) consider moving this block to a central file as it's not
|
||||
// user-editable.
|
||||
// LMIC_CFG_*_ENA -- either 1 or 0 based on whether that region
|
||||
// is enabled. Note: these must be after the code that special-cases
|
||||
// CFG_as923jp.
|
||||
#if defined(CFG_eu868)
|
||||
# define LMIC_CFG_eu868_ENA 1
|
||||
#else
|
||||
# define LMIC_CFG_eu868_ENA 0
|
||||
#endif
|
||||
|
||||
#if defined(CFG_us915)
|
||||
# define LMIC_CFG_us915_ENA 1
|
||||
#else
|
||||
# define LMIC_CFG_us915_ENA 0
|
||||
#endif
|
||||
|
||||
#if defined(CFG_cn783)
|
||||
# define LMIC_CFG_cn783_ENA 1
|
||||
#else
|
||||
# define LMIC_CFG_cn783_ENA 0
|
||||
#endif
|
||||
|
||||
#if defined(CFG_eu433)
|
||||
# define LMIC_CFG_eu433_ENA 1
|
||||
#else
|
||||
# define LMIC_CFG_eu433_ENA 0
|
||||
#endif
|
||||
|
||||
#if defined(CFG_au915)
|
||||
# define LMIC_CFG_au915_ENA 1
|
||||
#else
|
||||
# define LMIC_CFG_au915_ENA 0
|
||||
#endif
|
||||
|
||||
#if defined(CFG_cn490)
|
||||
# define LMIC_CFG_cn490_ENA 1
|
||||
#else
|
||||
# define LMIC_CFG_cn490_ENA 0
|
||||
#endif
|
||||
|
||||
#if defined(CFG_as923)
|
||||
# define LMIC_CFG_as923_ENA 1
|
||||
#else
|
||||
# define LMIC_CFG_as923_ENA 0
|
||||
#endif
|
||||
|
||||
#if defined(CFG_kr920)
|
||||
# define LMIC_CFG_kr920_ENA 1
|
||||
#else
|
||||
# define LMIC_CFG_kr920_ENA 0
|
||||
#endif
|
||||
|
||||
#if defined(CFG_in866)
|
||||
# define LMIC_CFG_in866_ENA 1
|
||||
#else
|
||||
# define LMIC_CFG_in866_ENA 0
|
||||
#endif
|
||||
|
||||
/// \brief Bitmask of configured regions
|
||||
///
|
||||
/// Our input is a -D of one of CFG_eu868, CFG_us915, CFG_as923, CFG_au915, CFG_in866
|
||||
/// More will be added in the the future. So at this point we create CFG_region with
|
||||
/// following values. These are in order of the sections in the manual. Not all of the
|
||||
/// below are supported yet.
|
||||
///
|
||||
/// CFG_as923jp is treated as a special case of CFG_as923, so it's not included in
|
||||
/// the below.
|
||||
///
|
||||
/// TODO(tmm@mcci.com) consider moving this block to a central file as it's not
|
||||
/// user-editable.
|
||||
///
|
||||
# define CFG_LMIC_REGION_MASK \
|
||||
((LMIC_CFG_eu868_ENA << LMIC_REGION_eu868) | \
|
||||
(LMIC_CFG_us915_ENA << LMIC_REGION_us915) | \
|
||||
(LMIC_CFG_cn783_ENA << LMIC_REGION_cn783) | \
|
||||
(LMIC_CFG_eu433_ENA << LMIC_REGION_eu433) | \
|
||||
(LMIC_CFG_au915_ENA << LMIC_REGION_au915) | \
|
||||
(LMIC_CFG_cn490_ENA << LMIC_REGION_cn490) | \
|
||||
(LMIC_CFG_as923_ENA << LMIC_REGION_as923) | \
|
||||
(LMIC_CFG_kr920_ENA << LMIC_REGION_kr920) | \
|
||||
(LMIC_CFG_in866_ENA << LMIC_REGION_in866) | \
|
||||
0)
|
||||
|
||||
/// \brief a bitmask of EU-like regions
|
||||
///
|
||||
/// EU-like regions have up to 16 channels individually programmable via downlink.
|
||||
///
|
||||
/// TODO(tmm@mcci.com) consider moving this block to a central file as it's not
|
||||
/// user-editable.
|
||||
#define CFG_LMIC_EU_like_MASK ( \
|
||||
(1 << LMIC_REGION_eu868) | \
|
||||
/* (1 << LMIC_REGION_us915) | */ \
|
||||
@ -188,12 +247,14 @@ Revision history:
|
||||
(1 << LMIC_REGION_in866) | \
|
||||
0)
|
||||
|
||||
// a bitmask of` US-like regions -- these are regions with 64 fixed 125 kHz channels
|
||||
// overlaid by 8 500 kHz channels. The channel frequencies can't be changed, but
|
||||
// subsets of channels can be selected via masks.
|
||||
//
|
||||
// TODO(tmm@mcci.com) consider moving this block to a central file as it's not
|
||||
// user-editable.
|
||||
/// \brief bitmask of` US-like regions
|
||||
///
|
||||
/// US-like regions have 64 fixed 125-kHz channels overlaid by 8 500-kHz
|
||||
/// channels. The channel frequencies can't be changed, but
|
||||
/// subsets of channels can be selected via masks.
|
||||
///
|
||||
/// TODO(tmm@mcci.com) consider moving this block to a central file as it's not
|
||||
/// user-editable.
|
||||
#define CFG_LMIC_US_like_MASK ( \
|
||||
/* (1 << LMIC_REGION_eu868) | */ \
|
||||
(1 << LMIC_REGION_us915) | \
|
||||
@ -211,16 +272,19 @@ Revision history:
|
||||
// TODO(tmm@mcci.com) consider moving this block to a central file as it's not
|
||||
// user-editable.
|
||||
//
|
||||
|
||||
/// \brief true if configured region is EU-like, false otherwise.
|
||||
#define CFG_LMIC_EU_like (!!(CFG_LMIC_REGION_MASK & CFG_LMIC_EU_like_MASK))
|
||||
/// \brief true if configured region is US-like, false otherwise.
|
||||
#define CFG_LMIC_US_like (!!(CFG_LMIC_REGION_MASK & CFG_LMIC_US_like_MASK))
|
||||
|
||||
//
|
||||
// The supported LMIC LoRaWAAN spec versions. These need to be numerically ordered,
|
||||
// The supported LMIC LoRaWAN spec versions. These need to be numerically ordered,
|
||||
// so that we can (for example) compare
|
||||
//
|
||||
// LMIC_LORAWAN_SPEC_VERSION < LMIC_LORAWAN_SPEC_VERSION_1_0_3.
|
||||
//
|
||||
#define LMIC_LORAWAN_SPEC_VERSION_1_0_2 0x01000200u
|
||||
#define LMIC_LORAWAN_SPEC_VERSION_1_0_3 0x01000300u
|
||||
#define LMIC_LORAWAN_SPEC_VERSION_1_0_2 0x01000200u /// Refers to LoRaWAN spec 1.0.2
|
||||
#define LMIC_LORAWAN_SPEC_VERSION_1_0_3 0x01000300u /// Refers to LoRaWAN spec 1.0.3
|
||||
|
||||
#endif /* _LMIC_CONFIG_PRECONDITIONS_H_ */
|
||||
|
@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Copyright (c) 2014-2016 IBM Corporation.
|
||||
* Copyright (c) 2017, 2019 MCCI Corporation.
|
||||
* Copyright (c) 2017, 2019-2021 MCCI Corporation.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
@ -49,6 +49,14 @@ CONST_TABLE(u1_t, _DR2RPS_CRC)[] = {
|
||||
ILLEGAL_RPS
|
||||
};
|
||||
|
||||
bit_t
|
||||
LMICeu868_validDR(dr_t dr) {
|
||||
// use subtract here to avoid overflow
|
||||
if (dr >= LENOF_TABLE(_DR2RPS_CRC) - 2)
|
||||
return 0;
|
||||
return TABLE_GET_U1(_DR2RPS_CRC, dr+1)!=ILLEGAL_RPS;
|
||||
}
|
||||
|
||||
static CONST_TABLE(u1_t, maxFrameLens)[] = {
|
||||
59+5, 59+5, 59+5, 123+5, 250+5, 250+5, 250+5, 250+5
|
||||
};
|
||||
@ -140,17 +148,28 @@ static CONST_TABLE(u4_t, bandAssignments)[] = {
|
||||
865000000 /* .. 868400000 */ | BAND_CENTI,
|
||||
};
|
||||
|
||||
///
|
||||
/// \brief query number of default channels.
|
||||
///
|
||||
u1_t LMIC_queryNumDefaultChannels() {
|
||||
return NUM_DEFAULT_CHANNELS;
|
||||
}
|
||||
|
||||
///
|
||||
/// \brief LMIC_setupChannel for EU 868
|
||||
///
|
||||
/// \note according to LoRaWAN 1.0.3 section 5.6, "the acceptable range
|
||||
/// for **ChIndex** is N to 16", where N is our \c NUM_DEFAULT_CHANNELS.
|
||||
/// This routine is used internally for MAC commands, so we enforce
|
||||
/// this for the extenal API as well.
|
||||
///
|
||||
bit_t LMIC_setupChannel(u1_t chidx, u4_t freq, u2_t drmap, s1_t band) {
|
||||
// zero the band bits in freq, just in case.
|
||||
freq &= ~3;
|
||||
|
||||
if (chidx < NUM_DEFAULT_CHANNELS) {
|
||||
// can't disable a default channel.
|
||||
if (freq == 0)
|
||||
return 0;
|
||||
// can't change a default channel.
|
||||
else if (freq != (LMIC.channelFreq[chidx] & ~3))
|
||||
return 0;
|
||||
// can't do anything to a default channel.
|
||||
return 0;
|
||||
}
|
||||
bit_t fEnable = (freq != 0);
|
||||
if (chidx >= MAX_CHANNELS)
|
||||
@ -204,32 +223,111 @@ ostime_t LMICeu868_nextJoinTime(ostime_t time) {
|
||||
return time;
|
||||
}
|
||||
|
||||
///
|
||||
/// \brief change the TX channel given the desired tx time.
|
||||
///
|
||||
/// \param [in] now is the time at which we want to transmit. In fact, it's always
|
||||
/// the current time.
|
||||
///
|
||||
/// \returns the actual time at which we can transmit. \c LMIC.txChnl is set to the
|
||||
/// selected channel.
|
||||
///
|
||||
/// \details
|
||||
/// We scan all the channels, creating a mask of all enabled channels that are
|
||||
/// feasible at the earliest possible time. We then randomly choose one from
|
||||
/// that, updating the shuffle mask.
|
||||
///
|
||||
/// One sublety is that we have to cope with an artifact of the shuffler.
|
||||
/// It will zero out bits for candidates that are real candidates, but
|
||||
/// not in the time window, and not consider them as early as it should.
|
||||
/// So we keep a mask of all feasible channels, and make sure that they
|
||||
/// remain set in the shuffle mask if appropriate.
|
||||
///
|
||||
ostime_t LMICeu868_nextTx(ostime_t now) {
|
||||
u1_t bmap = 0xF;
|
||||
do {
|
||||
ostime_t mintime = now + /*8h*/sec2osticks(28800);
|
||||
u1_t band = 0;
|
||||
for (u1_t bi = 0; bi<4; bi++) {
|
||||
if ((bmap & (1 << bi)) && mintime - LMIC.bands[bi].avail > 0)
|
||||
mintime = LMIC.bands[band = bi].avail;
|
||||
}
|
||||
// Find next channel in given band
|
||||
u1_t chnl = LMIC.bands[band].lastchnl;
|
||||
for (u1_t ci = 0; ci<MAX_CHANNELS; ci++) {
|
||||
if ((chnl = (chnl + 1)) >= MAX_CHANNELS)
|
||||
chnl -= MAX_CHANNELS;
|
||||
if ((LMIC.channelMap & (1 << chnl)) != 0 && // channel enabled
|
||||
(LMIC.channelDrMap[chnl] & (1 << (LMIC.datarate & 0xF))) != 0 &&
|
||||
band == (LMIC.channelFreq[chnl] & 0x3)) { // in selected band
|
||||
LMIC.txChnl = LMIC.bands[band].lastchnl = chnl;
|
||||
return mintime;
|
||||
}
|
||||
}
|
||||
if ((bmap &= ~(1 << band)) == 0) {
|
||||
// No feasible channel found!
|
||||
return mintime;
|
||||
}
|
||||
} while (1);
|
||||
ostime_t mintime = now + /*8h*/sec2osticks(28800);
|
||||
u2_t availMap;
|
||||
u2_t feasibleMap;
|
||||
u1_t bandMap;
|
||||
|
||||
// set mintime to the earliest time of all enabled channels
|
||||
// (can't just look at bands); and for a given channel, we
|
||||
// can't tell if we're ready till we've checked all possible
|
||||
// avail times.
|
||||
bandMap = 0;
|
||||
for (u1_t chnl = 0; chnl < MAX_CHANNELS; ++chnl) {
|
||||
u2_t chnlBit = 1 << chnl;
|
||||
|
||||
// none at any higher numbers?
|
||||
if (LMIC.channelMap < chnlBit)
|
||||
break;
|
||||
|
||||
// not enabled?
|
||||
if ((LMIC.channelMap & chnlBit) == 0)
|
||||
continue;
|
||||
|
||||
// not feasible?
|
||||
if ((LMIC.channelDrMap[chnl] & (1 << (LMIC.datarate & 0xF))) == 0)
|
||||
continue;
|
||||
|
||||
u1_t const band = LMIC.channelFreq[chnl] & 0x3;
|
||||
u1_t const thisBandBit = 1 << band;
|
||||
// already considered?
|
||||
if ((bandMap & thisBandBit) != 0)
|
||||
continue;
|
||||
|
||||
// consider this band.
|
||||
bandMap |= thisBandBit;
|
||||
|
||||
// enabled, not considered, feasible: adjust the min time.
|
||||
if ((s4_t)(mintime - LMIC.bands[band].avail) > 0)
|
||||
mintime = LMIC.bands[band].avail;
|
||||
}
|
||||
|
||||
// make a mask of candidates available for use
|
||||
availMap = 0;
|
||||
feasibleMap = 0;
|
||||
for (u1_t chnl = 0; chnl < MAX_CHANNELS; ++chnl) {
|
||||
u2_t chnlBit = 1 << chnl;
|
||||
|
||||
// none at any higher numbers?
|
||||
if (LMIC.channelMap < chnlBit)
|
||||
break;
|
||||
|
||||
// not enabled?
|
||||
if ((LMIC.channelMap & chnlBit) == 0)
|
||||
continue;
|
||||
|
||||
// not feasible?
|
||||
if ((LMIC.channelDrMap[chnl] & (1 << (LMIC.datarate & 0xF))) == 0)
|
||||
continue;
|
||||
|
||||
// This channel is feasible. But might not be available.
|
||||
feasibleMap |= chnlBit;
|
||||
|
||||
// not available yet?
|
||||
u1_t const band = LMIC.channelFreq[chnl] & 0x3;
|
||||
if ((s4_t)(LMIC.bands[band].avail - mintime) > 0)
|
||||
continue;
|
||||
|
||||
// ok: this is a candidate.
|
||||
availMap |= chnlBit;
|
||||
}
|
||||
|
||||
// find the next available chennel.
|
||||
u2_t saveShuffleMap = LMIC.channelShuffleMap;
|
||||
int candidateCh = LMIC_findNextChannel(&LMIC.channelShuffleMap, &availMap, 1, LMIC.txChnl == 0xFF ? -1 : LMIC.txChnl);
|
||||
|
||||
// restore bits in the shuffleMap that were on, but might have reset
|
||||
// if availMap was used to refresh shuffleMap. These are channels that
|
||||
// are feasble but not yet candidates due to band saturation
|
||||
LMIC.channelShuffleMap |= saveShuffleMap & feasibleMap & ~availMap;
|
||||
|
||||
if (candidateCh >= 0) {
|
||||
// update the channel; otherwise we'll just use the
|
||||
// most recent one.
|
||||
LMIC.txChnl = candidateCh;
|
||||
}
|
||||
return mintime;
|
||||
}
|
||||
|
||||
|
||||
|
@ -128,7 +128,9 @@ void LMICeulike_initJoinLoop(uint8_t nDefaultChannels, s1_t adrTxPow) {
|
||||
#if CFG_TxContinuousMode
|
||||
LMIC.txChnl = 0
|
||||
#else
|
||||
LMIC.txChnl = os_getRndU1() % nDefaultChannels;
|
||||
uint16_t enableMap = (1 << nDefaultChannels) - 1;
|
||||
LMIC.channelShuffleMap = enableMap;
|
||||
LMIC.txChnl = LMIC_findNextChannel(&LMIC.channelShuffleMap, &enableMap, 1, -1);
|
||||
#endif
|
||||
LMIC.adrTxPow = adrTxPow;
|
||||
// TODO(tmm@mcci.com) don't use EU directly, use a table. That
|
||||
@ -166,12 +168,11 @@ void LMICeulike_updateTx(ostime_t txbeg) {
|
||||
//
|
||||
ostime_t LMICeulike_nextJoinState(uint8_t nDefaultChannels) {
|
||||
u1_t failed = 0;
|
||||
u2_t enableMap = (1 << nDefaultChannels) - 1;
|
||||
|
||||
// Try each default channel with same DR
|
||||
// If all fail try next lower datarate
|
||||
if (++LMIC.txChnl == /* NUM_DEFAULT_CHANNELS */ nDefaultChannels)
|
||||
LMIC.txChnl = 0;
|
||||
if ((++LMIC.txCnt % nDefaultChannels) == 0) {
|
||||
if (LMIC.channelShuffleMap == 0) {
|
||||
// Lower DR every nth try (having all default channels with same DR)
|
||||
//
|
||||
// TODO(tmm@mcci.com) add new DR_REGION_JOIN_MIN instead of LORAWAN_DR0;
|
||||
@ -179,7 +180,6 @@ ostime_t LMICeulike_nextJoinState(uint8_t nDefaultChannels) {
|
||||
// the failed flag here. This will cause the outer caller to take the
|
||||
// appropriate join path. Or add new LMICeulike_GetLowestJoinDR()
|
||||
//
|
||||
|
||||
// TODO(tmm@mcci.com) - see above; please remove regional dependency from this file.
|
||||
#if CFG_region == LMIC_REGION_as923
|
||||
// in the join of AS923 v1.1 or older, only DR2 is used.
|
||||
@ -187,15 +187,22 @@ ostime_t LMICeulike_nextJoinState(uint8_t nDefaultChannels) {
|
||||
LMIC.datarate = AS923_DR_SF10;
|
||||
failed = 1;
|
||||
#else
|
||||
if (LMIC.datarate == LORAWAN_DR0)
|
||||
if (LMIC.datarate == LORAWAN_DR0) {
|
||||
failed = 1; // we have tried all DR - signal EV_JOIN_FAILED
|
||||
else {
|
||||
} else {
|
||||
LMICcore_setDrJoin(DRCHG_NOJACC, decDR((dr_t)LMIC.datarate));
|
||||
}
|
||||
#endif
|
||||
}
|
||||
// Clear NEXTCHNL because join state engine controls channel hopping
|
||||
|
||||
// find new channel, avoiding repeats.
|
||||
int newCh = LMIC_findNextChannel(&LMIC.channelShuffleMap, &enableMap, 1, LMIC.txChnl);
|
||||
if (newCh >= 0)
|
||||
LMIC.txChnl = newCh;
|
||||
|
||||
// Clear OP_NEXTCHNL because join state engine controls channel hopping
|
||||
LMIC.opmode &= ~OP_NEXTCHNL;
|
||||
|
||||
// Move txend to randomize synchronized concurrent joins.
|
||||
// Duty cycle is based on txend.
|
||||
ostime_t const time = LMICbandplan_nextJoinTime(os_getTime());
|
||||
@ -220,6 +227,27 @@ ostime_t LMICeulike_nextJoinState(uint8_t nDefaultChannels) {
|
||||
}
|
||||
#endif // !DISABLE_JOIN
|
||||
|
||||
#if !defined(DISABLE_JOIN)
|
||||
void LMICeulike_processJoinAcceptCFList(void) {
|
||||
if ( LMICbandplan_hasJoinCFlist() &&
|
||||
LMIC.frame[OFF_CFLIST + 15] == LORAWAN_JoinAccept_CFListType_FREQUENCIES) {
|
||||
u1_t dlen;
|
||||
u1_t nDefault = LMIC_queryNumDefaultChannels();
|
||||
|
||||
dlen = OFF_CFLIST;
|
||||
for( u1_t chidx = nDefault; chidx < nDefault + 5; chidx++, dlen+=3 ) {
|
||||
u4_t freq = LMICbandplan_convFreq(&LMIC.frame[dlen]);
|
||||
if( freq ) {
|
||||
LMIC_setupChannel(chidx, freq, 0, -1);
|
||||
#if LMIC_DEBUG_LEVEL > 1
|
||||
LMIC_DEBUG_PRINTF("%"LMIC_PRId_ostime_t": Setup channel, idx=%d, freq=%"PRIu32"\n", os_getTime(), chidx, freq);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif // !DISABLE_JOIN
|
||||
|
||||
void LMICeulike_saveAdrState(lmic_saved_adr_state_t *pStateBuffer) {
|
||||
os_copyMem(
|
||||
pStateBuffer->channelFreq,
|
||||
|
@ -66,6 +66,11 @@ enum { BAND_MILLI = 0, BAND_CENTI = 1, BAND_DECI = 2, BAND_AUX = 3 };
|
||||
// there's a CFList on joins for EU-like plans
|
||||
#define LMICbandplan_hasJoinCFlist() (1)
|
||||
|
||||
/// \brief process CFLists from JoinAccept for EU-like regions
|
||||
void LMICeulike_processJoinAcceptCFList(void);
|
||||
/// \brief by default, EU-like plans use LMICeulike_processJoinAcceptCFList
|
||||
#define LMICbandplan_processJoinAcceptCFList LMICeulike_processJoinAcceptCFList
|
||||
|
||||
#define LMICbandplan_advanceBeaconChannel() \
|
||||
do { /* nothing */ } while (0)
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Copyright (c) 2014-2016 IBM Corporation.
|
||||
* Copyright (c) 2017, 2019 MCCI Corporation.
|
||||
* Copyright (c) 2017, 2019-2021 MCCI Corporation.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
@ -49,6 +49,14 @@ CONST_TABLE(u1_t, _DR2RPS_CRC)[] = {
|
||||
ILLEGAL_RPS
|
||||
};
|
||||
|
||||
bit_t
|
||||
LMICin866_validDR(dr_t dr) {
|
||||
// use subtract here to avoid overflow
|
||||
if (dr >= LENOF_TABLE(_DR2RPS_CRC) - 2)
|
||||
return 0;
|
||||
return TABLE_GET_U1(_DR2RPS_CRC, dr+1)!=ILLEGAL_RPS;
|
||||
}
|
||||
|
||||
static CONST_TABLE(u1_t, maxFrameLens)[] = {
|
||||
59+5, 59+5, 59+5, 123+5, 250+5, 250+5, 0, 250+5
|
||||
};
|
||||
@ -134,17 +142,27 @@ bit_t LMIC_setupBand(u1_t bandidx, s1_t txpow, u2_t txcap) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
///
|
||||
/// \brief query number of default channels.
|
||||
///
|
||||
u1_t LMIC_queryNumDefaultChannels() {
|
||||
return NUM_DEFAULT_CHANNELS;
|
||||
}
|
||||
|
||||
///
|
||||
/// \brief LMIC_setupChannel for IN region
|
||||
///
|
||||
/// \note according to LoRaWAN 1.3 section 5.6, "the acceptable range
|
||||
/// for **ChIndex** is N to 16", where N is our \c NUM_DEFAULT_CHANNELS.
|
||||
/// This routine is used internally for MAC commands, so we enforce
|
||||
/// this for the extenal API as well.
|
||||
///
|
||||
bit_t LMIC_setupChannel(u1_t chidx, u4_t freq, u2_t drmap, s1_t band) {
|
||||
// zero the band bits in freq, just in case.
|
||||
freq &= ~3;
|
||||
|
||||
if (chidx < NUM_DEFAULT_CHANNELS) {
|
||||
// can't disable a default channel.
|
||||
if (freq == 0)
|
||||
return 0;
|
||||
// can't change a default channel.
|
||||
else if (freq != (LMIC.channelFreq[chidx] & ~3))
|
||||
return 0;
|
||||
return 0;
|
||||
}
|
||||
bit_t fEnable = (freq != 0);
|
||||
if (chidx >= MAX_CHANNELS)
|
||||
@ -173,28 +191,44 @@ u4_t LMICin866_convFreq(xref2cu1_t ptr) {
|
||||
return freq;
|
||||
}
|
||||
|
||||
// return the next time, but also do channel hopping here
|
||||
// since there's no duty cycle limitation, and no dwell limitation,
|
||||
// we simply loop through the channels sequentially.
|
||||
///
|
||||
/// \brief change the TX channel given the desired tx time.
|
||||
///
|
||||
/// \param [in] now is the time at which we want to transmit. In fact, it's always
|
||||
/// the current time.
|
||||
///
|
||||
/// \returns the actual time at which we can transmit. \c LMIC.txChnl is set to the
|
||||
/// selected channel.
|
||||
///
|
||||
/// \details
|
||||
/// We scan all the bands, creating a mask of all enabled channels that are
|
||||
/// feasible at the earliest possible time. We then randomly choose one from
|
||||
/// that, updating the shuffle mask.
|
||||
///
|
||||
/// Since there's no duty cycle limitation, and no dwell limitation,
|
||||
/// we just choose a channel from the shuffle and return the current time.
|
||||
///
|
||||
ostime_t LMICin866_nextTx(ostime_t now) {
|
||||
const u1_t band = BAND_MILLI;
|
||||
uint16_t availmask;
|
||||
|
||||
for (u1_t ci = 0; ci < MAX_CHANNELS; ci++) {
|
||||
// Find next channel in given band
|
||||
u1_t chnl = LMIC.bands[band].lastchnl;
|
||||
for (u1_t ci = 0; ci<MAX_CHANNELS; ci++) {
|
||||
if ((chnl = (chnl + 1)) >= MAX_CHANNELS)
|
||||
chnl -= MAX_CHANNELS;
|
||||
if ((LMIC.channelMap & (1 << chnl)) != 0 && // channel enabled
|
||||
(LMIC.channelDrMap[chnl] & (1 << (LMIC.datarate & 0xF))) != 0 &&
|
||||
band == (LMIC.channelFreq[chnl] & 0x3)) { // in selected band
|
||||
LMIC.txChnl = LMIC.bands[band].lastchnl = chnl;
|
||||
return now;
|
||||
}
|
||||
}
|
||||
// scan all the enabled channels and make a mask of candidates
|
||||
availmask = 0;
|
||||
for (u1_t chnl = 0; chnl < MAX_CHANNELS; ++chnl) {
|
||||
// not enabled?
|
||||
if ((LMIC.channelMap & (1 << chnl)) == 0)
|
||||
continue;
|
||||
// not feasible?
|
||||
if ((LMIC.channelDrMap[chnl] & (1 << (LMIC.datarate & 0xF))) == 0)
|
||||
continue;
|
||||
availmask |= 1 << chnl;
|
||||
}
|
||||
|
||||
// no enabled channel found! just use the last channel.
|
||||
// now: calculate the mask
|
||||
int candidateCh = LMIC_findNextChannel(&LMIC.channelShuffleMap, &availmask, 1, LMIC.txChnl == 0xFF ? -1 : LMIC.txChnl);
|
||||
if (candidateCh >= 0) {
|
||||
// update the channel.
|
||||
LMIC.txChnl = candidateCh;
|
||||
}
|
||||
return now;
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Copyright (c) 2014-2016 IBM Corporation.
|
||||
* Copyright (c) 2017, 2019 MCCI Corporation.
|
||||
* Copyright (c) 2017, 2019-2021 MCCI Corporation.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
@ -47,6 +47,14 @@ CONST_TABLE(u1_t, _DR2RPS_CRC)[] = {
|
||||
ILLEGAL_RPS, // [6]
|
||||
};
|
||||
|
||||
bit_t
|
||||
LMICkr920_validDR(dr_t dr) {
|
||||
// use subtract here to avoid overflow
|
||||
if (dr >= LENOF_TABLE(_DR2RPS_CRC) - 2)
|
||||
return 0;
|
||||
return TABLE_GET_U1(_DR2RPS_CRC, dr+1)!=ILLEGAL_RPS;
|
||||
}
|
||||
|
||||
static CONST_TABLE(u1_t, maxFrameLens)[] = {
|
||||
59+5, 59+5, 59+5, 123+5, 250+5, 250+5
|
||||
};
|
||||
@ -145,17 +153,28 @@ bit_t LMIC_setupBand(u1_t bandidx, s1_t txpow, u2_t txcap) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
///
|
||||
/// \brief query number of default channels.
|
||||
///
|
||||
u1_t LMIC_queryNumDefaultChannels() {
|
||||
return NUM_DEFAULT_CHANNELS;
|
||||
}
|
||||
|
||||
///
|
||||
/// \brief LMIC_setupChannel for KR920
|
||||
///
|
||||
/// \note according to LoRaWAN 1.3 section 5.6, "the acceptable range
|
||||
/// for **ChIndex** is N to 16", where N is our \c NUM_DEFAULT_CHANNELS.
|
||||
/// This routine is used internally for MAC commands, so we enforce
|
||||
/// this for the extenal API as well.
|
||||
///
|
||||
bit_t LMIC_setupChannel(u1_t chidx, u4_t freq, u2_t drmap, s1_t band) {
|
||||
// zero the band bits in freq, just in case.
|
||||
freq &= ~3;
|
||||
|
||||
if (chidx < NUM_DEFAULT_CHANNELS) {
|
||||
// can't disable a default channel.
|
||||
if (freq == 0)
|
||||
return 0;
|
||||
// can't change a default channel.
|
||||
else if (freq != (LMIC.channelFreq[chidx] & ~3))
|
||||
return 0;
|
||||
return 0;
|
||||
}
|
||||
bit_t fEnable = (freq != 0);
|
||||
if (chidx >= MAX_CHANNELS)
|
||||
@ -184,28 +203,44 @@ u4_t LMICkr920_convFreq(xref2cu1_t ptr) {
|
||||
return freq;
|
||||
}
|
||||
|
||||
// return the next time, but also do channel hopping here
|
||||
// since there's no duty cycle limitation, and no dwell limitation,
|
||||
// we simply loop through the channels sequentially.
|
||||
///
|
||||
/// \brief change the TX channel given the desired tx time.
|
||||
///
|
||||
/// \param [in] now is the time at which we want to transmit. In fact, it's always
|
||||
/// the current time.
|
||||
///
|
||||
/// \returns the actual time at which we can transmit. \c LMIC.txChnl is set to the
|
||||
/// selected channel.
|
||||
///
|
||||
/// \details
|
||||
/// We scan all the bands, creating a mask of all enabled channels that are
|
||||
/// feasible at the earliest possible time. We then randomly choose one from
|
||||
/// that, updating the shuffle mask.
|
||||
///
|
||||
/// Since there's no duty cycle limitation, and no dwell limitation,
|
||||
/// we just choose a channel from the shuffle and return the current time.
|
||||
///
|
||||
ostime_t LMICkr920_nextTx(ostime_t now) {
|
||||
const u1_t band = BAND_MILLI;
|
||||
uint16_t availmask;
|
||||
|
||||
for (u1_t ci = 0; ci < MAX_CHANNELS; ci++) {
|
||||
// Find next channel in given band
|
||||
u1_t chnl = LMIC.bands[band].lastchnl;
|
||||
for (u1_t ci = 0; ci<MAX_CHANNELS; ci++) {
|
||||
if ((chnl = (chnl + 1)) >= MAX_CHANNELS)
|
||||
chnl -= MAX_CHANNELS;
|
||||
if ((LMIC.channelMap & (1 << chnl)) != 0 && // channel enabled
|
||||
(LMIC.channelDrMap[chnl] & (1 << (LMIC.datarate & 0xF))) != 0 &&
|
||||
band == (LMIC.channelFreq[chnl] & 0x3)) { // in selected band
|
||||
LMIC.txChnl = LMIC.bands[band].lastchnl = chnl;
|
||||
return now;
|
||||
}
|
||||
}
|
||||
// scan all the enabled channels and make a mask of candidates
|
||||
availmask = 0;
|
||||
for (u1_t chnl = 0; chnl < MAX_CHANNELS; ++chnl) {
|
||||
// not enabled?
|
||||
if ((LMIC.channelMap & (1 << chnl)) == 0)
|
||||
continue;
|
||||
// not feasible?
|
||||
if ((LMIC.channelDrMap[chnl] & (1 << (LMIC.datarate & 0xF))) == 0)
|
||||
continue;
|
||||
availmask |= 1 << chnl;
|
||||
}
|
||||
|
||||
// no enabled channel found! just use the last channel.
|
||||
// now: calculate the mask
|
||||
int candidateCh = LMIC_findNextChannel(&LMIC.channelShuffleMap, &availmask, 1, LMIC.txChnl == 0xFF ? -1 : LMIC.txChnl);
|
||||
if (candidateCh >= 0) {
|
||||
// update the channel.
|
||||
LMIC.txChnl = candidateCh;
|
||||
}
|
||||
return now;
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Copyright (c) 2014-2016 IBM Corporation.
|
||||
* Copyright (c) 2017, 2019 MCCI Corporation.
|
||||
* Copyright (c) 2017, 2019-2021 MCCI Corporation.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
@ -55,6 +55,14 @@ CONST_TABLE(u1_t, _DR2RPS_CRC)[] = {
|
||||
ILLEGAL_RPS // [14]
|
||||
};
|
||||
|
||||
bit_t
|
||||
LMICus915_validDR(dr_t dr) {
|
||||
// use subtract here to avoid overflow
|
||||
if (dr >= LENOF_TABLE(_DR2RPS_CRC) - 2)
|
||||
return 0;
|
||||
return TABLE_GET_U1(_DR2RPS_CRC, dr+1)!=ILLEGAL_RPS;
|
||||
}
|
||||
|
||||
static CONST_TABLE(u1_t, maxFrameLens)[] = {
|
||||
19+5, 61+5, 133+5, 250+5, 250+5, 0, 0,0,
|
||||
61+5, 133+5, 250+5, 250+5, 250+5, 250+5
|
||||
@ -99,21 +107,34 @@ u4_t LMICus915_convFreq(xref2cu1_t ptr) {
|
||||
return freq;
|
||||
}
|
||||
|
||||
///
|
||||
/// \brief query number of default channels.
|
||||
///
|
||||
/// For US, we have no programmable channels; all channels
|
||||
/// are fixed. Return the total channel count.
|
||||
///
|
||||
u1_t LMIC_queryNumDefaultChannels() {
|
||||
return 64 + 8;
|
||||
}
|
||||
|
||||
///
|
||||
/// \brief LMIC_setupChannel for US915
|
||||
///
|
||||
/// \note there are no progammable channels for US915, so this API
|
||||
/// always returns FALSE.
|
||||
///
|
||||
bit_t LMIC_setupChannel(u1_t chidx, u4_t freq, u2_t drmap, s1_t band) {
|
||||
LMIC_API_PARAMETER(chidx);
|
||||
LMIC_API_PARAMETER(freq);
|
||||
LMIC_API_PARAMETER(drmap);
|
||||
LMIC_API_PARAMETER(band);
|
||||
|
||||
if (chidx < 72 || chidx >= 72 + MAX_XCHANNELS)
|
||||
return 0; // channels 0..71 are hardwired
|
||||
LMIC.xchFreq[chidx - 72] = freq;
|
||||
// TODO(tmm@mcci.com): don't use US SF directly, use something from the LMIC context or a static const
|
||||
LMIC.xchDrMap[chidx - 72] = drmap == 0 ? DR_RANGE_MAP(US915_DR_SF10, US915_DR_SF8C) : drmap;
|
||||
LMIC.channelMap[chidx >> 4] |= (1 << (chidx & 0xF));
|
||||
return 1;
|
||||
return 0; // channels 0..71 are hardwired
|
||||
}
|
||||
|
||||
bit_t LMIC_disableChannel(u1_t channel) {
|
||||
bit_t result = 0;
|
||||
if (channel < 72 + MAX_XCHANNELS) {
|
||||
if (channel < 72) {
|
||||
if (ENABLED_CHANNEL(channel)) {
|
||||
result = 1;
|
||||
if (IS_CHANNEL_125khz(channel))
|
||||
@ -128,7 +149,7 @@ bit_t LMIC_disableChannel(u1_t channel) {
|
||||
|
||||
bit_t LMIC_enableChannel(u1_t channel) {
|
||||
bit_t result = 0;
|
||||
if (channel < 72 + MAX_XCHANNELS) {
|
||||
if (channel < 72) {
|
||||
if (!ENABLED_CHANNEL(channel)) {
|
||||
result = 1;
|
||||
if (IS_CHANNEL_125khz(channel))
|
||||
@ -197,13 +218,7 @@ void LMICus915_updateTx(ostime_t txbeg) {
|
||||
} else {
|
||||
// at 500kHz bandwidth, we're allowed more power.
|
||||
LMIC.txpow = 26;
|
||||
if (chnl < 64 + 8) {
|
||||
LMIC.freq = US915_500kHz_UPFBASE + (chnl - 64)*US915_500kHz_UPFSTEP;
|
||||
}
|
||||
else {
|
||||
ASSERT(chnl < 64 + 8 + MAX_XCHANNELS);
|
||||
LMIC.freq = LMIC.xchFreq[chnl - 72];
|
||||
}
|
||||
LMIC.freq = US915_500kHz_UPFBASE + (chnl - 64)*US915_500kHz_UPFSTEP;
|
||||
}
|
||||
|
||||
// Update global duty cycle stats
|
||||
|
@ -36,36 +36,34 @@
|
||||
# error "LMICuslike_getFirst500kHzDR() not defined by bandplan"
|
||||
#endif
|
||||
|
||||
static void setNextChannel(uint start, uint end, uint count) {
|
||||
///
|
||||
/// \brief set LMIC.txChan to the next selected channel.
|
||||
///
|
||||
/// \param [in] start first channel number
|
||||
/// \param [in] end one past the last channel number
|
||||
///
|
||||
/// \details
|
||||
/// We set up a call to LMIC_findNextChannel using the channelShuffleMap and
|
||||
/// the channelEnableMap. We subset these based on start and end. \p start must
|
||||
/// be a multiple of 16.
|
||||
///
|
||||
static void setNextChannel(uint16_t start, uint16_t end, uint16_t count) {
|
||||
ASSERT(count>0);
|
||||
ASSERT(start<end);
|
||||
ASSERT(count <= (end - start));
|
||||
// We used to pick a random channel once and then just increment. That is not per spec.
|
||||
// Now we use a new random number each time, because they are not very expensive.
|
||||
// Regarding the algo below, we cannot pick a number and scan until we hit an enabled channel.
|
||||
// That would result in the first enabled channel following a set of disabled ones
|
||||
// being used more frequently than the other enabled channels.
|
||||
ASSERT((start & 0xF) == 0);
|
||||
uint16_t const mapStart = start >> 4;
|
||||
uint16_t const mapEntries = (end - start + 15) >> 4;
|
||||
|
||||
// Last used channel is in range. It is not a candidate, per spec.
|
||||
uint lastTxChan = LMIC.txChnl;
|
||||
if (start <= lastTxChan && lastTxChan<end &&
|
||||
// Adjust count only if still enabled. Otherwise, no chance of selection.
|
||||
ENABLED_CHANNEL(lastTxChan)) {
|
||||
--count;
|
||||
if (count == 0) {
|
||||
return; // Only one active channel, so keep using it.
|
||||
}
|
||||
}
|
||||
int candidate = start + LMIC_findNextChannel(
|
||||
LMIC.channelShuffleMap + mapStart,
|
||||
LMIC.channelMap + mapStart,
|
||||
mapEntries,
|
||||
LMIC.txChnl == 0xFF ? -1 : LMIC.txChnl
|
||||
);
|
||||
|
||||
uint nth = os_getRndU1() % count;
|
||||
for (u1_t chnl = start; chnl<end; chnl++) {
|
||||
// Scan for nth enabled channel that is not the last channel used
|
||||
if (chnl != lastTxChan && ENABLED_CHANNEL(chnl) && (nth--) == 0) {
|
||||
LMIC.txChnl = chnl;
|
||||
return;
|
||||
}
|
||||
}
|
||||
// No feasible channel found! Keep old one.
|
||||
if (candidate >= 0)
|
||||
LMIC.txChnl = candidate;
|
||||
}
|
||||
|
||||
|
||||
@ -87,8 +85,11 @@ void LMICuslike_initDefaultChannels(bit_t fJoin) {
|
||||
for (u1_t i = 0; i<4; i++)
|
||||
LMIC.channelMap[i] = 0xFFFF;
|
||||
LMIC.channelMap[4] = 0x00FF;
|
||||
os_clearMem(LMIC.channelShuffleMap, sizeof(LMIC.channelShuffleMap));
|
||||
LMIC.activeChannels125khz = 64;
|
||||
LMIC.activeChannels500khz = 8;
|
||||
// choose a random channel.
|
||||
LMIC.txChnl = 0xFF;
|
||||
}
|
||||
|
||||
// verify that a given setting is permitted
|
||||
@ -230,11 +231,10 @@ bit_t LMICuslike_isDataRateFeasible(dr_t dr) {
|
||||
#if !defined(DISABLE_JOIN)
|
||||
void LMICuslike_initJoinLoop(void) {
|
||||
// set an initial condition so that setNextChannel()'s preconds are met
|
||||
LMIC.txChnl = 0;
|
||||
LMIC.txChnl = 0xFF;
|
||||
|
||||
// then chose a new channel. This gives us a random first channel for
|
||||
// the join. Minor nit: if channel 0 is enabled, it will never be used
|
||||
// as the first join channel. The join logic uses the current txChnl,
|
||||
// the join. The join logic uses the current txChnl,
|
||||
// then changes after the rx window expires; so we need to set a valid
|
||||
// starting point.
|
||||
setNextChannel(0, 64, LMIC.activeChannels125khz);
|
||||
@ -277,10 +277,13 @@ ostime_t LMICuslike_nextJoinState(void) {
|
||||
if (LMIC.datarate != LMICuslike_getFirst500kHzDR()) {
|
||||
// assume that 500 kHz equiv of last 125 kHz channel
|
||||
// is also enabled, and use it next.
|
||||
LMIC.txChnl_125kHz = LMIC.txChnl;
|
||||
LMIC.txChnl = 64 + (LMIC.txChnl >> 3);
|
||||
LMICcore_setDrJoin(DRCHG_SET, LMICuslike_getFirst500kHzDR());
|
||||
}
|
||||
else {
|
||||
// restore invariant
|
||||
LMIC.txChnl = LMIC.txChnl_125kHz;
|
||||
setNextChannel(0, 64, LMIC.activeChannels125khz);
|
||||
|
||||
// TODO(tmm@mcci.com) parameterize
|
||||
@ -290,6 +293,7 @@ ostime_t LMICuslike_nextJoinState(void) {
|
||||
}
|
||||
LMICcore_setDrJoin(DRCHG_SET, dr);
|
||||
}
|
||||
// tell the main loop that we've already selected a channel.
|
||||
LMIC.opmode &= ~OP_NEXTCHNL;
|
||||
|
||||
// TODO(tmm@mcci.com): change delay to (0:1) secs + a known t0, but randomized;
|
||||
@ -311,6 +315,32 @@ ostime_t LMICuslike_nextJoinState(void) {
|
||||
}
|
||||
#endif
|
||||
|
||||
#if !defined(DISABLE_JOIN)
|
||||
void LMICuslike_processJoinAcceptCFList(void) {
|
||||
if ( LMICbandplan_hasJoinCFlist() &&
|
||||
LMIC.frame[OFF_CFLIST + 15] == LORAWAN_JoinAccept_CFListType_MASK ) {
|
||||
u1_t dlen;
|
||||
|
||||
dlen = OFF_CFLIST;
|
||||
for( u1_t chidx = 0; chidx < 8 * sizeof(LMIC.channelMap); chidx += 16, dlen += 2 ) {
|
||||
u2_t mask = os_rlsbf2(&LMIC.frame[dlen]);
|
||||
#if LMIC_DEBUG_LEVEL > 1
|
||||
LMIC_DEBUG_PRINTF("%"LMIC_PRId_ostime_t": Setup channel mask, group=%u, mask=%04x\n", os_getTime(), chidx, mask);
|
||||
#endif
|
||||
for ( u1_t chnum = chidx; chnum < chidx + 16; ++chnum, mask >>= 1) {
|
||||
if (chnum >= 72) {
|
||||
break;
|
||||
} else if (mask & 1) {
|
||||
LMIC_enableChannel(chnum);
|
||||
} else {
|
||||
LMIC_disableChannel(chnum);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif // !DISABLE_JOIN
|
||||
|
||||
void LMICuslike_saveAdrState(lmic_saved_adr_state_t *pStateBuffer) {
|
||||
os_copyMem(
|
||||
pStateBuffer->channelMap,
|
||||
|
@ -63,8 +63,14 @@ LMICuslike_isValidBeacon1(const uint8_t *d) {
|
||||
// provide a default LMICbandplan_joinAcceptChannelClear()
|
||||
#define LMICbandplan_joinAcceptChannelClear() do { } while (0)
|
||||
|
||||
// no CFList on joins for US-like plans
|
||||
#define LMICbandplan_hasJoinCFlist() (0)
|
||||
/// \brief there's a CFList on joins for US-like plans
|
||||
#define LMICbandplan_hasJoinCFlist() (1)
|
||||
|
||||
/// \brief process CFLists from JoinAccept for EU-like regions
|
||||
void LMICuslike_processJoinAcceptCFList(void);
|
||||
/// \brief by default, EU-like plans use LMICuslike_processJoinAcceptCFList
|
||||
#define LMICbandplan_processJoinAcceptCFList LMICuslike_processJoinAcceptCFList
|
||||
|
||||
|
||||
#define LMICbandplan_advanceBeaconChannel() \
|
||||
do { LMIC.bcnChnl = (LMIC.bcnChnl+1) & 7; } while (0)
|
||||
|
@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Copyright (c) 2014-2016 IBM Corporation.
|
||||
* Copyritght (c) 2017 MCCI Corporation.
|
||||
* Copyright (c) 2017-2021 MCCI Corporation.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
@ -445,6 +445,13 @@ enum {
|
||||
LEN_JA = 17,
|
||||
LEN_JAEXT = 17+16
|
||||
};
|
||||
|
||||
enum {
|
||||
// JoinAccept CFList types
|
||||
LORAWAN_JoinAccept_CFListType_FREQUENCIES = 0, ///< the CFList contains 5 frequencies
|
||||
LORAWAN_JoinAccept_CFListType_MASK = 1, ///< the CFList contains channel-mask data
|
||||
};
|
||||
|
||||
enum {
|
||||
// Data frame format
|
||||
OFF_DAT_HDR = 0,
|
||||
@ -638,17 +645,8 @@ static inline rps_t makeRps (sf_t sf, bw_t bw, cr_t cr, int ih, int nocrc) {
|
||||
#define MAKERPS(sf,bw,cr,ih,nocrc) ((rps_t)((sf) | ((bw)<<3) | ((cr)<<5) | ((nocrc)?(1<<7):0) | ((ih&0xFF)<<8)))
|
||||
// Two frames with params r1/r2 would interfere on air: same SFx + BWx
|
||||
static inline int sameSfBw(rps_t r1, rps_t r2) { return ((r1^r2)&0x1F) == 0; }
|
||||
|
||||
extern CONST_TABLE(u1_t, _DR2RPS_CRC)[];
|
||||
static inline rps_t updr2rps (dr_t dr) { return (rps_t)TABLE_GET_U1(_DR2RPS_CRC, dr+1); }
|
||||
static inline rps_t dndr2rps (dr_t dr) { return setNocrc(updr2rps(dr),1); }
|
||||
static inline int isFasterDR (dr_t dr1, dr_t dr2) { return dr1 > dr2; }
|
||||
static inline int isSlowerDR (dr_t dr1, dr_t dr2) { return dr1 < dr2; }
|
||||
static inline dr_t incDR (dr_t dr) { return TABLE_GET_U1(_DR2RPS_CRC, dr+2)==ILLEGAL_RPS ? dr : (dr_t)(dr+1); } // increase data rate
|
||||
static inline dr_t decDR (dr_t dr) { return TABLE_GET_U1(_DR2RPS_CRC, dr )==ILLEGAL_RPS ? dr : (dr_t)(dr-1); } // decrease data rate
|
||||
static inline dr_t assertDR (dr_t dr) { return TABLE_GET_U1(_DR2RPS_CRC, dr+1)==ILLEGAL_RPS ? (dr_t)DR_DFLTMIN : dr; } // force into a valid DR
|
||||
static inline bit_t validDR (dr_t dr) { return TABLE_GET_U1(_DR2RPS_CRC, dr+1)!=ILLEGAL_RPS; } // in range
|
||||
static inline dr_t lowerDR (dr_t dr, u1_t n) { while(n--){dr=decDR(dr);} return dr; } // decrease data rate by n steps
|
||||
|
||||
//
|
||||
// BEG: Keep in sync with lorabase.hpp
|
||||
|
Loading…
Reference in New Issue
Block a user