Files
ESP8266lib/ext/rf/SX1276.h
2020-07-07 23:39:13 +02:00

581 lines
16 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; // max output: +14 dB
static constexpr const uint8_t PA_OUTPUT_PA_BOOST_PIN = 1; // max output: +20 dB
// 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;
uint8_t curTXP = 17;
public:
struct PacketDetails {
int8_t rssi; // can be smaller than -128! -> use getter method (-128 - 1 -> + 127)
int8_t snr4; // SNR*4 (must be scaled by 0.25)
float getSNR() const {return snr4 * 0.25f;}
int16_t getRSSI() const {return (rssi<0) ? (rssi) : (-256 + rssi);}
};
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); // 0b11 = boost on, 150% LNA current
// set auto AGC
writeRegister(REG_MODEM_CONFIG_3, 0x04); // LNA-Gain set by the internal AGC loop
// 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;
}
/**
* configure the TX power (in dB)
* two output pins are possible:
* RFO (max +14dB)
* PA_BOOST (max +20dB)
*/
void setTxPower(int level, int outputPin = PA_OUTPUT_PA_BOOST_PIN) {
debugMod1(NAME, "setTx(%d dB)", level);
// the +14 dB pin // RFO
if (PA_OUTPUT_RFO_PIN == outputPin) {
if (level < 0 || level > 14) {errorMod1(NAME, "setTxPower(RFO, %d) TX Power out of range", level); return;}
curTXP = level; // cache
writeRegister(REG_PA_CONFIG, 0x70 | level);
// the +20dbPin PA BOOST
} else if (PA_OUTPUT_PA_BOOST_PIN == outputPin) {
if (level < 2 || level > 20) {errorMod1(NAME, "setTxPower(PA_BOOST, %d) TX Power out of range", level); return;}
curTXP = level; // cache
if (level > 17) {
// 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(150);
} else {
//Default value PA_HF/LF or +17dBm
writeRegister(REG_PA_DAC, 0x84);
setOCP(100);
}
// enable PA_BOOST, and set (level-3-2) // a level of 15 is MAX and equals +20dB with PABoost
writeRegister(REG_PA_CONFIG, PA_BOOST | (level - 2));
} else {
debugMod(NAME, "failed to set TX Power");
}
}
/** get the current transmit power (from cache) */
uint8_t getTxPower() {
return curTXP;
}
/** 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);
}
/** is the device currently transmitting? */
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) {
debugMod2(NAME, "tx(%d bytes, async:%d)", len, async);
//if (len > MAX_PKT_LENGTH) {return -1;}
if (isTransmitting()) {return 0;}
// put in standby mode
idle();
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, "tx done");
}
return len;
}
/** blocking wait for TX to finish */
void waitTxComplete() {
}
/** switch to RX mode, but only for a single packet */
void rxSingle() {
enableRX(true);
}
/** switch to RX mode, continuously */
void rxContinuous() {
enableRX(false);
}
/**
* fetch one received packet, if any is available.
* requires rxSingle()/rxContinuous() 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)); // can be smaller than -128 !! -> overflow -> use getter method
det->snr4 = ((int8_t)readRegister(REG_PKT_SNR_VALUE)); // SNR*4 (must be scaled by 0.25) -> use getter method
}
// 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(10*1000); // 10 ms. this is the minimum when running with 100 Ticks FreeRTOS
}
/**
* configure explicit header mode.
* TX: the SX1276 appends an automatically created header in front of every packet
* RX: the SX1276 expects and automatically reads the header in front of every received packet
* Note: the header contains the packet's length, and the FEC (codingRate) for the content
* the header itself always has a coding rate of 8/4(?)
*/
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);
// }
/** switch to RX-mode. either for a single frame or permanent */
void enableRX(bool single) {
debugMod1(NAME, "rx(single: %d)", 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);
}
}
/** 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));
}
/** 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();
}
/** over-current protection */
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 | (0b11111 & 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