diff --git a/keywords.txt b/keywords.txt index 71a1a76a..329fcc9d 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 @@ -246,3 +248,5 @@ ERR_INVALID_MODULATION_PARAMETERS LITERAL1 ERR_SPI_CMD_TIMEOUT LITERAL1 ERR_SPI_CMD_INVALID LITERAL1 ERR_SPI_CMD_FAILED LITERAL1 +ERR_INVALID_SLEEP_PERIOD LITERAL1 +ERR_INVALID_RX_PERIOD LITERAL1 diff --git a/src/TypeDef.h b/src/TypeDef.h index c9898a93..c7ab81d7 100644 --- a/src/TypeDef.h +++ b/src/TypeDef.h @@ -524,6 +524,21 @@ */ #define ERR_SPI_CMD_FAILED -707 +/*! + \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, or longer than the maximum possible +*/ +#define ERR_INVALID_SLEEP_PERIOD -708 + +/*! + \brief The supplied Rx period is invalid. + + The specified Rx period is shorter or longer than the hardware can handle. +*/ +#define ERR_INVALID_RX_PERIOD -709 + /*! \} */ diff --git a/src/modules/SX126x/SX126x.cpp b/src/modules/SX126x/SX126x.cpp index b93c890f..f84a15af 100644 --- a/src/modules/SX126x/SX126x.cpp +++ b/src/modules/SX126x/SX126x.cpp @@ -18,6 +18,7 @@ int16_t SX126x::begin(float bw, uint8_t sf, uint8_t cr, uint16_t syncWord, float _ldro = 0x00; _crcType = SX126X_LORA_CRC_ON; _preambleLength = preambleLength; + _tcxoDelay = 0; // set mode to standby int16_t state = standby(); @@ -443,6 +444,87 @@ int16_t SX126x::startTransmit(uint8_t* data, size_t len, uint8_t addr) { } int16_t SX126x::startReceive(uint32_t timeout) { + 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, uint32_t sleepPeriod) { + // datasheet claims time to go to sleep is ~500us, same to wake up, compensate for that with 1 ms + TCXO delay + uint32_t transitionTime = _tcxoDelay + 1000; + sleepPeriod -= transitionTime; + + // divide by 15.625 + uint32_t rxPeriodRaw = (rxPeriod * 8) / 125; + uint32_t sleepPeriodRaw = (sleepPeriod * 8) / 125; + + // check 24 bit limit and zero value (likely not intended) + if((rxPeriodRaw & 0xFF000000) || (rxPeriodRaw == 0)) { + return(ERR_INVALID_RX_PERIOD); + } + + // this check of the high byte also catches underflow when we subtracted transitionTime + if((sleepPeriodRaw & 0xFF000000) || (sleepPeriodRaw == 0)) { + return(ERR_INVALID_SLEEP_PERIOD); + } + + int16_t state = startReceiveCommon(); + if(state != ERR_NONE) { + return(state); + } + + uint8_t 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 = ((uint32_t)(10 * 1000) << _sf) / (10 * _bwKhz); + uint32_t sleepPeriod = symbolLength * sleepSymbols; + RADIOLIB_DEBUG_PRINT(F("Auto sleep period: ")); + RADIOLIB_DEBUG_PRINTLN(sleepPeriod); + + // 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 = max( + (symbolLength * (senderPreambleLength + 1) - (sleepPeriod - 1000)) / 2, // (A) + symbolLength * (minSymbols + 1)); //(B) + RADIOLIB_DEBUG_PRINT(F("Auto wake period: ")); + RADIOLIB_DEBUG_PRINTLN(wakePeriod); + + //If our sleep period is shorter than our transition time, just use the standard startReceive + if(sleepPeriod < _tcxoDelay + 1016) { + return(startReceive()); + } + + return(startReceiveDutyCycle(wakePeriod, sleepPeriod)); +} + +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) { @@ -457,12 +539,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); } @@ -1072,6 +1148,8 @@ int16_t SX126x::setTCXO(float voltage, uint32_t delay) { data[2] = (uint8_t)((delayValue >> 8) & 0xFF); data[3] = (uint8_t)(delayValue & 0xFF); + _tcxoDelay = 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..10e1184d 100644 --- a/src/modules/SX126x/SX126x.h +++ b/src/modules/SX126x/SX126x.h @@ -499,6 +499,31 @@ 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. + Note that this function assumes the unit will take 500us + TCXO_delay to change state. See datasheet section 13.1.7, version 1.2. + + \param rxPeriod The duration the receiver will be in Rx mode, in microseconds. + + \param sleepPeriod The duration the receiver will not be in Rx mode, in microseconds. + + \returns \ref status_codes + */ + int16_t startReceiveDutyCycle(uint32_t rxPeriod, uint32_t sleepPeriod); + + /*! + \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 set to zero, the currently configured preamble length will be used. Defaults to 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. Defaults to 8. + According to Semtech, receiver requires 8 symbols to reliably latch a preamble. This makes this method redundant when transmitter preamble length is less than 17 (2*minSymbols + 1). + + \returns \ref status_codes + */ + int16_t startReceiveDutyCycleAuto(uint16_t senderPreambleLength = 0, uint16_t minSymbols = 8); + /*! \brief Reads data received after calling startReceive method. @@ -747,7 +772,6 @@ class SX126x: public PhysicalLayer { \returns Expected time-on-air in microseconds. */ uint32_t getTimeOnAir(size_t len); - #ifndef RADIOLIB_GODMODE protected: #endif @@ -777,6 +801,7 @@ class SX126x: public PhysicalLayer { 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 +828,8 @@ class SX126x: public PhysicalLayer { float _dataRate; + uint32_t _tcxoDelay; + int16_t config(uint8_t modem); // common low-level SPI interface