Skip to content

Commit

Permalink
Partial Fix #442: clean up window calculations based on lab study
Browse files Browse the repository at this point in the history
  • Loading branch information
terrillmoore committed Dec 28, 2019
1 parent 9671dea commit a4044c0
Show file tree
Hide file tree
Showing 5 changed files with 83 additions and 41 deletions.
8 changes: 8 additions & 0 deletions src/lmic/config.h
Original file line number Diff line number Diff line change
Expand Up @@ -204,4 +204,12 @@
# define LMIC_LORAWAN_SPEC_VERSION LMIC_LORAWAN_SPEC_VERSION_1_0_3
#endif

// LMIC_ENABLE_arbitrary_clock_error
// We normally don't want to allow users to set wide clock errors, because
// we assume reasonably-disciplined os_getTime() values. But... there might
// be platforms that require wider errors.
#if !defined(LMIC_ENABLE_arbitrary_clock_error)
# define LMIC_ENABLE_arbitrary_clock_error 0 /* PARAM */
#endif

#endif // _lmic_config_h_
91 changes: 55 additions & 36 deletions src/lmic/lmic.c
Original file line number Diff line number Diff line change
Expand Up @@ -1430,43 +1430,63 @@ static void setupRx2 (void) {
radioRx();
}

ostime_t LMICcore_adjustForDrift (ostime_t delay, ostime_t hsym) {
if (LMIC.client.clockError != 0) {
// Calculate how much the clock will drift maximally after delay has
// passed. This indicates the amount of time we can be early
// _or_ late.
ostime_t drift = (int64_t)delay * LMIC.client.clockError / MAX_CLOCK_ERROR;

// Increase the receive window by twice the maximum drift (to
// compensate for a slow or a fast clock).
delay -= drift;

// adjust rxsyms (the size of the window in syms) according to our
// uncertainty. do this in a strange order to avoid a divide if we can.
// rely on hsym = Tsym / 2
if ((1023 - LMIC.rxsyms) * hsym < drift) {
LMIC.rxsyms = 1023;
} else {
LMIC.rxsyms = (rxsyms_t) (LMIC.rxsyms + drift / hsym);
//! \brief Adjust the delay (in ticks) of the target window-open time from nominal.
//! \param hsym the duration of one-half symbol in osticks.
//! \param rxsyms_in the nominal window length -- minimum length of time to delay.
//! \return Effective delay to use (positive for later, negative for earlier).
//! \post LMIC.rxsyms is set to the number of rxsymbols to be used for preamble timeout.
//! \bug For FSK, the radio driver ignores LMIC.rxsysms, and uses a fixed value of 4080 bits
//! (81 ms)
//!
//! \details The calculation of the RX Window opening time has to balance several things.
//! The system clock might be inaccurate. Generally, the LMIC assumes that the timebase
//! is accurage to 100 ppm, or 0.01%. 0.01% of a 6 second window is 600 microseconds.
//! For LoRa, the fastest data rates of interest is SF7 (1024 us/symbol); with an 8-byte
//! preamble, the shortest preamble is 8.092ms long. If using FSK, the symbol rate is
//! 20 microseconds, but the preamble is 8*5 bits long, so the preamble is 800 microseconds.
//! If the user has not set the clock error, an error of 0.01% is assumed.
ostime_t LMICcore_adjustForDrift (ostime_t delay, ostime_t hsym, rxsyms_t rxsyms_in) {
ostime_t rxoffset;

// decide if we want to move left or right of the reference time.
rxoffset = -LMICbandplan_RX_EXTRA_MARGIN_osticks;

u2_t clockerr = LMIC.client.clockError;

// Most crystal oscillators are 100 ppm. If things are that tight, there's
// no point in specifying a drift, as 6 seconds at 100ppm is +/- 600 microseconds.
// We position the windows at the front, and there's some extra margin, so...
// don't bother setting values <= 100 ppm.
if (clockerr != 0)
{
// client has set clock error. Limit this to 0.1% unless there's
// 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);
if (! LMIC_ENABLE_arbitrary_clock_error && clockerr > maxError)
{
clockerr = maxError;
}
}
}
return delay;
}

ostime_t LMICcore_RxWindowOffset (ostime_t hsym, rxsyms_t rxsyms_in) {
ostime_t const Tsym = 2 * hsym;
ostime_t rxsyms;
ostime_t rxoffset;
// If the clock is slow, the window needs to open earlier in our time
// in order to open at or before the specified time (in real world),.
// Don't bother to round, as this is very fine-grained.
// and bear in mind that the delay is always
ostime_t drift = (ostime_t)(((int64_t)delay * clockerr) / MAX_CLOCK_ERROR);

rxsyms = ((2 * (int)rxsyms_in - 8) * Tsym + LMICbandplan_RX_ERROR_ABS_osticks * 2 + Tsym - 1) / Tsym;
if (rxsyms < rxsyms_in) {
rxsyms = rxsyms_in;
}
setRxsyms(rxsyms);
// we add symbols if we're starting tx before the window.
rxoffset -= drift;

// if we're starting before the window, extend the number of symbols
// for timeout appropriately.
if (rxoffset < 0)
rxsyms_in += (-rxoffset + 2 * hsym - 1) / (2 * hsym);

rxoffset = (8 - rxsyms) * hsym - LMICbandplan_RX_EXTRA_MARGIN_osticks;
setRxsyms(rxsyms_in);

return rxoffset;
return delay + rxoffset;
}

static void schedRx12 (ostime_t delay, osjobcb_t func, u1_t dr) {
Expand All @@ -1478,8 +1498,8 @@ static void schedRx12 (ostime_t delay, osjobcb_t func, u1_t dr) {
// that are too long for SF12, and too short for other SFs, so we follow the
// Semtech reference code.
//
// This also sets LMIC.rxsyms.
LMIC.rxtime = LMIC.txend + LMICcore_adjustForDrift(delay + LMICcore_RxWindowOffset(hsym, LMICbandplan_MINRX_SYMS_LoRa_ClassA), hsym);
// This also sets LMIC.rxsyms. This is NOT normally used for FSK; see LMICbandplan_txDoneFSK()
LMIC.rxtime = LMIC.txend + LMICcore_adjustForDrift(delay, hsym, LMICbandplan_MINRX_SYMS_LoRa_ClassA);

LMIC_X_DEBUG_PRINTF("%"LMIC_PRId_ostime_t": sched Rx12 %"LMIC_PRId_ostime_t"\n", os_getTime(), LMIC.rxtime - RX_RAMPUP);
os_setTimedCallback(&LMIC.osjob, LMIC.rxtime - RX_RAMPUP, func);
Expand Down Expand Up @@ -1509,8 +1529,7 @@ static void txDone (ostime_t delay, osjobcb_t func) {
LMICbandplan_setRx1Params();

// LMIC.dndr carries the TX datarate (can be != LMIC.datarate [confirm retries etc.])
// Setup receive - LMIC.rxtime is preloaded with 1.5 symbols offset to tune
// into the middle of the 8 symbols preamble.
// Setup receive -- either schedule FSK or schedule rx1 or rx2 window.
if( LMICbandplan_isFSK() ) {
LMICbandplan_txDoneFSK(delay, func);
}
Expand Down
15 changes: 15 additions & 0 deletions src/lmic/lmic.h
Original file line number Diff line number Diff line change
Expand Up @@ -320,9 +320,24 @@ static inline bit_t LMIC_BEACON_SUCCESSFUL(lmic_beacon_error_t e) {
return e < 0;
}

// LMIC_CFG_max_clock_error_ppm
#if !defined(LMIC_CFG_max_clock_error_ppm)
# define LMIC_CFG_max_clock_error_ppm 2000 /* max clock error: 0.2% (2000 ppm) */
#endif


enum {
// This value represents 100% error in LMIC.clockError
MAX_CLOCK_ERROR = 65536,
//! \brief maximum clock error that users can specify: 2000 ppm (0.2%).
//! \details This is the limit for clock error, unless LMIC_ENABLE_arbitrary_clock_error is set.
//! The default is 2,000 ppm, which is .002, or 0.2%. If your clock error is bigger,
//! usually you want to calibrate it so that millis() and micros() are reasonably
//! accurate. Important: do not use clock error to compensate for late serving
//! of the LMIC. If you see that LMIC.radio.rxlate_count is increasing, you need
//! to adjust your application logic so the LMIC gets serviced promptly when a
//! Class A downlink (or beacon) is pending.
LMIC_kMaxClockError_ppm = 2000,
};

// callbacks for client alerts.
Expand Down
3 changes: 1 addition & 2 deletions src/lmic/lmic_bandplan.h
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,6 @@
// internal APIs
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);
ostime_t LMICcore_RxWindowOffset(ostime_t hsym, rxsyms_t rxsyms_in);
ostime_t LMICcore_adjustForDrift(ostime_t delay, ostime_t hsym, rxsyms_t rxsyms_in);

#endif // _lmic_bandplan_h_
7 changes: 4 additions & 3 deletions src/lmic/lmic_eu_like.c
Original file line number Diff line number Diff line change
Expand Up @@ -255,13 +255,14 @@ void LMICeulike_setRx1Freq(void) {
// Class A txDone handling for FSK.
void
LMICeulike_txDoneFSK(ostime_t delay, osjobcb_t func) {
ostime_t const hsym = us2osticksRound(80);
// one symbol == one bit at 50kHz == 20us.
ostime_t const hsym = us2osticksRound(10);

// start a little earlier.
// start a little earlier. PRERX_FSK is in bytes; one byte at 50 kHz == 160us
delay -= LMICbandplan_PRERX_FSK * us2osticksRound(160);

// set LMIC.rxtime and LMIC.rxsyms:
LMIC.rxtime = LMIC.txend + LMICcore_adjustForDrift(delay + LMICcore_RxWindowOffset(hsym, LMICbandplan_RXLEN_FSK), hsym);
LMIC.rxtime = LMIC.txend + LMICcore_adjustForDrift(delay, hsym, 8 * LMICbandplan_RXLEN_FSK);
os_setTimedCallback(&LMIC.osjob, LMIC.rxtime - RX_RAMPUP, func);
}

Expand Down

0 comments on commit a4044c0

Please sign in to comment.