diff --git a/doc/CLASS-C.md b/doc/CLASS-C.md index 769a6f31..d38b7e8a 100644 --- a/doc/CLASS-C.md +++ b/doc/CLASS-C.md @@ -7,16 +7,16 @@ These are working notes for Class C implementation, not really permanent documen - [x] make this modular (so that we don't make tiny devices carry the extra code footprint) - [x] add code for enabling / disabling class C mode (I think maybe there's a required uplink to announce that you're in class C) - [x] define in header file - - [ ] write code - when class C is enabled, need to set a flag and re-evaluate the FSM; in other words, if someone turns on class C while the LMIC is busy, we need to synchronize things. Ditto, I think, for turning things off. -- [ ] modify the radio driver to look for the class C flag on entry and stop reception without crashing. -- [ ] add api for stopping an ongoing reception (for switching from RX2 to TX or RX1) -- [ ] add code for processing class C downlinks -- [ ] add api for starting a class C reception (rework the RX2 start-reception api from class A?) -- [ ] add the code to the various states to start class C reception. + - [x] write code - when class C is enabled, need to set a flag and re-evaluate the FSM; in other words, if someone turns on class C while the LMIC is busy, we need to synchronize things. Ditto, I think, for turning things off. +- [x] distinguish class C reception from Class A or Class B. +- [x] change os_radio() to facilitate stopping ongoing Class C reception for Class A. +- [x] add code for processing class C downlinks +- [x] add API for starting a class C reception +- [x] add the code to the various states to start class C reception. ## Decisions -- Define "RX2 channel" to mean the channel, bandwidth and spreading factor used for class A RX2 downlinks, and for Class C downlinks. +- Further parameterize radio driver so it gets most of its parameters via LMIC.radio. - We often will say "RX2 channel is open" to mean that the radio is (supposed to be) receiving on the RX2 channel. - Define "RX2 Window" to be the *time window* for a class A RX2 Window. The spec uses this also to mean "the RX2 channel is receiving" but we need to keep this straight. - `LMIC_ENABLE_class_c` if non-zero enables the class C code. diff --git a/doc/RadioDriver.md b/doc/RadioDriver.md index c5d610d4..e75951bb 100644 --- a/doc/RadioDriver.md +++ b/doc/RadioDriver.md @@ -18,25 +18,26 @@ - [`os_radio(RADIO_TX)`](#os_radioradio_tx) - [`os_radio(RADIO_RX)`](#os_radioradio_rx) - [`os_radio(RADIO_RXON)`](#os_radioradio_rxon) + - [`os_radio(RADIO_RXON_C)`](#os_radioradio_rxon_c) - [`os_radio(RADIO_TX_AT)`](#os_radioradio_tx_at) - [Common parameters](#common-parameters) - - [`LMIC.rps` (IN)](#lmicrps-in) - - [`LMIC.freq` (IN)](#lmicfreq-in) + - [`LMIC.radio.rps` (IN)](#lmicradiorps-in) + - [`LMIC.radio.freq` (IN)](#lmicradiofreq-in) - [`LMIC.saveIrqFlags` (OUT)](#lmicsaveirqflags-out) - - [`LMIC.osjob` (IN/OUT)](#lmicosjob-inout) + - [`LMIC.radio.pRadioDoneJob` (IN/OUT)](#lmicradiopradiodonejob-inout) - [Transmit parameters](#transmit-parameters) - - [`LMIC.radio_txpow` (IN)](#lmicradio_txpow-in) - - [`LMIC.frame[]` (IN)](#lmicframe-in) - - [`LMIC.datalen` (IN)](#lmicdatalen-in) + - [`LMIC.radio.txpow` (IN)](#lmicradiotxpow-in) + - [`LMIC.radio.pFrame[]` (IN)](#lmicradiopframe-in) + - [`LMIC.radio.datalen` (IN)](#lmicradiodatalen-in) - [`LMIC.txend` (IN, OUT)](#lmictxend-in-out) -- [Receive parameters](#receive-parameters) - - [`LMIC.frame[]` (OUT)](#lmicframe-out) - - [`LMIC.datalen` (OUT)](#lmicdatalen-out) - - [`LMIC.rxtime` (IN/OUT)](#lmicrxtime-inout) - [`LMIC.lbt_ticks` (IN)](#lmiclbt_ticks-in) - [`LMIC.lbt_dbmax` (IN)](#lmiclbt_dbmax-in) - - [`LMIC.rxsyms` (IN)](#lmicrxsyms-in) - - [`LMIC.noRXIQinversion` (IN)](#lmicnorxiqinversion-in) +- [Receive parameters](#receive-parameters) + - [`LMIC.radio.pFrame[]` (OUT)](#lmicradiopframe-out) + - [`LMIC.radio.dataLen` (OUT)](#lmicradiodatalen-out) + - [`LMIC.radio.rxtime` (IN/OUT)](#lmicradiorxtime-inout) + - [`LMIC.radio.rxsyms` (IN)](#lmicradiorxsyms-in) + - [`LMIC.radio.flags` (IN)](#lmicradioflags-in) - [`LMIC.snr` (OUT)](#lmicsnr-out) - [`LMIC.rssi` (OUT)](#lmicrssi-out) @@ -68,17 +69,21 @@ When the operation completes, `LMIC.osjob` is scheduled. ### `os_radio(RADIO_RXON)` -The radio is placed in continuous receive mode. If a frame is received, `LMIC.osjob` is scheduled. Continuous receive is canceled by calling [`os_radio(RADIO_RST)`](#os_radioradio_rst). +The radio is placed in continuous receive mode. If a frame is received, `LMIC.osjob` is scheduled. Continuous receive is explicitly canceled by calling [`os_radio(RADIO_RST)`](#os_radioradio_rst) or implicitly by calling any other radio function. This operation is not supported in FSK mode. +### `os_radio(RADIO_RXON_C)` + +This operation is identical to `os_radio(RADIO_RXON)`, but redundant calls are suppressed. + ### `os_radio(RADIO_TX_AT)` This is like `os_radio(RADIO_TX)`, but the transmission is scheduled at `LMIC.txend`. ## Common parameters -### `LMIC.rps` (IN) +### `LMIC.radio.rps` (IN) This is the "radio parameter setting", and it encodes several radio settings. @@ -88,7 +93,7 @@ This is the "radio parameter setting", and it encodes several radio settings. - CRC enabled/disabled - Implicit header mode on/off. (If on, receive length must be known in advance.) -### `LMIC.freq` (IN) +### `LMIC.radio.freq` (IN) This specifies the frequency, in Hertz. @@ -96,21 +101,21 @@ This specifies the frequency, in Hertz. Updated for LoRa operations only; the IRQ flags at the time of interrupt. -### `LMIC.osjob` (IN/OUT) +### `LMIC.radio.pRadioDoneJob` (IN/OUT) -When asynchronous operations complete, `LMIC.osjob.func` is used as the callback function, and `LMIC.osjob` is used to schedule the work. +When asynchronous operations complete, `LMIC.radio.pRadioDoneJob->func` is used as the callback function, and `LMIC.radio.pRadioDoneJob` is used to schedule the work. ## Transmit parameters -### `LMIC.radio_txpow` (IN) +### `LMIC.radio.txpow` (IN) This specifies the transmit power in dBm. -### `LMIC.frame[]` (IN) +### `LMIC.radio.pFrame[]` (IN) The array of data to be sent. -### `LMIC.datalen` (IN) +### `LMIC.radio.datalen` (IN) The length of the array to be sent. @@ -120,37 +125,37 @@ For `RADIO_TX_AT`, an input parameter, for the scheduled TX time. For all transmissions, updated to the OS time at which the TX end interrupt was recognized. -## Receive parameters +### `LMIC.lbt_ticks` (IN) -### `LMIC.frame[]` (OUT) +How long to monitor for LBT, in OS ticks. -Filled with data received. +### `LMIC.lbt_dbmax` (IN) -### `LMIC.datalen` (OUT) +Maximum RSSI on channel before transmit. -Set to number of bytes received in total. +## Receive parameters -### `LMIC.rxtime` (IN/OUT) +### `LMIC.radio.pFrame[]` (OUT) -Input: When to start receiving, in OS tick time. +Filled with data received. -Output: time of RXDONE interrupt. (Note: FSK timeout doesn't currently set this cell on RX timeout.) +### `LMIC.radio.dataLen` (OUT) -### `LMIC.lbt_ticks` (IN) +Set to number of bytes received in total. -How long to monitor for LBT, in OS ticks. +### `LMIC.radio.rxtime` (IN/OUT) -### `LMIC.lbt_dbmax` (IN) +Input: When to start receiving, in OS tick time. Only used for `os_radio(RADIO_RX)`; not used for continuous receive. -Maximum RSSI on channel before transmit. +Output: time of RXDONE interrupt. (Note: FSK timeout doesn't currently set this cell on RX timeout.) -### `LMIC.rxsyms` (IN) +### `LMIC.radio.rxsyms` (IN) The timeout in symbols. Only used for `os_radio(RADIO_RX)`; not used for continuous receive. -### `LMIC.noRXIQinversion` (IN) +### `LMIC.radio.flags` (IN) -If true, disable IQ inversion during receive. +If the bit `LMIC_RADIO_FLAGS_NO_RX_IQ_INVERSION` is set, disable IQ inversion during receive. ### `LMIC.snr` (OUT) diff --git a/examples/raw-feather/raw-feather.ino b/examples/raw-feather/raw-feather.ino index e5064742..be06fc76 100644 --- a/examples/raw-feather/raw-feather.ino +++ b/examples/raw-feather/raw-feather.ino @@ -179,7 +179,7 @@ static void tx_func (osjob_t* job); // Transmit the given string and call the given function afterwards void tx(const char *str, osjobcb_t func) { // the radio is probably in RX mode; stop it. - os_radio(RADIO_RST); + os_radio(RADIO_RST, NULL); // wait a bit so the radio can come out of RX mode delay(1); @@ -199,7 +199,7 @@ void tx(const char *str, osjobcb_t func) { // Enable rx mode and call func when a packet is received void rx(osjobcb_t func) { LMIC.osjob.func = func; - LMIC.rxtime = os_getTime(); // RX _now_ + LMIC.nextRxTime = os_getTime(); // RX _now_ // Enable "continuous" RX (e.g. without a timeout, still stops after // receiving a packet) os_radio(RADIO_RXON); diff --git a/examples/raw-halconfig/raw-halconfig.ino b/examples/raw-halconfig/raw-halconfig.ino index 09db08c2..aedb3ae7 100644 --- a/examples/raw-halconfig/raw-halconfig.ino +++ b/examples/raw-halconfig/raw-halconfig.ino @@ -153,7 +153,7 @@ void tx(const char *str, osjobcb_t func) { // Enable rx mode and call func when a packet is received void rx(osjobcb_t func) { LMIC.osjob.func = func; - LMIC.rxtime = os_getTime(); // RX _now_ + LMIC.nextRxTime = os_getTime(); // RX _now_ // Enable "continuous" RX (e.g. without a timeout, still stops after // receiving a packet) os_radio(RADIO_RXON); diff --git a/examples/raw/raw.ino b/examples/raw/raw.ino index 6fe81890..ec65593f 100644 --- a/examples/raw/raw.ino +++ b/examples/raw/raw.ino @@ -75,7 +75,7 @@ void tx(const char *str, osjobcb_t func) { // Enable rx mode and call func when a packet is received void rx(osjobcb_t func) { LMIC.osjob.func = func; - LMIC.rxtime = os_getTime(); // RX _now_ + LMIC.nextRxTime = os_getTime(); // RX _now_ // Enable "continuous" RX (e.g. without a timeout, still stops after // receiving a packet) os_radio(RADIO_RXON); diff --git a/src/lmic/lmic.c b/src/lmic/lmic.c index 67856824..977305e6 100644 --- a/src/lmic/lmic.c +++ b/src/lmic/lmic.c @@ -32,6 +32,7 @@ #define LMIC_DR_LEGACY 0 #include "lmic_bandplan.h" +#include "lmic_implementation.h" #include "../se/i/lmic_secure_element_api.h" #if defined(DISABLE_BEACONS) && !defined(DISABLE_PING) @@ -47,6 +48,7 @@ static void engineUpdate(void); static bit_t processJoinAccept_badframe(void); static bit_t processJoinAccept_nojoinframe(void); +static osjobcbfn_t processRxCDnData; #if !defined(DISABLE_BEACONS) static void startScan (void); @@ -365,7 +367,6 @@ static bit_t setDrTxpow (u1_t reason, u1_t dr, s1_t pow) { return result; } - #if !defined(DISABLE_PING) void LMIC_stopPingable (void) { LMIC.opmode &= ~(OP_PINGABLE|OP_PINGINI); @@ -425,13 +426,13 @@ static void reportEventNoUpdate (ev_t ev) { // correct assumption if a port was provided. if (LMIC.txrxFlags & TXRX_PORT) - port = LMIC.frame[LMIC.dataBeg - 1]; + port = LMIC.radio.pFrame[LMIC.dataBeg - 1]; // notify the user. LMIC.client.rxMessageCb( LMIC.client.rxMessageUserData, port, - LMIC.frame + LMIC.dataBeg, + LMIC.radio.pFrame + LMIC.dataBeg, LMIC.dataLen ); } @@ -557,7 +558,7 @@ static lmic_beacon_error_t decodeBeacon (void) { if (LMIC.dataLen != LEN_BCN) { // implicit header RX guarantees this return LMIC_BEACON_ERROR_INVALID; } - xref2u1_t d = LMIC.frame; + xref2u1_t d = LMIC.radio.pFrame; if(! LMICbandplan_isValidBeacon1(d)) return LMIC_BEACON_ERROR_INVALID; // first (common) part fails CRC check // First set of fields is ok @@ -1056,8 +1057,10 @@ scan_mac_cmds( #if LMIC_ENABLE_DeviceTimeReq case MCMD_DeviceTimeAns: { - // don't process a spurious downlink. - if ( LMIC.txDeviceTimeReqState == lmic_RequestTimeState_rx ) { + // don't process a spurious downlink. And the spec says that + // the DeviceTimeAns must only be sent during a Class A downlink. + if ( LMIC_txrxFlags_isClassA(LMIC.txrxFlags) && + LMIC.txDeviceTimeReqState == lmic_RequestTimeState_rx ) { // remember that it's time to notify the client. LMIC.txDeviceTimeReqState = lmic_RequestTimeState_success; @@ -1112,7 +1115,7 @@ static void setAdrAckCount (s2_t count) { } static bit_t decodeFrame (void) { - xref2u1_t d = LMIC.frame; + xref2u1_t d = LMIC.radio.pFrame; u1_t hdr = d[0]; u1_t ftype = hdr & HDR_FTYPE; int dlen = LMIC.dataLen; @@ -1255,17 +1258,18 @@ static bit_t decodeFrame (void) { // We heard from network LMIC.adrChanged = LMIC.rejoinCnt = 0; setAdrAckCount(LINK_CHECK_INIT); -#if !defined(DISABLE_MCMD_RXParamSetupReq) // We heard from network "on a Class A downlink" - LMIC.dn2Ans = 0; + if (LMIC_txrxFlags_isClassA(LMIC.txrxFlags)) { +#if !defined(DISABLE_MCMD_RXParamSetupReq) + LMIC.dn2Ans = 0; #endif // !defined(DISABLE_MCMD_RXParamSetupReq) #if !defined(DISABLE_MCMD_RXTimingSetupReq) - // We heard from network "on a Class A downlink" - LMIC.macRxTimingSetupAns = 0; + LMIC.macRxTimingSetupAns = 0; #endif // !defined(DISABLE_MCMD_RXParamSetupReq) #if !defined(DISABLE_MCMD_DlChannelReq) && CFG_LMIC_EU_like - LMIC.macDlChannelAns = 0; + LMIC.macDlChannelAns = 0; #endif + } int m = LMIC.rssi - RSSI_OFF - getSensitivity(LMIC.rps); // for legacy reasons, LMIC.margin is set to the unsigned sensitivity. It can never be negative. @@ -1383,6 +1387,27 @@ static void setupRx2 (void) { radioRx(); } +// start Class C window +static void setupRxClassC (void) { +#if LMIC_ENABLE_class_c + if (LMIC.classC.flags.f.fEnabled) { + initTxrxFlags(__func__, LMIC_txrxFlags_setClassC(0)); + LMIC.radio.freq = LMIC.dn2Freq; + LMIC.radio.pFrame = LMIC.classC.frame; + LMIC.radio.rxtime = os_getTime(); + LMIC.radio.rps = dndr2rps(LMIC.dn2Dr); + LMIC.radio.rxsyms = 0; + LMIC.radio.dataLen = 0; + LMIC.radio.flags = 0; + if (LMIC.noRXIQinversion) { + LMIC.radio.flags |= LMIC_RADIO_FLAGS_NO_RX_IQ_INVERSION; + } + LMIC.classC.job.func = processRxCDnData; + os_radio_v2(RADIO_RXON_C, &LMIC.classC.job); + } +#endif // LMIC_ENABLE_class_c +} + //! \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. @@ -1462,10 +1487,10 @@ static void schedRx12 (ostime_t delay, osjobcb_t func, u1_t dr) { // time things accurately. // // 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.nextRxTime = 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 - os_getRadioRxRampup()); - os_setTimedCallback(&LMIC.osjob, LMIC.rxtime - os_getRadioRxRampup(), func); + LMIC_X_DEBUG_PRINTF("%"LMIC_PRId_ostime_t": sched Rx12 %"LMIC_PRId_ostime_t"\n", os_getTime(), LMIC.nextRxTime - os_getRadioRxRampup()); + os_setTimedCallback(&LMIC.osjob, LMIC.nextRxTime - os_getRadioRxRampup(), func); } static void setupRx1 (osjobcb_t func) { @@ -1478,7 +1503,7 @@ static void setupRx1 (osjobcb_t func) { } -// Called by HAL once TX complete and delivers exact end of TX time stamp in LMIC.rxtime +// Called by HAL once TX complete and delivers exact end of TX time stamp in LMIC.txend static void txDone (ostime_t delay, osjobcb_t func) { #if !defined(DISABLE_PING) if( (LMIC.opmode & (OP_TRACK|OP_PINGABLE|OP_PINGINI)) == (OP_TRACK|OP_PINGABLE) ) { @@ -1550,7 +1575,7 @@ static bit_t processJoinAccept (void) { } LMIC_SecureElement_Error_t seErr; - + seErr = LMIC_SecureElement_Default_decodeJoinAccept( LMIC.frame, dlen, LMIC.frame, @@ -1663,9 +1688,17 @@ static bit_t processJoinAccept_nojoinframe(void) { return 1; } +static void radioGetRxResults(void) { + if (LMIC.radio.state & LMIC_RADIO_EV_RXDONE) { + LMIC.dataLen = LMIC.radio.dataLen; + LMIC.rxtime = LMIC.radio.rxtime; + } +} + static void processRx2Jacc (xref2osjob_t osjob) { LMIC_API_PARAMETER(osjob); + radioGetRxResults(); if( LMIC.dataLen == 0 ) { initTxrxFlags(__func__, 0); // nothing in 1st/2nd DN slot } @@ -1682,10 +1715,11 @@ static void setupRx2Jacc (xref2osjob_t osjob) { setupRx2(); } - static void processRx1Jacc (xref2osjob_t osjob) { LMIC_API_PARAMETER(osjob); + radioGetRxResults(); + if( LMIC.dataLen == 0 || !processJoinAccept() ) schedRx12(DELAY_JACC2_osticks, FUNC_ADDR(setupRx2Jacc), LMIC.dn2Dr); } @@ -1714,11 +1748,13 @@ static bit_t processDnData(void); static void processRx2DnData (xref2osjob_t osjob) { LMIC_API_PARAMETER(osjob); + radioGetRxResults(); + if( LMIC.dataLen == 0 ) { initTxrxFlags(__func__, 0); // nothing in 1st/2nd DN slot // It could be that the gateway *is* sending a reply, but we // just didn't pick it up. To avoid TX'ing again while the - // gateay is not listening anyway, delay the next transmission + // gateway is not listening anyway, delay the next transmission // until DNW2_SAFETY_ZONE from now, and add up to 2 seconds of // extra randomization. // BUG(tmm@mcci.com) this delay is not needed for some @@ -1741,10 +1777,19 @@ static void setupRx2DnData (xref2osjob_t osjob) { static void processRx1DnData (xref2osjob_t osjob) { LMIC_API_PARAMETER(osjob); - if( LMIC.dataLen == 0 || !processDnData() ) + radioGetRxResults(); + if( LMIC.dataLen == 0 || !processDnData() ) { schedRx12(sec2osticks(LMIC.rxDelay +(int)DELAY_EXTDNW2), FUNC_ADDR(setupRx2DnData), LMIC.dn2Dr); + } + setupRxClassC(); } +static void processRxCDnData (xref2osjob_t osjob) { + LMIC_API_PARAMETER(osjob); + + radioGetRxResults(); + processDnData(); +} static void setupRx1DnData (xref2osjob_t osjob) { LMIC_API_PARAMETER(osjob); @@ -1757,6 +1802,7 @@ static void updataDone (xref2osjob_t osjob) { LMIC_API_PARAMETER(osjob); txDone(sec2osticks(LMIC.rxDelay), FUNC_ADDR(setupRx1DnData)); + setupRxClassC(); } // ======================================== @@ -1873,6 +1919,7 @@ static bit_t buildDataFrame (void) { LMIC.frame[OFF_DAT_HDR] = HDR_FTYPE_DAUP | HDR_MAJOR_V1; LMIC.frame[OFF_DAT_FCT] = (LMIC.dnConf | LMIC.adrEnabled | (sendAdrAckReq() ? FCT_ADRACKReq : 0) + | (LMICJ_isActiveClassB() ? FCT_CLASSB : 0) | (end-OFF_DAT_OPTS)); os_wlsbf4(LMIC.frame+OFF_DAT_ADDR, LMIC.devaddr); @@ -1938,6 +1985,8 @@ static void onBcnRx (xref2osjob_t osjob) { LMIC_API_PARAMETER(osjob); // If we arrive via job timer make sure to put radio to rest. + radioGetRxResults(); + os_radio(RADIO_RST); os_clearCallback(&LMIC.osjob); if( LMIC.dataLen == 0 ) { @@ -1978,8 +2027,8 @@ static void startScan (void) { LMIC.txCnt = LMIC.dnConf = LMIC.bcninfo.flags = 0; LMIC.opmode = (LMIC.opmode | OP_SCAN) & ~(OP_TXRXPEND); LMICbandplan_setBcnRxParams(); - LMIC.rxtime = LMIC.bcninfo.txtime = os_getTime() + sec2osticks(BCN_INTV_sec+1); - os_setTimedCallback(&LMIC.osjob, LMIC.rxtime, FUNC_ADDR(onBcnRx)); + LMIC.nextRxTime = LMIC.bcninfo.txtime = os_getTime() + sec2osticks(BCN_INTV_sec+1); + os_setTimedCallback(&LMIC.osjob, LMIC.nextRxTime, FUNC_ADDR(onBcnRx)); os_radio(RADIO_RXON); } @@ -1996,6 +2045,8 @@ bit_t LMIC_enableTracking (u1_t tryBcnInfo) { void LMIC_disableTracking (void) { + if (LMIC.opmode & OP_SCAN) + os_clearCallback(&LMIC.osjob); LMIC.opmode &= ~(OP_SCAN|OP_TRACK); LMIC.bcninfoTries = 0; engineUpdate(); @@ -2117,6 +2168,7 @@ void LMIC_unjoinAndRejoin(void) { static void processPingRx (xref2osjob_t osjob) { LMIC_API_PARAMETER(osjob); + radioGetRxResults(); if( LMIC.dataLen != 0 ) { initTxrxFlags(__func__, TXRX_PING); if( decodeFrame() ) { @@ -2128,27 +2180,31 @@ static void processPingRx (xref2osjob_t osjob) { } #endif // !DISABLE_PING -// process downlink data at close of RX window. Return zero if another RX window -// should be scheduled, non-zero to prevent scheduling of RX2 (if relevant). -// Confusingly, the caller actualyl does some of the calculation, so the answer from -// us is not always totaly right; the rx1 window check ignores our result unless -// LMIC.datalen was non zero before calling. -// -// Inputs: -// LMIC.dataLen number of bytes receieved; 0 --> no message at all received. -// LMIC.txCnt currnt confirmed uplink count, or 0 for unconfirmed. -// LMIC.txrxflags state of play for the Class A engine and message receipt. -// -// and many other flags in txcomplete(). +/// +/// \brief process downlinks. +/// +/// Process downlink data at close of RX window. Return zero if another RX window +/// should be scheduled, non-zero to prevent scheduling of RX2 (if relevant). +/// Confusingly, the caller actually does some of the calculation, so the answer from +/// us is not always totaly right; the rx1 window check ignores our result unless +/// LMIC.datalen was non zero before calling. +/// +/// Inputs: +/// LMIC.dataLen number of bytes receieved; 0 --> no message at all received. +/// LMIC.txCnt currnt confirmed uplink count, or 0 for unconfirmed. +/// LMIC.txrxflags state of play for the Class A engine and message receipt. +/// +/// and many other flags in txcomplete(). +/// // forward references. static bit_t processDnData_norx(void); static bit_t processDnData_txcomplete(void); static bit_t processDnData (void) { - // if no TXRXPEND, we shouldn't be here and can do nothign. + // if no TXRXPEND, we shouldn't be here and can do nothing. // formerly we asserted. - if ((LMIC.opmode & OP_TXRXPEND) == 0) + if ((LMIC.opmode & OP_TXRXPEND) == 0 && ! LMIC_txrxFlags_isClassC(LMIC.txrxFlags)) return 1; if( LMIC.dataLen == 0 ) { @@ -2159,7 +2215,7 @@ static bit_t processDnData (void) { } // if we get here, LMIC.dataLen != 0, so there is some // traffic. - else if( !decodeFrame() ) { + if( !decodeFrame() ) { // if we are in downlink window 1, we need to schedule // downlink window 2. if( (LMIC.txrxFlags & TXRX_DNW1) != 0 ) @@ -2170,7 +2226,9 @@ static bit_t processDnData (void) { // to close the books on this uplink attempt return processDnData_norx(); } - // downlink frame was accepted. This means that we're done. Except + + // + // Downlink frame was accepted. This means that we're done. Except // there's one bizarre corner case. If we sent a confirmed message // and got a downlink that didn't have an ACK, we have to retry. // It is not clear why the network is permitted to do this; the @@ -2178,7 +2236,11 @@ static bit_t processDnData (void) { // windows is clear confirmation that the uplink made it to the // network and was valid. However, compliance checks this, so // we have to handle it and retransmit. - else if (LMIC.txCnt != 0 && (LMIC.txrxFlags & TXRX_NACK) != 0) + // + // With Class C, it's very possible that they didn't hear our + // uplink, so it's reasonable that we have to check this. + // + if (LMIC.txCnt != 0 && (LMIC.txrxFlags & TXRX_NACK) != 0) { // grr. we're confirmed but the network downlink did not // set the ACK bit. We know txCnt is non-zero, so this @@ -2186,6 +2248,7 @@ static bit_t processDnData (void) { // want to do this unless it's a confirmed uplink. return processDnData_norx(); } + // the transmit of the uplink is really complete. else { return processDnData_txcomplete(); @@ -2247,7 +2310,7 @@ static bit_t processDnData_txcomplete(void) { // turn off all the repeat stuff. LMIC.txCnt = LMIC.upRepeatCount = 0; - // if there's pending mac data that's not piggyback, launch it now. + // if there's pending mac data, launch it now. if (LMIC.pendMacLen != 0) { if (LMIC.pendMacPiggyback) { LMICOS_logEvent("piggyback mac message"); @@ -2363,6 +2426,8 @@ static bit_t processDnData_txcomplete(void) { static void processBeacon (xref2osjob_t osjob) { LMIC_API_PARAMETER(osjob); + radioGetRxResults(); + ostime_t lasttx = LMIC.bcninfo.txtime; // save here - decodeBeacon might overwrite u1_t flags = LMIC.bcninfo.flags; ev_t ev; @@ -2466,6 +2531,15 @@ static void engineUpdate_inner (void) { } #endif // !DISABLE_JOIN +#if LMIC_ENABLE_class_c + if ( LMIC.classC.flags.f.fEnabled && ! LMICJ_isEnabledClassB()) { + // if no tx/rx scheduled, and we're joined, start a class C rx. + if (! LMICJ_isTxRequested() && LMIC.devaddr != 0) { + setupRxClassC(); + } + } +#endif + ostime_t now = os_getTime(); ostime_t txbeg = 0; @@ -2569,7 +2643,7 @@ static void engineUpdate_inner (void) { LMIC.opmode = (LMIC.opmode & ~(OP_POLL|OP_RNDTX)) | OP_TXRXPEND | OP_NEXTCHNL; LMICbandplan_updateTx(txbeg); // limit power to value asked in adr - LMIC.radio_txpow = LMIC.txpow > LMIC.adrTxPow ? LMIC.adrTxPow : LMIC.txpow; + LMIC.radio.txpow = LMIC.txpow > LMIC.adrTxPow ? LMIC.adrTxPow : LMIC.txpow; reportEventNoUpdate(EV_TXSTART); os_radio(RADIO_TX); return; @@ -2596,11 +2670,11 @@ static void engineUpdate_inner (void) { if( txbeg != 0 && (txbeg - LMIC.ping.rxtime) < 0 ) goto txdelay; LMIC.rxsyms = LMIC.ping.rxsyms; - LMIC.rxtime = LMIC.ping.rxtime; + LMIC.nextRxTime = LMIC.ping.rxtime; LMIC.freq = LMIC.ping.freq; LMIC.rps = dndr2rps(LMIC.ping.dr); LMIC.dataLen = 0; - ostime_t rxtime_ping = LMIC.rxtime - os_getRadioRxRampup(); + ostime_t rxtime_ping = LMIC.nextRxTime - os_getRadioRxRampup(); // did we miss the time? if (now - rxtime_ping > 0) { LMIC.opmode &= ~(OP_TRACK|OP_PINGABLE|OP_PINGINI|OP_REJOIN); @@ -2619,7 +2693,7 @@ static void engineUpdate_inner (void) { LMICbandplan_setBcnRxParams(); LMIC.rxsyms = LMIC.bcnRxsyms; - LMIC.rxtime = LMIC.bcnRxtime; + LMIC.nextRxTime = LMIC.bcnRxtime; if( now - rxtime >= 0 ) { LMIC.osjob.func = FUNC_ADDR(processBeacon); @@ -2671,7 +2745,7 @@ void LMIC_setDrTxpow (dr_t dr, s1_t txpow) { void LMIC_shutdown (void) { os_clearCallback(&LMIC.osjob); - os_radio(RADIO_RST); + os_radio_v2(RADIO_RST, NULL); LMIC.opmode |= OP_SHUTDOWN; } @@ -2809,16 +2883,12 @@ dr_t LMIC_feasibleDataRateForFrame(dr_t dr, u1_t payloadSize) { return dr; } -static bit_t isTxPathBusy(void) { - return (LMIC.opmode & (OP_POLL | OP_TXDATA | OP_JOINING | OP_TXRXPEND)) != 0; -} - bit_t LMIC_queryTxReady (void) { - return ! isTxPathBusy(); + return ! LMICJ_isTxPathBusy(); } static bit_t adjustDrForFrameIfNotBusy(u1_t len) { - if (isTxPathBusy()) { + if (LMICJ_isTxPathBusy()) { return 0; } dr_t newDr = LMIC_feasibleDataRateForFrame(LMIC.datarate, len); @@ -2834,7 +2904,7 @@ void LMIC_setTxData (void) { } void LMIC_setTxData_strict (void) { - if (isTxPathBusy()) { + if (LMICJ_isTxPathBusy()) { return; } @@ -2856,7 +2926,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 (isTxPathBusy()) { + if (LMICJ_isTxPathBusy()) { // already have a message queued return LMIC_ERROR_TX_BUSY; } @@ -3084,10 +3154,112 @@ u1_t LMIC_setBatteryLevel(u1_t uBattLevel) { /// \brief get battery level that is to be returned by `DevStatusAns`. /// /// \returns -/// This function returns the saved value of the battery level. +/// This function returns the saved value of the battery level (as used for +/// the DevStatusAns message). /// /// \see LMIC_setBatteryLevel() /// u1_t LMIC_getBatteryLevel(void) { return LMIC.client.devStatusAns_battery; } + +#if LMIC_ENABLE_class_c + +static osjobcbfn_t externalRequestCb; + +/// +/// \brief Turn class C operation off or on. +/// +/// \param fOnIfTrue [in] non-zero to enable Class C operation, +/// zero to disable Class C operation. +/// +/// \details +/// Because this function changes the state of the LMIC, and +/// might be called from a callback, it synchronizes to the +/// LMIC using an `osjob_t`, meaning that this function doesn't +/// take effect immediately. It only fails if trying to turn on +/// Class C but the LMIC is not +/// configured for class C operation, or if the LMIC is currently +/// running in Class B mode. +/// +/// \returns +/// This function non-zero for success, zero for failure. +/// +bit_t LMIC_enableClassC(bit_t fOnIfTrue) { + // if LMIC is stopped, we must rail. + if (LMICJ_isShutdown()) + return 0; + + // if LMIC is already requesting this state, succeed. + if (LMIC.classC.requests.f.fStateChangeRq && + LMIC.classC.requests.f.fTargetState == fOnIfTrue) { + LMICOS_logEventUint32("duplicate class C request", fOnIfTrue); + return 1; + } + + // otherwise, queue an update. + LMIC.classC.requests.f.fStateChangeRq = 1; + LMIC.classC.requests.f.fTargetState = fOnIfTrue; + + // if we've not already queued the callback, queue + // it now. + if (! LMIC.classC.requests.f.fPending) { + LMIC.classC.requests.f.fPending = 1; + os_setCallback(&LMIC.classC.job, externalRequestCb); + } + + /// indicate success. + return 1; +} + +/// +/// \brief process external requests +/// +/// \param [in] pJob points to the job structure that got us here. +/// +/// \details +/// \c externalRequestCb() is responsible for synchronously changing +/// the state of the LMIC in response to an external request. +/// +/// \returns +/// This function has no explicit result. +/// +static void externalRequestCb(osjob_t *pJob) { + bit_t fUpdateNeeded = 0; + + // reset the job pending flag. + LMIC.classC.requests.f.fPending = 0; + + // changing the class-C state? + if (LMIC.classC.requests.f.fStateChangeRq) { + const bit_t targetState = LMIC.classC.requests.f.fTargetState; + LMIC.classC.requests.f.fStateChangeRq = 0; + + // we can always disable + if (targetState == LMIC.classC.flags.f.fEnabled) { + LMIC.classC.flags.f.fEnabled = 0; + LMICOS_logEventUint32("disable class C", LMIC.opmode); + } + + // but if class B is enabled, or if we're shut down, we can't enable class C + else if (LMICJ_isEnabledClassB() || LMICJ_isShutdown()) { + // do nothing + LMICOS_logEventUint32("class B active, don't start class C", LMIC.opmode); + } + + // otherwise, enable class C. + else { + LMIC.classC.flags.f.fEnabled = 1; + LMICOS_logEventUint32("enable class C", LMIC.opmode); + // if nothing is happening, we need to make it happen. + if (LMICJ_isFsmIdle()) { + fUpdateNeeded = 1; + } + } + } + + // if an engine update is needed... + if (fUpdateNeeded) + engineUpdate(); +} +#endif // LMIC_ENABLE_class_c \ No newline at end of file diff --git a/src/lmic/lmic.h b/src/lmic/lmic.h index 2be9a90d..f3879b00 100644 --- a/src/lmic/lmic.h +++ b/src/lmic/lmic.h @@ -106,7 +106,7 @@ extern "C"{ ((((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(4, 2, 0, 1) /* 4.2.0-1 */ + ARDUINO_LMIC_VERSION_CALC(5, 0, 0, 1) /* 5.0.0-pre1 */ #define ARDUINO_LMIC_VERSION_GET_MAJOR(v) \ ((((v)*UINT32_C(1)) >> 24u) & 0xFFu) @@ -249,8 +249,15 @@ struct bcninfo_t { }; #endif // !DISABLE_BEACONS -// purpose of receive window - lmic_t.rxState -enum { RADIO_RST=0, RADIO_TX=1, RADIO_RX=2, RADIO_RXON=3, RADIO_TX_AT=4, }; +/// \brief radio driver request codes +enum { RADIO_RST=0, ///< reset, canceling any pending operations. + RADIO_TX=1, ///< transmit. + RADIO_RX=2, ///< receive single with time window + RADIO_RXON=3, ///< receive without time window + RADIO_TX_AT=4, ///< transmit at a specific time + RADIO_RXON_C=5, ///< open the class C window if possible. + }; + // Netid values / lmic_t.netid enum { NETID_NONE=(int)~0U, NETID_MASK=(int)0xFFFFFF }; // MAC operation modes (lmic_t.opmode). @@ -278,11 +285,67 @@ enum { TXRX_ACK = 0x80, //!< confirmed UP frame was acked TXRX_NOPORT = 0x20, //!< set if a frame with a port was RXed, clr if no frame/no port TXRX_PORT = 0x10, //!< set if a frame with a port was RXed, LMIC.frame[LMIC.dataBeg-1] => port TXRX_LENERR = 0x08, //!< set if frame was discarded due to length error. - TXRX_PING = 0x04, //!< received in a scheduled RX slot - TXRX_DNW2 = 0x02, //!< received in 2dn DN slot + TXRX_PING = 0x04, //!< received in a scheduled RX slot or class C + TXRX_DNW2 = 0x02, //!< received in 2dn DN slot or class C TXRX_DNW1 = 0x01, //!< received in 1st DN slot }; +/// +/// \brief check whether flags say that message was a Class C downlink +/// +static inline bit_t LMIC_txrxFlags_isClassC(u1_t flags) { + return (flags & (TXRX_PING | TXRX_DNW2 | TXRX_DNW1)) == (TXRX_PING | TXRX_DNW2); +} + +/// +/// \brief check whether flags say that message was RX1 downlink +/// +static inline bit_t LMIC_txrxFlags_isRx1(u1_t flags) { + return (flags & (TXRX_PING | TXRX_DNW2 | TXRX_DNW1)) == (TXRX_DNW1); +} + +/// +/// \brief check whether flags say that message was RX2 downlink +/// +static inline bit_t LMIC_txrxFlags_isRx2(u1_t flags) { + return (flags & (TXRX_PING | TXRX_DNW2 | TXRX_DNW1)) == (TXRX_DNW2); +} + +/// +/// \brief check whether flags say that message was a Class A downlink +/// +static inline bit_t LMIC_txrxFlags_isClassA(u1_t flags) { + return LMIC_txrxFlags_isRx1(flags) || LMIC_txrxFlags_isRx2(flags); +} + +/// +/// \brief change txrx flag image to indicate Class C downlink +/// +static inline u1_t LMIC_txrxFlags_setClassC(u1_t flags) { + return (flags & ~TXRX_DNW1) | (TXRX_PING | TXRX_DNW2); +} + +/// +/// \brief change txrx flag image to indicate Class A RX1 downlink +/// +static inline u1_t LMIC_txrxFlags_setRx1(u1_t flags) { + return (flags & ~(TXRX_PING | TXRX_DNW2)) | (TXRX_DNW1); +} + +/// +/// \brief change txrx flag image to indicate Class A RX2 downlink +/// +static inline u1_t LMIC_txrxFlags_setRx2(u1_t flags) { + return (flags & ~(TXRX_PING | TXRX_DNW1)) | (TXRX_DNW2); +} + +/// +/// \brief change txrx flag image to indicate Class B Ping-slot downlink +/// +static inline u1_t LMIC_txrxFlags_setRxPing(u1_t flags) { + return (flags & ~(TXRX_DNW2 | TXRX_DNW1)) | (TXRX_PING); +} + /// \brief Event codes for event callback enum _ev_t { EV_SCAN_TIMEOUT=1, EV_BEACON_FOUND, EV_BEACON_MISSED, EV_BEACON_TRACKED, EV_JOINING, @@ -364,20 +427,15 @@ enum lmic_beacon_error_e { /// \sa lmic_beacon_error_e typedef s1_t lmic_beacon_error_t; +/// \brief return \c true if beacon error code indicates a sucessful beacon. 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 + //! \brief This value represents 100% error in LMIC.clockError MAX_CLOCK_ERROR = 65536, - //! \brief maximum clock error that users can specify: 2000 ppm (0.2%). + //! \brief maximum clock error that users can specify: 4000 ppm (0.4%). //! \details This is the limit for clock error, unless LMIC_ENABLE_arbitrary_clock_error is set. //! The default is 4,000 ppm, which is .004, or 0.4%; this is what you get on an //! STM32L0 running with the HSI oscillator after cal. If your clock error is bigger, @@ -395,13 +453,21 @@ typedef void LMIC_ABI_STD lmic_rxmessage_cb_t(void *pUserData, uint8_t port, con typedef void LMIC_ABI_STD lmic_txmessage_cb_t(void *pUserData, int fSuccess); typedef void LMIC_ABI_STD lmic_event_cb_t(void *pUserData, ev_t e); -// network time request callback function -// defined unconditionally, because APIs and types can't change based on config. -// This is called when a time-request succeeds or when we get a downlink -// without time request, "completing" the pending time request. +/// +/// \brief network time request callback function type +/// +/// \param pUserData [in] value passed to corresponding network time request. +/// \param flagSuccess [in] \c true if a network time was received, otherwise \c false. +/// +/// A function of this type is called when a time-request succeeds or when we get a +/// downlink without time request, "completing" the pending time request. +/// +/// \note This function is defined unconditionally (even if network time support is +/// not enabled), because we don't want APIs and types to change based on config. +/// typedef void LMIC_ABI_STD lmic_request_network_time_cb_t(void *pUserData, int flagSuccess); -// how the network represents time. +/// \brief how the network represents time. typedef u4_t lmic_gpstime_t; // rather than deal with 1/256 second tick, we adjust ostime back @@ -409,45 +475,40 @@ typedef u4_t lmic_gpstime_t; typedef struct lmic_time_reference_s lmic_time_reference_t; struct lmic_time_reference_s { - // our best idea of when we sent the uplink (end of packet). + /// our best idea of when we sent the uplink (end of packet). ostime_t tLocal; - // the network's best idea of when we sent the uplink. + /// the network's best idea of when we sent the uplink. lmic_gpstime_t tNetwork; }; enum lmic_request_time_state_e { - lmic_RequestTimeState_idle = 0, // we're not doing anything - lmic_RequestTimeState_tx, // we want to tx a time request on next uplink - lmic_RequestTimeState_rx, // we have tx'ed, next downlink completes. - lmic_RequestTimeState_success // we sucessfully got time. + lmic_RequestTimeState_idle = 0, ///< we're not doing anything + lmic_RequestTimeState_tx, ///< we want to tx a time request on next uplink + lmic_RequestTimeState_rx, ///< we have tx'ed, next downlink completes. + lmic_RequestTimeState_success ///< we sucessfully got time. }; typedef u1_t lmic_request_time_state_t; enum lmic_engine_update_state_e { - lmic_EngineUpdateState_idle = 0, // engineUpdate is idle. - lmic_EngineUpdateState_busy = 1, // engineUpdate is busy, but has not been reentered. - lmic_EngineUpdateState_again = 2, // engineUpdate is busy, and has to be evaluated again. + lmic_EngineUpdateState_idle = 0, ///< engineUpdate is idle. + lmic_EngineUpdateState_busy = 1, ///< engineUpdate is busy, but has not been reentered. + lmic_EngineUpdateState_again = 2, ///< engineUpdate is busy, and has to be evaluated again. }; typedef u1_t lmic_engine_update_state_t; -/* - -Structure: lmic_client_data_t - -Function: - Holds LMIC client data that must live through LMIC_reset(). - -Description: - There are a variety of client registration linkage items that - must live through LMIC_reset(), because LMIC_reset() is called - at frame rollover time. We group them together into a structure - to make copies easy. - -*/ - -//! abstract type for collection of client data that survives LMIC_reset(). +/// +/// \brief abstract type for collection of client data that survives LMIC_reset(). +/// +/// \details +/// There are a variety of client registration linkage items that +/// must live through LMIC_reset(), because LMIC_reset() is called +/// at frame-rollover time. We group them together into a structure +/// to make copies easy. +/// +/// \sa lmic_client_data_s +/// typedef struct lmic_client_data_s lmic_client_data_t; //! contents of lmic_client_data_t @@ -479,63 +540,225 @@ struct lmic_client_data_s { u1_t devStatusAns_battery; //!< value to report in MCMD_DevStatusAns message. }; -/* - -Structure: lmic_radio_data_t - -Function: - Holds LMIC radio driver. +/****************************************************************************\ +| +| Radio driver interface +| +\****************************************************************************/ + +/// +/// \brief concrete type for holding the radio state mask. +/// +/// Must be wide enough to hold values of type lmic_radio_state_e. +/// +/// \sa lmic_radio_state_e +/// +typedef u1_t lmic_radio_state_t; + +/// +/// \brief radio driver state mask +/// +enum lmic_radio_state_e { + LMIC_RADIO_EV_NONE = 0u, ///< no events reported + LMIC_RADIO_EV_TXSTART = (1u << 0), ///< transmit started + LMIC_RADIO_EV_TXDONE = (1u << 1), ///< transmit complete, TXSTART will still + /// be set + LMIC_RADIO_EV_TXDEFER = (1u << 2), ///< transmit deferred (LBT) + LMIC_RADIO_EV_TXUNKNOWN = (1u << 3), ///< transmit unknown event. + LMIC_RADIO_EV_RXSTART = (1u << 4), ///< receive started + LMIC_RADIO_EV_RXDONE = (1u << 5), ///< rx complete + LMIC_RADIO_EV_RXTIMEOUT = (1u << 6), ///< rx timed out; RXDONE also set. + LMIC_RADIO_EV_RXUNKNOWN = (1u << 7), ///< rx timed out, not sure whay. +}; -Description: - Eventually this will be used for all portable things for the radio driver, - but for now it's where we can start to add things. +/// +/// \brief container type for radio driver state mask +/// \sa lmic_radio_state_e +/// +typedef u1_t lmic_radio_state_t; + +/// +/// \brief radio request flags +/// +enum lmic_radio_flags_e { + LMIC_RADIO_FLAGS_NO_RX_IQ_INVERSION = 1u << 0, ///< if set, don't invert IQ on receive +}; -*/ +/// +/// \brief container type for radio request flags +/// \sa lmic_radio_flags_e +/// +typedef u1_t lmic_radio_flags_t; -typedef struct lmic_radio_data_s lmic_radio_data_t; ///< Radio-specific data +/// +/// \brief Instance data for LMIC radio driver +/// +/// \details +/// Eventually this will be used for all portable things for the radio driver, +/// but for now it's where we can start to add things. +/// +typedef struct lmic_radio_data_s lmic_radio_data_t; + +/// +/// \brief Details of instance data for LMIC radio driver +/// struct lmic_radio_data_s { + /// frequency for this radio operation + u4_t freq; + /// pointer to buffer + u1_t *pFrame; + /// input: rxwindow open time; output: time of receipt of last bit. + ostime_t rxtime; + + /// radio parameter settings for this radio operation. (two bytes) + rps_t rps; + /// timeout in symbols (2 bytes) + rxsyms_t rxsyms; + + /// size of buffer (in for TX, out for RX; RX assumes actual size is + /// MAX_LEN_FRAME) + u1_t dataLen; + + /// various flags + lmic_radio_flags_t flags; + + /// the radio driver's copy of txpow, in dB limited by adrTxPow, and + /// also adjusted for EIRP/antenna gain considerations. + /// This is just the radio's idea of power. So if you are + /// controlling EIRP, and you have 3 dB antenna gain, this + /// needs to reduced by 3 dB. + s1_t txpow; + + /// radio state mask; used by radio driver. + lmic_radio_state_t state; + + /// job to be scheduled when radio operation completes. + osjob_t *pRadioDoneJob; + /// Total os ticks of accumulated delay error. Can overflow! ostime_t rxlate_ticks; - /// Count of rx late launches. - unsigned rxlate_count; /// Total os ticks of accumulated tx delay error. Can overflow! ostime_t txlate_ticks; + /// Count of rx late launches. + u2_t rxlate_count; /// Count of tx late launches. - unsigned txlate_count; + u2_t txlate_count; }; + +/****************************************************************************\ +| +| Class C definitions +| +\****************************************************************************/ + +/// \ingroup ClassC +/// @{ + +/// +/// \brief internal state flacs for class C operation +/// +/// \sa lmic_class_c_flags_u +/// typedef union lmic_class_c_flags_u lmic_class_c_flags_t; + +/// +/// \brief details of lmic_class_c_flags_t. +/// +/// \details +/// Two views are provided: the \c mask field, which +/// can be used for mass initialization; and the various +/// bits in the \c f field, which are used for normal +/// computations. +/// union lmic_class_c_flags_u { unsigned mask; ///< view all the flags as a word so we can reset them easily. - struct { + + /// \brief the Class C operating flags + struct lmic_class_c_flags_s { unsigned fEnabled: 1; ///< true if class C operation is enabled. unsigned fRx2Active: 1; ///< true if we think that RX2 is active. - } f; + } f; ///< access the class C flags individually. +}; + +/// +/// \brief requests from outside the LMIC to inside the LMIC, for class C +/// +/// \details +/// Enabling and disabling Class C operation can happen from outside the LMIC, +/// or as the result of processing a callback. It's very fragile to require +/// users to understand the LMIC state, so (at least for Class C), we provide +/// APIs that are safe to call at any time. The tradeoff is that the operation +/// is asynchronous -- it doesn't complete until the LMIC gets scheduled. +/// An object of this type is used to represent these requests. +/// +/// \sa lmic_class_c_requests_u +/// +typedef union lmic_class_c_requests_u lmic_class_c_requests_t; + +/// +/// \brief details of lmic_class_c_requests_t +/// +/// \details +/// Two views are provided: the \c mask field, which +/// can be used for mass initialization; and the various +/// bits in the \c f field, which are used for normal +/// computations. +/// +/// \sa lmic_class_c_requests_t +/// +union lmic_class_c_requests_u { + unsigned mask; ///< view all the flags as a word so we can reset them easily. + + /// \brief the Class C request flags + struct lmic_class_c_requests_s { + unsigned fPending: 1; ///< true while API job is pending. + unsigned fStateChangeRq: 1; ///< true if the outside world has asked for + /// a change in state; target state will be + /// the desired state. + unsigned fTargetState: 1; ///< true if enable requested; false otherwise. + } f; ///< access the Class C request flags individually. }; +/// /// \brief the structure containing class C state +/// +/// \details +/// State for class C operations are incapsulated in a single object, +/// so that conditional compiles are easier to read. +/// +/// \sa lmic_class_c_s +/// typedef struct lmic_class_c_s lmic_class_c_t; +/// +/// \brief details of class C state +/// +/// \sa lmic_class_c_t +/// struct lmic_class_c_s { - lmic_class_c_flags_t flags; ///< the state flags + osjob_t job; ///< the job for API requests + lmic_class_c_flags_t flags; ///< the state flags + lmic_class_c_requests_t requests; ///< the request flags + u1_t frame[MAX_LEN_FRAME]; }; -/* - -Structure: lmic_t +/// @} -Function: - Provides the instance data for the LMIC. - -*/ +/****************************************************************************\ +| +| The LMIC instance object +| +\****************************************************************************/ /// \brief Instance data for the LMIC. struct lmic_t { /// client setup data, survives LMIC_reset(). lmic_client_data_t client; - /// the OS job object. pointer alignment. + /// the OS job object. pointer alignment. This is only for use by the LMIC for + /// running the state machine. Don't use it for other purposes. osjob_t osjob; #if !defined(DISABLE_BEACONS) @@ -554,6 +777,7 @@ struct lmic_t { // Radio settings TX/RX (also accessed by HAL) ostime_t txend; ///< time of end of last transmit ostime_t rxtime; ///< time of end of last receive + ostime_t nextRxTime; ///< time of start of next receive // LBT info ostime_t lbt_ticks; ///< ticks to listen for interference before transmitting. @@ -618,11 +842,6 @@ struct lmic_t { rxsyms_t rxsyms; // symbols for receive timeout. u1_t dndr; s1_t txpow; // transmit dBm (administrative) - s1_t radio_txpow; // the radio driver's copy of txpow, in dB limited by adrTxPow, and - // also adjusted for EIRP/antenna gain considerations. - // This is just the radio's idea of power. So if you are - // controlling EIRP, and you have 3 dB antenna gain, this - // needs to reduced by 3 dB. s1_t lbt_dbmax; // max permissible dB on our channel (eg -80) u1_t txChnl; // channel for next TX @@ -693,7 +912,8 @@ struct lmic_t { #endif // Public part of MAC state u1_t txCnt; - u1_t txrxFlags; // transaction flags (TX-RX combo) + u2_t txrxFlags; ///< transaction flags (TX-RX combo) + u1_t dataBeg; // 0 or start of data (dataBeg-1 is port) u1_t dataLen; // 0 no data or zero length data, >0 byte count of data u1_t frame[MAX_LEN_FRAME]; @@ -715,6 +935,12 @@ struct lmic_t { //! The state of LMIC MAC layer is encapsulated in this variable. DECLARE_LMIC; //!< \internal +/****************************************************************************\ +| +| API functions +| +\****************************************************************************/ + //! Construct a bit map of allowed datarates from drlo to drhi (both included). #define DR_RANGE_MAP(drlo,drhi) (((u2_t)0xFFFF<<(drlo)) & ((u2_t)0xFFFF>>(15-(drhi)))) bit_t LMIC_setupBand (u1_t bandidx, s1_t txpow, u2_t txcap); @@ -799,7 +1025,7 @@ lmic_compliance_rx_action_t LMIC_complianceRxMessage(u1_t port, const u1_t *pMes // they want to. #if LMIC_ENABLE_class_c -///! \brief turn class C operation off or on. By default, it's off. +/// \brief turn class C operation off or on. By default, it's off. bit_t LMIC_enableClassC(bit_t fOnIfTrue); #else static inline bit_t LMIC_enableClassC(bit_t fOnIfTrue) { diff --git a/src/lmic/lmic_compat.h b/src/lmic/lmic_compat.h index 96dca495..9a30e341 100644 --- a/src/lmic/lmic_compat.h +++ b/src/lmic/lmic_compat.h @@ -21,52 +21,51 @@ Copyright notice and license info: #ifndef _lmic_compat_h_ /* prevent multiple includes */ #define _lmic_compat_h_ -#ifdef __cplusplus -extern "C"{ -#endif +#include "lmic_env.h" + +LMIC_BEGIN_DECLS #ifndef ARDUINO_LMIC_VERSION # error "This file is normally included from lmic.h, not stand alone" #endif -#define LMIC_DEPRECATE(m) _Pragma(#m) +#define LMIC_DEPRECATED_MACRO(m) _Pragma(#m) +#define LMIC_DEPRECATED_FUNCTION(reason) __attribute__((__deprecated__(reason))) #if ! defined(LMIC_REGION_au921) && ARDUINO_LMIC_VERSION < ARDUINO_LMIC_VERSION_CALC(5,0,0,0) -# define LMIC_REGION_au921 LMIC_DEPRECATE(GCC warning "LMIC_REGION_au921 is deprecated, EOL at V5, use LMIC_REGION_au915") \ +# define LMIC_REGION_au921 LMIC_DEPRECATED_MACRO(GCC warning "LMIC_REGION_au921 is deprecated, EOL at V5, use LMIC_REGION_au915") \ LMIC_REGION_au915 // Frequency plan symbols -# define AU921_DR_SF12 LMIC_DEPRECATE(GCC warning "A921 symbols are deprecated EOL V5, use AU915") AU915_DR_SF12 -# define AU921_DR_SF11 LMIC_DEPRECATE(GCC warning "A921 symbols are deprecated EOL V5, use AU915") AU915_DR_SF11 -# define AU921_DR_SF10 LMIC_DEPRECATE(GCC warning "A921 symbols are deprecated EOL V5, use AU915") AU915_DR_SF10 -# define AU921_DR_SF9 LMIC_DEPRECATE(GCC warning "A921 symbols are deprecated EOL V5, use AU915") AU915_DR_SF9 -# define AU921_DR_SF8 LMIC_DEPRECATE(GCC warning "A921 symbols are deprecated EOL V5, use AU915") AU915_DR_SF8 -# define AU921_DR_SF7 LMIC_DEPRECATE(GCC warning "A921 symbols are deprecated EOL V5, use AU915") AU915_DR_SF7 -# define AU921_DR_SF8C LMIC_DEPRECATE(GCC warning "A921 symbols are deprecated EOL V5, use AU915") AU915_DR_SF8C -# define AU921_DR_NONE LMIC_DEPRECATE(GCC warning "A921 symbols are deprecated EOL V5, use AU915") AU915_DR_NONE -# define AU921_DR_SF12CR LMIC_DEPRECATE(GCC warning "A921 symbols are deprecated EOL V5, use AU915") AU915_DR_SF12CR -# define AU921_DR_SF11CR LMIC_DEPRECATE(GCC warning "A921 symbols are deprecated EOL V5, use AU915") AU915_DR_SF11CR -# define AU921_DR_SF10CR LMIC_DEPRECATE(GCC warning "A921 symbols are deprecated EOL V5, use AU915") AU915_DR_SF10CR -# define AU921_DR_SF9CR LMIC_DEPRECATE(GCC warning "A921 symbols are deprecated EOL V5, use AU915") AU915_DR_SF9CR -# define AU921_DR_SF8CR LMIC_DEPRECATE(GCC warning "A921 symbols are deprecated EOL V5, use AU915") AU915_DR_SF8CR -# define AU921_DR_SF7CR LMIC_DEPRECATE(GCC warning "A921 symbols are deprecated EOL V5, use AU915") AU915_DR_SF7CR -# define AU921_125kHz_UPFBASE LMIC_DEPRECATE(GCC warning "A921 symbols are deprecated EOL V5, use AU915") AU915_125kHz_UPFBASE -# define AU921_125kHz_UPFSTEP LMIC_DEPRECATE(GCC warning "A921 symbols are deprecated EOL V5, use AU915") AU915_125kHz_UPFSTEP -# define AU921_500kHz_UPFBASE LMIC_DEPRECATE(GCC warning "A921 symbols are deprecated EOL V5, use AU915") AU915_500kHz_UPFBASE -# define AU921_500kHz_UPFSTEP LMIC_DEPRECATE(GCC warning "A921 symbols are deprecated EOL V5, use AU915") AU915_500kHz_UPFSTEP -# define AU921_500kHz_DNFBASE LMIC_DEPRECATE(GCC warning "A921 symbols are deprecated EOL V5, use AU915") AU915_500kHz_DNFBASE -# define AU921_500kHz_DNFSTEP LMIC_DEPRECATE(GCC warning "A921 symbols are deprecated EOL V5, use AU915") AU915_500kHz_DNFSTEP -# define AU921_FREQ_MIN LMIC_DEPRECATE(GCC warning "A921 symbols are deprecated EOL V5, use AU915") AU915_FREQ_MIN -# define AU921_FREQ_MAX LMIC_DEPRECATE(GCC warning "A921 symbols are deprecated EOL V5, use AU915") AU915_FREQ_MAX -# define AU921_TX_EIRP_MAX_DBM LMIC_DEPRECATE(GCC warning "A921 symbols are deprecated EOL V5, use AU915") AU915_TX_EIRP_MAX_DBM -# define AU921_INITIAL_TxParam_UplinkDwellTime LMIC_DEPRECATE(GCC warning "A921 symbols are deprecated EOL V5, use AU915") AU915_INITIAL_TxParam_UplinkDwellTime -# define AU921_UPLINK_DWELL_TIME_osticks LMIC_DEPRECATE(GCC warning "A921 symbols are deprecated EOL V5, use AU915") AU915_UPLINK_DWELL_TIME_osticks -# define DR_PAGE_AU921 LMIC_DEPRECATE(GCC warning "A921 symbols are deprecated EOL V5, use AU915") DR_PAGE_AU915 -# define AU921_LMIC_REGION_EIRP LMIC_DEPRECATE(GCC warning "A921 symbols are deprecated EOL V5, use AU915") AU915_LMIC_REGION_EIRP +# define AU921_DR_SF12 LMIC_DEPRECATED_MACRO(GCC warning "A921 symbols are deprecated EOL V5, use AU915") AU915_DR_SF12 +# define AU921_DR_SF11 LMIC_DEPRECATED_MACRO(GCC warning "A921 symbols are deprecated EOL V5, use AU915") AU915_DR_SF11 +# define AU921_DR_SF10 LMIC_DEPRECATED_MACRO(GCC warning "A921 symbols are deprecated EOL V5, use AU915") AU915_DR_SF10 +# define AU921_DR_SF9 LMIC_DEPRECATED_MACRO(GCC warning "A921 symbols are deprecated EOL V5, use AU915") AU915_DR_SF9 +# define AU921_DR_SF8 LMIC_DEPRECATED_MACRO(GCC warning "A921 symbols are deprecated EOL V5, use AU915") AU915_DR_SF8 +# define AU921_DR_SF7 LMIC_DEPRECATED_MACRO(GCC warning "A921 symbols are deprecated EOL V5, use AU915") AU915_DR_SF7 +# define AU921_DR_SF8C LMIC_DEPRECATED_MACRO(GCC warning "A921 symbols are deprecated EOL V5, use AU915") AU915_DR_SF8C +# define AU921_DR_NONE LMIC_DEPRECATED_MACRO(GCC warning "A921 symbols are deprecated EOL V5, use AU915") AU915_DR_NONE +# define AU921_DR_SF12CR LMIC_DEPRECATED_MACRO(GCC warning "A921 symbols are deprecated EOL V5, use AU915") AU915_DR_SF12CR +# define AU921_DR_SF11CR LMIC_DEPRECATED_MACRO(GCC warning "A921 symbols are deprecated EOL V5, use AU915") AU915_DR_SF11CR +# define AU921_DR_SF10CR LMIC_DEPRECATED_MACRO(GCC warning "A921 symbols are deprecated EOL V5, use AU915") AU915_DR_SF10CR +# define AU921_DR_SF9CR LMIC_DEPRECATED_MACRO(GCC warning "A921 symbols are deprecated EOL V5, use AU915") AU915_DR_SF9CR +# define AU921_DR_SF8CR LMIC_DEPRECATED_MACRO(GCC warning "A921 symbols are deprecated EOL V5, use AU915") AU915_DR_SF8CR +# define AU921_DR_SF7CR LMIC_DEPRECATED_MACRO(GCC warning "A921 symbols are deprecated EOL V5, use AU915") AU915_DR_SF7CR +# define AU921_125kHz_UPFBASE LMIC_DEPRECATED_MACRO(GCC warning "A921 symbols are deprecated EOL V5, use AU915") AU915_125kHz_UPFBASE +# define AU921_125kHz_UPFSTEP LMIC_DEPRECATED_MACRO(GCC warning "A921 symbols are deprecated EOL V5, use AU915") AU915_125kHz_UPFSTEP +# define AU921_500kHz_UPFBASE LMIC_DEPRECATED_MACRO(GCC warning "A921 symbols are deprecated EOL V5, use AU915") AU915_500kHz_UPFBASE +# define AU921_500kHz_UPFSTEP LMIC_DEPRECATED_MACRO(GCC warning "A921 symbols are deprecated EOL V5, use AU915") AU915_500kHz_UPFSTEP +# define AU921_500kHz_DNFBASE LMIC_DEPRECATED_MACRO(GCC warning "A921 symbols are deprecated EOL V5, use AU915") AU915_500kHz_DNFBASE +# define AU921_500kHz_DNFSTEP LMIC_DEPRECATED_MACRO(GCC warning "A921 symbols are deprecated EOL V5, use AU915") AU915_500kHz_DNFSTEP +# define AU921_FREQ_MIN LMIC_DEPRECATED_MACRO(GCC warning "A921 symbols are deprecated EOL V5, use AU915") AU915_FREQ_MIN +# define AU921_FREQ_MAX LMIC_DEPRECATED_MACRO(GCC warning "A921 symbols are deprecated EOL V5, use AU915") AU915_FREQ_MAX +# define AU921_TX_EIRP_MAX_DBM LMIC_DEPRECATED_MACRO(GCC warning "A921 symbols are deprecated EOL V5, use AU915") AU915_TX_EIRP_MAX_DBM +# define AU921_INITIAL_TxParam_UplinkDwellTime LMIC_DEPRECATED_MACRO(GCC warning "A921 symbols are deprecated EOL V5, use AU915") AU915_INITIAL_TxParam_UplinkDwellTime +# define AU921_UPLINK_DWELL_TIME_osticks LMIC_DEPRECATED_MACRO(GCC warning "A921 symbols are deprecated EOL V5, use AU915") AU915_UPLINK_DWELL_TIME_osticks +# define DR_PAGE_AU921 LMIC_DEPRECATED_MACRO(GCC warning "A921 symbols are deprecated EOL V5, use AU915") DR_PAGE_AU915 +# define AU921_LMIC_REGION_EIRP LMIC_DEPRECATED_MACRO(GCC warning "A921 symbols are deprecated EOL V5, use AU915") AU915_LMIC_REGION_EIRP #endif -#ifdef __cplusplus -} -#endif +LMIC_END_DECLS #endif /* _lmic_compat_h_ */ diff --git a/src/lmic/lmic_eu_like.c b/src/lmic/lmic_eu_like.c index 733ff3c8..0976a3f3 100644 --- a/src/lmic/lmic_eu_like.c +++ b/src/lmic/lmic_eu_like.c @@ -290,8 +290,8 @@ LMICeulike_txDoneFSK(ostime_t delay, osjobcb_t func) { delay -= LMICbandplan_PRERX_FSK * us2osticksRound(160); // set LMIC.rxtime and LMIC.rxsyms: - LMIC.rxtime = LMIC.txend + LMICcore_adjustForDrift(delay, hsym, 8 * LMICbandplan_RXLEN_FSK); - os_setTimedCallback(&LMIC.osjob, LMIC.rxtime - os_getRadioRxRampup(), func); + LMIC.nextRxTime = LMIC.txend + LMICcore_adjustForDrift(delay, hsym, 8 * LMICbandplan_RXLEN_FSK); + os_setTimedCallback(&LMIC.osjob, LMIC.nextRxTime - os_getRadioRxRampup(), func); } #endif // CFG_LMIC_EU_like diff --git a/src/lmic/lmic_implementation.h b/src/lmic/lmic_implementation.h new file mode 100644 index 00000000..d35584d9 --- /dev/null +++ b/src/lmic/lmic_implementation.h @@ -0,0 +1,94 @@ +/* + +Module: lmic_implementation.h + +Function: + Internal file containing LMIC implementation APIs + +Copyright and License: + This file copyright (C) 2022 by + + MCCI Corporation + 3520 Krums Corners Road + Ithaca, NY 14850 + + See accompanying LICENSE file for copyright and license information. + +Author: + Terry Moore, MCCI Corporation February 2022 + +*/ + +#ifndef _lmic_implementation_h_ +#define _lmic_implementation_h_ /* prevent multiple includes */ + +#pragma once + +#ifndef _lmic_h_ +# include "lmic.h" +#endif + +LMIC_BEGIN_DECLS + +static inline bit_t LMICJ_isShutdown(void) { + return (LMIC.opmode & OP_SHUTDOWN) != 0; +} + +static inline bit_t LMICJ_isFsmIdle() { + return ! (LMIC.opmode & (OP_SCAN|OP_TXRXPEND|OP_SHUTDOWN)); +} + +static inline bit_t LMICJ_isTxPathBusy(void) { + return (LMIC.opmode & (OP_POLL | OP_TXDATA | OP_JOINING | OP_TXRXPEND)) != 0; +} + +static inline bit_t LMICJ_isTxRequested(void) { + return (LMIC.opmode & (OP_JOINING|OP_REJOIN|OP_TXDATA|OP_POLL)) != 0; +} + +static inline bit_t LMICJ_isActiveBeaconTracking(void) { +#if defined(DISABLE_BEACON) + return 0; +#else + return (LMIC.opmode & OP_TRACK) != 0; +#endif +} + +static inline bit_t LMICJ_isEnabledBeaconTracking(void) { +#if defined(DISABLE_BEACON) || defined(DISABLE_PING) + return 0; +#else + return (LMIC.opmode & (OP_SCAN | OP_TRACK)) != 0; +#endif +} + +/// +/// \brief return \c true if Class B has been enabled. +/// +/// \note This means both that tracking has been started, and LMIC_setPingable() +/// has been called. It prevents class C operation. However, it doesn't mean +/// that we've locked onto the beacon yet; the ClassB bit will not necessarily +/// be set in an uplink. +/// +static inline bit_t LMICJ_isEnabledClassB(void) { +#if defined(DISABLE_BEACON) || defined(DISABLE_PING) + return 0; +#else + return (LMIC.opmode & (OP_SCAN | OP_TRACK)) != 0 && (LMIC.opmode & OP_PINGABLE) != 0; +#endif +} + +/// +/// \brief return \c true if Class B is being reported active in uplinks. +/// +static inline bit_t LMICJ_isActiveClassB(void) { +#if defined(DISABLE_BEACON) || defined(DISABLE_PING) + return 0; +#else + return (LMIC.opmode & (OP_TRACK | OP_PINGABLE)) == (OP_TRACK | OP_PINGABLE); +#endif // DISABLE_BEACKON +} + +LMIC_END_DECLS + +#endif /* _lmic_implementation_h_ */ diff --git a/src/lmic/oslmic.h b/src/lmic/oslmic.h index b5e2500f..0c4f3dda 100644 --- a/src/lmic/oslmic.h +++ b/src/lmic/oslmic.h @@ -212,6 +212,9 @@ uint os_getTimeSecs (void); #ifndef os_radio void os_radio (u1_t mode); #endif +#ifndef os_radio_v2 +void os_radio_v2 (u1_t mode, xref2osjob_t job); +#endif #ifndef os_getBattLevel u1_t os_getBattLevel (void); #endif diff --git a/src/lmic/radio.c b/src/lmic/radio.c index 9d0274ba..ee423d9d 100644 --- a/src/lmic/radio.c +++ b/src/lmic/radio.c @@ -423,7 +423,7 @@ static void opmode (u1_t mode) { static void opmodeLora() { u1_t u = OPMODE_LORA; #ifdef CFG_sx1276_radio - if (LMIC.freq <= SX127X_FREQ_LF_MAX) { + if (LMIC.radio.freq <= SX127X_FREQ_LF_MAX) { u |= OPMODE_FSK_SX1276_LowFrequencyModeOn; } #endif @@ -434,21 +434,72 @@ static void opmodeFSK() { u1_t u = OPMODE_FSK_SX127X_SETUP; #ifdef CFG_sx1276_radio - if (LMIC.freq <= SX127X_FREQ_LF_MAX) { + if (LMIC.radio.freq <= SX127X_FREQ_LF_MAX) { u |= OPMODE_FSK_SX1276_LowFrequencyModeOn; } #endif writeOpmode(u); } +/// +/// \brief check whether state indicates that a transmit is pending +/// +static inline bit_t os_radio_isTxActive(lmic_radio_state_t state) { + if ((state & (LMIC_RADIO_EV_TXSTART | LMIC_RADIO_EV_TXDONE)) == LMIC_RADIO_EV_TXSTART) + return 1; +} + +/// +/// \brief check whether state indicates that a receive is pending +/// +static inline bit_t os_radio_isRxActive(lmic_radio_state_t state) { + if ((state & (LMIC_RADIO_EV_RXSTART | LMIC_RADIO_EV_RXDONE)) == LMIC_RADIO_EV_RXSTART) + return 1; +} + +/// +/// \brief check whether state indicates that the radio is active +/// +static inline bit_t os_radio_isStateActive(lmic_radio_state_t state) { + return os_radio_isTxActive(state) || os_radio_isRxActive(state); +} + +/// +/// \brief check whether noRxIqInversion is selected +/// +static inline bit_t radioRq_getNoRxIqInversion() { + return (LMIC.radio.flags & LMIC_RADIO_FLAGS_NO_RX_IQ_INVERSION) != 0; +} + +/// +/// \brief Complete a radio operation +/// +/// \param event [in] Event flags to be set in `LMIC.radio.state`, from +/// lmic_radio_state_e. +/// +/// \details +/// If there's a job to be scheduled, schedule it. Otherwise log that there's +/// no job, and go on. +/// +static void radio_complete (lmic_radio_state_t event) { + LMIC.radio.state |= event; + if (LMIC.radio.pRadioDoneJob != NULL) { + os_setCallback(LMIC.radio.pRadioDoneJob, LMIC.radio.pRadioDoneJob->func); + LMIC.radio.pRadioDoneJob = NULL; + } + else { + LMICOS_logEvent("null LMIC.radio.pRadioDoneJob"); + } +} + // configure LoRa modem (cfg1, cfg2) static void configLoraModem () { - sf_t sf = getSf(LMIC.rps); + sf_t sf = getSf(LMIC.radio.rps); #ifdef CFG_sx1276_radio u1_t mc1 = 0, mc2 = 0, mc3 = 0; - bw_t const bw = getBw(LMIC.rps); + bw_t const bw = getBw(LMIC.radio.rps); switch (bw) { case BW125: mc1 |= SX1276_MC1_BW_125; break; @@ -457,7 +508,7 @@ static void configLoraModem () { default: ASSERT(0); } - switch( getCr(LMIC.rps) ) { + switch( getCr(LMIC.radio.rps) ) { case CR_4_5: mc1 |= SX1276_MC1_CR_4_5; break; case CR_4_6: mc1 |= SX1276_MC1_CR_4_6; break; case CR_4_7: mc1 |= SX1276_MC1_CR_4_7; break; @@ -466,15 +517,15 @@ static void configLoraModem () { ASSERT(0); } - if (getIh(LMIC.rps)) { + if (getIh(LMIC.radio.rps)) { mc1 |= SX1276_MC1_IMPLICIT_HEADER_MODE_ON; - writeReg(LORARegPayloadLength, getIh(LMIC.rps)); // required length + writeReg(LORARegPayloadLength, getIh(LMIC.radio.rps)); // required length } // set ModemConfig1 writeReg(LORARegModemConfig1, mc1); - mc2 = (SX1272_MC2_SF7 + ((sf-1)<<4) + ((LMIC.rxsyms >> 8) & 0x3) ); - if (getNocrc(LMIC.rps) == 0) { + mc2 = (SX1272_MC2_SF7 + ((sf-1)<<4) + ((LMIC.radio.rxsyms >> 8) & 0x3) ); + if (getNocrc(LMIC.radio.rps) == 0) { mc2 |= SX1276_MC2_RX_PAYLOAD_CRCON; } #if CFG_TxContinuousMode @@ -500,7 +551,7 @@ static void configLoraModem () { rHighBwOptimize2 = 0; if (bw == BW500) { - if (LMIC.freq > SX127X_FREQ_LF_MAX) { + if (LMIC.radio.freq > SX127X_FREQ_LF_MAX) { rHighBwOptimize1 = 0x02; rHighBwOptimize2 = 0x64; } else { @@ -514,33 +565,33 @@ static void configLoraModem () { writeReg(LORARegHighBwOptimize2, rHighBwOptimize2); #elif CFG_sx1272_radio - u1_t mc1 = (getBw(LMIC.rps)<<6); + u1_t mc1 = (getBw(LMIC.radio.rps)<<6); - switch( getCr(LMIC.rps) ) { + switch( getCr(LMIC.radio.rps) ) { case CR_4_5: mc1 |= SX1272_MC1_CR_4_5; break; case CR_4_6: mc1 |= SX1272_MC1_CR_4_6; break; case CR_4_7: mc1 |= SX1272_MC1_CR_4_7; break; case CR_4_8: mc1 |= SX1272_MC1_CR_4_8; break; } - if ((sf == SF11 || sf == SF12) && getBw(LMIC.rps) == BW125) { + if ((sf == SF11 || sf == SF12) && getBw(LMIC.radio.rps) == BW125) { mc1 |= SX1272_MC1_LOW_DATA_RATE_OPTIMIZE; } - if (getNocrc(LMIC.rps) == 0) { + if (getNocrc(LMIC.radio.rps) == 0) { mc1 |= SX1272_MC1_RX_PAYLOAD_CRCON; } - if (getIh(LMIC.rps)) { + if (getIh(LMIC.radio.rps)) { mc1 |= SX1272_MC1_IMPLICIT_HEADER_MODE_ON; - writeReg(LORARegPayloadLength, getIh(LMIC.rps)); // required length + writeReg(LORARegPayloadLength, getIh(LMIC.radio.rps)); // required length } // set ModemConfig1 writeReg(LORARegModemConfig1, mc1); // set ModemConfig2 (sf, AgcAutoOn=1 SymbTimeoutHi) u1_t mc2; - mc2 = (SX1272_MC2_SF7 + ((sf-1)<<4)) | 0x04 | ((LMIC.rxsyms >> 8) & 0x3); + mc2 = (SX1272_MC2_SF7 + ((sf-1)<<4)) | 0x04 | ((LMIC.radio.rxsyms >> 8) & 0x3); #if CFG_TxContinuousMode // Only for testing @@ -557,7 +608,7 @@ static void configLoraModem () { static void configChannel () { // set frequency: FQ = (FRF * 32 Mhz) / (2 ^ 19) - uint64_t frf = ((uint64_t)LMIC.freq << 19) / 32000000; + uint64_t frf = ((uint64_t)LMIC.radio.freq << 19) / 32000000; writeReg(RegFrfMsb, (u1_t)(frf>>16)); writeReg(RegFrfMid, (u1_t)(frf>> 8)); writeReg(RegFrfLsb, (u1_t)(frf>> 0)); @@ -591,7 +642,7 @@ static void configChannel () { static void configPower () { // our input paramter -- might be different than LMIC.txpow! - s1_t const req_pw = (s1_t)LMIC.radio_txpow; + s1_t const req_pw = (s1_t)LMIC.radio.txpow; // the effective power s1_t eff_pw; // the policy; we're going to compute this. @@ -623,7 +674,7 @@ static void configPower () { } } - policy = hal_getTxPowerPolicy(policy, eff_pw, LMIC.freq); + policy = hal_getTxPowerPolicy(policy, eff_pw, LMIC.radio.freq); switch (policy) { default: @@ -649,7 +700,7 @@ static void configPower () { // (And, of course, it might also be too large.) case LMICHAL_radio_tx_power_policy_paboost: // It seems that SX127x doesn't like eff_pw 10 when in FSK mode. - if (getSf(LMIC.rps) == FSK && eff_pw < 11) { + if (getSf(LMIC.radio.rps) == FSK && eff_pw < 11) { eff_pw = 11; } rPaDac = SX127X_PADAC_POWER_NORMAL; @@ -688,7 +739,7 @@ static void configPower () { } } - policy = hal_getTxPowerPolicy(policy, eff_pw, LMIC.freq); + policy = hal_getTxPowerPolicy(policy, eff_pw, LMIC.radio.freq); switch (policy) { default: @@ -777,11 +828,11 @@ static void txfsk () { // initialize the payload size and address pointers // TODO(tmm@mcci.com): datasheet says this is not used in variable packet length mode - writeReg(FSKRegPayloadLength, LMIC.dataLen+1); // (insert length byte into payload)) + writeReg(FSKRegPayloadLength, LMIC.radio.dataLen+1); // (insert length byte into payload)) // download length byte and buffer to the radio FIFO - writeReg(RegFifo, LMIC.dataLen); - writeBuf(RegFifo, LMIC.frame, LMIC.dataLen); + writeReg(RegFifo, LMIC.radio.dataLen); + writeBuf(RegFifo, LMIC.radio.pFrame, LMIC.radio.dataLen); // enable antenna switch for TX hal_pin_rxtx(1); @@ -794,7 +845,7 @@ static void txfsk () { ++LMIC.radio.txlate_count; } } - LMICOS_logEventUint32("+Tx FSK", LMIC.dataLen); + LMICOS_logEventUint32("+Tx FSK", LMIC.radio.dataLen); opmode(OPMODE_TX); } @@ -830,10 +881,10 @@ static void txlora () { // initialize the payload size and address pointers writeReg(LORARegFifoTxBaseAddr, 0x00); writeReg(LORARegFifoAddrPtr, 0x00); - writeReg(LORARegPayloadLength, LMIC.dataLen); + writeReg(LORARegPayloadLength, LMIC.radio.dataLen); // download buffer to the radio FIFO - writeBuf(RegFifo, LMIC.frame, LMIC.dataLen); + writeBuf(RegFifo, LMIC.radio.pFrame, LMIC.radio.dataLen); // enable antenna switch for TX hal_pin_rxtx(1); @@ -846,23 +897,23 @@ static void txlora () { ++LMIC.radio.txlate_count; } } - LMICOS_logEventUint32("+Tx LoRa", LMIC.dataLen); + LMICOS_logEventUint32("+Tx LoRa", LMIC.radio.dataLen); opmode(OPMODE_TX); #if LMIC_DEBUG_LEVEL > 0 - u1_t sf = getSf(LMIC.rps) + 6; // 1 == SF7 - u1_t bw = getBw(LMIC.rps); - u1_t cr = getCr(LMIC.rps); + u1_t sf = getSf(LMIC.radio.rps) + 6; // 1 == SF7 + u1_t bw = getBw(LMIC.radio.rps); + u1_t cr = getCr(LMIC.radio.rps); LMIC_DEBUG_PRINTF("%"LMIC_PRId_ostime_t": TXMODE, freq=%"PRIu32", len=%d, SF=%d, BW=%d, CR=4/%d, IH=%d\n", - os_getTime(), LMIC.freq, LMIC.dataLen, sf, + os_getTime(), LMIC.radio.freq, LMIC.radio.dataLen, sf, bw == BW125 ? 125 : (bw == BW250 ? 250 : 500), cr == CR_4_5 ? 5 : (cr == CR_4_6 ? 6 : (cr == CR_4_7 ? 7 : 8)), - getIh(LMIC.rps) + getIh(LMIC.radio.rps) ); #endif } -// start transmitter (buf=LMIC.frame, len=LMIC.dataLen) +// start transmitter (buf=LMIC.radio.pFrame, len=LMIC.radio.dataLen) static void starttx () { u1_t const rOpMode = readReg(RegOpMode); @@ -886,12 +937,12 @@ static void starttx () { if (rssi.max_rssi >= LMIC.lbt_dbmax) { // complete the request by scheduling the job - os_setCallback(&LMIC.osjob, LMIC.osjob.func); + radio_complete(LMIC_RADIO_EV_TXDONE | LMIC_RADIO_EV_TXDEFER); return; } } - if(getSf(LMIC.rps) == FSK) { // FSK modem + if(getSf(LMIC.radio.rps) == FSK) { // FSK modem txfsk(); } else { // LoRa modem txlora(); @@ -920,7 +971,7 @@ static void rxlate (u4_t nLate) { } } -// start LoRa receiver (time=LMIC.rxtime, timeout=LMIC.rxsyms, result=LMIC.frame[LMIC.dataLen]) +// start LoRa receiver (time=LMIC.radio.rxtime, timeout=LMIC.radio.rxsyms, result=LMIC.radio.pFrame[LMIC.radio.dataLen]) static void rxlora (u1_t rxmode) { // select LoRa modem (from sleep mode) opmodeLora(); @@ -941,19 +992,16 @@ static void rxlora (u1_t rxmode) { writeReg(RegLna, LNA_RX_GAIN); // set max payload size writeReg(LORARegPayloadMaxLength, MAX_LEN_FRAME); -#if !defined(DISABLE_INVERT_IQ_ON_RX) /* DEPRECATED(tmm@mcci.com); #250. remove test, always include code in V3 */ - // use inverted I/Q signal (prevent mote-to-mote communication) - // XXX: use flag to switch on/off inversion - if (LMIC.noRXIQinversion) { + // use inverted I/Q signal (prevent mote-to-mote communication) + if (radioRq_getNoRxIqInversion()) { writeReg(LORARegInvertIQ, readReg(LORARegInvertIQ) & ~(1<<6)); } else { writeReg(LORARegInvertIQ, readReg(LORARegInvertIQ)|(1<<6)); } -#endif // Errata 2.3 - receiver spurious reception of a LoRa signal - bw_t const bw = getBw(LMIC.rps); + bw_t const bw = getBw(LMIC.radio.rps); u1_t const rDetectOptimize = (readReg(LORARegDetectOptimize) & 0x78) | 0x03; if (bw < BW500) { writeReg(LORARegDetectOptimize, rDetectOptimize); @@ -964,7 +1012,7 @@ static void rxlora (u1_t rxmode) { } // set symbol timeout (for single rx) - writeReg(LORARegSymbTimeoutLsb, (uint8_t) LMIC.rxsyms); + writeReg(LORARegSymbTimeoutLsb, (uint8_t) LMIC.radio.rxsyms); // set sync word writeReg(LORARegSyncWord, LORA_MAC_PREAMBLE); @@ -983,13 +1031,13 @@ static void rxlora (u1_t rxmode) { // now instruct the radio to receive if (rxmode == RXMODE_SINGLE) { // single rx - u4_t nLate = hal_waitUntil(LMIC.rxtime); // busy wait until exact rx time + u4_t nLate = hal_waitUntil(LMIC.radio.rxtime); // busy wait until exact rx time opmode(OPMODE_RX_SINGLE); LMICOS_logEventUint32("+Rx LoRa Single", nLate); rxlate(nLate); #if LMIC_DEBUG_LEVEL > 0 ostime_t now = os_getTime(); - LMIC_DEBUG_PRINTF("start single rx: now-rxtime: %"LMIC_PRId_ostime_t"\n", now - LMIC.rxtime); + LMIC_DEBUG_PRINTF("start single rx: now-rxtime: %"LMIC_PRId_ostime_t"\n", now - LMIC.radio.rxtime); #endif } else { // continous rx (scan or rssi) LMICOS_logEventUint32("+Rx LoRa Continuous", rxmode); @@ -1000,16 +1048,16 @@ static void rxlora (u1_t rxmode) { if (rxmode == RXMODE_RSSI) { LMIC_DEBUG_PRINTF("RXMODE_RSSI\n"); } else { - u1_t sf = getSf(LMIC.rps) + 6; // 1 == SF7 - u1_t bw = getBw(LMIC.rps); - u1_t cr = getCr(LMIC.rps); + u1_t sf = getSf(LMIC.radio.rps) + 6; // 1 == SF7 + u1_t bw = getBw(LMIC.radio.rps); + u1_t cr = getCr(LMIC.radio.rps); LMIC_DEBUG_PRINTF("%"LMIC_PRId_ostime_t": %s, freq=%"PRIu32", SF=%d, BW=%d, CR=4/%d, IH=%d\n", os_getTime(), rxmode == RXMODE_SINGLE ? "RXMODE_SINGLE" : (rxmode == RXMODE_SCAN ? "RXMODE_SCAN" : "UNKNOWN_RX"), - LMIC.freq, sf, + LMIC.radio.freq, sf, bw == BW125 ? 125 : (bw == BW250 ? 250 : 500), cr == CR_4_5 ? 5 : (cr == CR_4_6 ? 6 : (cr == CR_4_7 ? 7 : 8)), - getIh(LMIC.rps) + getIh(LMIC.radio.rps) ); } #endif @@ -1017,11 +1065,11 @@ static void rxlora (u1_t rxmode) { static void rxfsk (u1_t rxmode) { // only single or continuous rx (no noise sampling) - if (rxmode == RXMODE_SCAN) { + if (rxmode == RXMODE_RSSI) { // indicate no bytes received. - LMIC.dataLen = 0; + LMIC.radio.dataLen = 0; // complete the request by scheduling the job. - os_setCallback(&LMIC.osjob, LMIC.osjob.func); + radio_complete(LMIC_RADIO_EV_RXDONE); } // select FSK modem (from sleep mode) @@ -1043,7 +1091,7 @@ static void rxfsk (u1_t rxmode) { // set preamble detection writeReg(FSKRegPreambleDetect, 0xAA); // enable, 2 bytes, 10 chip errors // set preamble timeout - writeReg(FSKRegRxTimeout2, 0xFF);//(LMIC.rxsyms+1)/2); + writeReg(FSKRegRxTimeout2, 0xFF);//(LMIC.radio.rxsyms+1)/2); // set bitrate, autoclear CRC setupFskRxTx(1); @@ -1055,7 +1103,7 @@ static void rxfsk (u1_t rxmode) { // now instruct the radio to receive if (rxmode == RXMODE_SINGLE) { - u4_t nLate = hal_waitUntil(LMIC.rxtime); // busy wait until exact rx time + u4_t nLate = hal_waitUntil(LMIC.radio.rxtime); // busy wait until exact rx time opmode(OPMODE_RX); // no single rx mode available in FSK LMICOS_logEventUint32("+Rx FSK", nLate); rxlate(nLate); @@ -1067,7 +1115,7 @@ static void rxfsk (u1_t rxmode) { static void startrx (u1_t rxmode) { ASSERT( (readReg(RegOpMode) & OPMODE_MASK) == OPMODE_SLEEP ); - if(getSf(LMIC.rps) == FSK) { // FSK modem + if(getSf(LMIC.radio.rps) == FSK) { // FSK modem rxfsk(rxmode); } else { // LoRa modem rxlora(rxmode); @@ -1162,6 +1210,7 @@ int radio_init () { return 1; } +/// /// \brief Generate an 8-bit uniformly-distributed integer. /// /// \details @@ -1201,6 +1250,7 @@ u1_t radio_rssi () { return r; } +/// /// \brief Measure the current broadband RSSI for the current channel. /// /// \details @@ -1229,7 +1279,7 @@ void radio_monitor_rssi(ostime_t nTicks, oslmic_radio_rssi_t *pRssi) { // while we're waiting for the PLLs to spin up, determine which // band we're in and choose the base RSSI. #if defined(CFG_sx1276_radio) - if (LMIC.freq > SX127X_FREQ_LF_MAX) { + if (LMIC.radio.freq > SX127X_FREQ_LF_MAX) { rssiAdjust = SX1276_RSSI_ADJUST_HF; } else { rssiAdjust = SX1276_RSSI_ADJUST_LF; @@ -1290,12 +1340,47 @@ static CONST_TABLE(u2_t, LORA_RXDONE_FIXUP)[] = { [SF12] = us2osticks(31189), // (1022 ticks) }; -// called by hal ext IRQ handler -// (radio goes to stanby mode after tx/rx operations) +/// +/// \brief legacy radio IRQ handler +/// +/// \param [in] dio is the image of the DIO0..n pins observed by the primary ISR. +/// +/// \details +/// The HAL is responsible for detecting transitions on the SX127x DIO pins, +/// and scheduling this routine. This routine must run as part of `os_runloop_once()`; +/// it must not be called directly from a primary ISR. +/// +/// This routine is for legacy use only; it is for use by older HALs that cannot +/// capture the interrupt time in the primary ISR. +/// +/// \sa radio_irq_handler_v2 +/// + void radio_irq_handler (u1_t dio) { radio_irq_handler_v2(dio, os_getTime()); } +/// +/// \brief Radio IRQ handler +/// +/// \param [in] dio is the image of the DIO0..n pins observed by the primary ISR. +/// \param [in] now is the HALs best estimate of the time of teh interrupt. +/// +/// \details +/// The HAL is responsible for detecting transitions on the SX127x DIO pins, +/// and scheduling this routine. This routine must run as part of `os_runloop_once()`; +/// it must not be called directly from a primary ISR. +/// +/// The radio registers are interrogated, and the interrupt is processed. The +/// radio is then put back to sleep, and the background is scheduled. +/// +/// \note +/// If continuous transmit mode is configured, then this routine immediately +/// schedules a new transmit, and never completes to the background. +/// +/// \sa radio_irq_handler_v2 +/// + void radio_irq_handler_v2 (u1_t dio, ostime_t now) { LMIC_API_PARAMETER(dio); @@ -1314,35 +1399,41 @@ void radio_irq_handler_v2 (u1_t dio, ostime_t now) { return; #else /* ! CFG_TxContinuousMode */ + lmic_radio_state_t event; + event = LMIC_RADIO_EV_NONE; + #if LMIC_DEBUG_LEVEL > 0 ostime_t const entry = now; #endif + if( (readReg(RegOpMode) & OPMODE_LORA) != 0) { // LORA modem u1_t flags = readReg(LORARegIrqFlags); LMIC.saveIrqFlags = flags; LMICOS_logEventUint32("radio_irq_handler_v2: LoRa", flags); LMIC_X_DEBUG_PRINTF("IRQ=%02x\n", flags); if( flags & IRQ_LORA_TXDONE_MASK ) { + event = LMIC_RADIO_EV_TXDONE; // save exact tx time LMIC.txend = now - us2osticks(43); // TXDONE FIXUP } else if( flags & IRQ_LORA_RXDONE_MASK ) { + event = LMIC_RADIO_EV_RXDONE; // save exact rx time - if(getBw(LMIC.rps) == BW125) { - now -= TABLE_GET_U2(LORA_RXDONE_FIXUP, getSf(LMIC.rps)); + if(getBw(LMIC.radio.rps) == BW125) { + now -= TABLE_GET_U2(LORA_RXDONE_FIXUP, getSf(LMIC.radio.rps)); } - LMIC.rxtime = now; + LMIC.radio.rxtime = now; // read the PDU and inform the MAC that we received something - LMIC.dataLen = (readReg(LORARegModemConfig1) & SX127X_MC1_IMPLICIT_HEADER_MODE_ON) ? + LMIC.radio.dataLen = (readReg(LORARegModemConfig1) & SX127X_MC1_IMPLICIT_HEADER_MODE_ON) ? readReg(LORARegPayloadLength) : readReg(LORARegRxNbBytes); // set FIFO read address pointer writeReg(LORARegFifoAddrPtr, readReg(LORARegFifoRxCurrentAddr)); // now read the FIFO - readBuf(RegFifo, LMIC.frame, LMIC.dataLen); + readBuf(RegFifo, LMIC.radio.pFrame, LMIC.radio.dataLen); // read rx quality parameters LMIC.snr = readReg(LORARegPktSnrValue); // SNR [dB] * 4 u1_t const rRssi = readReg(LORARegPktRssiValue); s2_t rssi = rRssi; - if (LMIC.freq > SX127X_FREQ_LF_MAX) + if (LMIC.radio.freq > SX127X_FREQ_LF_MAX) rssi += SX127X_RSSI_ADJUST_HF; else rssi += SX127X_RSSI_ADJUST_LF; @@ -1357,14 +1448,24 @@ void radio_irq_handler_v2 (u1_t dio, ostime_t now) { // ugh compatibility requires a biased range. RSSI LMIC.rssi = (s1_t) (RSSI_OFF + (rssi < -196 ? -196 : rssi > 63 ? 63 : rssi)); // RSSI [dBm] (-196...+63) } else if( flags & IRQ_LORA_RXTOUT_MASK ) { + event = LMIC_RADIO_EV_RXDONE | LMIC_RADIO_EV_RXTIMEOUT; // indicate timeout - LMIC.dataLen = 0; + LMIC.radio.dataLen = 0; #if LMIC_DEBUG_LEVEL > 0 ostime_t now2 = os_getTime(); LMIC_DEBUG_PRINTF("rxtimeout: entry: %"LMIC_PRId_ostime_t" rxtime: %"LMIC_PRId_ostime_t" entry-rxtime: %"LMIC_PRId_ostime_t" now-entry: %"LMIC_PRId_ostime_t" rxtime-txend: %"LMIC_PRId_ostime_t"\n", entry, - LMIC.rxtime, entry - LMIC.rxtime, now2 - entry, LMIC.rxtime-LMIC.txend); + LMIC.radio.rxtime, entry - LMIC.radio.rxtime, now2 - entry, LMIC.radio.rxtime-LMIC.txend); #endif } + else { + // we're not sure why we're here... treat as timeout. + LMIC.radio.dataLen = 0; + LMICOS_logEventUint32("unexpected LoRa radio interrupt", LMIC.radio.state); + if (os_radio_isTxActive(LMIC.radio.state)) + event |= LMIC_RADIO_EV_TXDONE | LMIC_RADIO_EV_TXUNKNOWN; + if (os_radio_isRxActive(LMIC.radio.state)) + event |= LMIC_RADIO_EV_RXDONE | LMIC_RADIO_EV_RXUNKNOWN; + } // mask all radio IRQs writeReg(LORARegIrqFlagsMask, 0xFF); // clear radio IRQ flags @@ -1376,26 +1477,35 @@ void radio_irq_handler_v2 (u1_t dio, ostime_t now) { LMICOS_logEventUint32("*radio_irq_handler_v2: FSK", ((u2_t)flags2 << 8) | flags1); if( flags2 & IRQ_FSK2_PACKETSENT_MASK ) { + event = LMIC_RADIO_EV_TXDONE; + // save exact tx time LMIC.txend = now; } else if( flags2 & IRQ_FSK2_PAYLOADREADY_MASK ) { + event = LMIC_RADIO_EV_RXDONE; + // save exact rx time - LMIC.rxtime = now; + LMIC.radio.rxtime = now; // read the PDU and inform the MAC that we received something - LMIC.dataLen = readReg(FSKRegPayloadLength); + LMIC.radio.dataLen = readReg(FSKRegPayloadLength); // now read the FIFO - readBuf(RegFifo, LMIC.frame, LMIC.dataLen); + readBuf(RegFifo, LMIC.radio.pFrame, LMIC.radio.dataLen); // read rx quality parameters LMIC.snr = 0; // SX127x doesn't give SNR for FSK. LMIC.rssi = -64 + RSSI_OFF; // SX127x doesn't give packet RSSI for FSK, // so substitute a dummy value. } else if( flags1 & IRQ_FSK1_TIMEOUT_MASK ) { + event = LMIC_RADIO_EV_RXDONE | LMIC_RADIO_EV_RXTIMEOUT; // indicate timeout - LMIC.dataLen = 0; + LMIC.radio.dataLen = 0; } else { - // ASSERT(0); // we're not sure why we're here... treat as timeout. - LMIC.dataLen = 0; + LMIC.radio.dataLen = 0; + LMICOS_logEventUint32("unexpected FSK radio interrupt", LMIC.radio.state); + if (os_radio_isTxActive(LMIC.radio.state)) + event |= LMIC_RADIO_EV_TXDONE | LMIC_RADIO_EV_TXUNKNOWN; + if (os_radio_isRxActive(LMIC.radio.state)) + event |= LMIC_RADIO_EV_RXDONE | LMIC_RADIO_EV_RXUNKNOWN; } // in FSK, we need to put the radio in standby first. @@ -1404,63 +1514,121 @@ void radio_irq_handler_v2 (u1_t dio, ostime_t now) { // go from standby to sleep opmode(OPMODE_SLEEP); // run os job (use preset func ptr) - os_setCallback(&LMIC.osjob, LMIC.osjob.func); + radio_complete(event); #endif /* ! CFG_TxContinuousMode */ } -/*! - -\brief Initiate a radio operation. - -\param mode [in] Selects the operation to be performed. - -\details -The requested radio operation is initiated. Some operations complete -immediately; others require hardware to do work, and don't complete until -an interrupt occurs. In that case, `LMIC.osjob` is scheduled. Because the -interrupt may occur right away, it's important that the caller initialize -`LMIC.osjob` before calling this routine. +static void os_radio_reset(void) { + // put radio to sleep + LMICOS_logEvent("os_radio_reset"); + opmode(OPMODE_SLEEP); + if (LMIC.radio.pRadioDoneJob != NULL) { + os_clearCallback(LMIC.radio.pRadioDoneJob); + LMIC.radio.pRadioDoneJob = NULL; + } + LMIC.radio.state = LMIC_RADIO_EV_NONE; +} -- `RADIO_RST` causes the radio to be put to sleep. No interrupt follows; -when control returns, the radio is ready for the next operation. +void os_radio (u1_t mode) { + LMIC.radio.freq = LMIC.freq; + LMIC.radio.pFrame = LMIC.frame; + LMIC.radio.rxtime = LMIC.nextRxTime; + LMIC.radio.rps = LMIC.rps; + LMIC.radio.rxsyms = LMIC.rxsyms; + LMIC.radio.dataLen = LMIC.radio.dataLen; + LMIC.radio.flags = 0; + if (LMIC.noRXIQinversion) + LMIC.radio.flags |= LMIC_RADIO_FLAGS_NO_RX_IQ_INVERSION; + os_radio_v2(mode, &LMIC.osjob); +} -- `RADIO_TX` and `RADIO_TX_AT` launch the transmission of a frame. An interrupt will -occur, which will cause `LMIC.osjob` to be scheduled with its current -function. +/// +/// \brief Initiate a radio operation. +/// +/// \param mode [in] Selects the operation to be performed. +/// \param pJob [inout] Job to be scheduled when operation completes. +/// +/// \details +/// The requested radio operation is initiated. Some operations complete +/// immediately; others require hardware to do work, and don't complete until +/// an interrupt occurs. In that case, `pJob` is scheduled. +/// Because the +/// interrupt may occur right away, it's important that the caller initialize +/// `pJob` before calling this routine. +/// +/// - `RADIO_RST` causes the radio to be put to sleep. No interrupt follows; +/// when control returns, the radio is ready for the next operation. `pJob` +/// is not scheduled. If an operation was pending, it is canceled. +/// +/// - `RADIO_TX` and `RADIO_TX_AT` launch the transmission of a frame. An interrupt will +/// occur, which will cause `pJob` to be scheduled with its current +/// function. +/// +/// - `RADIO_RX`, `RADIO_RX_ON` and `RADIO_RX_ON_C` launch either timed or +/// untimed receives. An interrupt will occur when a packet is recieved or +/// the receive times out, which will cause `pJob` to be scheduled with +/// its current function. `RADIO_RX_ON_C` is used to schedule a request that +/// might be redundat, for class C support. +/// +/// \note +/// It's a quirk of the implementation that timeouts may be handled by the very +/// same `pJob` that we use for completion. os_setCallback() cancels +/// any previous operation for the job. It's all a little fragile, because +/// most callers use the exact same `LMIC.osjob` object that is used for other +/// purposes throughout the LMIC. +/// -- `RADIO_RX` and `RADIO_RX_ON` launch either single or continuous receives. -An interrupt will occur when a packet is recieved or the receive times out, -which will cause `LMIC.osjob` to be scheduled with its current function. +void os_radio_v2 (u1_t mode, osjob_t *pJob) { + // handle redundant class-C requests. + if (mode == RADIO_RXON_C && + os_radio_isRxActive(LMIC.radio.state) && + pJob == LMIC.radio.pRadioDoneJob) { + LMICOS_logEventUint32("redundant class C RX", LMIC.radio.state); + return; + } -*/ + // handle requests while radio is active: cancel. + if (mode != RADIO_RST) { + if (LMIC.radio.state != LMIC_RADIO_EV_NONE) { + LMICOS_logEventUint32("request while radio active", LMIC.radio.state); + // recurse and kill the pending activity + os_radio_reset(); + } + // record job + LMIC.radio.pRadioDoneJob = pJob; + } -void os_radio (u1_t mode) { switch (mode) { case RADIO_RST: - // put radio to sleep - opmode(OPMODE_SLEEP); + os_radio_reset(); break; case RADIO_TX: // transmit frame now LMIC.txend = 0; - starttx(); // buf=LMIC.frame, len=LMIC.dataLen + LMIC.radio.state = LMIC_RADIO_EV_TXSTART; + starttx(); // buf=LMIC.radio.pFrame, len=LMIC.radio.dataLen break; case RADIO_TX_AT: + // make sure transmit time is non-zero to force + // scheduling at a specific time. if (LMIC.txend == 0) LMIC.txend = 1; + LMIC.radio.state = LMIC_RADIO_EV_TXSTART; starttx(); break; case RADIO_RX: // receive frame now (exactly at rxtime) - startrx(RXMODE_SINGLE); // buf=LMIC.frame, time=LMIC.rxtime, timeout=LMIC.rxsyms + LMIC.radio.state = LMIC_RADIO_EV_RXSTART; + startrx(RXMODE_SINGLE); // buf=LMIC.radio.pFrame, time=LMIC.radio.rxtime, timeout=LMIC.radio.rxsyms break; case RADIO_RXON: // start scanning for beacon now - startrx(RXMODE_SCAN); // buf=LMIC.frame + LMIC.radio.state = LMIC_RADIO_EV_RXSTART; + startrx(RXMODE_SCAN); // buf=LMIC.radio.pFrame break; } }