mirror of
https://github.com/markqvist/RNode_Firmware.git
synced 2026-04-27 14:30:33 +00:00
Add LR1121 radio support for T-Beam Supreme
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
This commit is contained in:
parent
180207aa2e
commit
5888438d82
9 changed files with 1106 additions and 19 deletions
64
Boards.h
64
Boards.h
|
|
@ -61,9 +61,13 @@
|
|||
#define MODEL_D9 0xD9 // LilyGO T-Deck, 868 MHz
|
||||
|
||||
#define PRODUCT_TBEAM_S_V1 0xEA
|
||||
#define BOARD_TBEAM_S_V1 0x3D
|
||||
#define MODEL_DB 0xDB // LilyGO T-Beam Supreme, 433 MHz
|
||||
#define MODEL_DC 0xDC // LilyGO T-Beam Supreme, 868 MHz
|
||||
#define BOARD_TBEAM_S_V1 0x3D // SX1262 variant
|
||||
#define MODEL_DB 0xDB // LilyGO T-Beam Supreme SX1262, 433 MHz
|
||||
#define MODEL_DC 0xDC // LilyGO T-Beam Supreme SX1262, 868 MHz
|
||||
#define BOARD_TBEAM_S_LR_V1 0x43 // LR1121 variant
|
||||
#define MODEL_D3 0xD3 // LilyGO T-Beam Supreme LR1121, 433 MHz
|
||||
#define MODEL_DF 0xDF // LilyGO T-Beam Supreme LR1121, 868 MHz
|
||||
#define MODEL_D7 0xD7 // LilyGO T-Beam Supreme LR1121, 2.4 GHz
|
||||
|
||||
#define PRODUCT_XIAO_S3 0xEB
|
||||
#define BOARD_XIAO_S3 0x3E
|
||||
|
|
@ -665,6 +669,60 @@
|
|||
#endif
|
||||
#endif
|
||||
|
||||
#elif BOARD_MODEL == BOARD_TBEAM_S_LR_V1
|
||||
#define IS_ESP32S3 true
|
||||
#define MODEM LR11XX
|
||||
// TODO: Re-enable firmware validation once proper signing is implemented
|
||||
#undef VALIDATE_FIRMWARE
|
||||
#define VALIDATE_FIRMWARE false
|
||||
#define HAS_BUSY true
|
||||
#define HAS_TCXO true
|
||||
|
||||
#define HAS_DISPLAY true
|
||||
#define HAS_CONSOLE true
|
||||
#define HAS_WIFI true
|
||||
#define HAS_BLUETOOTH false
|
||||
#define HAS_BLE true
|
||||
#define HAS_PMU true
|
||||
#define HAS_NP false
|
||||
#define HAS_SD false
|
||||
#define HAS_EEPROM true
|
||||
|
||||
#define HAS_INPUT true
|
||||
#define HAS_SLEEP false
|
||||
|
||||
#define PMU_IRQ 40
|
||||
#define I2C_SCL 41
|
||||
#define I2C_SDA 42
|
||||
|
||||
const int pin_btn_usr1 = 0;
|
||||
|
||||
const int pin_cs = 10;
|
||||
const int pin_reset = 5;
|
||||
const int pin_sclk = 12;
|
||||
const int pin_mosi = 11;
|
||||
const int pin_miso = 13;
|
||||
const int pin_tcxo_enable = -1;
|
||||
const int pin_dio = 1;
|
||||
const int pin_busy = 4;
|
||||
|
||||
const int SD_MISO = 37;
|
||||
const int SD_MOSI = 35;
|
||||
const int SD_CLK = 36;
|
||||
const int SD_CS = 47;
|
||||
|
||||
const int IMU_CS = 34;
|
||||
|
||||
#if HAS_NP == false
|
||||
#if defined(EXTERNAL_LEDS)
|
||||
const int pin_led_rx = 43;
|
||||
const int pin_led_tx = 43;
|
||||
#else
|
||||
const int pin_led_rx = 43;
|
||||
const int pin_led_tx = 43;
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#elif BOARD_MODEL == BOARD_XIAO_S3
|
||||
#define IS_ESP32S3 true
|
||||
#define MODEM SX1262
|
||||
|
|
|
|||
14
Display.h
14
Display.h
|
|
@ -22,7 +22,7 @@
|
|||
#elif BOARD_MODEL == BOARD_HELTEC_T114
|
||||
#include "ST7789.h"
|
||||
#define COLOR565(r, g, b) (((r & 0xF8) << 8) | ((g & 0xFC) << 3) | ((b & 0xF8) >> 3))
|
||||
#elif BOARD_MODEL == BOARD_TBEAM_S_V1
|
||||
#elif BOARD_MODEL == BOARD_TBEAM_S_V1 || BOARD_MODEL == BOARD_TBEAM_S_LR_V1
|
||||
#include <Adafruit_SH110X.h>
|
||||
#else
|
||||
#include <Wire.h>
|
||||
|
|
@ -84,7 +84,7 @@
|
|||
#define DISP_W 128
|
||||
#define DISP_H 64
|
||||
#define DISP_ADDR -1
|
||||
#elif BOARD_MODEL == BOARD_TBEAM_S_V1
|
||||
#elif BOARD_MODEL == BOARD_TBEAM_S_V1 || BOARD_MODEL == BOARD_TBEAM_S_LR_V1
|
||||
#define DISP_RST -1
|
||||
#define DISP_ADDR 0x3C
|
||||
#define SCL_OLED 18
|
||||
|
|
@ -112,7 +112,7 @@
|
|||
ST7789Spi display(&SPI1, DISPLAY_RST, DISPLAY_DC, DISPLAY_CS);
|
||||
#define SSD1306_WHITE ST77XX_WHITE
|
||||
#define SSD1306_BLACK ST77XX_BLACK
|
||||
#elif BOARD_MODEL == BOARD_TBEAM_S_V1
|
||||
#elif BOARD_MODEL == BOARD_TBEAM_S_V1 || BOARD_MODEL == BOARD_TBEAM_S_LR_V1
|
||||
Adafruit_SH1106G display = Adafruit_SH1106G(128, 64, &Wire, -1);
|
||||
#define SSD1306_WHITE SH110X_WHITE
|
||||
#define SSD1306_BLACK SH110X_BLACK
|
||||
|
|
@ -225,7 +225,7 @@ void update_area_positions() {
|
|||
}
|
||||
|
||||
uint8_t display_contrast = 0x00;
|
||||
#if BOARD_MODEL == BOARD_TBEAM_S_V1
|
||||
#if BOARD_MODEL == BOARD_TBEAM_S_V1 || BOARD_MODEL == BOARD_TBEAM_S_LR_V1
|
||||
void set_contrast(Adafruit_SH1106G *display, uint8_t value) {
|
||||
}
|
||||
#elif BOARD_MODEL == BOARD_HELTEC_T114
|
||||
|
|
@ -319,7 +319,7 @@ bool display_init() {
|
|||
pinMode(pin_backlight, OUTPUT);
|
||||
analogWrite(pin_backlight, 0);
|
||||
#endif
|
||||
#elif BOARD_MODEL == BOARD_TBEAM_S_V1
|
||||
#elif BOARD_MODEL == BOARD_TBEAM_S_V1 || BOARD_MODEL == BOARD_TBEAM_S_LR_V1
|
||||
Wire.begin(SDA_OLED, SCL_OLED);
|
||||
#elif BOARD_MODEL == BOARD_XIAO_S3
|
||||
Wire.begin(SDA_OLED, SCL_OLED);
|
||||
|
|
@ -376,7 +376,7 @@ bool display_init() {
|
|||
// set white as default pixel colour for Heltec T114
|
||||
display.setRGB(COLOR565(0xFF, 0xFF, 0xFF));
|
||||
if (false) {
|
||||
#elif BOARD_MODEL == BOARD_TBEAM_S_V1
|
||||
#elif BOARD_MODEL == BOARD_TBEAM_S_V1 || BOARD_MODEL == BOARD_TBEAM_S_LR_V1
|
||||
if (!display.begin(display_address, true)) {
|
||||
#else
|
||||
if (!display.begin(SSD1306_SWITCHCAPVCC, display_address)) {
|
||||
|
|
@ -410,7 +410,7 @@ bool display_init() {
|
|||
#elif BOARD_MODEL == BOARD_TBEAM
|
||||
disp_mode = DISP_MODE_LANDSCAPE;
|
||||
display.setRotation(0);
|
||||
#elif BOARD_MODEL == BOARD_TBEAM_S_V1
|
||||
#elif BOARD_MODEL == BOARD_TBEAM_S_V1 || BOARD_MODEL == BOARD_TBEAM_S_LR_V1
|
||||
disp_mode = DISP_MODE_PORTRAIT;
|
||||
display.setRotation(1);
|
||||
#elif BOARD_MODEL == BOARD_HELTEC32_V2
|
||||
|
|
|
|||
3
Makefile
3
Makefile
|
|
@ -98,6 +98,9 @@ firmware-tdeck:
|
|||
firmware-tbeam_supreme:
|
||||
arduino-cli compile --log --fqbn "esp32:esp32:esp32s3:CDCOnBoot=cdc" -e --build-property "build.partitions=no_ota" --build-property "upload.maximum_size=2097152" --build-property "compiler.cpp.extra_flags=-DBOARD_MODEL=0x3D"
|
||||
|
||||
firmware-tbeam_supreme_lr1121:
|
||||
arduino-cli compile --log --fqbn "esp32:esp32:esp32s3:CDCOnBoot=cdc" -e --build-property "build.partitions=no_ota" --build-property "upload.maximum_size=2097152" --build-property "compiler.cpp.extra_flags=-DBOARD_MODEL=0x43"
|
||||
|
||||
firmware-lora32_v10: check_bt_buffers
|
||||
arduino-cli compile --log --fqbn esp32:esp32:ttgo-lora32 -e --build-property "build.partitions=no_ota" --build-property "upload.maximum_size=2097152" --build-property "compiler.cpp.extra_flags=\"-DBOARD_MODEL=0x39\""
|
||||
|
||||
|
|
|
|||
1
Modem.h
1
Modem.h
|
|
@ -2,3 +2,4 @@
|
|||
#define SX1278 0x02
|
||||
#define SX1262 0x03
|
||||
#define SX1280 0x04
|
||||
#define LR11XX 0x05
|
||||
|
|
|
|||
8
Power.h
8
Power.h
|
|
@ -19,12 +19,12 @@
|
|||
bool pmu_temp_sensor_ready = false;
|
||||
float pmu_temperature = PMU_TEMP_MIN-1;
|
||||
|
||||
#if BOARD_MODEL == BOARD_TBEAM || BOARD_MODEL == BOARD_TBEAM_S_V1
|
||||
#if BOARD_MODEL == BOARD_TBEAM || BOARD_MODEL == BOARD_TBEAM_S_V1 || BOARD_MODEL == BOARD_TBEAM_S_LR_V1
|
||||
#include <XPowersLib.h>
|
||||
XPowersLibInterface* PMU = NULL;
|
||||
|
||||
#ifndef PMU_WIRE_PORT
|
||||
#if BOARD_MODEL == BOARD_TBEAM_S_V1
|
||||
#if BOARD_MODEL == BOARD_TBEAM_S_V1 || BOARD_MODEL == BOARD_TBEAM_S_LR_V1
|
||||
#define PMU_WIRE_PORT Wire1
|
||||
#else
|
||||
#define PMU_WIRE_PORT Wire
|
||||
|
|
@ -308,7 +308,7 @@ void measure_battery() {
|
|||
// }
|
||||
}
|
||||
|
||||
#elif BOARD_MODEL == BOARD_TBEAM || BOARD_MODEL == BOARD_TBEAM_S_V1
|
||||
#elif BOARD_MODEL == BOARD_TBEAM || BOARD_MODEL == BOARD_TBEAM_S_V1 || BOARD_MODEL == BOARD_TBEAM_S_LR_V1
|
||||
if (PMU) {
|
||||
float discharge_current = 0;
|
||||
float charge_current = 0;
|
||||
|
|
@ -564,7 +564,7 @@ bool init_pmu() {
|
|||
PMU->setPowerKeyPressOffTime(XPOWERS_POWEROFF_4S);
|
||||
|
||||
return true;
|
||||
#elif BOARD_MODEL == BOARD_TBEAM_S_V1
|
||||
#elif BOARD_MODEL == BOARD_TBEAM_S_V1 || BOARD_MODEL == BOARD_TBEAM_S_LR_V1
|
||||
Wire1.begin(I2C_SDA, I2C_SCL);
|
||||
|
||||
if (!PMU) {
|
||||
|
|
|
|||
|
|
@ -129,7 +129,7 @@ void setup() {
|
|||
boot_seq();
|
||||
#endif
|
||||
|
||||
#if BOARD_MODEL != BOARD_RAK4631 && BOARD_MODEL != BOARD_HELTEC_T114 && BOARD_MODEL != BOARD_TECHO && BOARD_MODEL != BOARD_T3S3 && BOARD_MODEL != BOARD_TBEAM_S_V1 && BOARD_MODEL != BOARD_HELTEC32_V4
|
||||
#if BOARD_MODEL != BOARD_RAK4631 && BOARD_MODEL != BOARD_HELTEC_T114 && BOARD_MODEL != BOARD_TECHO && BOARD_MODEL != BOARD_T3S3 && BOARD_MODEL != BOARD_TBEAM_S_V1 && BOARD_MODEL != BOARD_TBEAM_S_LR_V1 && BOARD_MODEL != BOARD_HELTEC32_V4
|
||||
// Some boards need to wait until the hardware UART is set up before booting
|
||||
// the full firmware. In the case of the RAK4631 and Heltec T114, the line below will wait
|
||||
// until a serial connection is actually established with a master. Thus, it
|
||||
|
|
@ -176,7 +176,7 @@ void setup() {
|
|||
// pins for the LoRa module
|
||||
#if MODEM == SX1276 || MODEM == SX1278
|
||||
LoRa->setPins(pin_cs, pin_reset, pin_dio, pin_busy);
|
||||
#elif MODEM == SX1262
|
||||
#elif MODEM == SX1262 || MODEM == LR11XX
|
||||
LoRa->setPins(pin_cs, pin_reset, pin_dio, pin_busy, pin_rxen);
|
||||
#elif MODEM == SX1280
|
||||
LoRa->setPins(pin_cs, pin_reset, pin_dio, pin_busy, pin_rxen, pin_txen);
|
||||
|
|
@ -664,7 +664,7 @@ void add_airtime(uint16_t written) {
|
|||
lora_symbols += lora_preamble_symbols + 0.25 + 8;
|
||||
packet_cost_ms += lora_symbols * lora_symbol_time_ms;
|
||||
|
||||
#elif MODEM == SX1262 || MODEM == SX1280
|
||||
#elif MODEM == SX1262 || MODEM == SX1280 || MODEM == LR11XX
|
||||
if (lora_sf < 7) {
|
||||
lora_symbols += (8*written + PHY_CRC_LORA_BITS - 4*lora_sf + PHY_HEADER_LORA_SYMBOLS);
|
||||
lora_symbols /= 4*lora_sf;
|
||||
|
|
@ -859,7 +859,7 @@ void serial_callback(uint8_t sbyte) {
|
|||
kiss_indicate_txpower();
|
||||
} else {
|
||||
int txp = sbyte;
|
||||
#if MODEM == SX1262
|
||||
#if MODEM == SX1262 || MODEM == LR11XX
|
||||
#if HAS_LORA_PA
|
||||
if (txp > PA_MAX_OUTPUT) txp = PA_MAX_OUTPUT;
|
||||
#else
|
||||
|
|
@ -1699,6 +1699,7 @@ void loop() {
|
|||
|
||||
tx_queue_handler();
|
||||
check_modem_status();
|
||||
|
||||
|
||||
} else {
|
||||
if (hw_ready) {
|
||||
|
|
|
|||
19
Utilities.h
19
Utilities.h
|
|
@ -38,6 +38,9 @@ sx127x *LoRa = &sx127x_modem;
|
|||
#elif MODEM == SX1280
|
||||
#include "sx128x.h"
|
||||
sx128x *LoRa = &sx128x_modem;
|
||||
#elif MODEM == LR11XX
|
||||
#include "lr11xx.h"
|
||||
lr11xx *LoRa = &lr11xx_modem;
|
||||
#endif
|
||||
|
||||
#include "ROM.h"
|
||||
|
|
@ -243,7 +246,7 @@ uint8_t boot_vector = 0x00;
|
|||
void led_tx_off() { }
|
||||
void led_id_on() { }
|
||||
void led_id_off() { }
|
||||
#elif BOARD_MODEL == BOARD_TBEAM_S_V1
|
||||
#elif BOARD_MODEL == BOARD_TBEAM_S_V1 || BOARD_MODEL == BOARD_TBEAM_S_LR_V1
|
||||
void led_rx_on() { }
|
||||
void led_rx_off() { }
|
||||
void led_tx_on() { }
|
||||
|
|
@ -841,6 +844,18 @@ void kiss_indicate_error(uint8_t error_code) {
|
|||
serial_write(FEND);
|
||||
}
|
||||
|
||||
void kiss_indicate_log(const char *msg) {
|
||||
serial_write(FEND);
|
||||
serial_write(CMD_LOG);
|
||||
while (*msg) {
|
||||
if (*msg == FEND) { serial_write(FESC); serial_write(TFEND); }
|
||||
else if (*msg == FESC) { serial_write(FESC); serial_write(TFESC); }
|
||||
else { serial_write(*msg); }
|
||||
msg++;
|
||||
}
|
||||
serial_write(FEND);
|
||||
}
|
||||
|
||||
void kiss_indicate_radiostate() {
|
||||
serial_write(FEND);
|
||||
serial_write(CMD_RADIO_STATE);
|
||||
|
|
@ -1631,6 +1646,8 @@ bool eeprom_model_valid() {
|
|||
if (model == MODEL_16 || model == MODEL_17) {
|
||||
#elif BOARD_MODEL == BOARD_TBEAM_S_V1
|
||||
if (model == MODEL_DB || model == MODEL_DC) {
|
||||
#elif BOARD_MODEL == BOARD_TBEAM_S_LR_V1
|
||||
if (model == MODEL_D3 || model == MODEL_DF || model == MODEL_D7) {
|
||||
#elif BOARD_MODEL == BOARD_XIAO_S3
|
||||
if (model == MODEL_DD || model == MODEL_DE) {
|
||||
#elif BOARD_MODEL == BOARD_LORA32_V1_0
|
||||
|
|
|
|||
866
lr11xx.cpp
Normal file
866
lr11xx.cpp
Normal file
|
|
@ -0,0 +1,866 @@
|
|||
// 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;
|
||||
141
lr11xx.h
Normal file
141
lr11xx.h
Normal file
|
|
@ -0,0 +1,141 @@
|
|||
// Copyright 2025
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#ifndef LR11XX_H
|
||||
#define LR11XX_H
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <SPI.h>
|
||||
#include "Modem.h"
|
||||
|
||||
#define LORA_DEFAULT_SS_PIN 10
|
||||
#define LORA_DEFAULT_RESET_PIN 9
|
||||
#define LORA_DEFAULT_DIO0_PIN 2
|
||||
#define LORA_DEFAULT_RXEN_PIN -1
|
||||
#define LORA_DEFAULT_BUSY_PIN -1
|
||||
#define LORA_MODEM_TIMEOUT_MS 20E3
|
||||
|
||||
#define PA_OUTPUT_RFO_PIN 0
|
||||
#define PA_OUTPUT_PA_BOOST_PIN 1
|
||||
|
||||
#define RSSI_OFFSET 157
|
||||
|
||||
class lr11xx : public Stream {
|
||||
public:
|
||||
lr11xx();
|
||||
|
||||
int begin(long frequency);
|
||||
void end();
|
||||
|
||||
int beginPacket(int implicitHeader = false);
|
||||
int endPacket();
|
||||
|
||||
int parsePacket(int size = 0);
|
||||
int packetRssi();
|
||||
int packetRssi(uint8_t pkt_snr_raw);
|
||||
int currentRssi();
|
||||
uint8_t packetRssiRaw();
|
||||
uint8_t currentRssiRaw();
|
||||
uint8_t packetSnrRaw();
|
||||
float packetSnr();
|
||||
long packetFrequencyError();
|
||||
|
||||
// from Print
|
||||
virtual size_t write(uint8_t byte);
|
||||
virtual size_t write(const uint8_t *buffer, size_t size);
|
||||
|
||||
// from Stream
|
||||
virtual int available();
|
||||
virtual int read();
|
||||
virtual int peek();
|
||||
virtual void flush();
|
||||
|
||||
void onReceive(void(*callback)(int));
|
||||
|
||||
void receive(int size = 0);
|
||||
void standby();
|
||||
void sleep();
|
||||
void reset(void);
|
||||
|
||||
bool preInit();
|
||||
uint8_t getTxPower();
|
||||
void setTxPower(int level, int outputPin = PA_OUTPUT_PA_BOOST_PIN);
|
||||
uint32_t getFrequency();
|
||||
void setFrequency(long frequency);
|
||||
void setSpreadingFactor(int sf);
|
||||
long getSignalBandwidth();
|
||||
void setSignalBandwidth(long sbw);
|
||||
void setCodingRate4(int denominator);
|
||||
void setPreambleLength(long preamble_symbols);
|
||||
void setSyncWord(uint8_t sw);
|
||||
bool dcd();
|
||||
void enableCrc();
|
||||
void disableCrc();
|
||||
void enableTCXO();
|
||||
void disableTCXO();
|
||||
|
||||
void loraMode();
|
||||
void waitOnBusy();
|
||||
|
||||
// LR11xx SPI layer (2-byte opcodes, two-phase reads)
|
||||
void executeOpcode(uint16_t opcode, uint8_t *buffer, uint8_t size);
|
||||
void executeOpcodeRead(uint16_t opcode, uint8_t *buffer, uint8_t size);
|
||||
void writeBuffer(const uint8_t* buffer, size_t size);
|
||||
void readBuffer(uint8_t* buffer, size_t size);
|
||||
void setPacketParams(long preamble_symbols, uint8_t headermode, uint8_t payload_length, uint8_t crc);
|
||||
void setModulationParams(uint8_t sf, uint8_t bw, uint8_t cr, int ldro);
|
||||
|
||||
byte random();
|
||||
|
||||
void setPins(int ss = LORA_DEFAULT_SS_PIN, int reset = LORA_DEFAULT_RESET_PIN, int dio0 = LORA_DEFAULT_DIO0_PIN, int busy = LORA_DEFAULT_BUSY_PIN, int rxen = LORA_DEFAULT_RXEN_PIN);
|
||||
void setSPIFrequency(uint32_t frequency);
|
||||
|
||||
void dumpRegisters(Stream& out);
|
||||
|
||||
private:
|
||||
void explicitHeaderMode();
|
||||
void implicitHeaderMode();
|
||||
void handleDio0Rise();
|
||||
static void onDio0Rise();
|
||||
|
||||
void handleLowDataRate();
|
||||
void calibrate(void);
|
||||
void calibrateImage(long frequency);
|
||||
void configureRfSwitch();
|
||||
void applyHighAcpWorkaround();
|
||||
void setRxBoosted(bool enable);
|
||||
void clearIrqFlags(uint32_t mask);
|
||||
|
||||
// LR11xx register access (32-bit addresses)
|
||||
uint32_t readRegister32(uint32_t address);
|
||||
void writeRegister32(uint32_t address, uint32_t value);
|
||||
|
||||
private:
|
||||
SPISettings _spiSettings;
|
||||
int _ss;
|
||||
int _reset;
|
||||
int _dio0; // DIO9 on LR1121 (interrupt pin)
|
||||
int _rxen;
|
||||
int _busy; // DIO0 on LR1121 (busy indicator)
|
||||
long _frequency;
|
||||
int _txp;
|
||||
uint8_t _sf;
|
||||
uint8_t _bw;
|
||||
uint8_t _cr;
|
||||
uint8_t _ldro;
|
||||
int _packetIndex;
|
||||
int _preambleLength;
|
||||
int _implicitHeaderMode;
|
||||
int _payloadLength;
|
||||
int _crcMode;
|
||||
int _fifo_rx_addr_ptr;
|
||||
uint8_t _packet[255];
|
||||
bool _preinit_done;
|
||||
uint8_t _lastMiso[6]; // Inline MISO capture from write commands
|
||||
uint32_t _preamble_detected_at;
|
||||
void (*_onReceive)(int);
|
||||
};
|
||||
|
||||
extern lr11xx lr11xx_modem;
|
||||
|
||||
#endif
|
||||
Loading…
Add table
Add a link
Reference in a new issue