mirror of
https://github.com/markqvist/RNode_Firmware.git
synced 2026-04-27 22:35:36 +00:00
Add driver and board support for the Semtech LR1121 radio on the LilyGo T-Beam Supreme V3.0 (BOARD_TBEAM_S_LR_V1 0x43). New files: - lr11xx.h/cpp: LR1121 driver extending Stream, matching the sx126x interface for duck-typed LoRa pointer compatibility. Implements 2-byte opcode SPI protocol with two-phase reads, inline MISO status capture, DIO9 interrupt handling, and carrier detection via preamble/header IRQ polling. Key LR1121 differences from SX126x handled in the driver: - SPI uses 2-byte opcodes with NSS release between command and response phases (vs single-phase on SX126x) - SetPacketParams PayloadLen is enforced in explicit header mode (SX126x ignores it) — receive() sets PayloadLen=0 to accept any length - ESP32S3 requires explicit SPI pin assignment via SPI.begin(sclk, miso, mosi, cs) - RF switch configured via SetDioAsRfSwitch command (DIO5/DIO6) rather than external GPIO control - TCXO at 3.0V via SetTcxoMode command - High Power PA with configurable output up to +22dBm Modified files: - Modem.h: add LR11XX modem type - Boards.h: add BOARD_TBEAM_S_LR_V1 with pin definitions and model codes (MODEL_D3/DF/D7 for 433/868/2400 MHz) - Utilities.h: add LR11XX modem selection, LED stubs, model validation, and kiss_indicate_log() for KISS-framed debug output - RNode_Firmware.ino: add LR11XX cases for setPins, symbol calculation, TX power clamping, and serial wait exclusion - Display.h: add BOARD_TBEAM_S_LR_V1 alongside BOARD_TBEAM_S_V1 - Power.h: add BOARD_TBEAM_S_LR_V1 alongside BOARD_TBEAM_S_V1 - Makefile: add firmware-tbeam_supreme_lr1121 build target
866 lines
24 KiB
C++
866 lines
24 KiB
C++
// Copyright 2025
|
|
// Licensed under the MIT license.
|
|
|
|
// LR11xx radio driver for RNode Firmware
|
|
// Supports LR1121 (and potentially LR1110/LR1120)
|
|
// Uses 2-byte opcode SPI protocol with two-phase reads
|
|
|
|
#include "lr11xx.h"
|
|
|
|
#if PLATFORM == PLATFORM_ESP32
|
|
#if defined(ESP32) and !defined(CONFIG_IDF_TARGET_ESP32S3)
|
|
#include "soc/rtc_wdt.h"
|
|
#endif
|
|
#define ISR_VECT IRAM_ATTR
|
|
#else
|
|
#define ISR_VECT
|
|
#endif
|
|
|
|
// ---- LR11xx System Opcodes (0x01xx) ----
|
|
#define OP_GET_STATUS_11XX 0x0100
|
|
#define OP_GET_VERSION_11XX 0x0101
|
|
#define OP_WRITE_REG_MEM_11XX 0x0105
|
|
#define OP_READ_REG_MEM_11XX 0x0106
|
|
#define OP_WRITE_BUFFER_11XX 0x0109
|
|
#define OP_READ_BUFFER_11XX 0x010A
|
|
#define OP_GET_ERRORS_11XX 0x010D
|
|
#define OP_CLEAR_ERRORS_11XX 0x010E
|
|
#define OP_CALIBRATE_11XX 0x010F
|
|
#define OP_SET_REG_MODE_11XX 0x0110
|
|
#define OP_CALIBRATE_IMAGE_11XX 0x0111
|
|
#define OP_SET_DIO_AS_RF_SWITCH_11XX 0x0112
|
|
#define OP_SET_DIO_IRQ_PARAMS_11XX 0x0113
|
|
#define OP_CLEAR_IRQ_11XX 0x0114
|
|
#define OP_SET_TCXO_MODE_11XX 0x0117
|
|
#define OP_SET_SLEEP_11XX 0x011B
|
|
#define OP_SET_STANDBY_11XX 0x011C
|
|
#define OP_SET_FS_11XX 0x011D
|
|
|
|
// ---- LR11xx Radio Opcodes (0x02xx) ----
|
|
#define OP_GET_RX_BUFFER_STATUS_11XX 0x0203
|
|
#define OP_GET_PACKET_STATUS_11XX 0x0204
|
|
#define OP_GET_RSSI_INST_11XX 0x0205
|
|
#define OP_SET_RX_11XX 0x0209
|
|
#define OP_SET_TX_11XX 0x020A
|
|
#define OP_SET_RF_FREQUENCY_11XX 0x020B
|
|
#define OP_SET_CAD_PARAMS_11XX 0x020D
|
|
#define OP_SET_PACKET_TYPE_11XX 0x020E
|
|
#define OP_SET_MODULATION_PARAMS_11XX 0x020F
|
|
#define OP_SET_PACKET_PARAMS_11XX 0x0210
|
|
#define OP_SET_TX_PARAMS_11XX 0x0211
|
|
#define OP_SET_RX_TX_FALLBACK_11XX 0x0213
|
|
#define OP_SET_PA_CONFIG_11XX 0x0215
|
|
#define OP_SET_CAD_11XX 0x0218
|
|
#define OP_SET_RX_BOOSTED_11XX 0x0227
|
|
#define OP_SET_LORA_SYNC_WORD_11XX 0x022B
|
|
|
|
// ---- LR11xx IRQ Masks (32-bit) ----
|
|
#define IRQ_TX_DONE_11XX 0x00000004 // bit 2
|
|
#define IRQ_RX_DONE_11XX 0x00000008 // bit 3
|
|
#define IRQ_PREAMBLE_DET_11XX 0x00000010 // bit 4
|
|
#define IRQ_SYNC_HEADER_VALID_11XX 0x00000020 // bit 5
|
|
#define IRQ_HEADER_ERR_11XX 0x00000040 // bit 6
|
|
#define IRQ_CRC_ERR_11XX 0x00000080 // bit 7
|
|
#define IRQ_CAD_DONE_11XX 0x00000100 // bit 8
|
|
#define IRQ_CAD_DETECTED_11XX 0x00000200 // bit 9
|
|
#define IRQ_TIMEOUT_11XX 0x00000400 // bit 10
|
|
// Only documented IRQ bits (2-11, 21-25)
|
|
#define IRQ_ALL_11XX 0x03E00FFC
|
|
|
|
// ---- LR11xx Mode Constants ----
|
|
#define MODE_STDBY_RC_11XX 0x00
|
|
#define MODE_STDBY_XOSC_11XX 0x01
|
|
#define MODE_PACKET_TYPE_LORA_11XX 0x02
|
|
#define MODE_FALLBACK_STDBY_RC_11XX 0x01
|
|
|
|
// ---- LR11xx PA Constants ----
|
|
#define PA_SEL_LP_11XX 0x00
|
|
#define PA_SEL_HP_11XX 0x01
|
|
#define PA_SEL_HF_11XX 0x02
|
|
#define PA_REG_SUPPLY_VREG_11XX 0x00
|
|
#define PA_REG_SUPPLY_VBAT_11XX 0x01
|
|
|
|
// ---- LR11xx TCXO Voltage ----
|
|
#define MODE_TCXO_3_0V_11XX 0x06
|
|
|
|
// ---- LR11xx Register Addresses ----
|
|
#define REG_HIGH_ACP_11XX 0x00F30054
|
|
|
|
// ---- LR11xx Device Types (from GetVersion) ----
|
|
#define DEVICE_LR1121 0x03
|
|
|
|
// ---- LR11xx Sync Word ----
|
|
#define SYNC_WORD_PRIVATE_11XX 0x12
|
|
|
|
lr11xx::lr11xx() :
|
|
_spiSettings(8E6, MSBFIRST, SPI_MODE0),
|
|
_ss(LORA_DEFAULT_SS_PIN), _reset(LORA_DEFAULT_RESET_PIN),
|
|
_dio0(LORA_DEFAULT_DIO0_PIN), _rxen(LORA_DEFAULT_RXEN_PIN),
|
|
_busy(LORA_DEFAULT_BUSY_PIN),
|
|
_frequency(0), _txp(17), _sf(0x07), _bw(0x04), _cr(0x01), _ldro(0x00),
|
|
_packetIndex(0), _preambleLength(18), _implicitHeaderMode(0),
|
|
_payloadLength(255), _crcMode(1), _fifo_rx_addr_ptr(0),
|
|
_preinit_done(false), _preamble_detected_at(0), _onReceive(NULL)
|
|
{
|
|
}
|
|
|
|
// --- SPI Primitives (2-byte opcodes, two-phase reads) ---
|
|
|
|
void lr11xx::waitOnBusy() {
|
|
unsigned long time = millis();
|
|
if (_busy != -1) {
|
|
while (digitalRead(_busy) == HIGH) {
|
|
if (millis() >= (time + 100)) { break; }
|
|
}
|
|
}
|
|
}
|
|
|
|
void lr11xx::executeOpcode(uint16_t opcode, uint8_t *buffer, uint8_t size) {
|
|
waitOnBusy();
|
|
digitalWrite(_ss, LOW);
|
|
SPI.beginTransaction(_spiSettings);
|
|
// Capture MISO: during writes the chip returns Stat1, Stat2, IrqStatus
|
|
// inline (per user manual Section 3.1). Store for use by handleDio0Rise().
|
|
int misoIdx = 0;
|
|
_lastMiso[misoIdx++] = SPI.transfer((opcode >> 8) & 0xFF);
|
|
_lastMiso[misoIdx++] = SPI.transfer(opcode & 0xFF);
|
|
for (int i = 0; i < size; i++) {
|
|
if (misoIdx < 6) {
|
|
_lastMiso[misoIdx++] = SPI.transfer(buffer[i]);
|
|
} else {
|
|
SPI.transfer(buffer[i]);
|
|
}
|
|
}
|
|
SPI.endTransaction();
|
|
digitalWrite(_ss, HIGH);
|
|
}
|
|
|
|
void lr11xx::executeOpcodeRead(uint16_t opcode, uint8_t *buffer, uint8_t size) {
|
|
waitOnBusy();
|
|
|
|
// Phase 1: send command
|
|
digitalWrite(_ss, LOW);
|
|
SPI.beginTransaction(_spiSettings);
|
|
SPI.transfer((opcode >> 8) & 0xFF);
|
|
SPI.transfer(opcode & 0xFF);
|
|
SPI.endTransaction();
|
|
digitalWrite(_ss, HIGH);
|
|
|
|
// Wait for chip to finish processing the command
|
|
waitOnBusy();
|
|
|
|
// Phase 2: read response
|
|
digitalWrite(_ss, LOW);
|
|
SPI.beginTransaction(_spiSettings);
|
|
SPI.transfer(0x00); // dummy/status byte
|
|
for (int i = 0; i < size; i++) {
|
|
buffer[i] = SPI.transfer(0x00);
|
|
}
|
|
SPI.endTransaction();
|
|
digitalWrite(_ss, HIGH);
|
|
}
|
|
|
|
uint32_t lr11xx::readRegister32(uint32_t address) {
|
|
uint8_t addr_buf[5];
|
|
addr_buf[0] = (address >> 24) & 0xFF;
|
|
addr_buf[1] = (address >> 16) & 0xFF;
|
|
addr_buf[2] = (address >> 8) & 0xFF;
|
|
addr_buf[3] = address & 0xFF;
|
|
addr_buf[4] = 0x01;
|
|
|
|
waitOnBusy();
|
|
digitalWrite(_ss, LOW);
|
|
SPI.beginTransaction(_spiSettings);
|
|
SPI.transfer((OP_READ_REG_MEM_11XX >> 8) & 0xFF);
|
|
SPI.transfer(OP_READ_REG_MEM_11XX & 0xFF);
|
|
for (int i = 0; i < 5; i++) SPI.transfer(addr_buf[i]);
|
|
SPI.endTransaction();
|
|
digitalWrite(_ss, HIGH);
|
|
|
|
waitOnBusy();
|
|
|
|
uint8_t val[4];
|
|
digitalWrite(_ss, LOW);
|
|
SPI.beginTransaction(_spiSettings);
|
|
SPI.transfer(0x00);
|
|
for (int i = 0; i < 4; i++) val[i] = SPI.transfer(0x00);
|
|
SPI.endTransaction();
|
|
digitalWrite(_ss, HIGH);
|
|
|
|
return ((uint32_t)val[0] << 24) | ((uint32_t)val[1] << 16) |
|
|
((uint32_t)val[2] << 8) | val[3];
|
|
}
|
|
|
|
void lr11xx::writeRegister32(uint32_t address, uint32_t value) {
|
|
uint8_t buf[8];
|
|
buf[0] = (address >> 24) & 0xFF;
|
|
buf[1] = (address >> 16) & 0xFF;
|
|
buf[2] = (address >> 8) & 0xFF;
|
|
buf[3] = address & 0xFF;
|
|
buf[4] = (value >> 24) & 0xFF;
|
|
buf[5] = (value >> 16) & 0xFF;
|
|
buf[6] = (value >> 8) & 0xFF;
|
|
buf[7] = value & 0xFF;
|
|
executeOpcode(OP_WRITE_REG_MEM_11XX, buf, 8);
|
|
}
|
|
|
|
void lr11xx::writeBuffer(const uint8_t* buffer, size_t size) {
|
|
waitOnBusy();
|
|
digitalWrite(_ss, LOW);
|
|
SPI.beginTransaction(_spiSettings);
|
|
SPI.transfer((OP_WRITE_BUFFER_11XX >> 8) & 0xFF);
|
|
SPI.transfer(OP_WRITE_BUFFER_11XX & 0xFF);
|
|
for (size_t i = 0; i < size; i++) {
|
|
SPI.transfer(buffer[i]);
|
|
}
|
|
SPI.endTransaction();
|
|
digitalWrite(_ss, HIGH);
|
|
}
|
|
|
|
void lr11xx::readBuffer(uint8_t* buffer, size_t size) {
|
|
uint8_t cmd_buf[2];
|
|
cmd_buf[0] = _fifo_rx_addr_ptr;
|
|
cmd_buf[1] = (uint8_t)size;
|
|
|
|
waitOnBusy();
|
|
digitalWrite(_ss, LOW);
|
|
SPI.beginTransaction(_spiSettings);
|
|
SPI.transfer((OP_READ_BUFFER_11XX >> 8) & 0xFF);
|
|
SPI.transfer(OP_READ_BUFFER_11XX & 0xFF);
|
|
SPI.transfer(cmd_buf[0]);
|
|
SPI.transfer(cmd_buf[1]);
|
|
SPI.endTransaction();
|
|
digitalWrite(_ss, HIGH);
|
|
|
|
waitOnBusy();
|
|
digitalWrite(_ss, LOW);
|
|
SPI.beginTransaction(_spiSettings);
|
|
SPI.transfer(0x00);
|
|
for (size_t i = 0; i < size; i++) {
|
|
buffer[i] = SPI.transfer(0x00);
|
|
}
|
|
SPI.endTransaction();
|
|
digitalWrite(_ss, HIGH);
|
|
}
|
|
|
|
void lr11xx::clearIrqFlags(uint32_t mask) {
|
|
uint8_t buf[4];
|
|
buf[0] = (mask >> 24) & 0xFF;
|
|
buf[1] = (mask >> 16) & 0xFF;
|
|
buf[2] = (mask >> 8) & 0xFF;
|
|
buf[3] = mask & 0xFF;
|
|
executeOpcode(OP_CLEAR_IRQ_11XX, buf, 4);
|
|
}
|
|
|
|
// --- Lifecycle ---
|
|
|
|
void lr11xx::setPins(int ss, int reset, int dio0, int busy, int rxen) {
|
|
_ss = ss;
|
|
_reset = reset;
|
|
_dio0 = dio0;
|
|
_busy = busy;
|
|
_rxen = rxen;
|
|
}
|
|
|
|
void lr11xx::reset() {
|
|
if (_reset != -1) {
|
|
pinMode(_reset, OUTPUT);
|
|
digitalWrite(_reset, LOW);
|
|
delay(1);
|
|
digitalWrite(_reset, HIGH);
|
|
delay(10);
|
|
}
|
|
}
|
|
|
|
bool lr11xx::preInit() {
|
|
pinMode(_ss, OUTPUT);
|
|
digitalWrite(_ss, HIGH);
|
|
|
|
#if BOARD_MODEL == BOARD_TBEAM_S_LR_V1
|
|
SPI.begin(pin_sclk, pin_miso, pin_mosi, pin_cs);
|
|
#else
|
|
SPI.begin();
|
|
#endif
|
|
|
|
reset();
|
|
|
|
if (_busy != -1) { pinMode(_busy, INPUT); }
|
|
|
|
long start = millis();
|
|
uint8_t device_type = 0;
|
|
while (((millis() - start) < 2000) && (millis() >= start)) {
|
|
uint8_t version_buf[4] = {0};
|
|
executeOpcodeRead(OP_GET_VERSION_11XX, version_buf, 4);
|
|
device_type = version_buf[1];
|
|
if (device_type == DEVICE_LR1121) {
|
|
break;
|
|
}
|
|
delay(100);
|
|
}
|
|
|
|
if (device_type != DEVICE_LR1121) {
|
|
return false;
|
|
}
|
|
|
|
_preinit_done = true;
|
|
return true;
|
|
}
|
|
|
|
int lr11xx::begin(long frequency) {
|
|
_frequency = frequency;
|
|
|
|
reset();
|
|
|
|
if (_busy != -1) { pinMode(_busy, INPUT); }
|
|
|
|
if (!_preinit_done) {
|
|
if (!preInit()) {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
standby();
|
|
|
|
// DC-DC regulator
|
|
uint8_t reg_mode = 0x01;
|
|
executeOpcode(OP_SET_REG_MODE_11XX, ®_mode, 1);
|
|
|
|
configureRfSwitch();
|
|
enableTCXO();
|
|
|
|
uint8_t clear_buf[4] = {0};
|
|
executeOpcode(OP_CLEAR_ERRORS_11XX, clear_buf, 4);
|
|
calibrate();
|
|
calibrateImage(frequency);
|
|
|
|
loraMode();
|
|
setFrequency(frequency);
|
|
setSyncWord(SYNC_WORD_PRIVATE_11XX);
|
|
|
|
uint8_t fallback = MODE_FALLBACK_STDBY_RC_11XX;
|
|
executeOpcode(OP_SET_RX_TX_FALLBACK_11XX, &fallback, 1);
|
|
|
|
setModulationParams(_sf, _bw, _cr, _ldro);
|
|
setPacketParams(_preambleLength, _implicitHeaderMode, _payloadLength, _crcMode);
|
|
setTxPower(_txp);
|
|
setRxBoosted(true);
|
|
clearIrqFlags(IRQ_ALL_11XX);
|
|
|
|
return 1;
|
|
}
|
|
|
|
void lr11xx::end() {
|
|
sleep();
|
|
SPI.end();
|
|
_preinit_done = false;
|
|
}
|
|
|
|
// --- TX Path ---
|
|
|
|
int lr11xx::beginPacket(int implicitHeader) {
|
|
standby();
|
|
if (implicitHeader) {
|
|
implicitHeaderMode();
|
|
} else {
|
|
explicitHeaderMode();
|
|
}
|
|
_payloadLength = 0;
|
|
setPacketParams(_preambleLength, _implicitHeaderMode, _payloadLength, _crcMode);
|
|
return 1;
|
|
}
|
|
|
|
int lr11xx::endPacket() {
|
|
setPacketParams(_preambleLength, _implicitHeaderMode, _payloadLength, _crcMode);
|
|
|
|
// Write accumulated packet data to TX buffer in one shot
|
|
writeBuffer(_packet, _payloadLength);
|
|
|
|
applyHighAcpWorkaround();
|
|
|
|
uint8_t timeout[3] = {0x00, 0x00, 0x00};
|
|
executeOpcode(OP_SET_TX_11XX, timeout, 3);
|
|
|
|
// Poll for TX_DONE
|
|
// GetStatus Phase 2 returns: [stat] [IRQ3] [IRQ2] [IRQ1] [IRQ0]
|
|
bool timed_out = false;
|
|
// Use a simple timeout based on payload length
|
|
uint32_t w_timeout = millis() + LORA_MODEM_TIMEOUT_MS;
|
|
|
|
while (millis() < w_timeout) {
|
|
uint8_t irq_buf[5] = {0};
|
|
executeOpcodeRead(OP_GET_STATUS_11XX, irq_buf, 5);
|
|
uint32_t irq_status = ((uint32_t)irq_buf[1] << 24) | ((uint32_t)irq_buf[2] << 16) |
|
|
((uint32_t)irq_buf[3] << 8) | irq_buf[4];
|
|
if (irq_status & IRQ_TX_DONE_11XX) break;
|
|
yield();
|
|
}
|
|
|
|
if (millis() >= w_timeout) { timed_out = true; }
|
|
|
|
clearIrqFlags(IRQ_ALL_11XX);
|
|
|
|
return !timed_out;
|
|
}
|
|
|
|
size_t lr11xx::write(uint8_t byte) {
|
|
return write(&byte, sizeof(byte));
|
|
}
|
|
|
|
size_t lr11xx::write(const uint8_t *buffer, size_t size) {
|
|
if ((_payloadLength + size) > 255) {
|
|
size = 255 - _payloadLength;
|
|
}
|
|
// Buffer locally in _packet. LR11xx WriteBuffer8 always writes from offset 0
|
|
// (no auto-incrementing FIFO pointer like SX126x), so we accumulate here and
|
|
// write all at once in endPacket().
|
|
memcpy(_packet + _payloadLength, buffer, size);
|
|
_payloadLength += size;
|
|
return size;
|
|
}
|
|
|
|
// --- RX Path ---
|
|
|
|
int lr11xx::parsePacket(int size) {
|
|
// Not used in RNode firmware (uses onReceive callback instead)
|
|
return 0;
|
|
}
|
|
|
|
int ISR_VECT lr11xx::available() {
|
|
uint8_t buf[2] = {0};
|
|
executeOpcodeRead(OP_GET_RX_BUFFER_STATUS_11XX, buf, 2);
|
|
return buf[0] - _packetIndex;
|
|
}
|
|
|
|
int ISR_VECT lr11xx::read() {
|
|
if (!available()) { return -1; }
|
|
|
|
if (_packetIndex == 0) {
|
|
uint8_t rxbuf[2] = {0};
|
|
executeOpcodeRead(OP_GET_RX_BUFFER_STATUS_11XX, rxbuf, 2);
|
|
int size = rxbuf[0];
|
|
_fifo_rx_addr_ptr = rxbuf[1];
|
|
readBuffer(_packet, size);
|
|
}
|
|
|
|
uint8_t byte = _packet[_packetIndex];
|
|
_packetIndex++;
|
|
return byte;
|
|
}
|
|
|
|
int lr11xx::peek() {
|
|
if (!available()) { return -1; }
|
|
|
|
if (_packetIndex == 0) {
|
|
uint8_t rxbuf[2] = {0};
|
|
executeOpcodeRead(OP_GET_RX_BUFFER_STATUS_11XX, rxbuf, 2);
|
|
int size = rxbuf[0];
|
|
_fifo_rx_addr_ptr = rxbuf[1];
|
|
readBuffer(_packet, size);
|
|
}
|
|
|
|
return _packet[_packetIndex];
|
|
}
|
|
|
|
void lr11xx::flush() {
|
|
}
|
|
|
|
void lr11xx::onReceive(void(*callback)(int)) {
|
|
_onReceive = callback;
|
|
|
|
if (callback) {
|
|
pinMode(_dio0, INPUT);
|
|
|
|
// Route only RX_DONE to DIO9
|
|
uint8_t irq_buf[8];
|
|
uint32_t irq_mask = IRQ_RX_DONE_11XX;
|
|
irq_buf[0] = (irq_mask >> 24) & 0xFF;
|
|
irq_buf[1] = (irq_mask >> 16) & 0xFF;
|
|
irq_buf[2] = (irq_mask >> 8) & 0xFF;
|
|
irq_buf[3] = irq_mask & 0xFF;
|
|
irq_buf[4] = (irq_mask >> 24) & 0xFF;
|
|
irq_buf[5] = (irq_mask >> 16) & 0xFF;
|
|
irq_buf[6] = (irq_mask >> 8) & 0xFF;
|
|
irq_buf[7] = irq_mask & 0xFF;
|
|
executeOpcode(OP_SET_DIO_IRQ_PARAMS_11XX, irq_buf, 8);
|
|
|
|
attachInterrupt(digitalPinToInterrupt(_dio0), lr11xx::onDio0Rise, RISING);
|
|
} else {
|
|
detachInterrupt(digitalPinToInterrupt(_dio0));
|
|
}
|
|
}
|
|
|
|
void lr11xx::receive(int size) {
|
|
if (size > 0) {
|
|
implicitHeaderMode();
|
|
_payloadLength = size;
|
|
} else {
|
|
explicitHeaderMode();
|
|
_payloadLength = 0;
|
|
}
|
|
setPacketParams(_preambleLength, _implicitHeaderMode, _payloadLength, _crcMode);
|
|
|
|
clearIrqFlags(IRQ_ALL_11XX);
|
|
|
|
uint8_t mode[3] = {0xFF, 0xFF, 0xFF}; // continuous RX
|
|
executeOpcode(OP_SET_RX_11XX, mode, 3);
|
|
}
|
|
|
|
// Named handleDio0Rise for consistency with the sx126x driver convention.
|
|
// On the LR1121 this actually handles DIO9 (the primary interrupt pin),
|
|
// not DIO0 (which is the BUSY signal on LR11xx).
|
|
void ISR_VECT lr11xx::handleDio0Rise() {
|
|
// ClearIrq is a single-phase write command (ISR-safe). The chip returns
|
|
// Stat1, Stat2, and IrqStatus inline on MISO during the transaction
|
|
// (per user manual Section 3.1), captured by executeOpcode into _lastMiso.
|
|
clearIrqFlags(IRQ_RX_DONE_11XX | IRQ_CRC_ERR_11XX | IRQ_HEADER_ERR_11XX);
|
|
|
|
uint32_t irq = ((uint32_t)_lastMiso[2] << 24) | ((uint32_t)_lastMiso[3] << 16) |
|
|
((uint32_t)_lastMiso[4] << 8) | _lastMiso[5];
|
|
|
|
// Reject if header CRC (bit 6) or payload CRC (bit 7) failed
|
|
if ((irq & (IRQ_CRC_ERR_11XX | IRQ_HEADER_ERR_11XX)) == 0) {
|
|
// Reset read position so read()/peek() fetch from the start of the new packet
|
|
_packetIndex = 0;
|
|
|
|
uint8_t rxbuf[2] = {0};
|
|
executeOpcodeRead(OP_GET_RX_BUFFER_STATUS_11XX, rxbuf, 2);
|
|
int packetLength = rxbuf[0];
|
|
_fifo_rx_addr_ptr = rxbuf[1];
|
|
|
|
// Guard against spurious interrupts delivering zero-length packets
|
|
if (packetLength > 0 && _onReceive) {
|
|
_onReceive(packetLength);
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
void ISR_VECT lr11xx::onDio0Rise() {
|
|
lr11xx_modem.handleDio0Rise();
|
|
}
|
|
|
|
// --- Modem Control ---
|
|
|
|
void lr11xx::standby() {
|
|
uint8_t mode = MODE_STDBY_RC_11XX;
|
|
executeOpcode(OP_SET_STANDBY_11XX, &mode, 1);
|
|
}
|
|
|
|
void lr11xx::sleep() {
|
|
uint8_t buf[5] = {0};
|
|
buf[0] = 0x01;
|
|
executeOpcode(OP_SET_SLEEP_11XX, buf, 5);
|
|
}
|
|
|
|
// --- RF Configuration ---
|
|
|
|
uint32_t lr11xx::getFrequency() {
|
|
return _frequency;
|
|
}
|
|
|
|
void lr11xx::setFrequency(long frequency) {
|
|
_frequency = frequency;
|
|
uint8_t buf[4];
|
|
buf[0] = ((uint32_t)frequency >> 24) & 0xFF;
|
|
buf[1] = ((uint32_t)frequency >> 16) & 0xFF;
|
|
buf[2] = ((uint32_t)frequency >> 8) & 0xFF;
|
|
buf[3] = (uint32_t)frequency & 0xFF;
|
|
executeOpcode(OP_SET_RF_FREQUENCY_11XX, buf, 4);
|
|
}
|
|
|
|
void lr11xx::setSpreadingFactor(int sf) {
|
|
if (sf < 5) sf = 5;
|
|
else if (sf > 12) sf = 12;
|
|
_sf = sf;
|
|
handleLowDataRate();
|
|
setModulationParams(sf, _bw, _cr, _ldro);
|
|
}
|
|
|
|
long lr11xx::getSignalBandwidth() {
|
|
switch (_bw) {
|
|
case 0x03: return 62.5E3;
|
|
case 0x04: return 125E3;
|
|
case 0x05: return 250E3;
|
|
case 0x06: return 500E3;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void lr11xx::setSignalBandwidth(long sbw) {
|
|
if (sbw <= 62.5E3) {
|
|
_bw = 0x03;
|
|
} else if (sbw <= 125E3) {
|
|
_bw = 0x04;
|
|
} else if (sbw <= 250E3) {
|
|
_bw = 0x05;
|
|
} else {
|
|
_bw = 0x06;
|
|
}
|
|
handleLowDataRate();
|
|
setModulationParams(_sf, _bw, _cr, _ldro);
|
|
}
|
|
|
|
void lr11xx::setCodingRate4(int denominator) {
|
|
if (denominator < 5) denominator = 5;
|
|
else if (denominator > 8) denominator = 8;
|
|
_cr = denominator - 4;
|
|
setModulationParams(_sf, _bw, _cr, _ldro);
|
|
}
|
|
|
|
void lr11xx::setPreambleLength(long length) {
|
|
_preambleLength = length;
|
|
setPacketParams(length, _implicitHeaderMode, _payloadLength, _crcMode);
|
|
}
|
|
|
|
// --- Power Control ---
|
|
|
|
uint8_t lr11xx::getTxPower() {
|
|
return _txp;
|
|
}
|
|
|
|
void lr11xx::setTxPower(int level, int outputPin) {
|
|
uint8_t pa_buf[4];
|
|
pa_buf[0] = PA_SEL_HP_11XX;
|
|
pa_buf[1] = PA_REG_SUPPLY_VBAT_11XX;
|
|
pa_buf[2] = 0x04;
|
|
pa_buf[3] = 0x07;
|
|
executeOpcode(OP_SET_PA_CONFIG_11XX, pa_buf, 4);
|
|
|
|
if (level > 22) level = 22;
|
|
else if (level < -9) level = -9;
|
|
_txp = level;
|
|
|
|
uint8_t tx_buf[2];
|
|
tx_buf[0] = (uint8_t)level;
|
|
tx_buf[1] = 0x02;
|
|
executeOpcode(OP_SET_TX_PARAMS_11XX, tx_buf, 2);
|
|
}
|
|
|
|
// --- Signal Quality ---
|
|
|
|
uint8_t lr11xx::currentRssiRaw() {
|
|
uint8_t byte = 0;
|
|
executeOpcodeRead(OP_GET_RSSI_INST_11XX, &byte, 1);
|
|
return byte;
|
|
}
|
|
|
|
int lr11xx::currentRssi() {
|
|
uint8_t byte = 0;
|
|
executeOpcodeRead(OP_GET_RSSI_INST_11XX, &byte, 1);
|
|
return -(int(byte)) / 2;
|
|
}
|
|
|
|
uint8_t lr11xx::packetRssiRaw() {
|
|
uint8_t buf[3] = {0};
|
|
executeOpcodeRead(OP_GET_PACKET_STATUS_11XX, buf, 3);
|
|
return buf[0];
|
|
}
|
|
|
|
int lr11xx::packetRssi() {
|
|
uint8_t buf[3] = {0};
|
|
executeOpcodeRead(OP_GET_PACKET_STATUS_11XX, buf, 3);
|
|
return -buf[0] / 2;
|
|
}
|
|
|
|
int lr11xx::packetRssi(uint8_t pkt_snr_raw) {
|
|
return packetRssi();
|
|
}
|
|
|
|
uint8_t ISR_VECT lr11xx::packetSnrRaw() {
|
|
uint8_t buf[3] = {0};
|
|
executeOpcodeRead(OP_GET_PACKET_STATUS_11XX, buf, 3);
|
|
return buf[1];
|
|
}
|
|
|
|
float lr11xx::packetSnr() {
|
|
uint8_t buf[3] = {0};
|
|
executeOpcodeRead(OP_GET_PACKET_STATUS_11XX, buf, 3);
|
|
return float((int8_t)buf[1]) * 0.25;
|
|
}
|
|
|
|
long lr11xx::packetFrequencyError() {
|
|
return 0;
|
|
}
|
|
|
|
// --- Channel Monitoring ---
|
|
|
|
bool lr11xx::dcd() {
|
|
uint8_t irq_buf[5] = {0};
|
|
executeOpcodeRead(OP_GET_STATUS_11XX, irq_buf, 5);
|
|
// GetStatus returns extra status byte at position 0, IRQ flags at bytes 1-4
|
|
uint32_t irq = ((uint32_t)irq_buf[1] << 24) | ((uint32_t)irq_buf[2] << 16) |
|
|
((uint32_t)irq_buf[3] << 8) | irq_buf[4];
|
|
uint32_t now = millis();
|
|
|
|
bool preamble = irq & IRQ_PREAMBLE_DET_11XX;
|
|
bool header = irq & IRQ_SYNC_HEADER_VALID_11XX;
|
|
bool carrier_detected = false;
|
|
bool false_preamble = false;
|
|
|
|
// Header without preamble is a stranded flag from a previous detection
|
|
// where ClearIrq cleared the preamble but not the header. Clear it to
|
|
// prevent permanent carrier detection deadlock.
|
|
if (header && !preamble) {
|
|
clearIrqFlags(IRQ_SYNC_HEADER_VALID_11XX);
|
|
} else if (header && preamble) {
|
|
carrier_detected = true;
|
|
}
|
|
|
|
if (preamble) {
|
|
carrier_detected = true;
|
|
if (_preamble_detected_at == 0) { _preamble_detected_at = now; }
|
|
if (now - _preamble_detected_at > (uint32_t)(_preambleLength * (1 << _sf) / (getSignalBandwidth() / 1000) + 8 * (1 << _sf) / (getSignalBandwidth() / 1000))) {
|
|
_preamble_detected_at = 0;
|
|
if (!header) {
|
|
false_preamble = true;
|
|
clearIrqFlags(IRQ_PREAMBLE_DET_11XX | IRQ_SYNC_HEADER_VALID_11XX);
|
|
} else {
|
|
clearIrqFlags(IRQ_PREAMBLE_DET_11XX);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Note: unlike SX126x, we don't call receive() on false preamble.
|
|
// The LR11xx stays in continuous RX mode and DIO9 is re-armed in
|
|
// handleDio0Rise() via SetRx. Calling receive() here would disrupt
|
|
// any in-progress reception because dcd() runs inside a critical
|
|
// section during active packet reception.
|
|
|
|
return carrier_detected;
|
|
}
|
|
|
|
// --- CRC ---
|
|
|
|
void lr11xx::enableCrc() {
|
|
_crcMode = 1;
|
|
setPacketParams(_preambleLength, _implicitHeaderMode, _payloadLength, _crcMode);
|
|
}
|
|
|
|
void lr11xx::disableCrc() {
|
|
_crcMode = 0;
|
|
setPacketParams(_preambleLength, _implicitHeaderMode, _payloadLength, _crcMode);
|
|
}
|
|
|
|
// --- TCXO ---
|
|
|
|
void lr11xx::enableTCXO() {
|
|
uint8_t buf[4];
|
|
buf[0] = MODE_TCXO_3_0V_11XX;
|
|
buf[1] = 0x00;
|
|
buf[2] = 0x00;
|
|
buf[3] = 0xFF;
|
|
executeOpcode(OP_SET_TCXO_MODE_11XX, buf, 4);
|
|
}
|
|
|
|
void lr11xx::disableTCXO() {
|
|
}
|
|
|
|
// --- Sync Word ---
|
|
|
|
void lr11xx::setSyncWord(uint8_t sw) {
|
|
executeOpcode(OP_SET_LORA_SYNC_WORD_11XX, &sw, 1);
|
|
}
|
|
|
|
// --- Misc ---
|
|
|
|
byte lr11xx::random() {
|
|
return currentRssiRaw();
|
|
}
|
|
|
|
void lr11xx::setSPIFrequency(uint32_t frequency) {
|
|
_spiSettings = SPISettings(frequency, MSBFIRST, SPI_MODE0);
|
|
}
|
|
|
|
void lr11xx::dumpRegisters(Stream& out) {
|
|
}
|
|
|
|
// --- LR11xx-specific internal methods ---
|
|
|
|
void lr11xx::loraMode() {
|
|
uint8_t mode = MODE_PACKET_TYPE_LORA_11XX;
|
|
executeOpcode(OP_SET_PACKET_TYPE_11XX, &mode, 1);
|
|
}
|
|
|
|
void lr11xx::setModulationParams(uint8_t sf, uint8_t bw, uint8_t cr, int ldro) {
|
|
uint8_t buf[4];
|
|
buf[0] = sf;
|
|
buf[1] = bw;
|
|
buf[2] = cr;
|
|
buf[3] = ldro;
|
|
executeOpcode(OP_SET_MODULATION_PARAMS_11XX, buf, 4);
|
|
}
|
|
|
|
void lr11xx::setPacketParams(long preamble, uint8_t headermode, uint8_t length, uint8_t crc) {
|
|
uint8_t buf[6];
|
|
buf[0] = (preamble >> 8) & 0xFF;
|
|
buf[1] = preamble & 0xFF;
|
|
buf[2] = headermode;
|
|
buf[3] = length;
|
|
buf[4] = crc;
|
|
buf[5] = 0x00;
|
|
executeOpcode(OP_SET_PACKET_PARAMS_11XX, buf, 6);
|
|
}
|
|
|
|
void lr11xx::handleLowDataRate() {
|
|
if (long((1 << _sf) / (getSignalBandwidth() / 1000)) > 16) {
|
|
_ldro = 0x01;
|
|
} else {
|
|
_ldro = 0x00;
|
|
}
|
|
}
|
|
|
|
void lr11xx::calibrate() {
|
|
uint8_t mode = MODE_STDBY_RC_11XX;
|
|
executeOpcode(OP_SET_STANDBY_11XX, &mode, 1);
|
|
uint8_t cal = 0x3F;
|
|
executeOpcode(OP_CALIBRATE_11XX, &cal, 1);
|
|
delay(5);
|
|
waitOnBusy();
|
|
}
|
|
|
|
void lr11xx::calibrateImage(long frequency) {
|
|
uint8_t image_freq[2] = {0};
|
|
if (frequency >= 430E6 && frequency <= 440E6) { image_freq[0] = 0x6B; image_freq[1] = 0x6F; }
|
|
else if (frequency >= 470E6 && frequency <= 510E6) { image_freq[0] = 0x75; image_freq[1] = 0x81; }
|
|
else if (frequency >= 779E6 && frequency <= 787E6) { image_freq[0] = 0xC1; image_freq[1] = 0xC5; }
|
|
else if (frequency >= 863E6 && frequency <= 870E6) { image_freq[0] = 0xD7; image_freq[1] = 0xDB; }
|
|
else if (frequency >= 902E6 && frequency <= 928E6) { image_freq[0] = 0xE1; image_freq[1] = 0xE9; }
|
|
executeOpcode(OP_CALIBRATE_IMAGE_11XX, image_freq, 2);
|
|
waitOnBusy();
|
|
}
|
|
|
|
void lr11xx::configureRfSwitch() {
|
|
uint8_t buf[8];
|
|
buf[0] = 0x03; // enable DIO5 (bit 0) + DIO6 (bit 1)
|
|
buf[1] = 0x00; // standby
|
|
buf[2] = 0x01; // RX: DIO5=HIGH, DIO6=LOW
|
|
buf[3] = 0x02; // TX: DIO5=LOW, DIO6=HIGH
|
|
buf[4] = 0x02; // TX_HP
|
|
buf[5] = 0x00; // TX_HF
|
|
buf[6] = 0x00; // GNSS
|
|
buf[7] = 0x00; // WiFi
|
|
executeOpcode(OP_SET_DIO_AS_RF_SWITCH_11XX, buf, 8);
|
|
}
|
|
|
|
void lr11xx::applyHighAcpWorkaround() {
|
|
uint32_t val = readRegister32(REG_HIGH_ACP_11XX);
|
|
val &= ~(1UL << 30);
|
|
writeRegister32(REG_HIGH_ACP_11XX, val);
|
|
}
|
|
|
|
void lr11xx::setRxBoosted(bool enable) {
|
|
uint8_t val = enable ? 0x01 : 0x00;
|
|
executeOpcode(OP_SET_RX_BOOSTED_11XX, &val, 1);
|
|
}
|
|
|
|
void lr11xx::explicitHeaderMode() {
|
|
_implicitHeaderMode = 0;
|
|
setPacketParams(_preambleLength, _implicitHeaderMode, _payloadLength, _crcMode);
|
|
}
|
|
|
|
void lr11xx::implicitHeaderMode() {
|
|
_implicitHeaderMode = 1;
|
|
setPacketParams(_preambleLength, _implicitHeaderMode, _payloadLength, _crcMode);
|
|
}
|
|
|
|
lr11xx lr11xx_modem;
|