From 6fa4aa3ebbbb332a0f3d3b44b043f5f8f57a3cc5 Mon Sep 17 00:00:00 2001 From: jgromes Date: Fri, 19 Apr 2024 20:30:53 +0200 Subject: [PATCH] [LR11x0] Added GFSK modem support (#679) --- .../LR11x0_GFSK_Modem/LR11x0_GFSK_Modem.ino | 153 ++++++ src/modules/LR11x0/LR11x0.cpp | 449 +++++++++++++++++- src/modules/LR11x0/LR11x0.h | 131 ++++- 3 files changed, 698 insertions(+), 35 deletions(-) create mode 100644 examples/LR11x0/LR11x0_GFSK_Modem/LR11x0_GFSK_Modem.ino diff --git a/examples/LR11x0/LR11x0_GFSK_Modem/LR11x0_GFSK_Modem.ino b/examples/LR11x0/LR11x0_GFSK_Modem/LR11x0_GFSK_Modem.ino new file mode 100644 index 00000000..66e4090d --- /dev/null +++ b/examples/LR11x0/LR11x0_GFSK_Modem/LR11x0_GFSK_Modem.ino @@ -0,0 +1,153 @@ +/* + RadioLib LR11x0 GFSK Modem Example + + This example shows how to use GFSK modem in LR11x0 chips. + + NOTE: The sketch below is just a guide on how to use + GFSK modem, so this code should not be run directly! + Instead, modify the other examples to use GFSK + modem and use the appropriate configuration + methods. + + For default module settings, see the wiki page + https://github.com/jgromes/RadioLib/wiki/Default-configuration#lr11x0---gfsk-modem + + For full API reference, see the GitHub Pages + https://jgromes.github.io/RadioLib/ +*/ + +// include the library +#include + +// LR1110 has the following connections: +// NSS pin: 10 +// DIO1 pin: 2 +// NRST pin: 3 +// BUSY pin: 9 +LR1110 radio = new Module(10, 2, 3, 9); + +// or using RadioShield +// https://github.com/jgromes/RadioShield +//LR1110 radio = RadioShield.ModuleA; + +void setup() { + Serial.begin(9600); + + // initialize LR1110 with default settings + Serial.print(F("[LR1110] Initializing ... ")); + int state = radio.beginGFSK(); + if (state == RADIOLIB_ERR_NONE) { + Serial.println(F("success!")); + } else { + Serial.print(F("failed, code ")); + Serial.println(state); + while (true); + } + + // if needed, you can switch between any of the modems + // + // radio.begin() start LoRa modem (and disable GFSK) + // radio.beginGFSK() start GFSK modem (and disable LoRa) + + // the following settings can also + // be modified at run-time + state = radio.setFrequency(433.5); + state = radio.setBitRate(100.0); + state = radio.setFrequencyDeviation(10.0); + state = radio.setRxBandwidth(250.0); + state = radio.setOutputPower(10.0); + state = radio.setDataShaping(RADIOLIB_SHAPING_1_0); + uint8_t syncWord[] = {0x01, 0x23, 0x45, 0x67, + 0x89, 0xAB, 0xCD, 0xEF}; + state = radio.setSyncWord(syncWord, 8); + if (state != RADIOLIB_ERR_NONE) { + Serial.print(F("Unable to set configuration, code ")); + Serial.println(state); + while (true); + } + + // GFSK modem on LR11x0 can handle the sync word setting in bits, not just + // whole bytes. The value used is left-justified. + // This makes same result as radio.setSyncWord(syncWord, 8): + state = radio.setSyncBits(syncWord, 64); + // This will use 0x012 as sync word (12 bits only): + state = radio.setSyncBits(syncWord, 12); + + // GFSK modem allows advanced CRC configuration + // Default is CCIT CRC16 (2 bytes, initial 0x1D0F, polynomial 0x1021, inverted) + // Set CRC to IBM CRC (2 bytes, initial 0xFFFF, polynomial 0x8005, non-inverted) + state = radio.setCRC(2, 0xFFFF, 0x8005, false); + // set CRC length to 0 to disable CRC + + #warning "This sketch is just an API guide! Read the note at line 6." +} + +void loop() { + // GFSK modem can use the same transmit/receive methods + // as the LoRa modem, even their interrupt-driven versions + + // transmit GFSK packet + int state = radio.transmit("Hello World!"); + /* + byte byteArr[] = {0x01, 0x23, 0x45, 0x67, + 0x89, 0xAB, 0xCD, 0xEF}; + int state = radio.transmit(byteArr, 8); + */ + if (state == RADIOLIB_ERR_NONE) { + Serial.println(F("[LR1110] Packet transmitted successfully!")); + } else if (state == RADIOLIB_ERR_PACKET_TOO_LONG) { + Serial.println(F("[LR1110] Packet too long!")); + } else if (state == RADIOLIB_ERR_TX_TIMEOUT) { + Serial.println(F("[LR1110] Timed out while transmitting!")); + } else { + Serial.println(F("[LR1110] Failed to transmit packet, code ")); + Serial.println(state); + } + + // receive GFSK packet + String str; + state = radio.receive(str); + /* + byte byteArr[8]; + int state = radio.receive(byteArr, 8); + */ + if (state == RADIOLIB_ERR_NONE) { + Serial.println(F("[LR1110] Received packet!")); + Serial.print(F("[LR1110] Data:\t")); + Serial.println(str); + } else if (state == RADIOLIB_ERR_RX_TIMEOUT) { + Serial.println(F("[LR1110] Timed out while waiting for packet!")); + } else { + Serial.print(F("[LR1110] Failed to receive packet, code ")); + Serial.println(state); + } + + // GFSK modem has built-in address filtering system + // it can be enabled by setting node address, broadcast + // address, or both + // + // to transmit packet to a particular address, + // use the following methods: + // + // radio.transmit("Hello World!", address); + // radio.startTransmit("Hello World!", address); + + // set node address to 0x02 + state = radio.setNodeAddress(0x02); + // set broadcast address to 0xFF + state = radio.setBroadcastAddress(0xFF); + if (state != RADIOLIB_ERR_NONE) { + Serial.println(F("[LR1110] Unable to set address filter, code ")); + Serial.println(state); + } + + // address filtering can also be disabled + // NOTE: calling this method will also erase previously set + // node and broadcast address + /* + state = radio.disableAddressFiltering(); + if (state != RADIOLIB_ERR_NONE) { + Serial.println(F("Unable to remove address filter, code ")); + } + */ +} diff --git a/src/modules/LR11x0/LR11x0.cpp b/src/modules/LR11x0/LR11x0.cpp index ef1043fe..5c05e4c0 100644 --- a/src/modules/LR11x0/LR11x0.cpp +++ b/src/modules/LR11x0/LR11x0.cpp @@ -109,15 +109,48 @@ int16_t LR11x0::beginGFSK(float br, float freqDev, float rxBw, uint16_t preamble // set mode to standby int16_t state = standby(); RADIOLIB_ASSERT(state); - - // TODO implement GFSK - (void)br; - (void)freqDev; - (void)rxBw; - (void)preambleLength; - (void)tcxoVoltage; - return(RADIOLIB_ERR_UNSUPPORTED); + // set TCXO control, if requested + if(!this->XTAL && tcxoVoltage > 0.0) { + state = setTCXO(tcxoVoltage); + RADIOLIB_ASSERT(state); + } + + // configure settings not accessible by API + state = config(RADIOLIB_LR11X0_PACKET_TYPE_GFSK); + RADIOLIB_ASSERT(state); + + // configure publicly accessible settings + state = setBitRate(br); + RADIOLIB_ASSERT(state); + + state = setFrequencyDeviation(freqDev); + RADIOLIB_ASSERT(state); + + state = setRxBandwidth(rxBw); + RADIOLIB_ASSERT(state); + + state = setPreambleLength(preambleLength); + RADIOLIB_ASSERT(state); + + // set publicly accessible settings that are not a part of begin method + uint8_t sync[] = { 0x12, 0xAD }; + state = setSyncWord(sync, 2); + RADIOLIB_ASSERT(state); + + state = setDataShaping(RADIOLIB_SHAPING_NONE); + RADIOLIB_ASSERT(state); + + state = setEncoding(RADIOLIB_ENCODING_NRZ); + RADIOLIB_ASSERT(state); + + state = variablePacketLengthMode(RADIOLIB_LR11X0_MAX_PACKET_LENGTH); + RADIOLIB_ASSERT(state); + + state = setCRC(2); + RADIOLIB_ASSERT(state); + + return(RADIOLIB_ERR_NONE); } int16_t LR11x0::reset() { @@ -267,12 +300,12 @@ int16_t LR11x0::standby(uint8_t mode, bool wakeup) { // set RF switch (if present) this->mod->setRfSwitchState(Module::MODE_IDLE); - // TODO this will block BUSY forever - (void)wakeup; - /*if(wakeup) { - // pull NSS low to wake up + if(wakeup) { + // pull NSS low for a while to wake up this->mod->hal->digitalWrite(this->mod->getCs(), this->mod->hal->GpioLevelLow); - }*/ + this->mod->hal->delay(1); + this->mod->hal->digitalWrite(this->mod->getCs(), this->mod->hal->GpioLevelHigh); + } uint8_t buff[] = { mode }; return(this->SPIcommand(RADIOLIB_LR11X0_CMD_SET_STANDBY, true, buff, 1)); @@ -577,6 +610,327 @@ int16_t LR11x0::setSyncWord(uint8_t syncWord) { return(setLoRaSyncWord(syncWord)); } +int16_t LR11x0::setBitRate(float br) { + RADIOLIB_CHECK_RANGE(br, 0.6, 300.0, RADIOLIB_ERR_INVALID_BIT_RATE); + + // check active modem + uint8_t type = RADIOLIB_LR11X0_PACKET_TYPE_NONE; + int16_t state = getPacketType(&type); + RADIOLIB_ASSERT(state); + if(type != RADIOLIB_LR11X0_PACKET_TYPE_GFSK) { + return(RADIOLIB_ERR_WRONG_MODEM); + } + + // set bit rate value + // TODO implement fractional bit rate configuration + this->bitRate = br * 1000.0; + return(setModulationParamsGFSK(this->bitRate, this->pulseShape, this->rxBandwidth, this->frequencyDev)); +} + +int16_t LR11x0::setFrequencyDeviation(float freqDev) { + // check active modem + uint8_t type = RADIOLIB_LR11X0_PACKET_TYPE_NONE; + int16_t state = getPacketType(&type); + RADIOLIB_ASSERT(state); + if(type != RADIOLIB_LR11X0_PACKET_TYPE_GFSK) { + return(RADIOLIB_ERR_WRONG_MODEM); + } + + // set frequency deviation to lowest available setting (required for digimodes) + float newFreqDev = freqDev; + if(freqDev < 0.0) { + newFreqDev = 0.6; + } + + RADIOLIB_CHECK_RANGE(newFreqDev, 0.6, 200.0, RADIOLIB_ERR_INVALID_FREQUENCY_DEVIATION); + this->frequencyDev = freqDev * 1000.0; + return(setModulationParamsGFSK(this->bitRate, this->pulseShape, this->rxBandwidth, this->frequencyDev)); +} + +int16_t LR11x0::setRxBandwidth(float rxBw) { + // check active modem + uint8_t type = RADIOLIB_LR11X0_PACKET_TYPE_NONE; + int16_t state = getPacketType(&type); + RADIOLIB_ASSERT(state); + if(type != RADIOLIB_LR11X0_PACKET_TYPE_GFSK) { + return(RADIOLIB_ERR_WRONG_MODEM); + } + + // check modulation parameters + /*if(2 * this->frequencyDev + this->bitRate > rxBw * 1000.0) { + return(RADIOLIB_ERR_INVALID_MODULATION_PARAMETERS); + }*/ + + // check allowed receiver bandwidth values + if(fabs(rxBw - 4.8) <= 0.001) { + this->rxBandwidth = RADIOLIB_LR11X0_GFSK_RX_BW_4_8; + } else if(fabs(rxBw - 5.8) <= 0.001) { + this->rxBandwidth = RADIOLIB_LR11X0_GFSK_RX_BW_5_8; + } else if(fabs(rxBw - 7.3) <= 0.001) { + this->rxBandwidth = RADIOLIB_LR11X0_GFSK_RX_BW_7_3; + } else if(fabs(rxBw - 9.7) <= 0.001) { + this->rxBandwidth = RADIOLIB_LR11X0_GFSK_RX_BW_9_7; + } else if(fabs(rxBw - 11.7) <= 0.001) { + this->rxBandwidth = RADIOLIB_LR11X0_GFSK_RX_BW_11_7; + } else if(fabs(rxBw - 14.6) <= 0.001) { + this->rxBandwidth = RADIOLIB_LR11X0_GFSK_RX_BW_14_6; + } else if(fabs(rxBw - 19.5) <= 0.001) { + this->rxBandwidth = RADIOLIB_LR11X0_GFSK_RX_BW_19_5; + } else if(fabs(rxBw - 23.4) <= 0.001) { + this->rxBandwidth = RADIOLIB_LR11X0_GFSK_RX_BW_23_4; + } else if(fabs(rxBw - 29.3) <= 0.001) { + this->rxBandwidth = RADIOLIB_LR11X0_GFSK_RX_BW_29_3; + } else if(fabs(rxBw - 39.0) <= 0.001) { + this->rxBandwidth = RADIOLIB_LR11X0_GFSK_RX_BW_39_0; + } else if(fabs(rxBw - 46.9) <= 0.001) { + this->rxBandwidth = RADIOLIB_LR11X0_GFSK_RX_BW_46_9; + } else if(fabs(rxBw - 58.6) <= 0.001) { + this->rxBandwidth = RADIOLIB_LR11X0_GFSK_RX_BW_58_6; + } else if(fabs(rxBw - 78.2) <= 0.001) { + this->rxBandwidth = RADIOLIB_LR11X0_GFSK_RX_BW_78_2; + } else if(fabs(rxBw - 93.8) <= 0.001) { + this->rxBandwidth = RADIOLIB_LR11X0_GFSK_RX_BW_93_8; + } else if(fabs(rxBw - 117.3) <= 0.001) { + this->rxBandwidth = RADIOLIB_LR11X0_GFSK_RX_BW_117_3; + } else if(fabs(rxBw - 156.2) <= 0.001) { + this->rxBandwidth = RADIOLIB_LR11X0_GFSK_RX_BW_156_2; + } else if(fabs(rxBw - 187.2) <= 0.001) { + this->rxBandwidth = RADIOLIB_LR11X0_GFSK_RX_BW_187_2; + } else if(fabs(rxBw - 234.3) <= 0.001) { + this->rxBandwidth = RADIOLIB_LR11X0_GFSK_RX_BW_234_3; + } else if(fabs(rxBw - 312.0) <= 0.001) { + this->rxBandwidth = RADIOLIB_LR11X0_GFSK_RX_BW_312_0; + } else if(fabs(rxBw - 373.6) <= 0.001) { + this->rxBandwidth = RADIOLIB_LR11X0_GFSK_RX_BW_373_6; + } else if(fabs(rxBw - 467.0) <= 0.001) { + this->rxBandwidth = RADIOLIB_LR11X0_GFSK_RX_BW_467_0; + } else { + return(RADIOLIB_ERR_INVALID_RX_BANDWIDTH); + } + + // update modulation parameters + return(setModulationParamsGFSK(this->bitRate, this->pulseShape, this->rxBandwidth, this->frequencyDev)); +} + +int16_t LR11x0::setSyncWord(uint8_t* syncWord, size_t len) { + if((!syncWord) || (!len) || (len > RADIOLIB_LR11X0_GFSK_SYNC_WORD_LEN)) { + return(RADIOLIB_ERR_INVALID_SYNC_WORD); + } + + // check active modem + uint8_t type = RADIOLIB_LR11X0_PACKET_TYPE_NONE; + int16_t state = getPacketType(&type); + RADIOLIB_ASSERT(state); + if(type != RADIOLIB_LR11X0_PACKET_TYPE_GFSK) { + return(RADIOLIB_ERR_WRONG_MODEM); + } + + // update sync word length + this->syncWordLength = len*8; + state = setPacketParamsGFSK(this->preambleLengthGFSK, this->preambleDetLength, this->syncWordLength, this->addrComp, this->packetType, RADIOLIB_LR11X0_MAX_PACKET_LENGTH, this->crcTypeGFSK, this->whitening); + RADIOLIB_ASSERT(state); + + // sync word is passed most-significant byte first + uint8_t fullSyncWord[RADIOLIB_LR11X0_GFSK_SYNC_WORD_LEN] = { 0 }; + memcpy(fullSyncWord, syncWord, len); + return(setGfskSyncWord(fullSyncWord)); +} + +int16_t LR11x0::setSyncBits(uint8_t *syncWord, uint8_t bitsLen) { + if((!syncWord) || (!bitsLen) || (bitsLen > 8*RADIOLIB_LR11X0_GFSK_SYNC_WORD_LEN)) { + return(RADIOLIB_ERR_INVALID_SYNC_WORD); + } + + // check active modem + uint8_t type = RADIOLIB_LR11X0_PACKET_TYPE_NONE; + int16_t state = getPacketType(&type); + RADIOLIB_ASSERT(state); + if(type != RADIOLIB_LR11X0_PACKET_TYPE_GFSK) { + return(RADIOLIB_ERR_WRONG_MODEM); + } + + uint8_t bytesLen = bitsLen / 8; + if ((bitsLen % 8) != 0) { + bytesLen++; + } + + return(setSyncWord(syncWord, bytesLen)); +} + +int16_t LR11x0::setNodeAddress(uint8_t nodeAddr) { + // check active modem + uint8_t type = RADIOLIB_LR11X0_PACKET_TYPE_NONE; + int16_t state = getPacketType(&type); + RADIOLIB_ASSERT(state); + if(type != RADIOLIB_LR11X0_PACKET_TYPE_GFSK) { + return(RADIOLIB_ERR_WRONG_MODEM); + } + + // enable address filtering (node only) + this->addrComp = RADIOLIB_LR11X0_GFSK_ADDR_FILTER_NODE; + state = setPacketParamsGFSK(this->preambleLengthGFSK, this->preambleDetLength, this->syncWordLength, this->addrComp, this->packetType, RADIOLIB_LR11X0_MAX_PACKET_LENGTH, this->crcTypeGFSK, this->whitening); + RADIOLIB_ASSERT(state); + + // set node address + this->node = nodeAddr; + return(setPacketAdrs(this->node, 0)); +} + +int16_t LR11x0::setBroadcastAddress(uint8_t broadAddr) { + // check active modem + uint8_t type = RADIOLIB_LR11X0_PACKET_TYPE_NONE; + int16_t state = getPacketType(&type); + RADIOLIB_ASSERT(state); + if(type != RADIOLIB_LR11X0_PACKET_TYPE_GFSK) { + return(RADIOLIB_ERR_WRONG_MODEM); + } + + // enable address filtering (node and broadcast) + this->addrComp = RADIOLIB_LR11X0_GFSK_ADDR_FILTER_NODE_BROADCAST; + state = setPacketParamsGFSK(this->preambleLengthGFSK, this->preambleDetLength, this->syncWordLength, this->addrComp, this->packetType, RADIOLIB_LR11X0_MAX_PACKET_LENGTH, this->crcTypeGFSK, this->whitening); + RADIOLIB_ASSERT(state); + + // set node and broadcast address + return(setPacketAdrs(this->node, broadAddr)); +} + +int16_t LR11x0::disableAddressFiltering() { + // check active modem + uint8_t type = RADIOLIB_LR11X0_PACKET_TYPE_NONE; + int16_t state = getPacketType(&type); + RADIOLIB_ASSERT(state); + if(type != RADIOLIB_LR11X0_PACKET_TYPE_GFSK) { + return(RADIOLIB_ERR_WRONG_MODEM); + } + + // disable address filterin + this->addrComp = RADIOLIB_LR11X0_GFSK_ADDR_FILTER_DISABLED; + return(setPacketParamsGFSK(this->preambleLengthGFSK, this->preambleDetLength, this->syncWordLength, this->addrComp, this->packetType, RADIOLIB_LR11X0_MAX_PACKET_LENGTH, this->crcTypeGFSK, this->whitening)); +} + +int16_t LR11x0::setDataShaping(uint8_t sh) { + // check active modem + uint8_t type = RADIOLIB_LR11X0_PACKET_TYPE_NONE; + int16_t state = getPacketType(&type); + RADIOLIB_ASSERT(state); + if(type != RADIOLIB_LR11X0_PACKET_TYPE_GFSK) { + return(RADIOLIB_ERR_WRONG_MODEM); + } + + // set data shaping + switch(sh) { + case RADIOLIB_SHAPING_NONE: + this->pulseShape = RADIOLIB_LR11X0_GFSK_SHAPING_NONE; + break; + case RADIOLIB_SHAPING_0_3: + this->pulseShape = RADIOLIB_LR11X0_GFSK_SHAPING_GAUSSIAN_BT_0_3; + break; + case RADIOLIB_SHAPING_0_5: + this->pulseShape = RADIOLIB_LR11X0_GFSK_SHAPING_GAUSSIAN_BT_0_5; + break; + case RADIOLIB_SHAPING_0_7: + this->pulseShape = RADIOLIB_LR11X0_GFSK_SHAPING_GAUSSIAN_BT_0_7; + break; + case RADIOLIB_SHAPING_1_0: + this->pulseShape = RADIOLIB_LR11X0_GFSK_SHAPING_GAUSSIAN_BT_1_0; + break; + default: + return(RADIOLIB_ERR_INVALID_DATA_SHAPING); + } + + // update modulation parameters + return(setModulationParamsGFSK(this->bitRate, this->pulseShape, this->rxBandwidth, this->frequencyDev)); +} + +int16_t LR11x0::setEncoding(uint8_t encoding) { + return(setWhitening(encoding)); +} + +int16_t LR11x0::fixedPacketLengthMode(uint8_t len) { + return(setPacketMode(RADIOLIB_LR11X0_GFSK_PACKET_LENGTH_FIXED, len)); +} + +int16_t LR11x0::variablePacketLengthMode(uint8_t maxLen) { + return(setPacketMode(RADIOLIB_LR11X0_GFSK_PACKET_LENGTH_VARIABLE, maxLen)); +} + +int16_t LR11x0::setWhitening(bool enabled, uint16_t initial) { + // check active modem + uint8_t type = RADIOLIB_LR11X0_PACKET_TYPE_NONE; + int16_t state = getPacketType(&type); + RADIOLIB_ASSERT(state); + if(type != RADIOLIB_LR11X0_PACKET_TYPE_GFSK) { + return(RADIOLIB_ERR_WRONG_MODEM); + } + + if(!enabled) { + // disable whitening + this->whitening = RADIOLIB_LR11X0_GFSK_WHITENING_DISABLED; + + } else { + // enable whitening + this->whitening = RADIOLIB_LR11X0_GFSK_WHITENING_ENABLED; + + // write initial whitening value + state = setGfskWhitParams(initial); + RADIOLIB_ASSERT(state); + } + + return(setPacketParamsGFSK(this->preambleLengthGFSK, this->preambleDetLength, this->syncWordLength, this->addrComp, this->packetType, RADIOLIB_LR11X0_MAX_PACKET_LENGTH, this->crcTypeGFSK, this->whitening)); +} + +int16_t LR11x0::setDataRate(DataRate_t dr) { + // select interpretation based on active modem + uint8_t type = RADIOLIB_LR11X0_PACKET_TYPE_NONE; + int16_t state = getPacketType(&type); + RADIOLIB_ASSERT(state); + + if(type == RADIOLIB_LR11X0_PACKET_TYPE_GFSK) { + // set the bit rate + state = this->setBitRate(dr.fsk.bitRate); + RADIOLIB_ASSERT(state); + + // set the frequency deviation + state = this->setFrequencyDeviation(dr.fsk.freqDev); + + } else if(type == RADIOLIB_LR11X0_PACKET_TYPE_LORA) { + // set the spreading factor + state = this->setSpreadingFactor(dr.lora.spreadingFactor); + RADIOLIB_ASSERT(state); + + // set the bandwidth + state = this->setBandwidth(dr.lora.bandwidth); + RADIOLIB_ASSERT(state); + + // set the coding rate + state = this->setCodingRate(dr.lora.codingRate); + } + + return(state); +} + +int16_t LR11x0::checkDataRate(DataRate_t dr) { + // select interpretation based on active modem + uint8_t type = RADIOLIB_LR11X0_PACKET_TYPE_NONE; + int16_t state = getPacketType(&type); + RADIOLIB_ASSERT(state); + + if(type == RADIOLIB_LR11X0_PACKET_TYPE_GFSK) { + RADIOLIB_CHECK_RANGE(dr.fsk.bitRate, 0.6, 300.0, RADIOLIB_ERR_INVALID_BIT_RATE); + RADIOLIB_CHECK_RANGE(dr.fsk.freqDev, 0.6, 200.0, RADIOLIB_ERR_INVALID_FREQUENCY_DEVIATION); + return(RADIOLIB_ERR_NONE); + + } else if(type == RADIOLIB_LR11X0_PACKET_TYPE_LORA) { + RADIOLIB_CHECK_RANGE(dr.lora.spreadingFactor, 5, 12, RADIOLIB_ERR_INVALID_SPREADING_FACTOR); + RADIOLIB_CHECK_RANGE(dr.lora.bandwidth, 0.0, 510.0, RADIOLIB_ERR_INVALID_BANDWIDTH); + RADIOLIB_CHECK_RANGE(dr.lora.codingRate, 5, 8, RADIOLIB_ERR_INVALID_CODING_RATE); + return(RADIOLIB_ERR_NONE); + + } + + return(RADIOLIB_ERR_UNKNOWN); +} + int16_t LR11x0::setPreambleLength(size_t preambleLength) { // check active modem uint8_t type = RADIOLIB_LR11X0_PACKET_TYPE_NONE; @@ -585,6 +939,10 @@ int16_t LR11x0::setPreambleLength(size_t preambleLength) { if(type == RADIOLIB_LR11X0_PACKET_TYPE_LORA) { this->preambleLengthLoRa = preambleLength; return(setPacketParamsLoRa(this->preambleLengthLoRa, this->headerType, this->implicitLen, this->crcTypeLoRa, (uint8_t)this->invertIQEnabled)); + } else if(type == RADIOLIB_LR11X0_PACKET_TYPE_GFSK) { + this->preambleLengthGFSK = preambleLength; + this->preambleDetLength = RADIOLIB_LR11X0_GFSK_PREAMBLE_DETECT_16_BITS; + return(setPacketParamsGFSK(this->preambleLengthGFSK, this->preambleDetLength, this->syncWordLength, this->addrComp, this->packetType, RADIOLIB_LR11X0_MAX_PACKET_LENGTH, this->crcTypeGFSK, this->whitening)); } return(RADIOLIB_ERR_WRONG_MODEM); @@ -645,7 +1003,7 @@ int16_t LR11x0::setTCXO(float voltage, uint32_t delay) { return(setTcxoMode(tune, delayValue)); } -int16_t LR11x0::setCRC(uint8_t len, uint16_t initial, uint16_t polynomial, bool inverted) { +int16_t LR11x0::setCRC(uint8_t len, uint32_t initial, uint32_t polynomial, bool inverted) { // check active modem uint8_t type = RADIOLIB_LR11X0_PACKET_TYPE_NONE; int16_t state = getPacketType(&type); @@ -653,18 +1011,40 @@ int16_t LR11x0::setCRC(uint8_t len, uint16_t initial, uint16_t polynomial, bool if(type == RADIOLIB_LR11X0_PACKET_TYPE_LORA) { // LoRa CRC doesn't allow to set CRC polynomial, initial value, or inversion this->crcTypeLoRa = len > 0 ? RADIOLIB_LR11X0_LORA_CRC_ENABLED : RADIOLIB_LR11X0_LORA_CRC_DISABLED; - return(setPacketParamsLoRa(this->preambleLengthLoRa, this->crcTypeLoRa, this->implicitLen, this->headerType, (uint8_t)this->invertIQEnabled)); + state = setPacketParamsLoRa(this->preambleLengthLoRa, this->crcTypeLoRa, this->implicitLen, this->headerType, (uint8_t)this->invertIQEnabled); } else if(type == RADIOLIB_LR11X0_PACKET_TYPE_GFSK) { - // TODO add GFSK support - (void)initial; - (void)polynomial; - (void)inverted; - return(RADIOLIB_ERR_UNSUPPORTED); + // update packet parameters + switch(len) { + case 0: + this->crcTypeGFSK = RADIOLIB_LR11X0_GFSK_CRC_DISABLED; + break; + case 1: + if(inverted) { + this->crcTypeGFSK = RADIOLIB_LR11X0_GFSK_CRC_1_BYTE_INV; + } else { + this->crcTypeGFSK = RADIOLIB_LR11X0_GFSK_CRC_1_BYTE; + } + break; + case 2: + if(inverted) { + this->crcTypeGFSK = RADIOLIB_LR11X0_GFSK_CRC_2_BYTE_INV; + } else { + this->crcTypeGFSK = RADIOLIB_LR11X0_GFSK_CRC_2_BYTE; + } + break; + default: + return(RADIOLIB_ERR_INVALID_CRC_CONFIGURATION); + } + + state = setPacketParamsGFSK(this->preambleLengthGFSK, this->preambleDetLength, this->syncWordLength, this->addrComp, this->packetType, RADIOLIB_LR11X0_MAX_PACKET_LENGTH, this->crcTypeGFSK, this->whitening); + RADIOLIB_ASSERT(state); + + state = setGfskCrcParams(initial, polynomial); } - return(RADIOLIB_ERR_WRONG_MODEM); + return(state); } int16_t LR11x0::invertIQ(bool enable) { @@ -793,7 +1173,7 @@ uint32_t LR11x0::getTimeOnAir(size_t len) { return(((uint32_t(1) << this->spreadingFactor) / this->bandwidthKhz) * N_symbol * 1000.0); } else if(type == RADIOLIB_LR11X0_PACKET_TYPE_GFSK) { - return(((uint32_t)len * 8 * 1000) / this->bitRateKbps); + return(((uint32_t)len * 8 * 1000000UL) / this->bitRate); } @@ -911,6 +1291,24 @@ int16_t LR11x0::config(uint8_t modem) { return(state); } +int16_t LR11x0::setPacketMode(uint8_t mode, uint8_t len) { + // check active modem + uint8_t type = RADIOLIB_LR11X0_PACKET_TYPE_NONE; + int16_t state = getPacketType(&type); + RADIOLIB_ASSERT(state); + if(type != RADIOLIB_LR11X0_PACKET_TYPE_GFSK) { + return(RADIOLIB_ERR_WRONG_MODEM); + } + + // set requested packet mode + state = setPacketParamsGFSK(this->preambleLengthGFSK, this->preambleDetLength, this->syncWordLength, this->addrComp, mode, len, this->crcTypeGFSK, this->whitening); + RADIOLIB_ASSERT(state); + + // update cached value + this->packetType = mode; + return(state); +} + Module* LR11x0::getMod() { return(this->mod); } @@ -1311,9 +1709,8 @@ int16_t LR11x0::getPacketStatusGFSK(float* rssiSync, float* rssiAvg, uint8_t* rx int16_t state = this->SPIcommand(RADIOLIB_LR11X0_CMD_GET_PACKET_STATUS, false, buff, sizeof(buff)); // pass the replies - // TODO do the value conversion for RSSI (fixed point?) - if(rssiSync) { *rssiSync = (float)buff[0]; } - if(rssiAvg) { *rssiAvg = (float)buff[1]; } + if(rssiSync) { *rssiSync = (float)buff[0] / -2.0f; } + if(rssiAvg) { *rssiAvg = (float)buff[1] / -2.0f; } if(rxLen) { *rxLen = buff[2]; } if(stat) { *stat = buff[3]; } @@ -1334,7 +1731,7 @@ int16_t LR11x0::setGfskSyncWord(uint8_t* sync) { if(!sync) { return(RADIOLIB_ERR_MEMORY_ALLOCATION_FAILED); } - return(this->SPIcommand(RADIOLIB_LR11X0_CMD_SET_GFSK_SYNC_WORD, false, sync, RADIOLIB_LR11X0_GFSK_SYNC_WORD_LEN)); + return(this->SPIcommand(RADIOLIB_LR11X0_CMD_SET_GFSK_SYNC_WORD, true, sync, RADIOLIB_LR11X0_GFSK_SYNC_WORD_LEN)); } int16_t LR11x0::setLoRaPublicNetwork(bool pub) { diff --git a/src/modules/LR11x0/LR11x0.h b/src/modules/LR11x0/LR11x0.h index 88c4eee7..5bc3dc3d 100644 --- a/src/modules/LR11x0/LR11x0.h +++ b/src/modules/LR11x0/LR11x0.h @@ -752,8 +752,121 @@ class LR11x0: public PhysicalLayer { int16_t setSyncWord(uint8_t syncWord); /*! - \brief Sets preamble length for LoRa or FSK modem. Allowed values range from 1 to 65535. - \param preambleLength Preamble length to be set in symbols (LoRa) or bits (FSK). + \brief Sets GFSK bit rate. Allowed values range from 0.6 to 300.0 kbps. + \param br FSK bit rate to be set in kbps. + \returns \ref status_codes + */ + int16_t setBitRate(float br); + + /*! + \brief Sets GFSK frequency deviation. Allowed values range from 0.0 to 200.0 kHz. + \param freqDev GFSK frequency deviation to be set in kHz. + \returns \ref status_codes + */ + int16_t setFrequencyDeviation(float freqDev) override; + + /*! + \brief Sets GFSK receiver bandwidth. Allowed values are 4.8, 5.8, 7.3, 9.7, 11.7, 14.6, 19.5, + 23.4, 29.3, 39.0, 46.9, 58.6, 78.2, 93.8, 117.3, 156.2, 187.2, 234.3, 312.0, 373.6 and 467.0 kHz. + \param rxBw GFSK receiver bandwidth to be set in kHz. + \returns \ref status_codes + */ + int16_t setRxBandwidth(float rxBw); + + /*! + \brief Sets GFSK sync word in the form of array of up to 8 bytes. + \param syncWord GFSK sync word to be set. + \param len GFSK sync word length in bytes. + \returns \ref status_codes + */ + int16_t setSyncWord(uint8_t* syncWord, size_t len) override; + + /*! + \brief Sets GFSK sync word in the form of array of up to 8 bytes. + \param syncWord GFSK sync word to be set. + \param bitsLen GFSK sync word length in bits. If length is not divisible by 8, + least significant bits of syncWord will be ignored. + \returns \ref status_codes + */ + int16_t setSyncBits(uint8_t *syncWord, uint8_t bitsLen); + + /*! + \brief Sets node address. Calling this method will also enable address filtering for node address only. + \param nodeAddr Node address to be set. + \returns \ref status_codes + */ + int16_t setNodeAddress(uint8_t nodeAddr); + + /*! + \brief Sets broadcast address. Calling this method will also enable address + filtering for node and broadcast address. + \param broadAddr Node address to be set. + \returns \ref status_codes + */ + int16_t setBroadcastAddress(uint8_t broadAddr); + + /*! + \brief Disables address filtering. Calling this method will also erase previously set addresses. + \returns \ref status_codes + */ + int16_t disableAddressFiltering(); + + /*! + \brief Sets time-bandwidth product of Gaussian filter applied for shaping. + Allowed values are RADIOLIB_SHAPING_0_3, RADIOLIB_SHAPING_0_5, RADIOLIB_SHAPING_0_7 or RADIOLIB_SHAPING_1_0. + Set to RADIOLIB_SHAPING_NONE to disable data shaping. + \param sh Time-bandwidth product of Gaussian filter to be set. + \returns \ref status_codes + */ + int16_t setDataShaping(uint8_t sh) override; + + /*! + \brief Sets transmission encoding. Available in GFSK mode only. Serves only as alias for PhysicalLayer compatibility. + \param encoding Encoding to be used. Set to 0 for NRZ, and 2 for whitening. + \returns \ref status_codes + */ + int16_t setEncoding(uint8_t encoding) override; + + /*! + \brief Set modem in fixed packet length mode. Available in GFSK mode only. + \param len Packet length. + \returns \ref status_codes + */ + int16_t fixedPacketLengthMode(uint8_t len = RADIOLIB_LR11X0_MAX_PACKET_LENGTH); + + /*! + \brief Set modem in variable packet length mode. Available in GFSK mode only. + \param maxLen Maximum packet length. + \returns \ref status_codes + */ + int16_t variablePacketLengthMode(uint8_t maxLen = RADIOLIB_LR11X0_MAX_PACKET_LENGTH); + + /*! + \brief Sets GFSK whitening parameters. + \param enabled True = Whitening enabled + \param initial Initial value used for the whitening LFSR in GFSK mode. + By default set to 0x01FF for compatibility with SX127x and LoRaWAN. + \returns \ref status_codes + */ + int16_t setWhitening(bool enabled, uint16_t initial = 0x01FF); + + /*! + \brief Set data. + \param dr Data rate struct. Interpretation depends on currently active modem (GFSK or LoRa). + \returns \ref status_codes + */ + int16_t setDataRate(DataRate_t dr) override; + + /*! + \brief Check the data rate can be configured by this module. + \param dr Data rate struct. Interpretation depends on currently active modem (GFSK or LoRa). + \returns \ref status_codes + */ + int16_t checkDataRate(DataRate_t dr) override; + + /*! + \brief Sets preamble length for LoRa or GFSK modem. Allowed values range from 1 to 65535. + \param preambleLength Preamble length to be set in symbols (LoRa) or bits (GFSK). \returns \ref status_codes */ int16_t setPreambleLength(size_t preambleLength) override; @@ -771,12 +884,12 @@ class LR11x0: public PhysicalLayer { /*! \brief Sets CRC configuration. \param len CRC length in bytes, Allowed values are 1 or 2, set to 0 to disable CRC. - \param initial Initial CRC value. FSK only. Defaults to 0x1D0F (CCIT CRC). - \param polynomial Polynomial for CRC calculation. FSK only. Defaults to 0x1021 (CCIT CRC). - \param inverted Invert CRC bytes. FSK only. Defaults to true (CCIT CRC). + \param initial Initial CRC value. GFSK only. Defaults to 0x1D0F (CCIT CRC). + \param polynomial Polynomial for CRC calculation. GFSK only. Defaults to 0x1021 (CCIT CRC). + \param inverted Invert CRC bytes. GFSK only. Defaults to true (CCIT CRC). \returns \ref status_codes */ - int16_t setCRC(uint8_t len, uint16_t initial = 0x1D0F, uint16_t polynomial = 0x1021, bool inverted = true); + int16_t setCRC(uint8_t len, uint32_t initial = 0x00001D0FUL, uint32_t polynomial = 0x00001021UL, bool inverted = true); /*! \brief Enable/disable inversion of the I and Q signals @@ -995,9 +1108,8 @@ class LR11x0: public PhysicalLayer { bool invertIQEnabled = false; // cached GFSK parameters - float bitRateKbps = 0; - uint8_t bitRate = 0; - uint8_t preambleDetLength = 0, rxBandwidth = 0, pulseShape = 0, crcTypeGFSK = 0, syncWordLength = 0, addrComp = 0, whitening = 0, packetType = 0; + uint32_t bitRate = 0, frequencyDev = 0; + uint8_t preambleDetLength = 0, rxBandwidth = 0, pulseShape = 0, crcTypeGFSK = 0, syncWordLength = 0, addrComp = 0, whitening = 0, packetType = 0, node = 0; uint16_t preambleLengthGFSK = 0; float dataRateMeasured = 0; @@ -1006,6 +1118,7 @@ class LR11x0: public PhysicalLayer { static int16_t SPIcheckStatus(Module* mod); bool findChip(uint8_t ver); int16_t config(uint8_t modem); + int16_t setPacketMode(uint8_t mode, uint8_t len); // common methods to avoid some copy-paste int16_t bleBeaconCommon(uint16_t cmd, uint8_t chan, uint8_t* payload, size_t len);