diff --git a/RFM69.cpp b/RFM69.cpp index 52501bc..f3a1785 100644 --- a/RFM69.cpp +++ b/RFM69.cpp @@ -47,7 +47,7 @@ RFM69::RFM69(uint8_t slaveSelectPin, uint8_t interruptPin, bool isRFM69HW) _promiscuousMode = false; _powerLevel = 31; _isRFM69HW = isRFM69HW; - #if defined(RF69_LISTENMODE_ENABLE) +#if defined(RF69_LISTENMODE_ENABLE) _isHighSpeed = true; _haveEncryptKey = false; uint32_t rxDuration = DEFAULT_LISTEN_RX_US; @@ -132,6 +132,10 @@ bool RFM69::initialize(uint8_t freqBand, uint8_t nodeID, uint8_t networkID) selfPointer = this; _address = nodeID; +#if defined(RF69_LISTENMODE_ENABLE) + _freqBand = freqBand; + _networkID = networkID; +#endif return true; } @@ -382,6 +386,9 @@ void RFM69::receiveBegin() { PAYLOADLEN = 0; ACK_REQUESTED = 0; ACK_RECEIVED = 0; +#if defined(RF69_LISTENMODE_ENABLE) + RF69_LISTEN_BURST_REMAINING_MS = 0; +#endif RSSI = 0; if (readReg(REG_IRQFLAGS2) & RF_IRQFLAGS2_PAYLOADREADY) writeReg(REG_PACKETCONFIG2, (readReg(REG_PACKETCONFIG2) & 0xFB) | RF_PACKET2_RXRESTART); // avoid RX deadlocks @@ -413,9 +420,15 @@ bool RFM69::receiveDone() { // To disable encryption: radio.encrypt(null) or radio.encrypt(0) // KEY HAS TO BE 16 bytes !!! void RFM69::encrypt(const char* key) { +#if defined(RF69_LISTENMODE_ENABLE) + _haveEncryptKey = key; +#endif setMode(RF69_MODE_STANDBY); if (key != 0) { +#if defined(RF69_LISTENMODE_ENABLE) + memcpy(_encryptKey, key, 16); +#endif select(); SPI.transfer(REG_AESKEY1 | 0x80); for (uint8_t i = 0; i < 16; i++) @@ -843,3 +856,275 @@ inline void RFM69::maybeInterrupts() // Only reenable interrupts if we're not being called from the ISR if (!_inISR) interrupts(); } + +//============================================================================= +// ListenMode specific functions +//============================================================================= +#if defined(RF69_LISTENMODE_ENABLE) +volatile uint16_t RFM69::RF69_LISTEN_BURST_REMAINING_MS = 0; + +//============================================================================= +// reinitRadio() - use base class initialization with saved values +//============================================================================= +bool RFM69::reinitRadio() +{ + if (!initialize(_freqBand, _address, _networkID)) return false; + if (_haveEncryptKey) RFM69::encrypt(_encryptKey); // Restore the encryption key if necessary + if (_isHighSpeed) writeReg(REG_LNA, (readReg(REG_LNA) & ~0x3) | RF_LNA_GAINSELECT_AUTO); + return true; +} + +static uint32_t getUsForResolution(uint8_t resolution) +{ + switch (resolution) { + case RF_LISTEN1_RESOL_RX_64: + case RF_LISTEN1_RESOL_IDLE_64: + return 64; + case RF_LISTEN1_RESOL_RX_4100: + case RF_LISTEN1_RESOL_IDLE_4100: + return 4100; + case RF_LISTEN1_RESOL_RX_262000: + case RF_LISTEN1_RESOL_IDLE_262000: + return 262000; + default: + // Whoops + return 0; + } +} + +static uint32_t getCoefForResolution(uint8_t resolution, uint32_t duration) +{ + uint32_t resolDuration = getUsForResolution(resolution); + uint32_t result = duration / resolDuration; + + // If the next-higher coefficient is closer, use that + if (abs(duration - ((result + 1) * resolDuration)) < abs(duration - (result * resolDuration))) + return result + 1; + + return result; +} + +static bool chooseResolutionAndCoef(uint8_t *resolutions, uint32_t duration, uint8_t& resolOut, uint8_t& coefOut) +{ + for (int i = 0; resolutions[i]; i++) { + uint32_t coef = getCoefForResolution(resolutions[i], duration); + if (coef <= 255) { + coefOut = coef; + resolOut = resolutions[i]; + return true; + } + } + + // out of range + return false; +} + +bool RFM69::listenModeSetDurations(uint32_t& rxDuration, uint32_t& idleDuration) +{ + uint8_t rxResolutions[] = { RF_LISTEN1_RESOL_RX_64, RF_LISTEN1_RESOL_RX_4100, RF_LISTEN1_RESOL_RX_262000, 0 }; + uint8_t idleResolutions[] = { RF_LISTEN1_RESOL_IDLE_64, RF_LISTEN1_RESOL_IDLE_4100, RF_LISTEN1_RESOL_IDLE_262000, 0 }; + + if (!chooseResolutionAndCoef(rxResolutions, rxDuration, _rxListenResolution, _rxListenCoef)) + return false; + + if (!chooseResolutionAndCoef(idleResolutions, idleDuration, _idleListenResolution, _idleListenCoef)) + return false; + + rxDuration = getUsForResolution(_rxListenResolution) * _rxListenCoef; + idleDuration = getUsForResolution(_idleListenResolution) * _idleListenCoef; + _listenCycleDurationUs = rxDuration + idleDuration; + + return true; +} + +void RFM69::listenModeGetDurations(uint32_t &rxDuration, uint32_t &idleDuration) +{ + rxDuration = getUsForResolution(_rxListenResolution) * _rxListenCoef; + idleDuration = getUsForResolution(_idleListenResolution) * _idleListenCoef; +} + +void RFM69::listenModeReset(void) +{ + DATALEN = 0; + SENDERID = 0; + TARGETID = 0; + PAYLOADLEN = 0; + ACK_REQUESTED = 0; + ACK_RECEIVED = 0; + RF69_LISTEN_BURST_REMAINING_MS = 0; +} + +//============================================================================= +// irq handler, simply calls listenModeInterruptHandler method so internal methods can be accessed easily +//============================================================================= +void RFM69::listenModeIrq() { selfPointer->listenModeInterruptHandler(); } + +//============================================================================= +// listenModeInterruptHandler() - only called by listen irq handler +//============================================================================= +void RFM69::listenModeInterruptHandler(void) +{ + if (DATALEN != 0) return; + + listenModeReset(); + noInterrupts(); + select(); + + union // union to simplify addressing of long and short parts of time offset + { + uint32_t l; + uint8_t b[4]; + } burstRemaining; + + burstRemaining.l = 0; + + SPI.transfer(REG_FIFO & 0x7F); + PAYLOADLEN = SPI.transfer(0); + PAYLOADLEN = PAYLOADLEN > 64 ? 64 : PAYLOADLEN; // precaution + TARGETID = SPI.transfer(0); + if(!(_promiscuousMode || TARGETID == _address || TARGETID == RF69_BROADCAST_ADDR) // match this node's address, or broadcast address or anything in promiscuous mode + || PAYLOADLEN < 3) // address situation could receive packets that are malformed and don't fit this library's extra fields + { + listenModeReset(); + goto out; + } + + // We've read the target, and will read the sender id and two time offset bytes for a total of 4 bytes + DATALEN = PAYLOADLEN - 4; + SENDERID = SPI.transfer(0); + burstRemaining.b[0] = SPI.transfer(0); // and get the time remaining + burstRemaining.b[1] = SPI.transfer(0); + RF69_LISTEN_BURST_REMAINING_MS = burstRemaining.l; + + for (uint8_t i = 0; i < DATALEN; i++) + DATA[i] = SPI.transfer(0); + + if (DATALEN < RF69_MAX_DATA_LEN) + DATA[DATALEN] = 0; // add null at end of string + +out: + unselect(); + interrupts(); +} + +//============================================================================= +// listenModeStart() - switch radio to Listen Mode in prep for sleep until burst +//============================================================================= +void RFM69::listenModeStart(void) +{ + //pRadio = this; + while (readReg(REG_IRQFLAGS2) & RF_IRQFLAGS2_PACKETSENT == 0x00); // wait for ModeReady + listenModeReset(); + + detachInterrupt(_interruptNum); + attachInterrupt(_interruptNum, listenModeIrq, RISING); + setMode(RF69_MODE_STANDBY); + writeReg(REG_DIOMAPPING1, RF_DIOMAPPING1_DIO0_01); + writeReg(REG_FRFMSB, readReg(REG_FRFMSB) + 1); + writeReg(REG_FRFLSB, readReg(REG_FRFLSB)); // MUST write to LSB to affect change! + + listenModeApplyHighSpeedSettings(); + + writeReg(REG_PACKETCONFIG1, RF_PACKET1_FORMAT_VARIABLE | RF_PACKET1_DCFREE_WHITENING | RF_PACKET1_CRC_ON | RF_PACKET1_CRCAUTOCLEAR_ON); + writeReg(REG_PACKETCONFIG2, RF_PACKET2_RXRESTARTDELAY_NONE | RF_PACKET2_AUTORXRESTART_ON | RF_PACKET2_AES_OFF); + writeReg(REG_SYNCVALUE1, 0x5A); + writeReg(REG_SYNCVALUE2, 0x5A); + writeReg(REG_LISTEN1, _rxListenResolution | _idleListenResolution | RF_LISTEN1_CRITERIA_RSSI | RF_LISTEN1_END_10); + writeReg(REG_LISTEN2, _idleListenCoef); + writeReg(REG_LISTEN3, _rxListenCoef); + writeReg(REG_RSSITHRESH, 180); + writeReg(REG_RXTIMEOUT2, 75); + writeReg(REG_OPMODE, RF_OPMODE_SEQUENCER_ON | RF_OPMODE_STANDBY); + writeReg(REG_OPMODE, RF_OPMODE_SEQUENCER_ON | RF_OPMODE_LISTEN_ON | RF_OPMODE_STANDBY); +} + +//============================================================================= +// listenModeEnd() - exit listen mode and reinit the radio +//============================================================================= +void RFM69::listenModeEnd(void) +{ + detachInterrupt(_interruptNum); + writeReg(REG_OPMODE, RF_OPMODE_SEQUENCER_ON | RF_OPMODE_LISTENABORT | RF_OPMODE_STANDBY); + writeReg(REG_OPMODE, RF_OPMODE_SEQUENCER_ON | RF_OPMODE_STANDBY); + writeReg(REG_RXTIMEOUT2, 0); + setMode(RF69_MODE_STANDBY); + while ((readReg(REG_IRQFLAGS1) & RF_IRQFLAGS1_MODEREADY) == 0x00); // wait for ModeReady + listenModeReset(); + reinitRadio(); +} + +void RFM69::listenModeApplyHighSpeedSettings() +{ + if (!_isHighSpeed) return; + writeReg(REG_BITRATEMSB, RF_BITRATEMSB_200000); + writeReg(REG_BITRATELSB, RF_BITRATELSB_200000); + writeReg(REG_FDEVMSB, RF_FDEVMSB_100000); + writeReg(REG_FDEVLSB, RF_FDEVLSB_100000); + writeReg( REG_RXBW, RF_RXBW_DCCFREQ_000 | RF_RXBW_MANT_20 | RF_RXBW_EXP_0 ); + + // Force LNA to the highest gain + //writeReg(REG_LNA, (readReg(REG_LNA) << 2) | RF_LNA_GAINSELECT_MAX); +} + +//============================================================================= +// sendBurst() - send a burst of packets to a sleeping listening node (or all) +//============================================================================= +void RFM69::listenModeSendBurst( uint8_t targetNode, void* buffer, uint8_t size ) +{ + detachInterrupt(_interruptNum); + setMode(RF69_MODE_STANDBY); + writeReg(REG_PACKETCONFIG1, RF_PACKET1_FORMAT_VARIABLE | RF_PACKET1_DCFREE_WHITENING | RF_PACKET1_CRC_ON | RF_PACKET1_CRCAUTOCLEAR_ON ); + writeReg(REG_PACKETCONFIG2, RF_PACKET2_RXRESTARTDELAY_NONE | RF_PACKET2_AUTORXRESTART_ON | RF_PACKET2_AES_OFF); + writeReg(REG_SYNCVALUE1, 0x5A); + writeReg(REG_SYNCVALUE2, 0x5A); + listenModeApplyHighSpeedSettings(); + writeReg(REG_FRFMSB, readReg(REG_FRFMSB) + 1); + writeReg(REG_FRFLSB, readReg(REG_FRFLSB)); // MUST write to LSB to affect change! + + union // union to simplify addressing of long and short parts of time offset + { + int32_t l; + uint8_t b[4]; + } timeRemaining; + + uint16_t cycleDurationMs = _listenCycleDurationUs / 1000; + timeRemaining.l = cycleDurationMs; + +#ifdef RF69_WL_DEBUG + Serial.print("Sending burst for "); + Serial.print(cycleDurationMs, DEC); + Serial.println(" ms"); +#endif + + setMode(RF69_MODE_TX); + uint32_t numSent = 0; + uint32_t startTime = millis(); + + while(timeRemaining.l > 0) { + noInterrupts(); + // write to FIFO + select(); + SPI.transfer(REG_FIFO | 0x80); + SPI.transfer(size + 4); // two bytes for target and sender node, two bytes for the burst time remaining + SPI.transfer(targetNode); + SPI.transfer(_address); + + // We send the burst time remaining with the packet so the receiver knows how long to wait before trying to reply + SPI.transfer(timeRemaining.b[0]); + SPI.transfer(timeRemaining.b[1]); + + for (uint8_t i = 0; i < size; i++) { + SPI.transfer(((uint8_t*) buffer)[i]); + } + + unselect(); + interrupts(); + + while ((readReg(REG_IRQFLAGS2) & RF_IRQFLAGS2_FIFONOTEMPTY) != 0x00); // make sure packet is sent before putting more into the FIFO + timeRemaining.l = cycleDurationMs - (millis() - startTime); + } + + setMode(RF69_MODE_STANDBY); + reinitRadio(); +} +#endif \ No newline at end of file diff --git a/RFM69.h b/RFM69.h index d07893e..492c95e 100644 --- a/RFM69.h +++ b/RFM69.h @@ -150,6 +150,14 @@ #define RFM69_CTL_SENDACK 0x80 #define RFM69_CTL_REQACK 0x40 +//#define RF69_LISTENMODE_ENABLE //comment this line out to compile sketches without the ListenMode (saves ~2k) + +#if defined(RF69_LISTENMODE_ENABLE) + // By default, receive for 256uS in listen mode and idle for ~1s + #define DEFAULT_LISTEN_RX_US 256 + #define DEFAULT_LISTEN_IDLE_US 1000000 +#endif + class RFM69 { public: static volatile uint8_t DATA[RF69_MAX_DATA_LEN]; // recv/xmit buf, including header & crc bytes @@ -221,6 +229,55 @@ class RFM69 { virtual void select(); virtual void unselect(); inline void maybeInterrupts(); + +#if defined(RF69_LISTENMODE_ENABLE) + //============================================================================= + // ListenMode specific declarations + //============================================================================= + public: + // When we receive a packet in listen mode, this is the time left in the sender's burst. + // You need to wait at least this long before trying to reply. + static volatile uint16_t RF69_LISTEN_BURST_REMAINING_MS; + + void listenModeStart(void); + void listenModeEnd(void); + void listenModeHighSpeed(bool highSpeed) { _isHighSpeed = highSpeed; } + + // rx and idle duration in microseconds + bool listenModeSetDurations(uint32_t& rxDuration, uint32_t& idleDuration); + + // The values passed to listenModeSetDurations() may be slightly different to accomodate + // what is allowed by the radio. This function returns the actual values used. + void listenModeGetDurations(uint32_t& rxDuration, uint32_t& idleDuration); + + // This repeatedly sends the message to the target node for the duration + // of an entire listen cycle. The amount of time remaining in the burst + // is transmitted to the receiver, and it is expected that the receiver + // wait for the burst to end before attempting a reply. + // See RF69_LISTEN_BURST_REMAINING_MS above. + void listenModeSendBurst(uint8_t targetNode, void* buffer, uint8_t size); + + protected: + void listenModeInterruptHandler(void); + void listenModeApplyHighSpeedSettings(); + void listenModeReset(); //resets variables used on the receiving end + bool reinitRadio(void); + static void listenModeIrq(); + + bool _isHighSpeed; + bool _haveEncryptKey; + char _encryptKey[16]; + + // Save these so we can reinitialize the radio after sending a burst + // or exiting listen mode. + uint8_t _freqBand; + uint8_t _networkID; + uint8_t _rxListenCoef; + uint8_t _rxListenResolution; + uint8_t _idleListenCoef; + uint8_t _idleListenResolution; + uint32_t _listenCycleDurationUs; +#endif }; -#endif +#endif \ No newline at end of file diff --git a/keywords.txt b/keywords.txt index 189e8b6..e5fb82f 100644 --- a/keywords.txt +++ b/keywords.txt @@ -77,3 +77,4 @@ PAYLOADLEN LITERAL2 ACK_REQUESTED LITERAL2 ACK_RECEIVED LITERAL2 RSSI LITERAL2 +RF69_LISTEN_BURST_REMAINING_MS LITERAL2 \ No newline at end of file