531 lines
14 KiB
C++
531 lines
14 KiB
C++
#ifndef RF_SX1276
|
|
#define RF_SX1276
|
|
|
|
/*
|
|
// registers
|
|
#define REG_FIFO 0x00
|
|
#define REG_OP_MODE 0x01
|
|
#define REG_FRF_MSB 0x06
|
|
#define REG_FRF_MID 0x07
|
|
#define REG_FRF_LSB 0x08
|
|
#define REG_PA_CONFIG 0x09
|
|
#define REG_OCP 0x0b
|
|
#define REG_LNA 0x0c
|
|
#define REG_FIFO_ADDR_PTR 0x0d
|
|
#define REG_FIFO_TX_BASE_ADDR 0x0e
|
|
#define REG_FIFO_RX_BASE_ADDR 0x0f
|
|
#define REG_FIFO_RX_CURRENT_ADDR 0x10
|
|
#define REG_IRQ_FLAGS 0x12
|
|
#define REG_RX_NB_BYTES 0x13
|
|
#define REG_PKT_SNR_VALUE 0x19
|
|
#define REG_PKT_RSSI_VALUE 0x1a
|
|
#define REG_MODEM_CONFIG_1 0x1d
|
|
#define REG_MODEM_CONFIG_2 0x1e
|
|
#define REG_PREAMBLE_MSB 0x20
|
|
#define REG_PREAMBLE_LSB 0x21
|
|
#define REG_PAYLOAD_LENGTH 0x22
|
|
#define REG_MODEM_CONFIG_3 0x26
|
|
#define REG_FREQ_ERROR_MSB 0x28
|
|
#define REG_FREQ_ERROR_MID 0x29
|
|
#define REG_FREQ_ERROR_LSB 0x2a
|
|
#define REG_RSSI_WIDEBAND 0x2c
|
|
#define REG_DETECTION_OPTIMIZE 0x31
|
|
#define REG_INVERTIQ 0x33
|
|
#define REG_DETECTION_THRESHOLD 0x37
|
|
#define REG_SYNC_WORD 0x39
|
|
#define REG_INVERTIQ2 0x3b
|
|
#define REG_DIO_MAPPING_1 0x40
|
|
#define REG_VERSION 0x42
|
|
#define REG_PA_DAC 0x4d
|
|
|
|
// modes
|
|
#define MODE_LONG_RANGE_MODE 0x80
|
|
#define MODE_SLEEP 0x00
|
|
#define MODE_STDBY 0x01
|
|
#define MODE_TX 0x03
|
|
#define MODE_RX_CONTINUOUS 0x05
|
|
#define MODE_RX_SINGLE 0x06
|
|
|
|
// PA config
|
|
#define PA_BOOST 0x80
|
|
|
|
// IRQ masks
|
|
#define IRQ_TX_DONE_MASK 0x08
|
|
#define IRQ_PAYLOAD_CRC_ERROR_MASK 0x20
|
|
#define IRQ_RX_DONE_MASK 0x40
|
|
|
|
#define MAX_PKT_LENGTH 255
|
|
|
|
#if (ESP8266 || ESP32)
|
|
#define ISR_PREFIX ICACHE_RAM_ATTR
|
|
#else
|
|
#define ISR_PREFIX
|
|
#endif
|
|
|
|
*/
|
|
|
|
#include "../../Debug.h"
|
|
|
|
template <typename SPI, int PIN_SLAVE_SEL, int PIN_RESET, int PIN_DIO0> class SX1276 {
|
|
|
|
SPI& spi;
|
|
|
|
static constexpr const uint8_t REG_FIFO = 0x00;
|
|
static constexpr const uint8_t REG_OP_MODE = 0x01;
|
|
static constexpr const uint8_t REG_FRF_MSB = 0x06;
|
|
static constexpr const uint8_t REG_FRF_MID = 0x07;
|
|
static constexpr const uint8_t REG_FRF_LSB = 0x08;
|
|
static constexpr const uint8_t REG_PA_CONFIG = 0x09;
|
|
static constexpr const uint8_t REG_OCP = 0x0b;
|
|
static constexpr const uint8_t REG_LNA = 0x0c;
|
|
static constexpr const uint8_t REG_FIFO_ADDR_PTR = 0x0d;
|
|
static constexpr const uint8_t REG_FIFO_TX_BASE_ADDR = 0x0e;
|
|
static constexpr const uint8_t REG_FIFO_RX_BASE_ADDR = 0x0f;
|
|
|
|
static constexpr const uint8_t REG_FIFO_RX_CURRENT_ADDR = 0x10;
|
|
static constexpr const uint8_t REG_IRQ_FLAGS = 0x12;
|
|
static constexpr const uint8_t REG_RX_NB_BYTES = 0x13;
|
|
static constexpr const uint8_t REG_PKT_SNR_VALUE = 0x19;
|
|
static constexpr const uint8_t REG_PKT_RSSI_VALUE = 0x1a;
|
|
static constexpr const uint8_t REG_MODEM_CONFIG_1 = 0x1d;
|
|
static constexpr const uint8_t REG_MODEM_CONFIG_2 = 0x1e;
|
|
|
|
static constexpr const uint8_t REG_PAYLOAD_LENGTH = 0x22;
|
|
static constexpr const uint8_t REG_MODEM_CONFIG_3 = 0x26;
|
|
|
|
static constexpr const uint8_t REG_DETECTION_OPTIMIZE = 0x31;
|
|
static constexpr const uint8_t REG_DETECTION_THRESHOLD =0x37;
|
|
|
|
|
|
static constexpr const uint8_t REG_VERSION = 0x42;
|
|
static constexpr const uint8_t REG_PA_DAC = 0x4d;
|
|
|
|
// modes
|
|
static constexpr const uint8_t MODE_LONG_RANGE_MODE = 0x80;
|
|
static constexpr const uint8_t MODE_SLEEP = 0x00;
|
|
static constexpr const uint8_t MODE_STDBY = 0x01;
|
|
static constexpr const uint8_t MODE_TX = 0x03;
|
|
static constexpr const uint8_t MODE_RX_CONTINUOUS = 0x05;
|
|
static constexpr const uint8_t MODE_RX_SINGLE = 0x06;
|
|
|
|
// PA config
|
|
static constexpr const uint8_t PA_BOOST = 0x80;
|
|
|
|
static constexpr const uint8_t PA_OUTPUT_RFO_PIN = 0;
|
|
static constexpr const uint8_t PA_OUTPUT_PA_BOOST_PIN = 1;
|
|
|
|
// IRQ marks
|
|
static constexpr const uint8_t IRQ_TX_DONE_MASK = 0x08;
|
|
static constexpr const uint8_t IRQ_PAYLOAD_CRC_ERROR_MASK = 0x20;
|
|
static constexpr const uint8_t IRQ_RX_DONE_MASK = 0x40;
|
|
|
|
static constexpr const uint8_t MAX_PKT_LENGTH = 255;
|
|
|
|
|
|
static constexpr const char* NAME = "LoRa";
|
|
|
|
uint32_t curFreq = 0;
|
|
|
|
|
|
public:
|
|
|
|
struct PacketDetails {
|
|
int8_t rssi;
|
|
float snr;
|
|
};
|
|
|
|
public:
|
|
|
|
SX1276(SPI& spi) : spi(spi) {
|
|
MyGPIO::setOutput(PIN_SLAVE_SEL);
|
|
MyGPIO::setOutput(PIN_RESET);
|
|
reset();
|
|
check();
|
|
init();
|
|
}
|
|
|
|
void init() {
|
|
|
|
debugMod(NAME, "init()");
|
|
|
|
// sleep mode
|
|
sleep();
|
|
|
|
// set the RF frequency
|
|
setFrequency(866E6);
|
|
|
|
// set base address
|
|
writeRegister(REG_FIFO_TX_BASE_ADDR, 0);
|
|
writeRegister(REG_FIFO_RX_BASE_ADDR, 0);
|
|
|
|
// set LNA boost
|
|
writeRegister(REG_LNA, readRegister(REG_LNA) | 0x03);
|
|
|
|
// set auto AGC
|
|
writeRegister(REG_MODEM_CONFIG_3, 0x04);
|
|
|
|
// set output power to 17 dBm
|
|
setTxPower(17);
|
|
|
|
// put in standby mode
|
|
idle();
|
|
|
|
debugMod(NAME, "init complete");
|
|
|
|
}
|
|
|
|
// void setUseCRC(bool use) {
|
|
// if (use) {
|
|
// writeRegister(REG_MODEM_CONFIG_2, readRegister(REG_MODEM_CONFIG_2) | 0x04);
|
|
// } else {
|
|
// writeRegister(REG_MODEM_CONFIG_2, readRegister(REG_MODEM_CONFIG_2) & 0xfb);
|
|
// }
|
|
// }
|
|
|
|
/** get the currently configured spreading factor */
|
|
int getSpreadingFactor() {
|
|
return readRegister(REG_MODEM_CONFIG_2) >> 4;
|
|
}
|
|
|
|
/**
|
|
* set the spreading factor to use
|
|
* BEWARE!! BOTH sides must agree on the same spreading factor or the transmission will fail!!
|
|
* 6 requires implicit header mode (you are responsible for sending the header)
|
|
* 7 = lowest quality, fastest
|
|
* 12 = highest quality, slowest
|
|
*/
|
|
void setSpreadingFactor(int sf) {
|
|
|
|
if (sf < 6 || sf > 12) {debugMod1(NAME, "setSpreadingFactor(%d) out of range", sf); return;}
|
|
|
|
if (sf == 6) {
|
|
writeRegister(REG_DETECTION_OPTIMIZE, 0xc5);
|
|
writeRegister(REG_DETECTION_THRESHOLD, 0x0c);
|
|
} else {
|
|
writeRegister(REG_DETECTION_OPTIMIZE, 0xc3);
|
|
writeRegister(REG_DETECTION_THRESHOLD, 0x0a);
|
|
}
|
|
|
|
writeRegister(REG_MODEM_CONFIG_2, (readRegister(REG_MODEM_CONFIG_2) & 0x0f) | ((sf << 4) & 0xf0));
|
|
setLdoFlag();
|
|
|
|
}
|
|
|
|
/** get the signal's transmit bandwidth (in Hz) */
|
|
uint32_t getSignalBandwidth() {
|
|
const uint8_t bw = (readRegister(REG_MODEM_CONFIG_1) >> 4);
|
|
switch (bw) {
|
|
case 0: return 7.8E3;
|
|
case 1: return 10.4E3;
|
|
case 2: return 15.6E3;
|
|
case 3: return 20.8E3;
|
|
case 4: return 31.25E3;
|
|
case 5: return 41.7E3;
|
|
case 6: return 62.5E3;
|
|
case 7: return 125E3;
|
|
case 8: return 250E3;
|
|
case 9: return 500E3;
|
|
default: return -1;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* set the FEC (Forward Error Correction) (see Table14)
|
|
* available values are: 4/5, 4/6, 4/7, 4/8
|
|
* the receiver is not required to know the FEC of the sender, as this value is transmitted within the header, which is always sent with 4/8
|
|
*/
|
|
void setCodingRate4(int denominator) {
|
|
if (denominator < 5 || denominator > 8) {debugMod1(NAME, "setCodingRate4(%d) out of range", denominator); return;}
|
|
const int cr = denominator - 4;
|
|
writeRegister(REG_MODEM_CONFIG_1, (readRegister(REG_MODEM_CONFIG_1) & 0xf1) | (cr << 1));
|
|
}
|
|
|
|
/** get the current FEC denominator 4/x */
|
|
int getCodingRate4() {
|
|
return ((readRegister(REG_MODEM_CONFIG_1) & 0b1110) >> 1) + 4;
|
|
}
|
|
|
|
|
|
/** calculate the current data-rate based on spreading-factor and bandwidth */
|
|
int16_t getDataRate() {
|
|
// https://blog.dbrgn.ch/2017/6/23/lorawan-data-rates/
|
|
const int sf = getSpreadingFactor();
|
|
const int bw = getSignalBandwidth();
|
|
if (bw == 125000) {
|
|
if (sf == 7) {return 5470;}
|
|
if (sf == 8) {return 3125;}
|
|
if (sf == 9) {return 1760;}
|
|
if (sf == 10) {return 980;}
|
|
if (sf == 11) {return 440;}
|
|
if (sf == 12) {return 250;}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
/** switch to idle mode */
|
|
void idle() {
|
|
debugMod(NAME, "idle()");
|
|
writeRegister(REG_OP_MODE, MODE_LONG_RANGE_MODE | MODE_STDBY);
|
|
}
|
|
|
|
/** switch to sleep mode */
|
|
void sleep() {
|
|
debugMod(NAME, "sleep()");
|
|
writeRegister(REG_OP_MODE, MODE_LONG_RANGE_MODE | MODE_SLEEP);
|
|
}
|
|
|
|
bool isTransmitting() {
|
|
if ((readRegister(REG_OP_MODE) & MODE_TX) == MODE_TX) {return true;}
|
|
if (readRegister(REG_IRQ_FLAGS) & IRQ_TX_DONE_MASK) {writeRegister(REG_IRQ_FLAGS, IRQ_TX_DONE_MASK);} // clear TX-done IRQ flag
|
|
return false;
|
|
}
|
|
|
|
/** send the given data */
|
|
int send(const uint8_t* data, uint8_t len, bool async) {
|
|
|
|
debugMod1(NAME, "send(%d bytes)", len);
|
|
|
|
//if (len > MAX_PKT_LENGTH) {return -1;}
|
|
if (isTransmitting()) {return 0;}
|
|
|
|
// put in standby mode
|
|
idle();
|
|
|
|
//if (implicitHeader) {
|
|
// implicitHeaderMode();
|
|
//} else {
|
|
explicitHeaderMode();
|
|
//}
|
|
|
|
// reset FIFO address and payload length
|
|
writeRegister(REG_FIFO_ADDR_PTR, 0);
|
|
writeRegister(REG_PAYLOAD_LENGTH, 0);
|
|
|
|
// write data into FIFO
|
|
debugMod(NAME, "writing FIFO");
|
|
for (uint16_t i = 0; i < len; ++i) {writeRegister(REG_FIFO, data[i]);}
|
|
writeRegister(REG_PAYLOAD_LENGTH, len);
|
|
|
|
|
|
//if ((async) && (_onTxDone)) writeRegister(REG_DIO_MAPPING_1, 0x40); // DIO0 => TXDONE
|
|
|
|
// put in TX mode
|
|
debugMod(NAME, "starting TX");
|
|
writeRegister(REG_OP_MODE, MODE_LONG_RANGE_MODE | MODE_TX);
|
|
|
|
if (!async) {
|
|
debugMod(NAME, "waiting");
|
|
while ((readRegister(REG_IRQ_FLAGS) & IRQ_TX_DONE_MASK) == 0) {yield();} // wait for TX done
|
|
writeRegister(REG_IRQ_FLAGS, IRQ_TX_DONE_MASK); // clear IRQ TX-done flag
|
|
debugMod(NAME, "done");
|
|
}
|
|
|
|
return len;
|
|
|
|
}
|
|
|
|
/** switch to RX-mode. either for a single frame or permanent */
|
|
void setRX(bool single) {
|
|
|
|
// reset FIFO address
|
|
writeRegister(REG_FIFO_ADDR_PTR, 0);
|
|
|
|
// put in single RX mode
|
|
if (single) {
|
|
writeRegister(REG_OP_MODE, MODE_LONG_RANGE_MODE | MODE_RX_SINGLE);
|
|
} else {
|
|
writeRegister(REG_OP_MODE, MODE_LONG_RANGE_MODE | MODE_RX_CONTINUOUS);
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/** fetch one received packet, if any is available. requires setRX(..) beforehand to actually receive something! */
|
|
int read(uint8_t* dst, PacketDetails* det) {
|
|
|
|
uint8_t irqFlags = readRegister(REG_IRQ_FLAGS);
|
|
|
|
explicitHeaderMode();
|
|
|
|
// clear all current IRQ flags
|
|
writeRegister(REG_IRQ_FLAGS, irqFlags);
|
|
|
|
// got something?
|
|
if ((irqFlags & IRQ_RX_DONE_MASK) && (irqFlags & IRQ_PAYLOAD_CRC_ERROR_MASK) == 0) {
|
|
|
|
// read packet length
|
|
uint8_t packetLength = readRegister(REG_RX_NB_BYTES);
|
|
|
|
// set FIFO address to current RX address
|
|
writeRegister(REG_FIFO_ADDR_PTR, readRegister(REG_FIFO_RX_CURRENT_ADDR));
|
|
|
|
// read the packet
|
|
for (uint16_t i = 0; i < packetLength; ++i) {
|
|
dst[i] = readRegister(REG_FIFO);
|
|
}
|
|
|
|
// read the details?
|
|
if (det) {
|
|
det->rssi = (readRegister(REG_PKT_RSSI_VALUE) - (curFreq < 868E6 ? 164 : 157));
|
|
det->snr = ((int8_t)readRegister(REG_PKT_SNR_VALUE)) * 0.25f;
|
|
}
|
|
|
|
// put in standby mode
|
|
//idle();
|
|
return packetLength;
|
|
|
|
}
|
|
|
|
// nothing available
|
|
return 0;
|
|
|
|
}
|
|
|
|
/** debug output */
|
|
void debugDump() {
|
|
printf("SX1276: spreadingFactor: %d, bandwidth: %d Hz, dataRate: %d bit/s FEC: 4/%d \n", getSpreadingFactor(), getSignalBandwidth(), getDataRate(), getCodingRate4());
|
|
}
|
|
|
|
|
|
private:
|
|
|
|
void yield() {
|
|
usleep(5000);
|
|
}
|
|
|
|
void explicitHeaderMode() {
|
|
writeRegister(REG_MODEM_CONFIG_1, readRegister(REG_MODEM_CONFIG_1) & 0xfe);
|
|
}
|
|
|
|
void implicitHeaderMode() {
|
|
writeRegister(REG_MODEM_CONFIG_1, readRegister(REG_MODEM_CONFIG_1) | 0x01);
|
|
}
|
|
|
|
// uint8_t getMode() {
|
|
// const uint8_t mode = readRegister(REG_OP_MODE);
|
|
// debugMod1(NAME, "mode: %d", mode);
|
|
// return mode;
|
|
// }
|
|
|
|
/** set the RF frequency to use (in Hz) */
|
|
void setFrequency(uint32_t hz) {
|
|
this->curFreq = hz;
|
|
debugMod1(NAME, "setFrequency(%d Hz)", (int)hz);
|
|
const uint64_t frf = ((uint64_t)hz << 19) / 32000000;
|
|
writeRegister(REG_FRF_MSB, (uint8_t)(frf >> 16));
|
|
writeRegister(REG_FRF_MID, (uint8_t)(frf >> 8));
|
|
writeRegister(REG_FRF_LSB, (uint8_t)(frf >> 0));
|
|
}
|
|
|
|
/** configure the TX power (in dB). but what is the pin for??? */
|
|
void setTxPower(int level, int outputPin = PA_OUTPUT_PA_BOOST_PIN) {
|
|
|
|
debugMod1(NAME, "setTx(%d dB)", level);
|
|
|
|
if (PA_OUTPUT_RFO_PIN == outputPin) {
|
|
|
|
// RFO
|
|
if (level < 0) {level = 0;}
|
|
if (level > 14) {level = 14;}
|
|
writeRegister(REG_PA_CONFIG, 0x70 | level);
|
|
|
|
} else {
|
|
|
|
// PA BOOST
|
|
if (level > 17) {
|
|
|
|
if (level > 20) {level = 20;}
|
|
|
|
// subtract 3 from level, so 18 - 20 maps to 15 - 17
|
|
level -= 3;
|
|
|
|
// High Power +20 dBm Operation (Semtech SX1276/77/78/79 5.4.3.)
|
|
writeRegister(REG_PA_DAC, 0x87);
|
|
setOCP(140);
|
|
|
|
} else {
|
|
if (level < 2) {level = 2;}
|
|
//Default value PA_HF/LF or +17dBm
|
|
writeRegister(REG_PA_DAC, 0x84);
|
|
setOCP(100);
|
|
}
|
|
|
|
writeRegister(REG_PA_CONFIG, PA_BOOST | (level - 2));
|
|
|
|
}
|
|
}
|
|
|
|
/** perform hard reset */
|
|
void reset() {
|
|
debugMod(NAME, "reset");
|
|
MyGPIO::set(PIN_RESET);
|
|
usleep(50*1000);
|
|
MyGPIO::clear(PIN_RESET); // perform reset
|
|
usleep(50*1000);
|
|
MyGPIO::set(PIN_RESET);
|
|
usleep(50*1000);
|
|
debugMod(NAME, "reset done");
|
|
}
|
|
|
|
/** check the chip's version */
|
|
void check() {
|
|
idle();
|
|
uint8_t version = readRegister(REG_VERSION);
|
|
debugMod1(NAME, "version %d", version);
|
|
if (version != 0x12) {debugMod(NAME, "!! unsupported chip version detected");}
|
|
sleep();
|
|
}
|
|
|
|
/** what exactly is this? */
|
|
void setOCP(uint8_t mA) {
|
|
uint8_t ocpTrim = 27;
|
|
if (mA <= 120) {ocpTrim = (mA - 45) / 5;}
|
|
else if (mA <= 240) {ocpTrim = (mA + 30) / 10;}
|
|
writeRegister(REG_OCP, 0x20 | (0x1F & ocpTrim));
|
|
}
|
|
|
|
/** what exactly is this? Low-DataRate-Optimizer??? */
|
|
void setLdoFlag() {
|
|
|
|
// Section 4.1.1.5
|
|
uint64_t symbolDuration = 1000 / ( getSignalBandwidth() / (1L << getSpreadingFactor()) ) ;
|
|
|
|
// Section 4.1.1.6
|
|
bool ldoOn = symbolDuration > 16;
|
|
|
|
uint8_t config3 = readRegister(REG_MODEM_CONFIG_3);
|
|
if (ldoOn) {config3 = config3 | (1<<3);} else {config3 = config3 & ~(1<<3);}
|
|
//bitWrite(config3, 3, ldoOn);
|
|
writeRegister(REG_MODEM_CONFIG_3, config3);
|
|
|
|
}
|
|
|
|
|
|
private:
|
|
|
|
/** write value to register */
|
|
void writeRegister(uint8_t address, uint8_t value) {
|
|
transfer(address | 0x80, value);
|
|
}
|
|
|
|
/** read value from register */
|
|
uint8_t readRegister(uint8_t address) {
|
|
return transfer(address & 0x7f, 0x00);
|
|
}
|
|
|
|
uint8_t transfer(uint8_t address, uint8_t value) {
|
|
|
|
MyGPIO::clear(PIN_SLAVE_SEL); // select, active LOW
|
|
spi.writeByte(address);
|
|
const uint8_t res = spi.readWriteByte(value);
|
|
MyGPIO::set(PIN_SLAVE_SEL); // de-select
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
#endif
|