Files
Rocket2021/lib/SX12XX-LoRa/src/SX128XLT.cpp
2021-05-11 20:43:42 +03:00

2760 lines
61 KiB
C++

/*
Copyright 2019 - Stuart Robinson
Licensed under a MIT license displayed at the bottom of this document.
06/02/20
*/
/*
Parts of code Copyright (c) 2013, SEMTECH S.A.
See LICENSE.TXT file included in the library
*/
#include <SX128XLT.h>
#include <SPI.h>
#define LTUNUSED(v) (void) (v) //add LTUNUSED(variable); to avoid compiler warnings
#define USE_SPI_TRANSACTION
//#define SX128XDEBUG //enable debug messages
//#define RANGINGDEBUG //enable debug messages for ranging
//#define SX128XDEBUGRXTX //enable debug messages for RX TX switching
//#define SX128XDEBUGPINS //enable pin allocation debug messages
SX128XLT::SX128XLT()
{
}
/* Formats for :begin
1 All pins > begin(int8_t pinNSS, int8_t pinNRESET, int8_t pinRFBUSY, int8_t pinDIO1, int8_t pinDIO2, int8_t pinDIO3, int8_t pinRXEN, int8_t pinTXEN, uint8_t device)
2 NiceRF > begin(int8_t pinNSS, int8_t pinNRESET, int8_t pinRFBUSY, int8_t pinDIO1, uint8_t device)
3 Ebyte > begin(int8_t pinNSS, int8_t pinNRESET, int8_t pinRFBUSY, int8_t pinDIO1, int8_t pinRXEN, int8_t pinTXEN, uint8_t device);
*/
bool SX128XLT::begin(int8_t pinNSS, int8_t pinNRESET, int8_t pinRFBUSY, int8_t pinDIO1, int8_t pinDIO2, int8_t pinDIO3, int8_t pinRXEN, int8_t pinTXEN, uint8_t device)
{
//format 1 pins, assign all available pins
_NSS = pinNSS;
_NRESET = pinNRESET;
_RFBUSY = pinRFBUSY;
_DIO1 = pinDIO1;
_DIO2 = pinDIO2;
_DIO3 = pinDIO3;
_RXEN = pinRXEN;
_TXEN = pinTXEN;
_Device = device;
_TXDonePin = pinDIO1; //this is defalt pin for sensing TX done
_RXDonePin = pinDIO1; //this is defalt pin for sensing RX done
pinMode(_NSS, OUTPUT);
digitalWrite(_NSS, HIGH);
pinMode(_NRESET, OUTPUT);
digitalWrite(_NRESET, LOW);
pinMode(_RFBUSY, INPUT);
#ifdef SX128XDEBUGPINS
Serial.println(F("begin()"));
Serial.println(F("SX128XLT constructor instantiated successfully"));
Serial.print(F("NSS "));
Serial.println(_NSS);
Serial.print(F("NRESET "));
Serial.println(_NRESET);
Serial.print(F("RFBUSY "));
Serial.println(_RFBUSY);
Serial.print(F("DIO1 "));
Serial.println(_DIO1);
Serial.print(F("DIO2 "));
Serial.println(_DIO2);
Serial.print(F("DIO3 "));
Serial.println(_DIO3);
Serial.print(F("RX_EN "));
Serial.println(_RXEN);
Serial.print(F("TX_EN "));
Serial.println(_TXEN);
#endif
if (_DIO1 >= 0)
{
pinMode( _DIO1, INPUT);
}
if (_DIO2 >= 0)
{
pinMode( _DIO2, INPUT);
}
if (_DIO3 >= 0)
{
pinMode( _DIO3, INPUT);
}
if ((_RXEN >= 0) && (_TXEN >= 0))
{
#ifdef SX128XDEBUGPINS
Serial.println(F("RX_EN & TX_EN switching enabled"));
#endif
pinMode(_RXEN, OUTPUT);
pinMode(_TXEN, OUTPUT);
_rxtxpinmode = true;
}
else
{
#ifdef SX128XDEBUGPINS
Serial.println(F("RX_EN & TX_EN switching disabled"));
#endif
_rxtxpinmode = false;
}
resetDevice();
if (checkDevice())
{
return true;
}
return false;
}
bool SX128XLT::begin(int8_t pinNSS, int8_t pinNRESET, int8_t pinRFBUSY, int8_t pinDIO1, uint8_t device)
{
//format 2 pins for NiceRF, NSS, NRESET, RFBUSY, DIO1
_NSS = pinNSS;
_NRESET = pinNRESET;
_RFBUSY = pinRFBUSY;
_DIO1 = pinDIO1;
_DIO2 = -1;
_DIO3 = -1;
_RXEN = -1; //not defined, so mark as unused
_TXEN = -1; //not defined, so mark as unused
_Device = device;
_TXDonePin = pinDIO1; //this is defalt pin for sensing TX done
_RXDonePin = pinDIO1; //this is defalt pin for sensing RX done
pinMode(_NSS, OUTPUT);
digitalWrite(_NSS, HIGH);
pinMode(_NRESET, OUTPUT);
digitalWrite(_NRESET, LOW);
pinMode(_RFBUSY, INPUT);
#ifdef SX128XDEBUGPINS
Serial.println(F("format 2 NiceRF begin()"));
Serial.println(F("SX128XLT constructor instantiated successfully"));
Serial.print(F("NSS "));
Serial.println(_NSS);
Serial.print(F("NRESET "));
Serial.println(_NRESET);
Serial.print(F("RFBUSY "));
Serial.println(_RFBUSY);
Serial.print(F("DIO1 "));
Serial.println(_DIO1);
Serial.print(F("DIO2 "));
Serial.println(_DIO2);
Serial.print(F("DIO3 "));
Serial.println(_DIO3);
Serial.print(F("RX_EN "));
Serial.println(_RXEN);
Serial.print(F("TX_EN "));
Serial.println(_TXEN);
#endif
if (_DIO1 >= 0)
{
pinMode( _DIO1, INPUT);
}
#ifdef SX128XDEBUGPINS
Serial.println(F("RX_EN & TX_EN switching disabled"));
#endif
_rxtxpinmode = false;
resetDevice();
if (checkDevice())
{
return true;
}
return false;
}
bool SX128XLT::begin(int8_t pinNSS, int8_t pinNRESET, int8_t pinRFBUSY, int8_t pinDIO1, int8_t pinRXEN, int8_t pinTXEN, uint8_t device)
{
//format 3 pins for Ebyte, NSS, NRESET, RFBUSY, DIO1, RX_EN, TX_EN
_NSS = pinNSS;
_NRESET = pinNRESET;
_RFBUSY = pinRFBUSY;
_DIO1 = pinDIO1;
_DIO2 = -1;
_DIO3 = -1;
_RXEN = pinRXEN;
_TXEN = pinTXEN;
_Device = device;
_TXDonePin = pinDIO1; //this is defalt pin for sensing TX done
_RXDonePin = pinDIO1; //this is defalt pin for sensing RX done
pinMode(_NSS, OUTPUT);
digitalWrite(_NSS, HIGH);
pinMode(_NRESET, OUTPUT);
digitalWrite(_NRESET, LOW);
pinMode(_RFBUSY, INPUT);
#ifdef SX128XDEBUGPINS
Serial.println(F("format 3 Ebyte begin()"));
Serial.println(F("SX128XLT constructor instantiated successfully"));
Serial.print(F("NSS "));
Serial.println(_NSS);
Serial.print(F("NRESET "));
Serial.println(_NRESET);
Serial.print(F("RFBUSY "));
Serial.println(_RFBUSY);
Serial.print(F("DIO1 "));
Serial.println(_DIO1);
Serial.print(F("DIO2 "));
Serial.println(_DIO2);
Serial.print(F("DIO3 "));
Serial.println(_DIO3);
Serial.print(F("RX_EN "));
Serial.println(_RXEN);
Serial.print(F("TX_EN "));
Serial.println(_TXEN);
#endif
if (_DIO1 >= 0)
{
pinMode( _DIO1, INPUT);
}
if ((_RXEN >= 0) && (_TXEN >= 0))
{
#ifdef SX128XDEBUGPINS
Serial.println(F("RX_EN & TX_EN switching enabled"));
#endif
pinMode(_RXEN, OUTPUT);
pinMode(_TXEN, OUTPUT);
_rxtxpinmode = true;
}
else
{
#ifdef SX128XDEBUGPINS
Serial.println(F("RX_EN & TX_EN switching disabled"));
#endif
_rxtxpinmode = false;
}
resetDevice();
if (checkDevice())
{
return true;
}
return false;
}
void SX128XLT::rxEnable()
{
//Enable RX mode on device such as Ebyte E28-2G4M20S which have RX and TX enable pins
#ifdef SX128XDEBUGRXTX
Serial.println(F("rxEnable()"));
#endif
digitalWrite(_RXEN, HIGH);
digitalWrite(_TXEN, LOW);
}
void SX128XLT::txEnable()
{
//Enable RX mode on device such as Ebyte E28-2G4M20S which have RX and TX enable pins
#ifdef SX128XDEBUGRXTX
Serial.println(F("txEnable()"));
#endif
digitalWrite(_RXEN, LOW);
digitalWrite(_TXEN, HIGH);
}
void SX128XLT::checkBusy()
{
#ifdef SX128XDEBUG
//Serial.println(F("checkBusy()"));
#endif
uint8_t busy_timeout_cnt;
busy_timeout_cnt = 0;
while (digitalRead(_RFBUSY))
{
delay(1);
busy_timeout_cnt++;
if (busy_timeout_cnt > 10) //wait 5mS for busy to complete
{
Serial.println(F("ERROR - Busy Timeout!"));
resetDevice();
setMode(MODE_STDBY_RC);
config(); //re-run saved config
break;
}
}
}
bool SX128XLT::config()
{
#ifdef SX128XDEBUG
Serial.println(F("config()"));
#endif
resetDevice();
setMode(MODE_STDBY_RC);
setRegulatorMode(savedRegulatorMode);
setPacketType(savedPacketType);
setRfFrequency(savedFrequency, savedOffset);
setModulationParams(savedModParam1, savedModParam2, savedModParam3);
setPacketParams(savedPacketParam1, savedPacketParam2, savedPacketParam3, savedPacketParam4, savedPacketParam5, savedPacketParam6, savedPacketParam7);
setDioIrqParams(savedIrqMask, savedDio1Mask, savedDio2Mask, savedDio3Mask); //set for IRQ on RX done on DIO1
return true;
}
void SX128XLT::readRegisters(uint16_t address, uint8_t *buffer, uint16_t size)
{
#ifdef SX128XDEBUG
//Serial.println(F("readRegisters()"));
#endif
uint16_t index;
uint8_t addr_l, addr_h;
addr_h = address >> 8;
addr_l = address & 0x00FF;
checkBusy();
#ifdef USE_SPI_TRANSACTION //to use SPI_TRANSACTION enable define at beginning of CPP file
SPI.beginTransaction(SPISettings(LTspeedMaximum, LTdataOrder, LTdataMode));
#endif
digitalWrite(_NSS, LOW);
SPI.transfer(RADIO_READ_REGISTER);
SPI.transfer(addr_h); //MSB
SPI.transfer(addr_l); //LSB
SPI.transfer(0xFF);
for (index = 0; index < size; index++)
{
*(buffer + index) = SPI.transfer(0xFF);
}
digitalWrite(_NSS, HIGH);
#ifdef USE_SPI_TRANSACTION
SPI.endTransaction();
#endif
}
uint8_t SX128XLT::readRegister(uint16_t address)
{
#ifdef SX128XDEBUG
//Serial.println(F("readRegister()"));
#endif
uint8_t data;
readRegisters(address, &data, 1);
return data;
}
void SX128XLT::writeRegisters(uint16_t address, uint8_t *buffer, uint16_t size)
{
#ifdef SX128XDEBUG
//Serial.println(F("writeRegisters()"));
#endif
uint8_t addr_l, addr_h;
uint8_t i;
addr_l = address & 0xff;
addr_h = address >> 8;
checkBusy();
#ifdef USE_SPI_TRANSACTION //to use SPI_TRANSACTION enable define at beginning of CPP file
SPI.beginTransaction(SPISettings(LTspeedMaximum, LTdataOrder, LTdataMode));
#endif
digitalWrite(_NSS, LOW);
SPI.transfer(RADIO_WRITE_REGISTER);
SPI.transfer(addr_h); //MSB
SPI.transfer(addr_l); //LSB
for (i = 0; i < size; i++)
{
SPI.transfer(buffer[i]);
}
digitalWrite(_NSS, HIGH);
#ifdef USE_SPI_TRANSACTION
SPI.endTransaction();
#endif
//checkBusy();
}
void SX128XLT::writeRegister(uint16_t address, uint8_t value)
{
#ifdef SX128XDEBUG
//Serial.println(F("writeRegister()"));
#endif
writeRegisters( address, &value, 1 );
}
void SX128XLT::writeCommand(uint8_t Opcode, uint8_t *buffer, uint16_t size)
{
#ifdef SX128XDEBUG
//Serial.print(F("writeCommand() "));
//Serial.println(Opcode, HEX);
#endif
uint8_t index;
checkBusy();
#ifdef USE_SPI_TRANSACTION //to use SPI_TRANSACTION enable define at beginning of CPP file
SPI.beginTransaction(SPISettings(LTspeedMaximum, LTdataOrder, LTdataMode));
#endif
digitalWrite(_NSS, LOW);
SPI.transfer(Opcode);
for (index = 0; index < size; index++)
{
SPI.transfer(buffer[index]);
}
digitalWrite(_NSS, HIGH);
#ifdef USE_SPI_TRANSACTION
SPI.endTransaction();
#endif
if (Opcode != RADIO_SET_SLEEP)
{
checkBusy();
}
}
void SX128XLT::readCommand(uint8_t Opcode, uint8_t *buffer, uint16_t size)
{
#ifdef SX128XDEBUG
//Serial.println(F("readCommand()"));
#endif
uint8_t i;
checkBusy();
#ifdef USE_SPI_TRANSACTION //to use SPI_TRANSACTION enable define at beginning of CPP file
SPI.beginTransaction(SPISettings(LTspeedMaximum, LTdataOrder, LTdataMode));
#endif
digitalWrite(_NSS, LOW);
SPI.transfer(Opcode);
SPI.transfer(0xFF);
for ( i = 0; i < size; i++ )
{
*(buffer + i) = SPI.transfer(0xFF);
}
digitalWrite(_NSS, HIGH);
#ifdef USE_SPI_TRANSACTION
SPI.endTransaction();
#endif
//checkBusy();
}
void SX128XLT::resetDevice()
{
#ifdef SX128XDEBUG
Serial.println(F("resetDevice()"));
#endif
//timings taken from Semtech library
delay(20);
digitalWrite(_NRESET, LOW);
delay(50);
digitalWrite(_NRESET, HIGH);
delay(20);
}
bool SX128XLT::checkDevice()
{
//check there is a device out there, writes a register and reads back
#ifdef SX128XDEBUG
Serial.println(F("checkDevice()"));
#endif
uint8_t Regdata1, Regdata2;
Regdata1 = readRegister(0x0908); //low byte of frequency setting
writeRegister(0x0908, (Regdata1 + 1));
Regdata2 = readRegister(0x0908); //read changed value back
writeRegister(0x0908, Regdata1); //restore register to original value
if (Regdata2 == (Regdata1 + 1))
{
return true;
}
else
{
return false;
}
}
void SX128XLT::setupLoRa(uint32_t frequency, int32_t offset, uint8_t modParam1, uint8_t modParam2, uint8_t modParam3)
{
#ifdef SX128XDEBUG
Serial.println(F("setupLoRa()"));
#endif
setMode(MODE_STDBY_RC);
setRegulatorMode(USE_LDO);
setPacketType(PACKET_TYPE_LORA);
setRfFrequency(frequency, offset);
setBufferBaseAddress(0, 0);
setModulationParams(modParam1, modParam2, modParam3);
setPacketParams(12, LORA_PACKET_VARIABLE_LENGTH, 255, LORA_CRC_ON, LORA_IQ_NORMAL, 0, 0);
setDioIrqParams(IRQ_RADIO_ALL, (IRQ_TX_DONE + IRQ_RX_TX_TIMEOUT), 0, 0);
}
void SX128XLT::setMode(uint8_t modeconfig)
{
#ifdef SX128XDEBUG
Serial.println(F("setMode()"));
#endif
uint8_t Opcode = 0x80;
checkBusy();
#ifdef USE_SPI_TRANSACTION //to use SPI_TRANSACTION enable define at beginning of CPP file
SPI.beginTransaction(SPISettings(LTspeedMaximum, LTdataOrder, LTdataMode));
#endif
digitalWrite(_NSS, LOW);
SPI.transfer(Opcode);
SPI.transfer(modeconfig);
digitalWrite(_NSS, HIGH);
#ifdef USE_SPI_TRANSACTION
SPI.endTransaction();
#endif
_OperatingMode = modeconfig;
}
void SX128XLT::setRegulatorMode(uint8_t mode)
{
#ifdef SX128XDEBUG
Serial.println(F("setRegulatorMode()"));
#endif
savedRegulatorMode = mode;
writeCommand(RADIO_SET_REGULATORMODE, &mode, 1);
}
void SX128XLT::setPacketType(uint8_t packettype )
{
#ifdef SX128XDEBUG
Serial.println(F("setPacketType()"));
#endif
savedPacketType = packettype;
writeCommand(RADIO_SET_PACKETTYPE, &packettype, 1);
}
void SX128XLT::setRfFrequency(uint32_t frequency, int32_t offset)
{
#ifdef SX128XDEBUG
Serial.println(F("setRfFrequency()"));
#endif
savedFrequency = frequency;
savedOffset = offset;
frequency = frequency + offset;
uint8_t buffer[3];
uint32_t freqtemp = 0;
freqtemp = ( uint32_t )( (double) frequency / (double)FREQ_STEP);
buffer[0] = ( uint8_t )( ( freqtemp >> 16 ) & 0xFF );
buffer[1] = ( uint8_t )( ( freqtemp >> 8 ) & 0xFF );
buffer[2] = ( uint8_t )( freqtemp & 0xFF );
writeCommand(RADIO_SET_RFFREQUENCY, buffer, 3);
}
void SX128XLT::setBufferBaseAddress(uint8_t txBaseAddress, uint8_t rxBaseAddress)
{
#ifdef SX128XDEBUG
Serial.println(F("setBufferBaseAddress()"));
#endif
uint8_t buffer[2];
buffer[0] = txBaseAddress;
buffer[1] = rxBaseAddress;
writeCommand(RADIO_SET_BUFFERBASEADDRESS, buffer, 2);
}
void SX128XLT::setModulationParams(uint8_t modParam1, uint8_t modParam2, uint8_t modParam3)
{
//sequence is spreading factor, bandwidth, coding rate.
#ifdef SX128XDEBUG
Serial.println(F("setModulationParams()"));
#endif
uint8_t buffer[3];
savedModParam1 = modParam1;
savedModParam2 = modParam2;
savedModParam3 = modParam3;
buffer[0] = modParam1;
buffer[1] = modParam2;
buffer[2] = modParam3;
writeCommand(RADIO_SET_MODULATIONPARAMS, buffer, 3);
}
void SX128XLT::setPacketParams(uint8_t packetParam1, uint8_t packetParam2, uint8_t packetParam3, uint8_t packetParam4, uint8_t packetParam5, uint8_t packetParam6, uint8_t packetParam7)
{
//for LoRa order is PreambleLength, HeaderType, PayloadLength, CRC, InvertIQ/chirp invert, not used, not used
//for FLRC order is PreambleLength, SyncWordLength, SyncWordMatch, HeaderType, PayloadLength, CrcLength, Whitening
#ifdef SX128XDEBUG
Serial.println(F("SetPacketParams()"));
#endif
savedPacketParam1 = packetParam1;
savedPacketParam2 = packetParam2;
savedPacketParam3 = packetParam3;
savedPacketParam4 = packetParam4;
savedPacketParam5 = packetParam5;
savedPacketParam6 = packetParam6;
savedPacketParam7 = packetParam7;
uint8_t buffer[7];
buffer[0] = packetParam1;
buffer[1] = packetParam2;
buffer[2] = packetParam3;
buffer[3] = packetParam4;
buffer[4] = packetParam5;
buffer[5] = packetParam6;
buffer[6] = packetParam7;
writeCommand(RADIO_SET_PACKETPARAMS, buffer, 7);
}
void SX128XLT::setDioIrqParams(uint16_t irqMask, uint16_t dio1Mask, uint16_t dio2Mask, uint16_t dio3Mask )
{
#ifdef SX128XDEBUG
Serial.println(F("setDioIrqParams()"));
#endif
savedIrqMask = irqMask;
savedDio1Mask = dio1Mask;
savedDio2Mask = dio2Mask;
savedDio3Mask = dio3Mask;
uint8_t buffer[8];
buffer[0] = (uint8_t) (irqMask >> 8);
buffer[1] = (uint8_t) (irqMask & 0xFF);
buffer[2] = (uint8_t) (dio1Mask >> 8);
buffer[3] = (uint8_t) (dio1Mask & 0xFF);
buffer[4] = (uint8_t) (dio2Mask >> 8);
buffer[5] = (uint8_t) (dio2Mask & 0xFF);
buffer[6] = (uint8_t) (dio3Mask >> 8);
buffer[7] = (uint8_t) (dio3Mask & 0xFF);
writeCommand(RADIO_SET_DIOIRQPARAMS, buffer, 8);
}
void SX128XLT::setHighSensitivity()
{
//set bits 7,6 of REG_LNA_REGIME
#ifdef SX128XDEBUG
Serial.println(F("setHighSensitivity()"));
#endif
writeRegister(REG_LNA_REGIME, (readRegister(REG_LNA_REGIME) | 0xC0));
}
void SX128XLT::setLowPowerRX()
{
//clear bits 7,6 of REG_LNA_REGIME
#ifdef SX128XDEBUG
Serial.println(F("setLowPowerRX()"));
#endif
writeRegister(REG_LNA_REGIME, (readRegister(REG_LNA_REGIME) & 0x3F));
}
void SX128XLT::printModemSettings()
{
#ifdef SX128XDEBUG
Serial.println(F("printModemSettings()"));
#endif
printDevice();
Serial.print(F(","));
Serial.print(getFreqInt());
Serial.print(F("hz"));
if (savedPacketType == PACKET_TYPE_LORA)
{
Serial.print(F(",SF"));
Serial.print(getLoRaSF());
Serial.print(F(",BW"));
Serial.print(returnBandwidth(savedModParam2));
Serial.print(F(",CR4:"));
Serial.print((getLoRaCodingRate() + 4));
if (getInvertIQ() == LORA_IQ_INVERTED)
{
Serial.print(F(",IQInverted"));
}
else
{
Serial.print(F(",IQNormal"));
}
Serial.print(F(",Preamble_"));
Serial.print(getPreamble());
}
if (savedPacketType == PACKET_TYPE_FLRC)
{
if (savedPacketParam1 == 0)
{
Serial.print(F(",No_Syncword"));
}
if (savedPacketParam1 == 4)
{
Serial.print(F(",32bit_Syncword"));
}
switch (savedPacketParam3)
{
case RADIO_RX_MATCH_SYNCWORD_OFF:
Serial.print(F(",SYNCWORD_OFF"));
break;
case RADIO_RX_MATCH_SYNCWORD_1:
Serial.print(F(",SYNCWORD_1"));
break;
case RADIO_RX_MATCH_SYNCWORD_2:
Serial.print(F(",SYNCWORD_2"));
break;
case RADIO_RX_MATCH_SYNCWORD_1_2:
Serial.print(F(",SYNCWORD_1_2"));
break;
case RADIO_RX_MATCH_SYNCWORD_3:
Serial.print(F(",SYNCWORD_3"));
break;
case RADIO_RX_MATCH_SYNCWORD_1_3:
Serial.print(F(",SYNCWORD_1_3"));
break;
case RADIO_RX_MATCH_SYNCWORD_2_3:
Serial.print(F(",SYNCWORD_2_3"));
break;
case RADIO_RX_MATCH_SYNCWORD_1_2_3:
Serial.print(F(",SYNCWORD_1_2_3"));
break;
default:
Serial.print(F("Unknown_SYNCWORD"));
}
if (savedPacketParam4 == RADIO_PACKET_FIXED_LENGTH)
{
Serial.print(F(",PACKET_FIXED_LENGTH"));
}
if (savedPacketParam4 == RADIO_PACKET_VARIABLE_LENGTH)
{
Serial.print(F(",PACKET_VARIABLE_LENGTH"));
}
switch (savedPacketParam6)
{
case RADIO_CRC_OFF:
Serial.print(F(",CRC_OFF"));
break;
case RADIO_CRC_1_BYTES:
Serial.print(F(",CRC_1_BYTES"));
break;
case RADIO_CRC_2_BYTES:
Serial.print(F(",CRC_2_BYTES"));
break;
case RADIO_CRC_3_BYTES:
Serial.print(F(",CRC_3_BYTES"));
break;
default:
Serial.print(F(",Unknown_CRC"));
}
if (savedPacketParam7 == RADIO_WHITENING_ON)
{
Serial.print(F(",WHITENING_ON"));
}
if (savedPacketParam7 == RADIO_WHITENING_OFF)
{
Serial.print(F(",WHITENING_OFF"));
}
}
}
void SX128XLT::printDevice()
{
#ifdef SX128XDEBUG
Serial.println(F("printDevice()"));
#endif
switch (_Device)
{
case DEVICE_SX1280:
Serial.print(F("SX1280"));
break;
case DEVICE_SX1281:
Serial.print(F("SX1281"));
break;
default:
Serial.print(F("Unknown Device"));
}
}
uint32_t SX128XLT::getFreqInt()
{
#ifdef SX128XDEBUG
Serial.println(F("getFreqInt"));
#endif
//get the current set device frequency, return as long integer
uint8_t Msb = 0;
uint8_t Mid = 0;
uint8_t Lsb = 0;
uint32_t uinttemp;
float floattemp;
LTUNUSED(Msb); //to prevent a compiler warning
LTUNUSED(Mid); //to prevent a compiler warning
LTUNUSED(Lsb); //to prevent a compiler warning
if (savedPacketType == PACKET_TYPE_LORA)
{
Msb = readRegister(REG_RFFrequency23_16);
Mid = readRegister(REG_RFFrequency15_8);
Lsb = readRegister(REG_RFFrequency7_0);
}
if (savedPacketType == PACKET_TYPE_FLRC)
{
Msb = readRegister(REG_FLRC_RFFrequency23_16);
Mid = readRegister(REG_FLRC_RFFrequency15_8);
Lsb = readRegister(REG_FLRC_RFFrequency7_0);
}
floattemp = ((Msb * 0x10000ul) + (Mid * 0x100ul) + Lsb);
floattemp = ((floattemp * FREQ_STEP) / 1000000ul);
uinttemp = (uint32_t)(floattemp * 1000000);
return uinttemp;
}
uint8_t SX128XLT::getLoRaSF()
{
#ifdef SX128XDEBUG
Serial.println(F("getLoRaSF()"));
#endif
return (savedModParam1 >> 4);
}
uint32_t SX128XLT::returnBandwidth(uint8_t data)
{
#ifdef SX128XDEBUG
Serial.println(F("returnBandwidth()"));
#endif
switch (data)
{
case LORA_BW_0200:
return 203125;
case LORA_BW_0400:
return 406250;
case LORA_BW_0800:
return 812500;
case LORA_BW_1600:
return 1625000;
default:
break;
}
return 0x0; //so that a bandwidth not set can be identified
}
uint8_t SX128XLT::getLoRaCodingRate()
{
#ifdef SX128XDEBUG
Serial.println(F("getLoRaCodingRate"));
#endif
return savedModParam3;
}
uint8_t SX128XLT::getInvertIQ()
{
//IQ mode reg 0x33
#ifdef SX128XDEBUG
Serial.println(F("getInvertIQ"));
#endif
return savedPacketParam5;
}
uint16_t SX128XLT::getPreamble()
{
#ifdef SX128XDEBUG
Serial.println(F("getPreamble"));
#endif
return savedPacketParam1;
}
void SX128XLT::printOperatingSettings()
{
#ifdef SX128XDEBUG
Serial.println(F("printOperatingSettings()"));
#endif
printDevice();
Serial.print(F(",PacketMode_"));
switch (savedPacketType)
{
case PACKET_TYPE_GFSK:
Serial.print(F("GFSK"));
break;
case PACKET_TYPE_LORA:
Serial.print(F("LORA"));
break;
case PACKET_TYPE_RANGING:
Serial.print(F("RANGING"));
break;
case PACKET_TYPE_FLRC:
Serial.print(F("FLRC"));
break;
case PACKET_TYPE_BLE:
Serial.print(F("BLE"));
break;
default:
Serial.print(F("Unknown"));
}
switch (savedPacketParam2)
{
case LORA_PACKET_VARIABLE_LENGTH:
Serial.print(F(",Explicit"));
break;
case LORA_PACKET_FIXED_LENGTH:
Serial.print(F(",Implicit"));
break;
default:
Serial.print(F(",Unknown"));
}
Serial.print(F(",LNAgain_"));
if (getLNAgain() == 0xC0)
{
Serial.print(F("HighSensitivity"));
}
else
{
Serial.print(F("LowPowerRX"));
}
}
uint8_t SX128XLT::getLNAgain()
{
#ifdef SX128XDEBUG
Serial.println(F("getLNAgain"));
#endif
return (readRegister(REG_LNA_REGIME) & 0xC0);
}
void SX128XLT::printRegisters(uint16_t Start, uint16_t End)
{
//prints the contents of SX1280 registers to serial monitor
#ifdef SX128XDEBUG
Serial.println(F("printRegisters()"));
#endif
uint16_t Loopv1, Loopv2, RegData;
Serial.print(F("Reg 0 1 2 3 4 5 6 7 8 9 A B C D E F"));
Serial.println();
for (Loopv1 = Start; Loopv1 <= End;) //32 lines
{
Serial.print(F("0x"));
Serial.print((Loopv1), HEX); //print the register number
Serial.print(F(" "));
for (Loopv2 = 0; Loopv2 <= 15; Loopv2++)
{
RegData = readRegister(Loopv1);
if (RegData < 0x10)
{
Serial.print(F("0"));
}
Serial.print(RegData, HEX); //print the register number
Serial.print(F(" "));
Loopv1++;
}
Serial.println();
}
}
void SX128XLT::printASCIIPacket(uint8_t *buffer, uint8_t size)
{
#ifdef SX128XDEBUG
Serial.println(F("printASCIIPacket()"));
#endif
uint8_t index;
for (index = 0; index < size; index++)
{
Serial.write(buffer[index]);
}
}
uint8_t SX128XLT::transmit(uint8_t *txbuffer, uint8_t size, uint16_t timeout, int8_t txpower, uint8_t wait)
{
#ifdef SX128XDEBUG
Serial.println(F("transmit()"));
#endif
uint8_t index;
uint8_t bufferdata;
if (size == 0)
{
return false;
}
setMode(MODE_STDBY_RC);
setBufferBaseAddress(0, 0);
checkBusy();
#ifdef USE_SPI_TRANSACTION //to use SPI_TRANSACTION enable define at beginning of CPP file
SPI.beginTransaction(SPISettings(LTspeedMaximum, LTdataOrder, LTdataMode));
#endif
digitalWrite(_NSS, LOW);
SPI.transfer(RADIO_WRITE_BUFFER);
SPI.transfer(0);
for (index = 0; index < size; index++)
{
bufferdata = txbuffer[index];
SPI.transfer(bufferdata);
}
digitalWrite(_NSS, HIGH);
#ifdef USE_SPI_TRANSACTION
SPI.endTransaction();
#endif
_TXPacketL = size;
if (savedPacketType == PACKET_TYPE_LORA)
{
writeRegister(REG_LR_PAYLOADLENGTH, _TXPacketL); //only seems to work for lora
}
else if (savedPacketType == PACKET_TYPE_FLRC)
{
setPacketParams(savedPacketParam1, savedPacketParam2, savedPacketParam3, savedPacketParam4, _TXPacketL, savedPacketParam6, savedPacketParam7);
}
setTxParams(txpower, RAMP_TIME);
setDioIrqParams(IRQ_RADIO_ALL, (IRQ_TX_DONE + IRQ_RX_TX_TIMEOUT), 0, 0); //set for IRQ on TX done and timeout on DIO1
setTx(timeout); //this starts the TX
if (!wait)
{
return _TXPacketL;
}
while (!digitalRead(_TXDonePin)); //Wait for DIO1 to go high
if (readIrqStatus() & IRQ_RX_TX_TIMEOUT ) //check for timeout
{
return 0;
}
else
{
return _TXPacketL;
}
}
void SX128XLT::setTxParams(int8_t TXpower, uint8_t RampTime)
{
#ifdef SX128XDEBUG
Serial.println(F("setTxParams()"));
#endif
uint8_t buffer[2];
savedTXPower = TXpower;
//power register is set to 0 to 31 which is -18dBm to +12dBm
buffer[0] = (TXpower + 18);
buffer[1] = (uint8_t)RampTime;
writeCommand(RADIO_SET_TXPARAMS, buffer, 2);
}
void SX128XLT::setTx(uint16_t timeout)
{
#ifdef SX128XDEBUG
Serial.println(F("setTx()"));
#endif
if (_rxtxpinmode)
{
txEnable();
}
//Serial.print(F("timeout "));
//Serial.println(timeout);
//Serial.print(F("_PERIODBASE "));
//Serial.println(_PERIODBASE);
uint8_t buffer[3];
clearIrqStatus(IRQ_RADIO_ALL); //clear all interrupt flags
buffer[0] = _PERIODBASE;
buffer[1] = ( uint8_t )( ( timeout >> 8 ) & 0x00FF );
buffer[2] = ( uint8_t )( timeout & 0x00FF );
writeCommand(RADIO_SET_TX, buffer, 3 );
}
void SX128XLT::clearIrqStatus(uint16_t irqMask)
{
#ifdef SX128XDEBUG
Serial.println(F("clearIrqStatus()"));
#endif
uint8_t buffer[2];
buffer[0] = (uint8_t) (irqMask >> 8);
buffer[1] = (uint8_t) (irqMask & 0xFF);
writeCommand(RADIO_CLR_IRQSTATUS, buffer, 2);
}
uint16_t SX128XLT::readIrqStatus()
{
#ifdef SX128XDEBUG
Serial.print(F("readIrqStatus()"));
#endif
uint16_t temp;
uint8_t buffer[2];
readCommand(RADIO_GET_IRQSTATUS, buffer, 2);
temp = ((buffer[0] << 8) + buffer[1]);
return temp;
}
void SX128XLT::printIrqStatus()
{
#ifdef SX128XDEBUG
Serial.println(F("printIrqStatus()"));
#endif
uint16_t _IrqStatus;
_IrqStatus = readIrqStatus();
//0x0001
if (_IrqStatus & IRQ_TX_DONE)
{
Serial.print(F(",IRQ_TX_DONE"));
}
//0x0002
if (_IrqStatus & IRQ_RX_DONE)
{
Serial.print(F(",IRQ_RX_DONE"));
}
//0x0004
if (_IrqStatus & IRQ_SYNCWORD_VALID)
{
Serial.print(F(",IRQ_SYNCWORD_VALID"));
}
//0x0008
if (_IrqStatus & IRQ_SYNCWORD_ERROR)
{
Serial.print(F(",IRQ_SYNCWORD_ERROR"));
}
//0x0010
if (_IrqStatus & IRQ_HEADER_VALID)
{
Serial.print(F(",IRQ_HEADER_VALID"));
}
//0x0020
if (_IrqStatus & IRQ_HEADER_ERROR)
{
Serial.print(F(",IRQ_HEADER_ERROR"));
}
//0x0040
if (_IrqStatus & IRQ_CRC_ERROR)
{
Serial.print(F(",IRQ_CRC_ERROR"));
}
//0x0080
if (_IrqStatus & IRQ_RANGING_SLAVE_RESPONSE_DONE)
{
Serial.print(F(",IRQ_RANGING_SLAVE_RESPONSE_DONE"));
}
//0x0100
if (_IrqStatus & IRQ_RANGING_SLAVE_REQUEST_DISCARDED)
{
Serial.print(",IRQ_RANGING_SLAVE_REQUEST_DISCARDED");
}
//0x0200
if (_IrqStatus & IRQ_RANGING_MASTER_RESULT_VALID)
{
Serial.print(F(",IRQ_RANGING_MASTER_RESULT_VALID"));
}
//0x0400
if (_IrqStatus & IRQ_RANGING_MASTER_RESULT_TIMEOUT)
{
Serial.print(F(",IRQ_RANGING_MASTER_RESULT_TIMEOUT"));
}
//0x0800
if (_IrqStatus & IRQ_RANGING_SLAVE_REQUEST_VALID)
{
Serial.print(F(",IRQ_RANGING_SLAVE_REQUEST_VALID"));
}
//0x1000
if (_IrqStatus & IRQ_CAD_DONE)
{
Serial.print(F(",IRQ_CAD_DONE"));
}
//0x2000
if (_IrqStatus & IRQ_CAD_ACTIVITY_DETECTED)
{
Serial.print(F(",IRQ_CAD_ACTIVITY_DETECTED"));
}
//0x4000
if (_IrqStatus & IRQ_RX_TX_TIMEOUT)
{
Serial.print(F(",IRQ_RX_TX_TIMEOUT"));
}
//0x8000
if (_IrqStatus & IRQ_PREAMBLE_DETECTED)
{
Serial.print(F(",IRQ_PREAMBLE_DETECTED"));
}
}
uint16_t SX128XLT::CRCCCITT(uint8_t *buffer, uint8_t size, uint16_t start)
{
#ifdef SX128XDEBUG
Serial.println(F("CRCCCITT()"));
#endif
uint16_t index, libraryCRC;
uint8_t j;
libraryCRC = start; //start value for CRC16
for (index = 0; index < size; index++)
{
libraryCRC ^= (((uint16_t)buffer[index]) << 8);
for (j = 0; j < 8; j++)
{
if (libraryCRC & 0x8000)
libraryCRC = (libraryCRC << 1) ^ 0x1021;
else
libraryCRC <<= 1;
}
}
return libraryCRC;
}
uint8_t SX128XLT::receive(uint8_t *rxbuffer, uint8_t size, uint16_t timeout, uint8_t wait)
{
#ifdef SX128XDEBUG
Serial.println(F("receive()"));
#endif
uint8_t index, RXstart, RXend;
uint16_t regdata;
uint8_t buffer[2];
setDioIrqParams(IRQ_RADIO_ALL, (IRQ_RX_DONE + IRQ_RX_TX_TIMEOUT), 0, 0); //set for IRQ on RX done or timeout
setRx(timeout);
if (!wait)
{
return 0; //not wait requested so no packet length to pass
}
while (!digitalRead(_RXDonePin)); //Wait for DIO1 to go high
setMode(MODE_STDBY_RC); //ensure to stop further packet reception
regdata = readIrqStatus();
if ( (regdata & IRQ_HEADER_ERROR) | (regdata & IRQ_CRC_ERROR) | (regdata & IRQ_RX_TX_TIMEOUT ) ) //check if any of the preceding IRQs is set
{
return 0; //packet is errored somewhere so return 0
}
readCommand(RADIO_GET_RXBUFFERSTATUS, buffer, 2);
_RXPacketL = buffer[0];
if (_RXPacketL > size) //check passed buffer is big enough for packet
{
_RXPacketL = size; //truncate packet if not enough space
}
RXstart = buffer[1];
RXend = RXstart + _RXPacketL;
checkBusy();
#ifdef USE_SPI_TRANSACTION //to use SPI_TRANSACTION enable define at beginning of CPP file
SPI.beginTransaction(SPISettings(LTspeedMaximum, LTdataOrder, LTdataMode));
#endif
digitalWrite(_NSS, LOW); //start the burst read
SPI.transfer(RADIO_READ_BUFFER);
SPI.transfer(RXstart);
SPI.transfer(0xFF);
for (index = RXstart; index < RXend; index++)
{
regdata = SPI.transfer(0);
rxbuffer[index] = regdata;
}
digitalWrite(_NSS, HIGH);
#ifdef USE_SPI_TRANSACTION
SPI.endTransaction();
#endif
return _RXPacketL; //so we can check for packet having enough buffer space
}
uint8_t SX128XLT::readPacketRSSI()
{
#ifdef SX128XDEBUG
Serial.println(F("readPacketRSSI()"));
#endif
uint8_t status[5];
readCommand(RADIO_GET_PACKETSTATUS, status, 5) ;
_PacketRSSI = -status[0] / 2;
return _PacketRSSI;
}
uint8_t SX128XLT::readPacketSNR()
{
#ifdef SX128XDEBUG
Serial.println(F("readPacketSNR()"));
#endif
uint8_t status[5];
readCommand(RADIO_GET_PACKETSTATUS, status, 5) ;
if ( status[1] < 128 )
{
_PacketSNR = status[1] / 4 ;
}
else
{
_PacketSNR = (( status[1] - 256 ) / 4);
}
return _PacketSNR;
}
uint8_t SX128XLT::readRXPacketL()
{
#ifdef SX128XDEBUG
Serial.println(F("readRXPacketL()"));
#endif
uint8_t buffer[2];
readCommand(RADIO_GET_RXBUFFERSTATUS, buffer, 2);
_RXPacketL = buffer[0];
return _RXPacketL;
}
void SX128XLT::setRx(uint16_t timeout)
{
#ifdef SX128XDEBUG
Serial.println(F("setRx()"));
#endif
if (_rxtxpinmode)
{
rxEnable();
}
uint8_t buffer[3];
clearIrqStatus(IRQ_RADIO_ALL); //clear all interrupt flags
buffer[0] = _PERIODBASE; //use pre determined period base setting
buffer[1] = ( uint8_t ) ((timeout >> 8 ) & 0x00FF);
buffer[2] = ( uint8_t ) (timeout & 0x00FF);
writeCommand(RADIO_SET_RX, buffer, 3);
}
/***************************************************************************
//Start direct access SX buffer routines
***************************************************************************/
void SX128XLT::startWriteSXBuffer(uint8_t ptr)
{
#ifdef SX128XDEBUG
Serial.println(F("startWriteSXBuffer()"));
#endif
_TXPacketL = 0; //this variable used to keep track of bytes written
setMode(MODE_STDBY_RC);
setBufferBaseAddress(ptr, 0); //TX,RX
checkBusy();
#ifdef USE_SPI_TRANSACTION //to use SPI_TRANSACTION enable define at beginning of CPP file
SPI.beginTransaction(SPISettings(LTspeedMaximum, LTdataOrder, LTdataMode));
#endif
digitalWrite(_NSS, LOW);
SPI.transfer(RADIO_WRITE_BUFFER);
SPI.transfer(ptr); //address in SX buffer to write to
//SPI interface ready for byte to write to buffer
}
uint8_t SX128XLT::endWriteSXBuffer()
{
#ifdef SX128XDEBUG
Serial.println(F("endWriteSXBuffer()"));
#endif
digitalWrite(_NSS, HIGH);
#ifdef USE_SPI_TRANSACTION
SPI.endTransaction();
#endif
return _TXPacketL;
}
void SX128XLT::startReadSXBuffer(uint8_t ptr)
{
#ifdef SX128XDEBUG
Serial.println(F("startReadSXBuffer"));
#endif
_RXPacketL = 0;
checkBusy();
#ifdef USE_SPI_TRANSACTION //to use SPI_TRANSACTION enable define at beginning of CPP file
SPI.beginTransaction(SPISettings(LTspeedMaximum, LTdataOrder, LTdataMode));
#endif
digitalWrite(_NSS, LOW); //start the burst read
SPI.transfer(RADIO_READ_BUFFER);
SPI.transfer(ptr);
SPI.transfer(0xFF);
//next line would be data = SPI.transfer(0);
//SPI interface ready for byte to read from
}
uint8_t SX128XLT::endReadSXBuffer()
{
#ifdef SX128XDEBUG
Serial.println(F("endReadSXBuffer()"));
#endif
digitalWrite(_NSS, HIGH);
#ifdef USE_SPI_TRANSACTION
SPI.endTransaction();
#endif
return _RXPacketL;
}
void SX128XLT::writeUint8(uint8_t x)
{
#ifdef SX128XDEBUG
Serial.println(F("writeUint8()"));
#endif
SPI.transfer(x);
_TXPacketL++; //increment count of bytes written
}
uint8_t SX128XLT::readUint8()
{
#ifdef SX128XDEBUG
Serial.println(F("readUint8()"));
#endif
byte x;
x = SPI.transfer(0);
_RXPacketL++; //increment count of bytes read
return (x);
}
void SX128XLT::writeInt8(int8_t x)
{
#ifdef SX128XDEBUG
Serial.println(F("writeInt8()"));
#endif
SPI.transfer(x);
_TXPacketL++; //increment count of bytes written
}
int8_t SX128XLT::readInt8()
{
#ifdef SX128XDEBUG
Serial.println(F("readInt8()"));
#endif
int8_t x;
x = SPI.transfer(0);
_RXPacketL++; //increment count of bytes read
return (x);
}
void SX128XLT::writeInt16(int16_t x)
{
#ifdef SX128XDEBUG
Serial.println(F("writeInt16()"));
#endif
SPI.transfer(lowByte(x));
SPI.transfer(highByte(x));
_TXPacketL = _TXPacketL + 2; //increment count of bytes written
}
int16_t SX128XLT::readInt16()
{
#ifdef SX128XDEBUG
Serial.println(F("readInt16()"));
#endif
byte lowbyte, highbyte;
lowbyte = SPI.transfer(0);
highbyte = SPI.transfer(0);
_RXPacketL = _RXPacketL + 2; //increment count of bytes read
return ((highbyte << 8) + lowbyte);
}
void SX128XLT::writeUint16(uint16_t x)
{
#ifdef SX128XDEBUG
Serial.println(F("writeUint16()"));
#endif
SPI.transfer(lowByte(x));
SPI.transfer(highByte(x));
_TXPacketL = _TXPacketL + 2; //increment count of bytes written
}
uint16_t SX128XLT::readUint16()
{
#ifdef SX128XDEBUG
Serial.println(F("writeUint16()"));
#endif
byte lowbyte, highbyte;
lowbyte = SPI.transfer(0);
highbyte = SPI.transfer(0);
_RXPacketL = _RXPacketL + 2; //increment count of bytes read
return ((highbyte << 8) + lowbyte);
}
void SX128XLT::writeInt32(int32_t x)
{
#ifdef SX128XDEBUG
Serial.println(F("writeInt32()"));
#endif
byte i, j;
union
{
byte b[4];
int32_t f;
} data;
data.f = x;
for (i = 0; i < 4; i++)
{
j = data.b[i];
SPI.transfer(j);
}
_TXPacketL = _TXPacketL + 4; //increment count of bytes written
}
int32_t SX128XLT::readInt32()
{
#ifdef SX128XDEBUG
Serial.println(F("readInt32()"));
#endif
byte i, j;
union
{
byte b[4];
int32_t f;
} readdata;
for (i = 0; i < 4; i++)
{
j = SPI.transfer(0);
readdata.b[i] = j;
}
_RXPacketL = _RXPacketL + 4; //increment count of bytes read
return readdata.f;
}
void SX128XLT::writeUint32(uint32_t x)
{
#ifdef SX128XDEBUG
Serial.println(F("writeUint32()"));
#endif
byte i, j;
union
{
byte b[4];
uint32_t f;
} data;
data.f = x;
for (i = 0; i < 4; i++)
{
j = data.b[i];
SPI.transfer(j);
}
_TXPacketL = _TXPacketL + 4; //increment count of bytes written
}
uint32_t SX128XLT::readUint32()
{
#ifdef SX128XDEBUG
Serial.println(F("readUint32()"));
#endif
byte i, j;
union
{
byte b[4];
uint32_t f;
} readdata;
for (i = 0; i < 4; i++)
{
j = SPI.transfer(0);
readdata.b[i] = j;
}
_RXPacketL = _RXPacketL + 4; //increment count of bytes read
return readdata.f;
}
void SX128XLT::writeFloat(float x)
{
#ifdef SX128XDEBUG
Serial.println(F("writeFloat()"));
#endif
byte i, j;
union
{
byte b[4];
float f;
} data;
data.f = x;
for (i = 0; i < 4; i++)
{
j = data.b[i];
SPI.transfer(j);
}
_TXPacketL = _TXPacketL + 4; //increment count of bytes written
}
float SX128XLT::readFloat()
{
#ifdef SX128XDEBUG
Serial.println(F("readFloat()"));
#endif
byte i, j;
union
{
byte b[4];
float f;
} readdata;
for (i = 0; i < 4; i++)
{
j = SPI.transfer(0);
readdata.b[i] = j;
}
_RXPacketL = _RXPacketL + 4; //increment count of bytes read
return readdata.f;
}
uint8_t SX128XLT::transmitSXBuffer(uint8_t startaddr, uint8_t length, uint16_t timeout, int8_t txpower, uint8_t wait)
{
#ifdef SX128XDEBUG
Serial.println(F("transmitSXBuffer()"));
#endif
setBufferBaseAddress(startaddr, 0); //TX, RX
setPacketParams(savedPacketParam1, savedPacketParam2, length, savedPacketParam4, savedPacketParam5, savedPacketParam6, savedPacketParam7);
setTxParams(txpower, RAMP_TIME);
setDioIrqParams(IRQ_RADIO_ALL, (IRQ_TX_DONE + IRQ_RX_TX_TIMEOUT), 0, 0); //set for IRQ on TX done and timeout on DIO1
setTx(timeout); //this starts the TX
if (!wait)
{
return _TXPacketL;
}
while (!digitalRead(_TXDonePin)); //Wait for DIO1 to go high
if (readIrqStatus() & IRQ_RX_TX_TIMEOUT ) //check for timeout
{
return 0;
}
else
{
return _TXPacketL;
}
}
void SX128XLT::writeBuffer(uint8_t *txbuffer, uint8_t size)
{
#ifdef SX128XDEBUG1
Serial.println(F("writeBuffer()"));
#endif
uint8_t index, regdata;
_TXPacketL = _TXPacketL + size; //these are the number of bytes that will be added
size--; //loose one byte from size, the last byte written MUST be a 0
for (index = 0; index < size; index++)
{
regdata = txbuffer[index];
SPI.transfer(regdata);
}
SPI.transfer(0); //this ensures last byte of buffer written really is a null (0)
}
uint8_t SX128XLT::receiveSXBuffer(uint8_t startaddr, uint16_t timeout, uint8_t wait )
{
#ifdef SX127XDEBUG1
Serial.println(F("receiveSXBuffer()"));
#endif
uint16_t regdata;
uint8_t buffer[2];
setMode(MODE_STDBY_RC);
setBufferBaseAddress(0, startaddr); //order is TX RX
setDioIrqParams(IRQ_RADIO_ALL, (IRQ_RX_DONE + IRQ_RX_TX_TIMEOUT), 0, 0); //set for IRQ on RX done or timeout
setRx(timeout); //no actual RX timeout in this function
if (!wait)
{
return 0;
}
while (!digitalRead(_RXDonePin)); //Wait for DIO1 to go high
setMode(MODE_STDBY_RC); //ensure to stop further packet reception
regdata = readIrqStatus();
if ( (regdata & IRQ_HEADER_ERROR) | (regdata & IRQ_CRC_ERROR) | (regdata & IRQ_RX_TX_TIMEOUT ) )
{
return 0; //no RX done and header valid only, could be CRC error
}
readCommand(RADIO_GET_RXBUFFERSTATUS, buffer, 2);
_RXPacketL = buffer[0];
return _RXPacketL;
}
uint8_t SX128XLT::readBuffer(uint8_t *rxbuffer)
{
#ifdef SX128XDEBUG1
Serial.println(F("readBuffer()"));
#endif
uint8_t index = 0, regdata;
do //need to find the size of the buffer first
{
regdata = SPI.transfer(0);
rxbuffer[index] = regdata; //fill the buffer.
index++;
} while (regdata != 0); //keep reading until we have reached the null (0) at the buffer end
//or exceeded size of buffer allowed
_RXPacketL = _RXPacketL + index; //increment count of bytes read
return index; //return the actual size of the buffer, till the null (0) detected
}
void SX128XLT::setSyncWord1(uint32_t syncword)
{
#ifdef SX128XDEBUG1
Serial.println(F("setSyncWord1()"));
#endif
//For FLRC packet type, the SyncWord is one byte shorter and
//the base address is shifted by one byte
writeRegister( REG_FLRCSYNCWORD1_BASEADDR, ( syncword >> 24 ) & 0x000000FF );
writeRegister( REG_FLRCSYNCWORD1_BASEADDR + 1, ( syncword >> 16 ) & 0x000000FF );
writeRegister( REG_FLRCSYNCWORD1_BASEADDR + 2, ( syncword >> 8 ) & 0x000000FF );
writeRegister( REG_FLRCSYNCWORD1_BASEADDR + 3, syncword & 0x000000FF );
}
/***************************************************************************
//End direct access SX buffer routines
***************************************************************************/
//*******************************************************************************
//Start Ranging routines
//*******************************************************************************
void SX128XLT::setRangingSlaveAddress(uint32_t address)
{
//sets address of ranging slave
#ifdef SX128XDEBUG
Serial.println(F("SetRangingSlaveAddress()"));
#endif
uint8_t buffer[4];
buffer[0] = (address >> 24u ) & 0xFFu;
buffer[1] = (address >> 16u) & 0xFFu;
buffer[2] = (address >> 8u) & 0xFFu;
buffer[3] = (address & 0xFFu);
writeRegisters(0x916, buffer, 4 );
}
void SX128XLT::setRangingMasterAddress(uint32_t address)
{
//sets address of ranging master
#ifdef SX128XDEBUG
Serial.println(F("SetRangingMasterAddress()"));
#endif
uint8_t buffer[4];
buffer[0] = (address >> 24u ) & 0xFFu;
buffer[1] = (address >> 16u) & 0xFFu;
buffer[2] = (address >> 8u) & 0xFFu;
buffer[3] = (address & 0xFFu);
writeRegisters(0x912, buffer, 4 );
}
void SX128XLT::setRangingCalibration(uint16_t cal)
{
#ifdef SX128XDEBUG
Serial.println(F("setRangingCalibration()"));
#endif
savedCalibration = cal;
writeRegister( REG_LR_RANGINGRERXTXDELAYCAL, ( uint8_t )( ( cal >> 8 ) & 0xFF ) );
writeRegister( REG_LR_RANGINGRERXTXDELAYCAL + 1, ( uint8_t )( ( cal ) & 0xFF ) );
}
void SX128XLT::setRangingRole(uint8_t role)
{
#ifdef SX128XDEBUG
Serial.println(F("setRangingRole()"));
#endif
uint8_t buffer[1];
buffer[0] = role;
writeCommand(RADIO_SET_RANGING_ROLE, buffer, 1 );
}
uint32_t SX128XLT::getRangingResultRegValue(uint8_t resultType)
{
uint32_t valLsb = 0;
setMode(MODE_STDBY_XOSC);
writeRegister( 0x97F, readRegister( 0x97F ) | ( 1 << 1 ) ); // enable LORA modem clock
writeRegister( REG_LR_RANGINGRESULTCONFIG, ( readRegister( REG_LR_RANGINGRESULTCONFIG ) & MASK_RANGINGMUXSEL ) | ( ( ( ( uint8_t )resultType ) & 0x03 ) << 4 ) );
valLsb = ( ( (uint32_t) readRegister( REG_LR_RANGINGRESULTBASEADDR ) << 16 ) | ( (uint32_t) readRegister( REG_LR_RANGINGRESULTBASEADDR + 1 ) << 8 ) | ( readRegister( REG_LR_RANGINGRESULTBASEADDR + 2 ) ) );
setMode(MODE_STDBY_RC);
return valLsb;
}
double SX128XLT::getRangingDistance(uint8_t resultType, int32_t regval, float adjust)
{
float val = 0.0;
if (regval >= 0x800000) //raw reg value at low distance can goto 0x800000 which is negative, set distance to zero if this happens
{
regval = 0;
}
// Conversion from LSB to distance. For explanation on the formula, refer to Datasheet of SX1280
switch (resultType)
{
case RANGING_RESULT_RAW:
// Convert the ranging LSB to distance in meter. The theoretical conversion from register value to distance [m] is given by:
// distance [m] = ( complement2( register ) * 150 ) / ( 2^12 * bandwidth[MHz] ) ). The API provide BW in [Hz] so the implemented
// formula is complement2( register ) / bandwidth[Hz] * A, where A = 150 / (2^12 / 1e6) = 36621.09
val = ( double ) regval / ( double ) returnBandwidth(savedModParam2) * 36621.09375;
break;
case RANGING_RESULT_AVERAGED:
case RANGING_RESULT_DEBIASED:
case RANGING_RESULT_FILTERED:
Serial.print(F("??"));
val = ( double )regval * 20.0 / 100.0;
break;
default:
val = 0.0;
break;
}
val = val * adjust;
return val;
}
bool SX128XLT::setupRanging(uint32_t frequency, int32_t offset, uint8_t modParam1, uint8_t modParam2, uint8_t modParam3, uint32_t address, uint8_t role)
{
//sequence is frequency, offset, spreading factor, bandwidth, coding rate, calibration, role.
#ifdef SX128XDEBUG
Serial.println(F("setupRanging()"));
#endif
setMode(MODE_STDBY_RC);
setPacketType(PACKET_TYPE_RANGING);
setModulationParams(modParam1, modParam2, modParam3);
setPacketParams(12, LORA_PACKET_VARIABLE_LENGTH, 0, LORA_CRC_ON, LORA_IQ_NORMAL, 0, 0);
setRfFrequency(frequency, offset);
setRangingSlaveAddress(address);
setRangingMasterAddress(address);
setRangingCalibration(lookupCalibrationValue(modParam1, modParam2));
setRangingRole(role);
setHighSensitivity();
return true;
}
bool SX128XLT::transmitRanging(uint32_t address, uint16_t timeout, int8_t txpower, uint8_t wait)
{
#ifdef SX128XDEBUG
Serial.println(F("transmitRanging()"));
#endif
if ((_RXEN >= 0) || (_TXEN >= 0))
{
return false;
}
setMode(MODE_STDBY_RC);
setRangingMasterAddress(address);
setTxParams(txpower, RADIO_RAMP_02_US);
setDioIrqParams(IRQ_RADIO_ALL, (IRQ_TX_DONE + IRQ_RANGING_MASTER_RESULT_VALID + IRQ_RANGING_MASTER_RESULT_TIMEOUT), 0, 0);
setTx(timeout); //this sends the ranging packet
if (!wait)
{
return true;
}
while (!digitalRead(_TXDonePin)); //Wait for DIO1 to go high
if (readIrqStatus() & IRQ_RANGING_MASTER_RESULT_VALID ) //check for timeout
{
return true;
}
else
{
return false;
}
}
uint8_t SX128XLT::receiveRanging(uint32_t address, uint16_t timeout, int8_t txpower, uint8_t wait)
{
#ifdef SX128XDEBUG
Serial.println(F("receiveRanging()"));
#endif
setTxParams(txpower, RADIO_RAMP_02_US);
setRangingSlaveAddress(address);
setDioIrqParams(IRQ_RADIO_ALL, (IRQ_RANGING_SLAVE_RESPONSE_DONE + IRQ_RANGING_SLAVE_REQUEST_DISCARDED), 0, 0);
setRx(timeout);
if (!wait)
{
return NO_WAIT; //not wait requested so no packet length to pass
}
while (!digitalRead(_RXDonePin));
setMode(MODE_STDBY_RC); //ensure to stop further packet reception
if (readIrqStatus() & IRQ_RANGING_SLAVE_REQUEST_VALID)
{
return true;
}
else
{
return false; //so we can check for packet having enough buffer space
}
}
uint16_t SX128XLT::lookupCalibrationValue(uint8_t spreadingfactor, uint8_t bandwidth)
{
//this looks up the calibration value from the table in SX128XLT_Definitions.hifdef SX128XDEBUG
#ifdef SX128XDEBUG
Serial.println(F("lookupCalibrationValue()"));
#endif
switch (bandwidth)
{
case LORA_BW_0400:
savedCalibration = RNG_CALIB_0400[(spreadingfactor>>4)-5];
return savedCalibration;
case LORA_BW_0800:
savedCalibration = RNG_CALIB_0800[(spreadingfactor>>4)-5];
return savedCalibration;
case LORA_BW_1600:
savedCalibration = RNG_CALIB_1600[(spreadingfactor>>4)-5];
return savedCalibration;
default:
return 0xFFFF;
}
}
uint16_t SX128XLT::getSetCalibrationValue()
{
#ifdef SX128XDEBUG
Serial.println(F("getCalibrationValue()"));
#endif
return savedCalibration;;
}
//*******************************************************************************
//End Ranging routines
//*******************************************************************************
void SX128XLT::setSleep(uint8_t sleepconfig)
{
#ifdef SX128XDEBUG
Serial.println(F("setSleep()"));
#endif
setMode(MODE_STDBY_RC);
checkBusy();
#ifdef USE_SPI_TRANSACTION //to use SPI_TRANSACTION enable define at beginning of CPP file
SPI.beginTransaction(SPISettings(LTspeedMaximum, LTdataOrder, LTdataMode));
#endif
//need to save registers to device RAM first
digitalWrite(_NSS, LOW);
SPI.transfer(RADIO_SET_SAVECONTEXT);
digitalWrite(_NSS, HIGH);
checkBusy();
digitalWrite(_NSS, LOW);
SPI.transfer(RADIO_SET_SLEEP);
SPI.transfer(sleepconfig);
digitalWrite(_NSS, HIGH);
#ifdef USE_SPI_TRANSACTION
SPI.endTransaction();
#endif
delay(1); //allow time for shutdown
}
uint16_t SX128XLT::CRCCCITTSX(uint8_t startadd, uint8_t endadd, uint16_t startvalue)
{
//genrates a CRC of an area of the internal SX buffer
#ifdef SX126XDEBUG1
Serial.println(F("CRCCCITTSX()"));
#endif
uint16_t index, libraryCRC;
uint8_t j;
libraryCRC = startvalue; //start value for CRC16
startReadSXBuffer(startadd); //begin the buffer read
for (index = startadd; index <= endadd; index++)
{
libraryCRC ^= (((uint16_t) readUint8() ) << 8);
for (j = 0; j < 8; j++)
{
if (libraryCRC & 0x8000)
libraryCRC = (libraryCRC << 1) ^ 0x1021;
else
libraryCRC <<= 1;
}
}
endReadSXBuffer(); //end the buffer read
return libraryCRC;
}
uint8_t SX128XLT::getByteSXBuffer(uint8_t addr)
{
#ifdef SX128XDEBUG1
Serial.println(F("getByteSXBuffer()"));
#endif
uint8_t regdata;
setMode(MODE_STDBY_RC); //this is needed to ensure we can read from buffer OK.
#ifdef USE_SPI_TRANSACTION //to use SPI_TRANSACTION enable define at beginning of CPP file
SPI.beginTransaction(SPISettings(LTspeedMaximum, LTdataOrder, LTdataMode));
#endif
digitalWrite(_NSS, LOW); //start the burst read
SPI.transfer(RADIO_READ_BUFFER);
SPI.transfer(addr);
SPI.transfer(0xFF);
regdata = SPI.transfer(0);
digitalWrite(_NSS, HIGH);
#ifdef USE_SPI_TRANSACTION
SPI.endTransaction();
#endif
return regdata;
}
void SX128XLT::printSXBufferHEX(uint8_t start, uint8_t end)
{
#ifdef SX128XDEBUG
Serial.println(F("printSXBufferHEX()"));
#endif
uint8_t index, regdata;
setMode(MODE_STDBY_RC);
#ifdef USE_SPI_TRANSACTION //to use SPI_TRANSACTION enable define at beginning of CPP file
SPI.beginTransaction(SPISettings(LTspeedMaximum, LTdataOrder, LTdataMode));
#endif
digitalWrite(_NSS, LOW); //start the burst read
SPI.transfer(RADIO_READ_BUFFER);
SPI.transfer(start);
SPI.transfer(0xFF);
for (index = start; index <= end; index++)
{
regdata = SPI.transfer(0);
printHEXByte(regdata);
Serial.print(F(" "));
}
digitalWrite(_NSS, HIGH);
#ifdef USE_SPI_TRANSACTION
SPI.endTransaction();
#endif
}
void SX128XLT::printHEXByte(uint8_t temp)
{
if (temp < 0x10)
{
Serial.print(F("0"));
}
Serial.print(temp, HEX);
}
void SX128XLT::wake()
{
#ifdef SX128XDEBUG
Serial.println(F("wake()"));
#endif
digitalWrite(_NSS, LOW);
delay(1);
digitalWrite(_NSS, HIGH);
delay(1);
}
int32_t SX128XLT::getFrequencyErrorRegValue()
{
#ifdef SX128XDEBUG
Serial.println(F("getFrequencyErrorRegValue()"));
#endif
int32_t FrequencyError;
uint32_t regmsb, regmid, reglsb, allreg;
setMode(MODE_STDBY_XOSC);
regmsb = readRegister( REG_LR_ESTIMATED_FREQUENCY_ERROR_MSB );
regmsb = regmsb & 0x0F; //clear bit 20 which is always set
regmid = readRegister( REG_LR_ESTIMATED_FREQUENCY_ERROR_MSB + 1 );
reglsb = readRegister( REG_LR_ESTIMATED_FREQUENCY_ERROR_MSB + 2 );
setMode(MODE_STDBY_RC);
#ifdef LORADEBUG
Serial.println();
Serial.print(F("Registers "));
Serial.print(regmsb,HEX);
Serial.print(F(" "));
Serial.print(regmid,HEX);
Serial.print(F(" "));
Serial.println(reglsb,HEX);
#endif
allreg = (uint32_t) ( regmsb << 16 ) | ( regmid << 8 ) | reglsb;
if (allreg & 0x80000)
{
FrequencyError = (0xFFFFF - allreg) * -1;
}
else
{
FrequencyError = allreg;
}
return FrequencyError;
}
int32_t SX128XLT::getFrequencyErrorHz()
{
#ifdef SX128XDEBUG
Serial.println(F("getFrequencyErrorHz()"));
#endif
int32_t error, regvalue;
uint32_t bandwidth;
float divider;
bandwidth = returnBandwidth(savedModParam2); //gets the last configured bandwidth
divider = (float) 1625000 / bandwidth; //data sheet says 1600000, but bandwidth is 1625000
regvalue = getFrequencyErrorRegValue();
error = (FREQ_ERROR_CORRECTION * regvalue) / divider;
return error;
}
uint8_t SX128XLT::transmitAddressed(uint8_t *txbuffer, uint8_t size, char txpackettype, char txdestination, char txsource, uint32_t timeout, int8_t txpower, uint8_t wait)
{
#ifdef SX128XDEBUG
Serial.println(F("transmitAddressed()"));
#endif
uint8_t index;
uint8_t bufferdata;
if (size == 0)
{
return false;
}
setMode(MODE_STDBY_RC);
setBufferBaseAddress(0, 0);
checkBusy();
#ifdef USE_SPI_TRANSACTION //to use SPI_TRANSACTION enable define at beginning of CPP file
SPI.beginTransaction(SPISettings(LTspeedMaximum, LTdataOrder, LTdataMode));
#endif
digitalWrite(_NSS, LOW);
SPI.transfer(RADIO_WRITE_BUFFER);
SPI.transfer(0);
SPI.transfer(txpackettype); //Write the packet type
SPI.transfer(txdestination); //Destination node
SPI.transfer(txsource); //Source node
_TXPacketL = 3 + size; //we have added 3 header bytes to size
for (index = 0; index < size; index++)
{
bufferdata = txbuffer[index];
SPI.transfer(bufferdata);
}
digitalWrite(_NSS, HIGH);
#ifdef USE_SPI_TRANSACTION
SPI.endTransaction();
#endif
if (savedPacketType == PACKET_TYPE_LORA)
{
writeRegister(REG_LR_PAYLOADLENGTH, _TXPacketL); //only seems to work for lora
}
else if (savedPacketType == PACKET_TYPE_FLRC)
{
setPacketParams(savedPacketParam1, savedPacketParam2, savedPacketParam3, savedPacketParam4, _TXPacketL, savedPacketParam6, savedPacketParam7);
}
setTxParams(txpower, RAMP_TIME);
setDioIrqParams(IRQ_RADIO_ALL, (IRQ_TX_DONE + IRQ_RX_TX_TIMEOUT), 0, 0); //set for IRQ on TX done and timeout on DIO1
setTx(timeout); //this starts the TX
if (!wait)
{
return _TXPacketL;
}
while (!digitalRead(_TXDonePin)); //Wait for DIO1 to go high
if (readIrqStatus() & IRQ_RX_TX_TIMEOUT ) //check for timeout
{
return 0;
}
else
{
return _TXPacketL;
}
}
uint8_t SX128XLT::receiveAddressed(uint8_t *rxbuffer, uint8_t size, uint16_t timeout, uint8_t wait)
{
#ifdef SX128XDEBUG
Serial.println(F("receiveAddressed()"));
#endif
uint8_t index, RXstart, RXend;
uint16_t regdata;
uint8_t buffer[2];
setDioIrqParams(IRQ_RADIO_ALL, (IRQ_RX_DONE + IRQ_RX_TX_TIMEOUT), 0, 0); //set for IRQ on RX done or timeout
setRx(timeout);
if (!wait)
{
return 0; //not wait requested so no packet length to pass
}
while (!digitalRead(_RXDonePin)); //Wait for DIO1 to go high
setMode(MODE_STDBY_RC); //ensure to stop further packet reception
regdata = readIrqStatus();
if ( (regdata & IRQ_HEADER_ERROR) | (regdata & IRQ_CRC_ERROR) | (regdata & IRQ_RX_TX_TIMEOUT ) ) //check if any of the preceding IRQs is set
{
return 0; //packet is errored somewhere so return 0
}
readCommand(RADIO_GET_RXBUFFERSTATUS, buffer, 2);
_RXPacketL = buffer[0];
if (_RXPacketL > size) //check passed buffer is big enough for packet
{
_RXPacketL = size; //truncate packet if not enough space
}
RXstart = buffer[1];
RXend = RXstart + _RXPacketL;
checkBusy();
#ifdef USE_SPI_TRANSACTION //to use SPI_TRANSACTION enable define at beginning of CPP file
SPI.beginTransaction(SPISettings(LTspeedMaximum, LTdataOrder, LTdataMode));
#endif
digitalWrite(_NSS, LOW); //start the burst read
SPI.transfer(RADIO_READ_BUFFER);
SPI.transfer(RXstart);
SPI.transfer(0xFF);
_RXPacketType = SPI.transfer(0);
_RXDestination = SPI.transfer(0);
_RXSource = SPI.transfer(0);
for (index = RXstart; index < RXend; index++)
{
regdata = SPI.transfer(0);
rxbuffer[index] = regdata;
}
digitalWrite(_NSS, HIGH);
#ifdef USE_SPI_TRANSACTION
SPI.endTransaction();
#endif
return _RXPacketL; //so we can check for packet having enough buffer space
}
uint8_t SX128XLT::readRXPacketType()
{
#ifdef SX128XDEBUG
Serial.println(F("readRXPacketType()"));
#endif
return _RXPacketType;
}
uint8_t SX128XLT::readPacket(uint8_t *rxbuffer, uint8_t size)
{
#ifdef SX128XDEBUG
Serial.println(F("readPacket()"));
#endif
uint8_t index, regdata, RXstart, RXend;
uint8_t buffer[2];
readCommand(RADIO_GET_RXBUFFERSTATUS, buffer, 2);
_RXPacketL = buffer[0];
if (_RXPacketL > size) //check passed buffer is big enough for packet
{
_RXPacketL = size; //truncate packet if not enough space
}
RXstart = buffer[1];
RXend = RXstart + _RXPacketL;
#ifdef USE_SPI_TRANSACTION //to use SPI_TRANSACTION enable define at beginning of CPP file
SPI.beginTransaction(SPISettings(LTspeedMaximum, LTdataOrder, LTdataMode));
#endif
digitalWrite(_NSS, LOW); //start the burst read
SPI.transfer(RADIO_READ_BUFFER);
SPI.transfer(RXstart);
SPI.transfer(0xFF);
for (index = RXstart; index < RXend; index++)
{
regdata = SPI.transfer(0);
rxbuffer[index] = regdata;
}
digitalWrite(_NSS, HIGH);
#ifdef USE_SPI_TRANSACTION
SPI.endTransaction();
#endif
return _RXPacketL; //so we can check for packet having enough buffer space
}
uint16_t SX128XLT::addCRC(uint8_t data, uint16_t libraryCRC)
{
uint8_t j;
libraryCRC ^= ((uint16_t)data << 8);
for (j = 0; j < 8; j++)
{
if (libraryCRC & 0x8000)
libraryCRC = (libraryCRC << 1) ^ 0x1021;
else
libraryCRC <<= 1;
}
return libraryCRC;
}
void SX128XLT::writeBufferChar(char *txbuffer, uint8_t size)
{
#ifdef SX128XDEBUG1
Serial.println(F("writeBuffer()"));
#endif
uint8_t index, regdata;
_TXPacketL = _TXPacketL + size; //these are the number of bytes that will be added
size--; //loose one byte from size, the last byte written MUST be a 0
for (index = 0; index < size; index++)
{
regdata = txbuffer[index];
SPI.transfer(regdata);
}
SPI.transfer(0); //this ensures last byte of buffer writen really is a null (0)
}
uint8_t SX128XLT::readBufferChar(char *rxbuffer)
{
#ifdef SX128XDEBUG1
Serial.println(F("readBuffer()"));
#endif
uint8_t index = 0, regdata;
do //need to find the size of the buffer first
{
regdata = SPI.transfer(0);
rxbuffer[index] = regdata; //fill the buffer.
index++;
} while (regdata != 0); //keep reading until we have reached the null (0) at the buffer end
//or exceeded size of buffer allowed
_RXPacketL = _RXPacketL + index; //increment count of bytes read
return index; //return the actual size of the buffer, till the null (0) detected
}
/*
MIT license
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions
of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
*/