diff --git a/keywords.txt b/keywords.txt index 71a1a76a..93a463b5 100644 --- a/keywords.txt +++ b/keywords.txt @@ -115,6 +115,8 @@ setDio2AsRfSwitch KEYWORD2 getTimeOnAir KEYWORD2 setSyncBits KEYWORD2 setWhitening KEYWORD2 +startReceiveDutyCycle KEYWORD2 +startReceiveDutyCycleAuto KEYWORD2 # ESP8266 join KEYWORD2 diff --git a/src/TypeDef.h b/src/TypeDef.h index c9898a93..01697665 100644 --- a/src/TypeDef.h +++ b/src/TypeDef.h @@ -305,6 +305,19 @@ */ #define ERR_INVALID_ENCODING -23 +/*! + \brief The supplied sleep period is invalid. + + The specified sleep period is shorter than the time necessary to sleep and wake the hardware, + including TCXO delay +*/ +#define ERR_INVALID_SLEEP_PERIOD -24 + +/*! + \brief A specified value would cause an integer overflow +*/ +#define ERR_OVERFLOW -25 + // RF69-specific status codes /*! diff --git a/src/modules/SX126x/SX126x.cpp b/src/modules/SX126x/SX126x.cpp index e8db4995..44b9ee43 100644 --- a/src/modules/SX126x/SX126x.cpp +++ b/src/modules/SX126x/SX126x.cpp @@ -443,7 +443,91 @@ int16_t SX126x::startTransmit(uint8_t* data, size_t len, uint8_t addr) { } int16_t SX126x::startReceive(uint32_t timeout) { - // set DIO mapping + int16_t state = startReceiveCommon(); + if(state != ERR_NONE) { + return(state); + } + + // set mode to receive + state = setRx(timeout); + + return(state); +} + +int16_t SX126x::startReceiveDutyCycle(uint32_t rxPeriod_us, uint32_t sleepPeriod_us) +{ + // datasheet claims time to go to sleep is ~500us, same to wake up. + // compensate for that 1ms + tcxo delay: + uint32_t transitionTime = _tcxoDelay_us + 1000; + if (sleepPeriod_us < transitionTime + 16) { + return(ERR_INVALID_SLEEP_PERIOD); + } + sleepPeriod_us -= transitionTime; + + // divide by 15.625 + uint32_t rxPeriodRaw = (rxPeriod_us * 8) / 125; + uint32_t sleepPeriodRaw = (sleepPeriod_us * 8) / 125; + + // 24 bit limit: + if ((rxPeriodRaw & 0xFF000000) || (sleepPeriodRaw & 0xFF000000)) { + return(ERR_OVERFLOW); + } + + int16_t state = startReceiveCommon(); + if (state != ERR_NONE) { + return(state); + } + + byte data[6] = { + (rxPeriodRaw >> 16) & 0xFF, (rxPeriodRaw >> 8) & 0xFF, rxPeriodRaw & 0xFF, + (sleepPeriodRaw >> 16) & 0xFF, (sleepPeriodRaw >> 8) & 0xFF, sleepPeriodRaw & 0xFF + }; + return(SPIwriteCommand(SX126X_CMD_SET_RX_DUTY_CYCLE, data, 6)); +} + +int16_t SX126x::startReceiveDutyCycleAuto(uint16_t senderPreambleLength, uint16_t minSymbols) +{ + if (senderPreambleLength == 0) { + senderPreambleLength = _preambleLength; + } + + // worst case is that the sender starts transmiting when we're just less than minSymbols from going back to sleep. + // in this case, we don't catch minSymbols before going to sleep, + // so we must be awake for at least that long before the sender stops transmitting. + uint16_t sleepSymbols = senderPreambleLength - 2 * minSymbols; + + // if we're not to sleep at all, just use the standard startReceive. + if (2 * minSymbols > senderPreambleLength) { + return(startReceive()); + } + + uint32_t symbolLength_us = ((uint32_t)(10 * 1000) << _sf) / (10 * _bwKhz); + uint32_t sleepPeriod_us = symbolLength_us * sleepSymbols; + RADIOLIB_DEBUG_PRINT(F("Auto sleep period: ")); + RADIOLIB_DEBUG_PRINTLN(sleepPeriod_us); + // when the unit detects a preamble, it starts a timer that will timeout if it doesn't receive a header in time. + // the duration is sleepPeriod + 2 * wakePeriod. + // The sleepPeriod doesn't take into account shutdown and startup time for the unit (~1ms) + // We need to ensure that the timout is longer than senderPreambleLength. + // So we must satisfy: wakePeriod > (preamblePeriod - (sleepPeriod - 1000)) / 2. (A) + // we also need to ensure the unit is awake to see at least minSymbols. (B) + uint32_t wakePeriod_us = max( + (symbolLength_us * (senderPreambleLength + 1) - (sleepPeriod_us - 1000)) / 2, // (A) + symbolLength_us * (minSymbols + 1)); //(B) + RADIOLIB_DEBUG_PRINT(F("Auto wake period: ")); + RADIOLIB_DEBUG_PRINTLN(wakePeriod_us); + + //If our sleep period is shorter than our transition time, just use the standard startReceive + if (sleepPeriod_us < _tcxoDelay_us + 1016) { + return(startReceive()); + } + + return(startReceiveDutyCycle(wakePeriod_us, sleepPeriod_us)); +} + +int16_t SX126x::startReceiveCommon() +{ + // set DIO mapping int16_t state = setDioIrqParams(SX126X_IRQ_RX_DONE | SX126X_IRQ_TIMEOUT | SX126X_IRQ_CRC_ERR | SX126X_IRQ_HEADER_ERR, SX126X_IRQ_RX_DONE); if(state != ERR_NONE) { return(state); @@ -457,13 +541,6 @@ int16_t SX126x::startReceive(uint32_t timeout) { // clear interrupt flags state = clearIrqStatus(); - if(state != ERR_NONE) { - return(state); - } - - // set mode to receive - state = setRx(timeout); - return(state); } @@ -1041,6 +1118,8 @@ int16_t SX126x::setTCXO(float voltage, uint32_t delay) { data[2] = (uint8_t)((delayValue >> 8) & 0xFF); data[3] = (uint8_t)(delayValue & 0xFF); + _tcxoDelay_us = delay; + // enable TCXO control on DIO3 return(SPIwriteCommand(SX126X_CMD_SET_DIO3_AS_TCXO_CTRL, data, 4)); } diff --git a/src/modules/SX126x/SX126x.h b/src/modules/SX126x/SX126x.h index ff25687a..e36ab1b4 100644 --- a/src/modules/SX126x/SX126x.h +++ b/src/modules/SX126x/SX126x.h @@ -499,6 +499,36 @@ class SX126x: public PhysicalLayer { */ int16_t startReceive(uint32_t timeout = SX126X_RX_TIMEOUT_INF); + /*! + \brief Interrupt-driven receive method where the device mostly sleeps and periodically wakes to listen. + + \param rxPeriod_us The duration the receiver will be in Rx mode, in microseconds. + + \param sleepPeriod_us The duration the receiver will not be in Rx mode, in microseconds. + + \returns \ref status_codes + + Note that this function assumes the unit will take 500us to change state. See datasheet [SECTION]. + */ + int16_t startReceiveDutyCycle(uint32_t rxPeriod_us, uint32_t sleepPeriod_us); + + /*! + \brief Calls \ref startReceiveDutyCycle with rxPeriod and sleepPeriod set so the unit shouldn't miss any messages. + + \param senderPreambleLength Expected preamble length of the messages to receive. + If zero, uses the currently configured preamble length. Default zero. + + \param minSymbols Parameters will be chosen to ensure that the unit will catch at least this many symbols of any preamble of the specified length. + Default 3. + + Note that Semtech in the CAD application note state that 2 symbols are fine for CAD, + but in the RX duty cycle note specify that sleep duration should be 8 symbols less than preamble length (corresponding to minSymbols = 4). + Testing suggests that a value of 3 for minSymbols works at high SNR. + + \returns \ref status_codes + */ + int16_t startReceiveDutyCycleAuto(uint16_t senderPreambleLength = 0, uint16_t minSymbols = 3); + /*! \brief Reads data received after calling startReceive method. @@ -747,7 +777,6 @@ class SX126x: public PhysicalLayer { \returns Expected time-on-air in microseconds. */ uint32_t getTimeOnAir(size_t len); - #ifndef RADIOLIB_GODMODE protected: #endif @@ -776,7 +805,8 @@ class SX126x: public PhysicalLayer { uint32_t getPacketStatus(); uint16_t getDeviceErrors(); int16_t clearDeviceErrors(); - + + int16_t startReceiveCommon(); int16_t setFrequencyRaw(float freq); int16_t setOptimalHiPowerPaConfig(int8_t* inOutPower); int16_t setPacketMode(uint8_t mode, uint8_t len); @@ -803,6 +833,8 @@ class SX126x: public PhysicalLayer { float _dataRate; + uint32_t _tcxoDelay_us = 0; + int16_t config(uint8_t modem); // common low-level SPI interface