diff --git a/chip_test_example/lora_rx.cpp b/chip_test_example/lora_rx.cpp index 3cfcabd..9073eff 100644 --- a/chip_test_example/lora_rx.cpp +++ b/chip_test_example/lora_rx.cpp @@ -1,146 +1,93 @@ -// lora_rx.cpp — LR1121 receive test (robust RX loop) -// Usage: sudo ./lora_rx [-v] [--433|--868|--24|freq_hz] [--reset] [--tmo=ms] -// [--busy-gpio=N] [--reset-gpio=N] [--dio9-gpio=N] -// -v verbose step labels (shows exactly where init hangs) -// --433 433.05 MHz (default) -// --868 868 MHz -// --24 2403 MHz -// freq_hz any raw frequency in Hz -// --reset send Reboot(app) to escape bootloader, print fw before/after, exit -// -// TX and RX must use identical SF/BW/CR/freq settings. -// Requires SPI: sudo raspi-config → Interfaces → SPI → Yes → reboot #include -#include #include + #include "lr1121_malnus.hpp" +static void applyPaPreset(lr1121::Config &cfg, bool hp_mode) +{ + cfg.pa_sel = hp_mode ? 0x01 : 0x00; + cfg.tx_dbm = hp_mode ? 14 : 10; +} + +static bool parseArgs(int argc, char **argv, bool &verbose, bool &do_reset, bool &hp_mode) +{ + for (int i = 1; i < argc; ++i) { + if (std::strcmp(argv[i], "-v") == 0) { + verbose = true; + continue; + } + if (std::strcmp(argv[i], "--reset") == 0) { + do_reset = true; + continue; + } + if (std::strcmp(argv[i], "--hp") == 0) { + hp_mode = true; + continue; + } + if (std::strcmp(argv[i], "--lp") == 0) { + hp_mode = false; + continue; + } + std::fprintf(stderr, "Unknown option: %s\nUsage: sudo ./lora_rx [-v] [--reset] [--lp|--hp]\n", argv[i]); + return false; + } + return true; +} + int main(int argc, char **argv) { lr1121::Config cfg; - cfg.verbose = false; - cfg.pa_sel = 0x01; - cfg.tx_dbm = 14; - cfg.sf = 0x07; // SF7 - cfg.bw = 0x04; // 125 kHz - cfg.cr = 0x01; // CR 4/5 + cfg.freq_hz = lr1121::FREQ_433; + cfg.busy_gpio = 24; + cfg.reset_gpio = 25; + cfg.dio9_gpio = 4; + cfg.sf = 0x07; + cfg.bw = 0x04; + cfg.cr = 0x01; + constexpr uint32_t rx_timeout_ms = 1000; bool do_reset = false; - uint32_t rx_timeout_ms = 1000; // Short timeout keeps RX loop responsive. + bool hp_mode = false; - for (int i = 1; i < argc; ++i) { - if (std::strcmp(argv[i], "-v") == 0) cfg.verbose = true; - else if (std::strcmp(argv[i], "--433") == 0) cfg.freq_hz = lr1121::FREQ_433; - else if (std::strcmp(argv[i], "--868") == 0) cfg.freq_hz = lr1121::FREQ_868; - else if (std::strcmp(argv[i], "--24") == 0 || - std::strcmp(argv[i], "--2g4") == 0) cfg.freq_hz = lr1121::FREQ_2400; - else if (std::strcmp(argv[i], "--reset") == 0) do_reset = true; - else if (std::strncmp(argv[i], "--tmo=", 6) == 0) rx_timeout_ms = (uint32_t)std::strtoul(argv[i] + 6, nullptr, 10); - else if (std::strncmp(argv[i], "--busy-gpio=", 12) == 0) cfg.busy_gpio = (unsigned)std::strtoul(argv[i] + 12, nullptr, 10); - else if (std::strncmp(argv[i], "--reset-gpio=", 13) == 0) cfg.reset_gpio = (unsigned)std::strtoul(argv[i] + 13, nullptr, 10); - else if (std::strncmp(argv[i], "--dio9-gpio=", 12) == 0) cfg.dio9_gpio = (unsigned)std::strtoul(argv[i] + 12, nullptr, 10); - else cfg.freq_hz = (uint32_t)std::strtoul(argv[i], nullptr, 10); - } - - std::printf("lora_rx: %u Hz SF%u BW=0x%02X CR=0x%02X tmo=%ums gpio(busy=%u reset=%u dio9=%u)%s\n", - cfg.freq_hz, cfg.sf, cfg.bw, - cfg.cr, rx_timeout_ms, cfg.busy_gpio, cfg.reset_gpio, cfg.dio9_gpio, - cfg.verbose ? " [verbose]" : ""); - - if (do_reset) { - cfg.verbose = true; - lr1121::Radio radio; - if (!radio.beginRaw(cfg)) { - std::fprintf(stderr, "ERROR: cannot open SPI/GPIO\n"); - return 1; - } - auto v1 = radio.getVersion(); - std::printf("before reboot: hw=0x%02X type=0x%02X fw=0x%02X%02X\n", - v1.hw, v1.type, v1.fw_hi, v1.fw_lo); - std::printf("sending Reboot(app)...\n"); - radio.reboot(false); - auto v2 = radio.getVersion(); - std::printf("after reboot: hw=0x%02X type=0x%02X fw=0x%02X%02X\n", - v2.hw, v2.type, v2.fw_hi, v2.fw_lo); - if (v2.fw_hi >= 0x02) - std::printf("OK — application firmware active (fw >= 0x02xx)\n" - "Run without --reset to continue.\n"); - else - std::printf("Still in bootloader (fw=0x%02X%02X).\n", - v2.fw_hi, v2.fw_lo); - radio.end(); - return 0; - } + if (!parseArgs(argc, argv, cfg.verbose, do_reset, hp_mode)) return 2; + applyPaPreset(cfg, hp_mode); + std::printf("RX %u Hz, tmo=%ums, gpio(busy=%u reset=%u dio9=%u)%s\n", + cfg.freq_hz, rx_timeout_ms, cfg.busy_gpio, cfg.reset_gpio, cfg.dio9_gpio, cfg.verbose ? " [v]" : ""); lr1121::Radio radio; if (!radio.begin(cfg)) { - std::fprintf(stderr, "ERROR: radio init failed\n" - " Check: SPI enabled? wiring? DIO5/DIO6 connected?\n" - " Run with -v for step-by-step output\n"); + std::fprintf(stderr, "ERROR: radio init failed\n"); return 1; } - std::printf("Radio OK — listening (Ctrl+C to stop)\n\n"); + if (do_reset) { + std::puts("Applying soft settings reset..."); + if (!radio.softResetSettings()) { + std::fprintf(stderr, "ERROR: soft settings reset failed\n"); + return 1; + } + } + std::puts("Radio OK - listening..."); uint8_t buf[256]; - int pkt = 0; - int timeouts_in_row = 0; - int crc_in_row = 0; + int pkt = 0; uint32_t timeout_total = 0; uint32_t crc_total = 0; for (;;) { lr1121::RxInfo info{}; - int r = radio.receive(buf, (uint8_t)(sizeof(buf) - 1), rx_timeout_ms, &info); + const int r = radio.receive(buf, static_cast(sizeof(buf) - 1), rx_timeout_ms, &info); if (r > 0) { buf[r] = '\0'; - std::printf("[%4d] %d B rssi=%d dBm snr=%d dB '%s'\n", + std::printf("[%04d] %3d B rssi=%4d snr=%3d '%s'\n", ++pkt, r, info.rssi_dbm, info.snr_db, buf); - timeouts_in_row = 0; - crc_in_row = 0; } else if (r == -1) { - ++timeouts_in_row; ++timeout_total; - if ((timeout_total % 10u) == 0u) { - std::printf(" timeout x%u total (current streak=%d)\n", - timeout_total, timeouts_in_row); - } - - // If RX keeps stalling, fully reinitialize to recover reliably. - if (timeouts_in_row >= 20) { - std::printf(" RX stalled (timeouts streak=%d) -> reinitializing radio...\n", - timeouts_in_row); - radio.end(); - if (!radio.begin(cfg)) { - std::fprintf(stderr, "ERROR: radio reinit failed, retrying in 1s...\n"); - std::this_thread::sleep_for(std::chrono::seconds(1)); - } else { - std::printf(" radio reinitialized, listening...\n"); - timeouts_in_row = 0; - crc_in_row = 0; - } - } + if ((timeout_total % 10u) == 0u) std::printf("timeout x%u\n", timeout_total); } else if (r == -2) { - ++crc_in_row; ++crc_total; - std::printf(" CRC error (streak=%d total=%u)\n", crc_in_row, crc_total); - - if (crc_in_row >= 8) { - std::printf(" too many CRC errors -> reinitializing radio...\n"); - radio.end(); - if (!radio.begin(cfg)) { - std::fprintf(stderr, "ERROR: radio reinit failed, retrying in 1s...\n"); - std::this_thread::sleep_for(std::chrono::seconds(1)); - } else { - std::printf(" radio reinitialized, listening...\n"); - timeouts_in_row = 0; - crc_in_row = 0; - } - } + std::printf("crc error x%u\n", crc_total); } else { - std::printf(" RX unexpected code=%d\n", r); + std::printf("rx code=%d\n", r); } } - - radio.end(); - return 0; } diff --git a/chip_test_example/lora_tx.cpp b/chip_test_example/lora_tx.cpp index 935ab17..817afde 100644 --- a/chip_test_example/lora_tx.cpp +++ b/chip_test_example/lora_tx.cpp @@ -1,73 +1,80 @@ -// lora_tx.cpp — LR1121 transmit test -// Usage: sudo ./lora_tx [-v] [--433|--868|--24|freq_hz] [--lp|--hp] [--dbm=N] -// [--busy-gpio=N] [--reset-gpio=N] [--dio9-gpio=N] -// -v verbose step labels (shows exactly where init hangs) -// --433 433.05 MHz (default) -// --868 868 MHz -// --24 2403 MHz -// freq_hz any raw frequency in Hz -// -// Requires SPI: sudo raspi-config → Interfaces → SPI → Yes → reboot #include -#include #include -#include #include +#include + #include "lr1121_malnus.hpp" +static void applyPaPreset(lr1121::Config &cfg, bool hp_mode) +{ + cfg.pa_sel = hp_mode ? 0x01 : 0x00; + cfg.tx_dbm = hp_mode ? 14 : 10; +} + +static bool parseArgs(int argc, char **argv, bool &verbose, bool &do_reset, bool &hp_mode) +{ + for (int i = 1; i < argc; ++i) { + if (std::strcmp(argv[i], "-v") == 0) { + verbose = true; + continue; + } + if (std::strcmp(argv[i], "--reset") == 0) { + do_reset = true; + continue; + } + if (std::strcmp(argv[i], "--hp") == 0) { + hp_mode = true; + continue; + } + if (std::strcmp(argv[i], "--lp") == 0) { + hp_mode = false; + continue; + } + std::fprintf(stderr, "Unknown option: %s\nUsage: sudo ./lora_tx [-v] [--reset] [--lp|--hp]\n", argv[i]); + return false; + } + return true; +} + int main(int argc, char **argv) { lr1121::Config cfg; - cfg.verbose = false; - cfg.pa_sel = 0x00; // LP default: avoids LBD on weak VBAT rails - cfg.tx_dbm = 10; - cfg.sf = 0x07; // SF7 - cfg.bw = 0x04; // 125 kHz - cfg.cr = 0x01; // CR 4/5 + cfg.freq_hz = lr1121::FREQ_433; + cfg.busy_gpio = 24; + cfg.reset_gpio = 25; + cfg.dio9_gpio = 4; + cfg.sf = 0x07; + cfg.bw = 0x04; + cfg.cr = 0x01; + bool do_reset = false; + bool hp_mode = false; - for (int i = 1; i < argc; ++i) { - if (std::strcmp(argv[i], "-v") == 0) cfg.verbose = true; - else if (std::strcmp(argv[i], "--433") == 0) cfg.freq_hz = lr1121::FREQ_433; - else if (std::strcmp(argv[i], "--868") == 0) cfg.freq_hz = lr1121::FREQ_868; - else if (std::strcmp(argv[i], "--24") == 0 || - std::strcmp(argv[i], "--2g4") == 0) cfg.freq_hz = lr1121::FREQ_2400; - else if (std::strcmp(argv[i], "--hp") == 0) cfg.pa_sel = 0x01; - else if (std::strcmp(argv[i], "--lp") == 0) cfg.pa_sel = 0x00; - else if (std::strncmp(argv[i], "--dbm=", 6) == 0) cfg.tx_dbm = (int8_t)std::atoi(argv[i] + 6); - else if (std::strncmp(argv[i], "--busy-gpio=", 12) == 0) cfg.busy_gpio = (unsigned)std::strtoul(argv[i] + 12, nullptr, 10); - else if (std::strncmp(argv[i], "--reset-gpio=", 13) == 0) cfg.reset_gpio = (unsigned)std::strtoul(argv[i] + 13, nullptr, 10); - else if (std::strncmp(argv[i], "--dio9-gpio=", 12) == 0) cfg.dio9_gpio = (unsigned)std::strtoul(argv[i] + 12, nullptr, 10); - else cfg.freq_hz = (uint32_t)std::strtoul(argv[i], nullptr, 10); - } + if (!parseArgs(argc, argv, cfg.verbose, do_reset, hp_mode)) return 2; + applyPaPreset(cfg, hp_mode); - std::printf("lora_tx: %u Hz SF%u BW=0x%02X PA=%s PWR=%d dBm gpio(busy=%u reset=%u dio9=%u)%s\n", - cfg.freq_hz, cfg.sf, cfg.bw, - cfg.pa_sel ? "HP" : "LP", (int)cfg.tx_dbm, - cfg.busy_gpio, cfg.reset_gpio, cfg.dio9_gpio, - cfg.verbose ? " [verbose]" : ""); + std::printf("TX %u Hz, %s PA, %d dBm, gpio(busy=%u reset=%u dio9=%u)%s\n", + cfg.freq_hz, cfg.pa_sel ? "HP" : "LP", static_cast(cfg.tx_dbm), + cfg.busy_gpio, cfg.reset_gpio, cfg.dio9_gpio, cfg.verbose ? " [v]" : ""); lr1121::Radio radio; if (!radio.begin(cfg)) { - std::fprintf(stderr, "ERROR: radio init failed\n" - " Check: SPI enabled? wiring? DIO5/DIO6 connected?\n" - " If fw looks like bootloader: sudo ./lora_rx --reset\n" - " Run with -v for step-by-step output\n"); + std::fprintf(stderr, "ERROR: radio init failed\n"); return 1; } - auto ver = radio.getVersion(); - if (ver.fw_hi < 0x02) - std::fprintf(stderr, "hint: fw=0x%02X%02X — try: sudo ./lora_rx --reset\n", - ver.fw_hi, ver.fw_lo); - std::printf("Radio OK — sending every second\n"); + if (do_reset) { + std::puts("Applying soft settings reset..."); + if (!radio.softResetSettings()) { + std::fprintf(stderr, "ERROR: soft settings reset failed\n"); + return 1; + } + } + std::puts("Radio OK - sending..."); for (int n = 0; ; ++n) { char msg[32]; - int len = std::snprintf(msg, sizeof(msg), "hello %d", n); - bool ok = radio.send((const uint8_t *)msg, (uint8_t)len); - std::printf("[%4d] tx '%s' → %s\n", n, msg, ok ? "OK" : "TIMEOUT"); + const int len = std::snprintf(msg, sizeof(msg), "hello %d", n); + const bool ok = radio.send(reinterpret_cast(msg), static_cast(len)); + std::printf("[%04d] %s: %s\n", n, msg, ok ? "OK" : "FAIL"); std::this_thread::sleep_for(std::chrono::seconds(1)); } - - radio.end(); - return 0; } diff --git a/chip_test_example/lr1121_malnus.hpp b/chip_test_example/lr1121_malnus.hpp index 36fbd4c..3ae1420 100644 --- a/chip_test_example/lr1121_malnus.hpp +++ b/chip_test_example/lr1121_malnus.hpp @@ -13,6 +13,7 @@ #pragma once #include +#include #include #include #include @@ -37,6 +38,7 @@ constexpr uint16_t OC_CALIBRATE_IMAGE = 0x0111; constexpr uint16_t OC_SET_DIO_AS_RFSW = 0x0112; constexpr uint16_t OC_SET_DIOIRQ = 0x0113; constexpr uint16_t OC_CLEAR_IRQ = 0x0114; +constexpr uint16_t OC_GET_IRQ = 0x0115; constexpr uint16_t OC_REBOOT = 0x0118; constexpr uint16_t OC_GET_VBAT = 0x0119; constexpr uint16_t OC_SET_STANDBY = 0x011C; @@ -68,6 +70,16 @@ constexpr uint32_t FREQ_433 = 433'050'000u; constexpr uint32_t FREQ_868 = 868'000'000u; constexpr uint32_t FREQ_2400 = 2'403'000'000u; +constexpr uint8_t LORA_PKT_HEADER_EXPLICIT = 0x00; +constexpr uint8_t LORA_PKT_PREAMBLE_LEN = 0x08; +constexpr uint8_t LORA_PKT_CRC_ON = 0x01; +constexpr uint8_t LORA_PKT_IQ_STD = 0x00; +constexpr uint8_t TX_RAMP_48US = 0x02; +constexpr uint8_t PKT_TYPE_LORA = 0x02; +constexpr uint32_t TX_TIMEOUT_MS = 3000; +constexpr uint32_t TX_POLL_GUARD_MS = 500; +constexpr int8_t MIN_TX_DBM_FALLBACK = 2; + struct Config { const char *spi_path = "/dev/spidev0.0"; uint32_t spi_hz = 8'000'000; @@ -104,6 +116,7 @@ class Radio { public: bool begin(const Config &cfg); bool beginRaw(const Config &cfg); + bool softResetSettings(); void end(); bool send(const uint8_t *data, uint8_t n); @@ -136,6 +149,7 @@ private: bool clearIrq(uint32_t mask = IRQ_ALL); uint32_t getIrq(); uint16_t getErrors(); + bool applyRadioSettings(); static void imgCalFreqs(uint32_t hz, uint8_t &f1, uint8_t &f2); static uint8_t computeLdRo(uint8_t sf, uint8_t bw); @@ -156,6 +170,7 @@ inline bool Radio::spiTransfer(uint8_t *buf, size_t len) inline bool Radio::wcmd(uint16_t op, const uint8_t *params, size_t n) { + if (n > 256) return false; if (!waitBusy()) return false; uint8_t cmd[258]{}; cmd[0] = static_cast(op >> 8); @@ -167,6 +182,7 @@ inline bool Radio::wcmd(uint16_t op, const uint8_t *params, size_t n) inline bool Radio::rcmd(uint16_t op, const uint8_t *params, size_t np, uint8_t *out, size_t nr) { + if (np > 30 || nr > 259) return false; if (!waitBusy()) return false; uint8_t cmd[32]{}; cmd[0] = static_cast(op >> 8); @@ -203,12 +219,12 @@ inline bool Radio::clearIrq(uint32_t mask) inline uint32_t Radio::getIrq() { - uint8_t b[6]{}; - if (!spiTransfer(b, sizeof(b))) return 0; - return (static_cast(b[2]) << 24) | - (static_cast(b[3]) << 16) | - (static_cast(b[4]) << 8) | - (static_cast(b[5])); + uint8_t b[4]{}; + if (!rcmd(OC_GET_IRQ, nullptr, 0, b, sizeof(b))) return 0; + return (static_cast(b[0]) << 24) | + (static_cast(b[1]) << 16) | + (static_cast(b[2]) << 8) | + static_cast(b[3]); } inline uint16_t Radio::getErrors() @@ -237,9 +253,13 @@ inline bool Radio::openSpi(const Config &cfg) uint8_t mode = SPI_MODE_0; uint8_t bits = 8; - if (ioctl(spi_fd_, SPI_IOC_WR_MODE, &mode) < 0) return false; - if (ioctl(spi_fd_, SPI_IOC_WR_BITS_PER_WORD, &bits) < 0) return false; - if (ioctl(spi_fd_, SPI_IOC_WR_MAX_SPEED_HZ, &cfg.spi_hz) < 0) return false; + if (ioctl(spi_fd_, SPI_IOC_WR_MODE, &mode) < 0 || + ioctl(spi_fd_, SPI_IOC_WR_BITS_PER_WORD, &bits) < 0 || + ioctl(spi_fd_, SPI_IOC_WR_MAX_SPEED_HZ, &cfg.spi_hz) < 0) { + ::close(spi_fd_); + spi_fd_ = -1; + return false; + } return true; } @@ -355,13 +375,15 @@ inline ChipVersion Radio::getVersion() inline bool Radio::beginRaw(const Config &cfg) { + end(); cfg_ = cfg; if (!openSpi(cfg_)) return false; - if (!openGpio(cfg_.reset_gpio, true, reset_fd_)) return false; - if (!openGpio(cfg_.busy_gpio, false, busy_fd_)) return false; - if (!openGpio(cfg_.dio9_gpio, false, dio9_fd_)) return false; + if (!openGpio(cfg_.reset_gpio, true, reset_fd_)) { end(); return false; } + if (!openGpio(cfg_.busy_gpio, false, busy_fd_)) { end(); return false; } + if (!openGpio(cfg_.dio9_gpio, false, dio9_fd_)) { end(); return false; } hardReset(); - return waitBusy(500); + if (!waitBusy(500)) { end(); return false; } + return true; } inline bool Radio::begin(const Config &cfg) @@ -377,58 +399,71 @@ inline bool Radio::begin(const Config &cfg) std::fprintf(stderr, "[lr1121] VBAT raw=0x%02X (~%.2fV)\n", raw, vb); } } - if (v.type != 0x03) return false; + if (v.type != 0x03) { end(); return false; } + return applyRadioSettings(); +} - if (!setStandbyRc()) return false; - { const uint8_t p[] = {0x01}; if (!wcmd(OC_SET_FALLBACK_MODE, p, sizeof(p))) return false; } - if (!clearIrq()) return false; - if (!setIrqMask(0, 0)) return false; - { const uint8_t p[] = {CALIB_ALL}; if (!wcmd(OC_CALIBRATE, p, sizeof(p))) return false; } - { const uint8_t p[] = {0x00}; (void)wcmd(OC_CLEAR_ERRORS, p, 0); } +inline bool Radio::applyRadioSettings() +{ + if (!setStandbyRc()) { end(); return false; } + { const uint8_t p[] = {0x01}; if (!wcmd(OC_SET_FALLBACK_MODE, p, sizeof(p))) { end(); return false; } } + if (!clearIrq()) { end(); return false; } + if (!setIrqMask(0, 0)) { end(); return false; } + { const uint8_t p[] = {CALIB_ALL}; if (!wcmd(OC_CALIBRATE, p, sizeof(p))) { end(); return false; } } + (void)wcmd(OC_CLEAR_ERRORS, nullptr, 0); - { const uint8_t p[] = {static_cast(cfg_.use_dcdc ? 0x01 : 0x00)}; if (!wcmd(OC_SET_REGMODE, p, sizeof(p))) return false; } - { const uint8_t p[] = {0x02}; if (!wcmd(OC_SET_PKT_TYPE, p, sizeof(p))) return false; } + { const uint8_t p[] = {static_cast(cfg_.use_dcdc ? 0x01 : 0x00)}; if (!wcmd(OC_SET_REGMODE, p, sizeof(p))) { end(); return false; } } + { const uint8_t p[] = {PKT_TYPE_LORA}; if (!wcmd(OC_SET_PKT_TYPE, p, sizeof(p))) { end(); return false; } } { const uint8_t p[] = { static_cast(cfg_.freq_hz >> 24), static_cast(cfg_.freq_hz >> 16), static_cast(cfg_.freq_hz >> 8), static_cast(cfg_.freq_hz), }; - if (!wcmd(OC_SET_RF_FREQ, p, sizeof(p))) return false; + if (!wcmd(OC_SET_RF_FREQ, p, sizeof(p))) { end(); return false; } } { uint8_t f1 = 0, f2 = 0; imgCalFreqs(cfg_.freq_hz, f1, f2); const uint8_t p[] = {f1, f2}; - if (!wcmd(OC_CALIBRATE_IMAGE, p, sizeof(p))) return false; + if (!wcmd(OC_CALIBRATE_IMAGE, p, sizeof(p))) { end(); return false; } } - { const uint8_t p[] = {0x00, 0x00}; if (!wcmd(OC_SET_PKT_ADRS, p, sizeof(p))) return false; } + { const uint8_t p[] = {0x00, 0x00}; if (!wcmd(OC_SET_PKT_ADRS, p, sizeof(p))) { end(); return false; } } { const uint8_t p[] = {cfg_.sf, cfg_.bw, cfg_.cr, computeLdRo(cfg_.sf, cfg_.bw)}; - if (!wcmd(OC_SET_MOD_PARAM, p, sizeof(p))) return false; + if (!wcmd(OC_SET_MOD_PARAM, p, sizeof(p))) { end(); return false; } } - { const uint8_t p[] = {0x00, 0x08, 0x00, 0xFF, 0x01, 0x00}; if (!wcmd(OC_SET_PKT_PARAM, p, sizeof(p))) return false; } + { const uint8_t p[] = {LORA_PKT_HEADER_EXPLICIT, LORA_PKT_PREAMBLE_LEN, 0x00, 0xFF, LORA_PKT_CRC_ON, LORA_PKT_IQ_STD}; if (!wcmd(OC_SET_PKT_PARAM, p, sizeof(p))) { end(); return false; } } { uint8_t duty = 0, hp_max = 0; computePaConfig(cfg_.pa_sel, cfg_.tx_dbm, duty, hp_max); const uint8_t p[] = {cfg_.pa_sel, cfg_.pa_supply, duty, hp_max}; - if (!wcmd(OC_SET_PA_CFG, p, sizeof(p))) return false; + if (!wcmd(OC_SET_PA_CFG, p, sizeof(p))) { end(); return false; } if (cfg_.verbose) { std::fprintf(stderr, "[lr1121] PA: sel=0x%02X supply=0x%02X duty=%u hp_max=%u dbm=%d\n", cfg_.pa_sel, cfg_.pa_supply, duty, hp_max, static_cast(cfg_.tx_dbm)); } } - { const uint8_t p[] = {static_cast(cfg_.tx_dbm), 0x02}; if (!wcmd(OC_SET_TX_PARAMS, p, sizeof(p))) return false; } - { const uint8_t p[] = {static_cast(cfg_.lora_wan ? 0x01 : 0x00)}; if (!wcmd(OC_SET_LORA_NET, p, sizeof(p))) return false; } + { const uint8_t p[] = {static_cast(cfg_.tx_dbm), TX_RAMP_48US}; if (!wcmd(OC_SET_TX_PARAMS, p, sizeof(p))) { end(); return false; } } + { const uint8_t p[] = {static_cast(cfg_.lora_wan ? 0x01 : 0x00)}; if (!wcmd(OC_SET_LORA_NET, p, sizeof(p))) { end(); return false; } } // DIO5/DIO6 RF switch mapping: STBY(0,0), RX(1,0), TX(0,1), TX_HP(0,1) - { const uint8_t p[] = {0x03, 0x00, 0x01, 0x02, 0x02, 0x00, 0x00, 0x00}; if (!wcmd(OC_SET_DIO_AS_RFSW, p, sizeof(p))) return false; } + { const uint8_t p[] = {0x03, 0x00, 0x01, 0x02, 0x02, 0x00, 0x00, 0x00}; if (!wcmd(OC_SET_DIO_AS_RFSW, p, sizeof(p))) { end(); return false; } } - if (!setStandbyXosc()) return false; + if (!setStandbyXosc()) { end(); return false; } if (cfg_.verbose) std::fprintf(stderr, "[lr1121] init OK: %u Hz SF%u BW=0x%02X CR=0x%02X\n", cfg_.freq_hz, cfg_.sf, cfg_.bw, cfg_.cr); return true; } +inline bool Radio::softResetSettings() +{ + if (spi_fd_ < 0 || busy_fd_ < 0 || reset_fd_ < 0 || dio9_fd_ < 0) return false; + if (!setStandbyRc()) return false; + (void)wcmd(OC_CLEAR_RXBUF, nullptr, 0); + (void)clearIrq(); + return applyRadioSettings(); +} + inline void Radio::reboot(bool stay_in_bootloader) { const uint8_t p[] = { static_cast(stay_in_bootloader ? 0x01 : 0x00) }; @@ -447,51 +482,79 @@ inline void Radio::end() inline bool Radio::send(const uint8_t *data, uint8_t n) { if (n == 0) return false; - if (!setStandbyXosc()) return false; - if (!clearIrq()) return false; - if (!setIrqMask(IRQ_TX_DONE | IRQ_TIMEOUT | IRQ_LBD, IRQ_TX_DONE | IRQ_TIMEOUT | IRQ_LBD)) return false; + uint8_t pa_sel = cfg_.pa_sel; + int8_t tx_dbm = cfg_.tx_dbm; - if (!wcmd(OC_WRITE_BUF8, data, n)) return false; - { const uint8_t p[] = {0x00, 0x08, 0x00, n, 0x01, 0x00}; if (!wcmd(OC_SET_PKT_PARAM, p, sizeof(p))) return false; } + for (int attempt = 0; attempt < 6; ++attempt) { + if (!setStandbyXosc()) return false; + if (!clearIrq()) return false; + if (!setIrqMask(IRQ_TX_DONE | IRQ_TIMEOUT | IRQ_LBD, IRQ_TX_DONE | IRQ_TIMEOUT | IRQ_LBD)) return false; - { - const uint32_t tx_steps = timeoutMsToRtcSteps(3000); - const uint8_t p[] = { - static_cast(tx_steps >> 16), - static_cast(tx_steps >> 8), - static_cast(tx_steps), - }; - if (!wcmd(OC_SET_TX, p, sizeof(p))) return false; - } - - const auto deadline = std::chrono::steady_clock::now() + std::chrono::milliseconds(3500); - while (std::chrono::steady_clock::now() < deadline) { - // Primary trigger is GPIO DIO9 (actual IRQ pin), then decode IRQ bits. - if (getGpioLine(dio9_fd_) == 0) { - std::this_thread::sleep_for(std::chrono::milliseconds(1)); - continue; + { + uint8_t duty = 0, hp_max = 0; + computePaConfig(pa_sel, tx_dbm, duty, hp_max); + const uint8_t p[] = {pa_sel, cfg_.pa_supply, duty, hp_max}; + if (!wcmd(OC_SET_PA_CFG, p, sizeof(p))) return false; } - const uint32_t irq = getIrq(); - if (irq & IRQ_TX_DONE) { - (void)clearIrq(); - (void)setIrqMask(0, 0); - return true; + { const uint8_t p[] = {static_cast(tx_dbm), TX_RAMP_48US}; if (!wcmd(OC_SET_TX_PARAMS, p, sizeof(p))) return false; } + if (!wcmd(OC_WRITE_BUF8, data, n)) return false; + { const uint8_t p[] = {LORA_PKT_HEADER_EXPLICIT, LORA_PKT_PREAMBLE_LEN, 0x00, n, LORA_PKT_CRC_ON, LORA_PKT_IQ_STD}; if (!wcmd(OC_SET_PKT_PARAM, p, sizeof(p))) return false; } + { + const uint32_t tx_steps = timeoutMsToRtcSteps(TX_TIMEOUT_MS); + const uint8_t p[] = { + static_cast(tx_steps >> 16), + static_cast(tx_steps >> 8), + static_cast(tx_steps), + }; + if (!wcmd(OC_SET_TX, p, sizeof(p))) return false; } - if (irq & (IRQ_TIMEOUT | IRQ_LBD)) { - if (cfg_.verbose) { - std::fprintf(stderr, "[lr1121] TX fail irq=0x%08X errs=0x%04X\n", irq, getErrors()); - if (irq & IRQ_LBD) std::fprintf(stderr, "[lr1121] LBD triggered: reduce TX power or use LP PA\n"); + + const auto deadline = std::chrono::steady_clock::now() + std::chrono::milliseconds(TX_TIMEOUT_MS + TX_POLL_GUARD_MS); + while (std::chrono::steady_clock::now() < deadline) { + if (getGpioLine(dio9_fd_) == 0) { + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + continue; } + const uint32_t irq = getIrq(); + if (irq & IRQ_TX_DONE) { + (void)clearIrq(); + (void)setIrqMask(0, 0); + return true; + } + if (irq & IRQ_TIMEOUT) { + if (cfg_.verbose) std::fprintf(stderr, "[lr1121] TX timeout irq=0x%08X errs=0x%04X\n", irq, getErrors()); + (void)clearIrq(); + (void)setIrqMask(0, 0); + return false; + } + if (irq & IRQ_LBD) { + (void)clearIrq(); + (void)setIrqMask(0, 0); + if (pa_sel == 0x01) { + pa_sel = 0x00; + if (tx_dbm > 10) tx_dbm = 10; + } else if (tx_dbm > MIN_TX_DBM_FALLBACK) { + tx_dbm = static_cast(tx_dbm - 2); + } else { + if (cfg_.verbose) std::fprintf(stderr, "[lr1121] TX fail: LBD at minimum power (%d dBm)\n", static_cast(tx_dbm)); + return false; + } + if (cfg_.verbose) { + std::fprintf(stderr, "[lr1121] LBD retry with %s PA %d dBm\n", + pa_sel ? "HP" : "LP", static_cast(tx_dbm)); + } + break; + } + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + } + + if (std::chrono::steady_clock::now() >= deadline) { + if (cfg_.verbose) std::fprintf(stderr, "[lr1121] TX poll timeout errs=0x%04X\n", getErrors()); (void)clearIrq(); (void)setIrqMask(0, 0); return false; } - std::this_thread::sleep_for(std::chrono::milliseconds(1)); } - - if (cfg_.verbose) std::fprintf(stderr, "[lr1121] TX poll timeout errs=0x%04X\n", getErrors()); - (void)clearIrq(); - (void)setIrqMask(0, 0); return false; }