diff --git a/chip_test_example/lora_rx.cpp b/chip_test_example/lora_rx.cpp index 9073eff..bbe5240 100644 --- a/chip_test_example/lora_rx.cpp +++ b/chip_test_example/lora_rx.cpp @@ -5,31 +5,22 @@ static void applyPaPreset(lr1121::Config &cfg, bool hp_mode) { - cfg.pa_sel = hp_mode ? 0x01 : 0x00; + cfg.pa_sel = hp_mode ? lr1121::PA_HP : lr1121::PA_LP; 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], "-v") == 0) verbose = true; + else if (std::strcmp(argv[i], "--reset") == 0) do_reset = true; + else if (std::strcmp(argv[i], "--hp") == 0) hp_mode = true; + else if (std::strcmp(argv[i], "--lp") == 0) hp_mode = false; + else { + std::fprintf(stderr, "Unknown option: %s\n" + "Usage: sudo ./lora_rx [-v] [--reset] [--lp|--hp]\n", argv[i]); + return false; } - 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; } @@ -37,31 +28,35 @@ static bool parseArgs(int argc, char **argv, bool &verbose, bool &do_reset, bool int main(int argc, char **argv) { lr1121::Config cfg; - 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 verbose = false; bool do_reset = false; - bool hp_mode = false; + bool hp_mode = false; - if (!parseArgs(argc, argv, cfg.verbose, do_reset, hp_mode)) return 2; + if (!parseArgs(argc, argv, 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]" : ""); + cfg.freq_hz, rx_timeout_ms, + cfg.busy_gpio, cfg.reset_gpio, cfg.dio9_gpio, + verbose ? " [v]" : ""); lr1121::Radio radio; if (!radio.begin(cfg)) { - std::fprintf(stderr, "ERROR: radio init failed\n"); + std::fprintf(stderr, "ERROR: radio init failed: %s\n", + lr1121::Radio::errorString(radio.lastError())); return 1; } + if (verbose) { + const auto v = radio.chipVersion(); + std::printf("[lr1121] hw=0x%02X type=0x%02X fw=0x%02X%02X vbat=%.2fV\n", + v.hw, v.type, v.fw_hi, v.fw_lo, radio.vbatVolts()); + } if (do_reset) { std::puts("Applying soft settings reset..."); if (!radio.softResetSettings()) { - std::fprintf(stderr, "ERROR: soft settings reset failed\n"); + std::fprintf(stderr, "ERROR: soft settings reset failed: %s\n", + lr1121::Radio::errorString(radio.lastError())); return 1; } } @@ -74,20 +69,21 @@ int main(int argc, char **argv) for (;;) { lr1121::RxInfo info{}; - const int r = radio.receive(buf, static_cast(sizeof(buf) - 1), rx_timeout_ms, &info); - + const int r = radio.receive(buf, uint8_t(sizeof(buf) - 1), + rx_timeout_ms, &info); if (r > 0) { buf[r] = '\0'; std::printf("[%04d] %3d B rssi=%4d snr=%3d '%s'\n", ++pkt, r, info.rssi_dbm, info.snr_db, buf); - } else if (r == -1) { - ++timeout_total; - if ((timeout_total % 10u) == 0u) std::printf("timeout x%u\n", timeout_total); } else if (r == -2) { - ++crc_total; - std::printf("crc error x%u\n", crc_total); + std::printf("crc error x%u\n", ++crc_total); + } else if (radio.lastError() == lr1121::Error::RxTimeout) { + ++timeout_total; + if ((timeout_total % 10u) == 0u) + std::printf("timeout x%u\n", timeout_total); } else { - std::printf("rx code=%d\n", r); + std::printf("rx error: %s\n", + lr1121::Radio::errorString(radio.lastError())); } } } diff --git a/chip_test_example/lora_tx.cpp b/chip_test_example/lora_tx.cpp index 817afde..4956500 100644 --- a/chip_test_example/lora_tx.cpp +++ b/chip_test_example/lora_tx.cpp @@ -7,31 +7,22 @@ static void applyPaPreset(lr1121::Config &cfg, bool hp_mode) { - cfg.pa_sel = hp_mode ? 0x01 : 0x00; + cfg.pa_sel = hp_mode ? lr1121::PA_HP : lr1121::PA_LP; 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], "-v") == 0) verbose = true; + else if (std::strcmp(argv[i], "--reset") == 0) do_reset = true; + else if (std::strcmp(argv[i], "--hp") == 0) hp_mode = true; + else if (std::strcmp(argv[i], "--lp") == 0) hp_mode = false; + else { + std::fprintf(stderr, "Unknown option: %s\n" + "Usage: sudo ./lora_tx [-v] [--reset] [--lp|--hp]\n", argv[i]); + return false; } - 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; } @@ -39,32 +30,34 @@ static bool parseArgs(int argc, char **argv, bool &verbose, bool &do_reset, bool int main(int argc, char **argv) { lr1121::Config cfg; - 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 verbose = false; bool do_reset = false; - bool hp_mode = false; + bool hp_mode = false; - if (!parseArgs(argc, argv, cfg.verbose, do_reset, hp_mode)) return 2; + if (!parseArgs(argc, argv, verbose, do_reset, hp_mode)) return 2; applyPaPreset(cfg, hp_mode); 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]" : ""); + cfg.freq_hz, cfg.pa_sel == lr1121::PA_HP ? "HP" : "LP", + int(cfg.tx_dbm), cfg.busy_gpio, cfg.reset_gpio, cfg.dio9_gpio, + verbose ? " [v]" : ""); lr1121::Radio radio; if (!radio.begin(cfg)) { - std::fprintf(stderr, "ERROR: radio init failed\n"); + std::fprintf(stderr, "ERROR: radio init failed: %s\n", + lr1121::Radio::errorString(radio.lastError())); return 1; } + if (verbose) { + const auto v = radio.chipVersion(); + std::printf("[lr1121] hw=0x%02X type=0x%02X fw=0x%02X%02X vbat=%.2fV\n", + v.hw, v.type, v.fw_hi, v.fw_lo, radio.vbatVolts()); + } if (do_reset) { std::puts("Applying soft settings reset..."); if (!radio.softResetSettings()) { - std::fprintf(stderr, "ERROR: soft settings reset failed\n"); + std::fprintf(stderr, "ERROR: soft settings reset failed: %s\n", + lr1121::Radio::errorString(radio.lastError())); return 1; } } @@ -73,8 +66,14 @@ int main(int argc, char **argv) for (int n = 0; ; ++n) { char msg[32]; 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"); + const bool ok = radio.send(reinterpret_cast(msg), + uint8_t(len)); + if (ok) { + std::printf("[%04d] %s: OK\n", n, msg); + } else { + std::printf("[%04d] %s: FAIL (%s)\n", n, msg, + lr1121::Radio::errorString(radio.lastError())); + } std::this_thread::sleep_for(std::chrono::seconds(1)); } } diff --git a/chip_test_example/lr1121_malnus.hpp b/chip_test_example/lr1121_malnus.hpp index 3ae1420..6d5a1e7 100644 --- a/chip_test_example/lr1121_malnus.hpp +++ b/chip_test_example/lr1121_malnus.hpp @@ -1,21 +1,18 @@ -// lr1121_malnus.hpp — minimal LR1121 LoRa driver for this board +// lr1121_malnus.hpp — minimal LR1121 LoRa driver (Linux, header-only). // -// Board wiring: -// SPI : /dev/spidev0.0 (NSS GPIO8) -// BUSY : GPIO24 -// RESET: GPIO25 -// RF switch controlled by LR1121 DIO5/DIO6 +// Defaults: SPI /dev/spidev0.0, BUSY GPIO24, RESET GPIO25, DIO9 GPIO4. +// RF switch is driven by the chip via DIO5/DIO6. // -// Command set references: -// - Semtech LR1121 user manual rev 1.2 -// - Semtech SWDR001 -// - RadioLib LR11x0 behavior +// The driver never logs — failures return false (or a negative code); read +// lastError()/errorString() for the reason. Callers decide what to print. +// +// Refs: Semtech LR1121 user manual rev 1.2, SWDR001, RadioLib LR11x0. #pragma once #include #include -#include #include +#include #include #include @@ -26,59 +23,53 @@ namespace lr1121 { -constexpr uint16_t OC_GET_VERSION = 0x0101; -constexpr uint16_t OC_WRITE_BUF8 = 0x0109; -constexpr uint16_t OC_READ_BUF8 = 0x010A; -constexpr uint16_t OC_CLEAR_RXBUF = 0x010B; -constexpr uint16_t OC_GET_ERRORS = 0x010D; -constexpr uint16_t OC_CLEAR_ERRORS = 0x010E; -constexpr uint16_t OC_CALIBRATE = 0x010F; -constexpr uint16_t OC_SET_REGMODE = 0x0110; -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; +// Opcodes (Semtech LR1121 user manual rev 1.2). +constexpr uint16_t + OC_GET_VERSION = 0x0101, OC_WRITE_BUF8 = 0x0109, + OC_READ_BUF8 = 0x010A, OC_CLEAR_RXBUF = 0x010B, + OC_GET_ERRORS = 0x010D, OC_CLEAR_ERRORS = 0x010E, + OC_CALIBRATE = 0x010F, OC_SET_REGMODE = 0x0110, + OC_CALIBRATE_IMG = 0x0111, OC_SET_DIO_AS_RFSW = 0x0112, + OC_SET_DIOIRQ = 0x0113, OC_CLEAR_IRQ = 0x0114, + OC_GET_IRQ = 0x0115, OC_REBOOT = 0x0118, + OC_GET_VBAT = 0x0119, OC_SET_STANDBY = 0x011C, + OC_GET_RXBUF_STA = 0x0203, OC_GET_PKT_STATUS = 0x0204, + OC_SET_LORA_NET = 0x0208, OC_SET_RX = 0x0209, + OC_SET_TX = 0x020A, OC_SET_RF_FREQ = 0x020B, + OC_SET_PKT_TYPE = 0x020E, OC_SET_MOD_PARAM = 0x020F, + OC_SET_PKT_PARAM = 0x0210, OC_SET_TX_PARAMS = 0x0211, + OC_SET_PKT_ADRS = 0x0212, OC_SET_FALLBACK_MODE = 0x0213, + OC_SET_PA_CFG = 0x0215; -constexpr uint16_t OC_GET_RXBUF_STA = 0x0203; -constexpr uint16_t OC_GET_PKT_STATUS = 0x0204; -constexpr uint16_t OC_SET_LORA_NET = 0x0208; -constexpr uint16_t OC_SET_RX = 0x0209; -constexpr uint16_t OC_SET_TX = 0x020A; -constexpr uint16_t OC_SET_RF_FREQ = 0x020B; -constexpr uint16_t OC_SET_PKT_TYPE = 0x020E; -constexpr uint16_t OC_SET_MOD_PARAM = 0x020F; -constexpr uint16_t OC_SET_PKT_PARAM = 0x0210; -constexpr uint16_t OC_SET_TX_PARAMS = 0x0211; -constexpr uint16_t OC_SET_PKT_ADRS = 0x0212; -constexpr uint16_t OC_SET_FALLBACK_MODE = 0x0213; -constexpr uint16_t OC_SET_PA_CFG = 0x0215; +constexpr uint8_t CALIB_ALL = 0x3F; +constexpr uint32_t IRQ_TX_DONE = 1u << 2, IRQ_RX_DONE = 1u << 3, + IRQ_CRC_ERR = 1u << 7, IRQ_TIMEOUT = 1u << 10, + IRQ_LBD = 1u << 21, IRQ_ALL = 0x1BF80FFCu; -constexpr uint8_t CALIB_ALL = 0x3F; +constexpr uint32_t FREQ_433 = 433'050'000u, + FREQ_868 = 868'000'000u, + FREQ_2400 = 2'403'000'000u; -constexpr uint32_t IRQ_TX_DONE = (1u << 2); -constexpr uint32_t IRQ_RX_DONE = (1u << 3); -constexpr uint32_t IRQ_CRC_ERR = (1u << 7); -constexpr uint32_t IRQ_TIMEOUT = (1u << 10); -constexpr uint32_t IRQ_LBD = (1u << 21); -constexpr uint32_t IRQ_ALL = 0x1BF80FFCu; +constexpr uint8_t PA_LP = 0x00, PA_HP = 0x01, PKT_TYPE_LORA = 0x02; +constexpr uint8_t LORA_HEADER_EXPLICIT = 0x00, LORA_CRC_ON = 0x01, LORA_IQ_STD = 0x00; +constexpr uint16_t LORA_PREAMBLE_LEN = 8; +constexpr uint8_t TX_RAMP_48US = 0x02; +constexpr uint32_t TX_TIMEOUT_MS = 3000, TX_POLL_GUARD_MS = 500; +constexpr int8_t MIN_TX_DBM_FALLBACK = 2; -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; +enum class Error : uint8_t { + Ok = 0, + NotReady, + SpiOpen, + SpiIo, + GpioOpen, + BusyTimeout, + BadChip, + TxTimeout, + TxLbd, + RxTimeout, + RxCrc, +}; struct Config { const char *spi_path = "/dev/spidev0.0"; @@ -86,17 +77,16 @@ struct Config { const char *gpio_chip = "/dev/gpiochip0"; unsigned busy_gpio = 24; unsigned reset_gpio = 25; - unsigned dio9_gpio = 4; // IRQ line from LR1121 + unsigned dio9_gpio = 4; uint32_t freq_hz = FREQ_433; - uint8_t sf = 0x07; - uint8_t bw = 0x04; - uint8_t cr = 0x01; + uint8_t sf = 7; + uint8_t bw = 0x04; // 125 kHz + uint8_t cr = 0x01; // 4/5 int8_t tx_dbm = 10; - uint8_t pa_sel = 0x00; // LP default to avoid LBD on weak VBAT rails + uint8_t pa_sel = PA_LP; // LP avoids LBD on weak VBAT uint8_t pa_supply = 0x00; bool use_dcdc = false; bool lora_wan = false; - bool verbose = false; }; struct RxInfo { @@ -106,10 +96,10 @@ struct RxInfo { }; struct ChipVersion { - uint8_t hw; - uint8_t type; - uint8_t fw_hi; - uint8_t fw_lo; + uint8_t hw = 0; + uint8_t type = 0; + uint8_t fw_hi = 0; + uint8_t fw_lo = 0; }; class Radio { @@ -119,44 +109,68 @@ public: bool softResetSettings(); void end(); + // Returns true on TX_DONE, false otherwise; check lastError() for reason. bool send(const uint8_t *data, uint8_t n); - int receive(uint8_t *buf, uint8_t cap, uint32_t timeout_ms, RxInfo *rx_info = nullptr); - ChipVersion getVersion(); + // Returns bytes received (>=0), -1 on timeout/error, -2 on CRC error. + int receive(uint8_t *buf, uint8_t cap, uint32_t timeout_ms, + RxInfo *rx_info = nullptr); + + // Runtime tuning. Call from standby (the driver leaves the chip in + // standby_xosc after begin()/send()/receive()). + bool setFrequency(uint32_t hz); + bool setTxPower(int8_t dbm, uint8_t pa_sel = PA_LP); + bool setModulation(uint8_t sf, uint8_t bw, uint8_t cr); + + // Diagnostics — read on demand, never side-effects. + ChipVersion chipVersion(); + uint16_t chipErrors(); + uint8_t vbatRaw(); + float vbatVolts() { return vbatRaw() / 34.0f; } + const Config &config() const { return cfg_; } + Error lastError() const { return last_err_; } + static const char *errorString(Error e); + void reboot(bool stay_in_bootloader = false); private: Config cfg_{}; - int spi_fd_ = -1; - int busy_fd_ = -1; - int reset_fd_ = -1; - int dio9_fd_ = -1; + int spi_fd_ = -1, busy_fd_ = -1, reset_fd_ = -1, dio9_fd_ = -1; + Error last_err_ = Error::NotReady; - bool openSpi(const Config &cfg); + bool fail(Error e) { last_err_ = e; return false; } + + bool openSpi(); bool openGpio(unsigned line, bool out, int &fd_out); - int getGpioLine(int fd); - void setGpioLine(int fd, int value); + int readGpio(int fd); + void writeGpio(int fd, int value); void hardReset(); bool waitBusy(int timeout_ms = 1000); bool spiTransfer(uint8_t *buf, size_t len); bool wcmd(uint16_t op, const uint8_t *params = nullptr, size_t n = 0); - bool rcmd(uint16_t op, const uint8_t *params, size_t np, uint8_t *out, size_t nr); + bool wcmd(uint16_t op, std::initializer_list il); + bool rcmd(uint16_t op, const uint8_t *params, size_t np, + uint8_t *out, size_t nr); - bool setStandbyXosc(); - bool setStandbyRc(); + bool setStandbyXosc() { return wcmd(OC_SET_STANDBY, {0x01}); } + bool setStandbyRc() { return wcmd(OC_SET_STANDBY, {0x00}); } bool setIrqMask(uint32_t irq1, uint32_t irq2 = 0); bool clearIrq(uint32_t mask = IRQ_ALL); uint32_t getIrq(); - uint16_t getErrors(); bool applyRadioSettings(); + bool writePktParam(uint8_t payload_len); + bool writePaCfg(uint8_t pa_sel, int8_t dbm); - static void imgCalFreqs(uint32_t hz, uint8_t &f1, uint8_t &f2); - static uint8_t computeLdRo(uint8_t sf, uint8_t bw); + static void imgCalFreqs(uint32_t hz, uint8_t &f1, uint8_t &f2); + static uint8_t computeLdRo(uint8_t sf, uint8_t bw); static uint32_t timeoutMsToRtcSteps(uint32_t ms); - static void computePaConfig(uint8_t pa_sel, int8_t dbm, uint8_t &duty, uint8_t &hp_max); + static void computePaConfig(uint8_t pa_sel, int8_t dbm, + uint8_t &duty, uint8_t &hp_max); }; +// ---------- SPI / command primitives ---------- + inline bool Radio::spiTransfer(uint8_t *buf, size_t len) { spi_ioc_transfer tr{}; @@ -171,94 +185,74 @@ 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; + if (!waitBusy()) return fail(Error::BusyTimeout); uint8_t cmd[258]{}; - cmd[0] = static_cast(op >> 8); - cmd[1] = static_cast(op & 0xFF); + cmd[0] = uint8_t(op >> 8); + cmd[1] = uint8_t(op); if (params && n) std::memcpy(cmd + 2, params, n); - if (!spiTransfer(cmd, 2 + n)) return false; - return waitBusy(); + if (!spiTransfer(cmd, 2 + n)) return fail(Error::SpiIo); + if (!waitBusy()) return fail(Error::BusyTimeout); + return true; } -inline bool Radio::rcmd(uint16_t op, const uint8_t *params, size_t np, uint8_t *out, size_t nr) +inline bool Radio::wcmd(uint16_t op, std::initializer_list il) +{ + return wcmd(op, il.begin(), il.size()); +} + +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; + if (!waitBusy()) return fail(Error::BusyTimeout); uint8_t cmd[32]{}; - cmd[0] = static_cast(op >> 8); - cmd[1] = static_cast(op & 0xFF); + cmd[0] = uint8_t(op >> 8); + cmd[1] = uint8_t(op); if (params && np) std::memcpy(cmd + 2, params, np); - if (!spiTransfer(cmd, 2 + np)) return false; - - if (!waitBusy()) return false; + if (!spiTransfer(cmd, 2 + np)) return fail(Error::SpiIo); + if (!waitBusy()) return fail(Error::BusyTimeout); uint8_t rsp[260]{}; - if (!spiTransfer(rsp, nr + 1)) return false; + if (!spiTransfer(rsp, nr + 1)) return fail(Error::SpiIo); std::memcpy(out, rsp + 1, nr); return true; } inline bool Radio::setIrqMask(uint32_t irq1, uint32_t irq2) { - const uint8_t p[8] = { - static_cast(irq1 >> 24), static_cast(irq1 >> 16), - static_cast(irq1 >> 8), static_cast(irq1), - static_cast(irq2 >> 24), static_cast(irq2 >> 16), - static_cast(irq2 >> 8), static_cast(irq2), - }; - return wcmd(OC_SET_DIOIRQ, p, sizeof(p)); + return wcmd(OC_SET_DIOIRQ, { + uint8_t(irq1 >> 24), uint8_t(irq1 >> 16), uint8_t(irq1 >> 8), uint8_t(irq1), + uint8_t(irq2 >> 24), uint8_t(irq2 >> 16), uint8_t(irq2 >> 8), uint8_t(irq2), + }); } inline bool Radio::clearIrq(uint32_t mask) { - const uint8_t p[4] = { - static_cast(mask >> 24), static_cast(mask >> 16), - static_cast(mask >> 8), static_cast(mask), - }; - return wcmd(OC_CLEAR_IRQ, p, sizeof(p)); + return wcmd(OC_CLEAR_IRQ, { + uint8_t(mask >> 24), uint8_t(mask >> 16), uint8_t(mask >> 8), uint8_t(mask), + }); } inline uint32_t Radio::getIrq() { 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]); + return (uint32_t(b[0]) << 24) | (uint32_t(b[1]) << 16) | + (uint32_t(b[2]) << 8) | uint32_t(b[3]); } -inline uint16_t Radio::getErrors() -{ - uint8_t e[2]{}; - if (!rcmd(OC_GET_ERRORS, nullptr, 0, e, sizeof(e))) return 0xFFFF; - return static_cast((e[0] << 8) | e[1]); -} +// ---------- SPI / GPIO open ---------- -inline bool Radio::setStandbyRc() +inline bool Radio::openSpi() { - const uint8_t p[] = {0x00}; - return wcmd(OC_SET_STANDBY, p, sizeof(p)); -} + spi_fd_ = ::open(cfg_.spi_path, O_RDWR); + if (spi_fd_ < 0) return fail(Error::SpiOpen); -inline bool Radio::setStandbyXosc() -{ - const uint8_t p[] = {0x01}; - return wcmd(OC_SET_STANDBY, p, sizeof(p)); -} - -inline bool Radio::openSpi(const Config &cfg) -{ - spi_fd_ = ::open(cfg.spi_path, O_RDWR); - if (spi_fd_ < 0) return false; - - uint8_t mode = SPI_MODE_0; - uint8_t bits = 8; - 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; + uint8_t mode = SPI_MODE_0, bits = 8; + 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 fail(Error::SpiOpen); } return true; } @@ -266,30 +260,30 @@ inline bool Radio::openSpi(const Config &cfg) inline bool Radio::openGpio(unsigned line, bool out, int &fd_out) { int chip = ::open(cfg_.gpio_chip, O_RDWR); - if (chip < 0) return false; + if (chip < 0) return fail(Error::GpioOpen); gpiohandle_request req{}; - req.lineoffsets[0] = line; - req.lines = 1; - req.flags = out ? GPIOHANDLE_REQUEST_OUTPUT : GPIOHANDLE_REQUEST_INPUT; + req.lineoffsets[0] = line; + req.lines = 1; + req.flags = out ? GPIOHANDLE_REQUEST_OUTPUT : GPIOHANDLE_REQUEST_INPUT; req.default_values[0] = out ? 1 : 0; std::strncpy(req.consumer_label, "lr1121", sizeof(req.consumer_label) - 1); + const int rc = ioctl(chip, GPIO_GET_LINEHANDLE_IOCTL, &req); ::close(chip); - if (rc < 0) return false; - + if (rc < 0) return fail(Error::GpioOpen); fd_out = req.fd; return true; } -inline int Radio::getGpioLine(int fd) +inline int Radio::readGpio(int fd) { gpiohandle_data d{}; if (ioctl(fd, GPIOHANDLE_GET_LINE_VALUES_IOCTL, &d) < 0) return 1; return d.values[0]; } -inline void Radio::setGpioLine(int fd, int value) +inline void Radio::writeGpio(int fd, int value) { gpiohandle_data d{}; d.values[0] = value; @@ -298,9 +292,9 @@ inline void Radio::setGpioLine(int fd, int value) inline void Radio::hardReset() { - setGpioLine(reset_fd_, 0); + writeGpio(reset_fd_, 0); std::this_thread::sleep_for(std::chrono::milliseconds(1)); - setGpioLine(reset_fd_, 1); + writeGpio(reset_fd_, 1); std::this_thread::sleep_for(std::chrono::milliseconds(10)); } @@ -308,49 +302,45 @@ inline bool Radio::waitBusy(int timeout_ms) { const int loops = timeout_ms * 20; for (int i = 0; i < loops; ++i) { - if (getGpioLine(busy_fd_) == 0) return true; + if (readGpio(busy_fd_) == 0) return true; std::this_thread::sleep_for(std::chrono::microseconds(50)); } return false; } +// ---------- Math helpers ---------- + inline void Radio::imgCalFreqs(uint32_t hz, uint8_t &f1, uint8_t &f2) { const uint32_t mhz = hz / 1'000'000u; - uint32_t lo = 430, hi = 440; - if (mhz < 446) { - lo = 430; hi = 440; - } else if (mhz < 740) { - lo = 470; hi = 510; - } else if (mhz < 890) { - lo = 860; hi = 876; - } else if (mhz < 2000) { - lo = 902; hi = 928; - } else { - lo = 2400; hi = 2500; - } - f1 = static_cast(lo / 4); - f2 = static_cast((hi + 3) / 4); + uint32_t lo, hi; + if (mhz < 446) { lo = 430; hi = 440; } + else if (mhz < 740) { lo = 470; hi = 510; } + else if (mhz < 890) { lo = 860; hi = 876; } + else if (mhz < 2000) { lo = 902; hi = 928; } + else { lo = 2400; hi = 2500; } + f1 = uint8_t(lo / 4); + f2 = uint8_t((hi + 3) / 4); } inline uint8_t Radio::computeLdRo(uint8_t sf, uint8_t bw) { static const uint32_t bw_hz[] = {0, 15625, 31250, 62500, 125000, 250000, 500000}; const uint32_t bw_val = (bw < 7) ? bw_hz[bw] : 125000; - const uint32_t sym_ms = (1000u << sf) / bw_val; - return (sym_ms > 16) ? 1 : 0; + return ((1000u << sf) / bw_val) > 16 ? 1 : 0; } inline uint32_t Radio::timeoutMsToRtcSteps(uint32_t ms) { if (ms == 0) return 0x00FFFFFFu; - const uint64_t steps = (static_cast(ms) * 32768u) / 1000u; - return (steps > 0x00FFFFFFu) ? 0x00FFFFFFu : static_cast(steps); + const uint64_t steps = (uint64_t(ms) * 32768u) / 1000u; + return steps > 0x00FFFFFFu ? 0x00FFFFFFu : uint32_t(steps); } -inline void Radio::computePaConfig(uint8_t pa_sel, int8_t dbm, uint8_t &duty, uint8_t &hp_max) +inline void Radio::computePaConfig(uint8_t pa_sel, int8_t dbm, + uint8_t &duty, uint8_t &hp_max) { - if (pa_sel == 0x01) { + if (pa_sel == PA_HP) { if (dbm >= 22) { duty = 4; hp_max = 7; } else if (dbm >= 20) { duty = 3; hp_max = 5; } else if (dbm >= 17) { duty = 2; hp_max = 3; } @@ -361,157 +351,201 @@ inline void Radio::computePaConfig(uint8_t pa_sel, int8_t dbm, uint8_t &duty, ui hp_max = 0; if (dbm >= 14) duty = 7; else if (dbm >= 10) duty = 4; - else if (dbm >= 0) duty = 2; + else if (dbm >= 0) duty = 2; else duty = 0; } } -inline ChipVersion Radio::getVersion() +inline bool Radio::writePktParam(uint8_t payload_len) +{ + return wcmd(OC_SET_PKT_PARAM, { + uint8_t(LORA_PREAMBLE_LEN >> 8), + uint8_t(LORA_PREAMBLE_LEN), + LORA_HEADER_EXPLICIT, + payload_len, + LORA_CRC_ON, + LORA_IQ_STD, + }); +} + +inline bool Radio::writePaCfg(uint8_t pa_sel, int8_t dbm) +{ + uint8_t duty = 0, hp_max = 0; + computePaConfig(pa_sel, dbm, duty, hp_max); + return wcmd(OC_SET_PA_CFG, {pa_sel, cfg_.pa_supply, duty, hp_max}); +} + +// ---------- Diagnostics ---------- + +inline ChipVersion Radio::chipVersion() { uint8_t v[4]{}; (void)rcmd(OC_GET_VERSION, nullptr, 0, v, sizeof(v)); return {v[0], v[1], v[2], v[3]}; } +inline uint16_t Radio::chipErrors() +{ + uint8_t e[2]{}; + if (!rcmd(OC_GET_ERRORS, nullptr, 0, e, sizeof(e))) return 0xFFFF; + return uint16_t((e[0] << 8) | e[1]); +} + +inline uint8_t Radio::vbatRaw() +{ + uint8_t raw = 0; + (void)rcmd(OC_GET_VBAT, nullptr, 0, &raw, 1); + return raw; +} + +inline const char *Radio::errorString(Error e) +{ + static constexpr const char *names[] = { + "ok", "not initialised", "spi open failed", "spi io failed", + "gpio open failed", "busy line never released", "chip is not LR1121", + "tx timeout", "tx low-battery detect", "rx timeout", "rx crc error", + }; + const size_t i = size_t(e); + return i < (sizeof(names) / sizeof(*names)) ? names[i] : "unknown"; +} + +// ---------- Lifecycle ---------- + inline bool Radio::beginRaw(const Config &cfg) { end(); cfg_ = cfg; - if (!openSpi(cfg_)) 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; } + last_err_ = Error::Ok; + if (!openSpi()) 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(); - if (!waitBusy(500)) { end(); return false; } + if (!waitBusy(500)) { last_err_ = Error::BusyTimeout; end(); return false; } return true; } inline bool Radio::begin(const Config &cfg) { if (!beginRaw(cfg)) return false; - - const ChipVersion v = getVersion(); - if (cfg_.verbose) { - std::fprintf(stderr, "[lr1121] hw=0x%02X type=0x%02X fw=0x%02X%02X\n", v.hw, v.type, v.fw_hi, v.fw_lo); - uint8_t raw = 0; - if (rcmd(OC_GET_VBAT, nullptr, 0, &raw, 1)) { - const float vb = raw / 34.0f; - std::fprintf(stderr, "[lr1121] VBAT raw=0x%02X (~%.2fV)\n", raw, vb); - } - } - if (v.type != 0x03) { end(); return false; } + const ChipVersion v = chipVersion(); + if (v.type != 0x03) { last_err_ = Error::BadChip; end(); return false; } return applyRadioSettings(); } 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); + if (!setStandbyRc()) { end(); return false; } + if (!wcmd(OC_SET_FALLBACK_MODE, {0x01})) { end(); return false; } + if (!clearIrq()) { end(); return false; } + if (!setIrqMask(0, 0)) { end(); return false; } + if (!wcmd(OC_CALIBRATE, {CALIB_ALL})) { end(); return false; } + (void)wcmd(OC_CLEAR_ERRORS); - { 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))) { 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))) { end(); 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))) { end(); 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; } } + if (!wcmd(OC_SET_REGMODE, {uint8_t(cfg_.use_dcdc)})) { end(); return false; } + if (!wcmd(OC_SET_PKT_TYPE, {PKT_TYPE_LORA})) { end(); return false; } + if (!setFrequency(cfg_.freq_hz)) { end(); return false; } + if (!wcmd(OC_SET_PKT_ADRS, {0x00, 0x00})) { end(); return false; } + if (!setModulation(cfg_.sf, cfg_.bw, cfg_.cr)) { end(); return false; } + if (!writePktParam(0xFF)) { end(); return false; } + if (!setTxPower(cfg_.tx_dbm, cfg_.pa_sel)) { end(); return false; } + if (!wcmd(OC_SET_LORA_NET, {uint8_t(cfg_.lora_wan)})) { 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))) { 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)); - } + // DIO5/DIO6 RF switch: STBY(0,0), RX(1,0), TX(0,1), TX_HP(0,1) + if (!wcmd(OC_SET_DIO_AS_RFSW, {0x03, 0, 0x01, 0x02, 0x02, 0, 0, 0})) { + end(); 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; } } + if (!setStandbyXosc()) { end(); return false; } + return true; +} - // 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))) { end(); return false; } } +inline bool Radio::setFrequency(uint32_t hz) +{ + if (!wcmd(OC_SET_RF_FREQ, { + uint8_t(hz >> 24), uint8_t(hz >> 16), + uint8_t(hz >> 8), uint8_t(hz), + })) return false; + uint8_t f1, f2; + imgCalFreqs(hz, f1, f2); + if (!wcmd(OC_CALIBRATE_IMG, {f1, f2})) return false; + cfg_.freq_hz = hz; + return true; +} - 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); +inline bool Radio::setTxPower(int8_t dbm, uint8_t pa_sel) +{ + if (!writePaCfg(pa_sel, dbm)) return false; + if (!wcmd(OC_SET_TX_PARAMS, {uint8_t(dbm), TX_RAMP_48US})) return false; + cfg_.pa_sel = pa_sel; + cfg_.tx_dbm = dbm; + return true; +} + +inline bool Radio::setModulation(uint8_t sf, uint8_t bw, uint8_t cr) +{ + if (!wcmd(OC_SET_MOD_PARAM, {sf, bw, cr, computeLdRo(sf, bw)})) return false; + cfg_.sf = sf; + cfg_.bw = bw; + cfg_.cr = cr; return true; } inline bool Radio::softResetSettings() { - if (spi_fd_ < 0 || busy_fd_ < 0 || reset_fd_ < 0 || dio9_fd_ < 0) return false; + if (spi_fd_ < 0 || busy_fd_ < 0 || reset_fd_ < 0 || dio9_fd_ < 0) + return fail(Error::NotReady); if (!setStandbyRc()) return false; - (void)wcmd(OC_CLEAR_RXBUF, nullptr, 0); + (void)wcmd(OC_CLEAR_RXBUF); (void)clearIrq(); return applyRadioSettings(); } inline void Radio::reboot(bool stay_in_bootloader) { - const uint8_t p[] = { static_cast(stay_in_bootloader ? 0x01 : 0x00) }; - (void)wcmd(OC_REBOOT, p, sizeof(p)); + (void)wcmd(OC_REBOOT, {uint8_t(stay_in_bootloader ? 0x01 : 0x00)}); std::this_thread::sleep_for(std::chrono::milliseconds(300)); } inline void Radio::end() { - if (spi_fd_ >= 0) { ::close(spi_fd_); spi_fd_ = -1; } - if (busy_fd_ >= 0) { ::close(busy_fd_); busy_fd_ = -1; } + if (spi_fd_ >= 0) { ::close(spi_fd_); spi_fd_ = -1; } + if (busy_fd_ >= 0) { ::close(busy_fd_); busy_fd_ = -1; } if (reset_fd_ >= 0) { ::close(reset_fd_); reset_fd_ = -1; } - if (dio9_fd_ >= 0) { ::close(dio9_fd_); dio9_fd_ = -1; } + if (dio9_fd_ >= 0) { ::close(dio9_fd_); dio9_fd_ = -1; } } +// ---------- TX / RX ---------- + inline bool Radio::send(const uint8_t *data, uint8_t n) { - if (n == 0) return false; + if (n == 0) return fail(Error::TxTimeout); uint8_t pa_sel = cfg_.pa_sel; - int8_t tx_dbm = cfg_.tx_dbm; + int8_t tx_dbm = cfg_.tx_dbm; 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; + if (!clearIrq()) return false; + const uint32_t mask = IRQ_TX_DONE | IRQ_TIMEOUT | IRQ_LBD; + if (!setIrqMask(mask, mask)) return false; - { - 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 uint8_t p[] = {static_cast(tx_dbm), TX_RAMP_48US}; if (!wcmd(OC_SET_TX_PARAMS, p, sizeof(p))) return false; } + if (!writePaCfg(pa_sel, tx_dbm)) return false; + if (!wcmd(OC_SET_TX_PARAMS, {uint8_t(tx_dbm), TX_RAMP_48US})) 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 (!writePktParam(n)) return false; - const auto deadline = std::chrono::steady_clock::now() + std::chrono::milliseconds(TX_TIMEOUT_MS + TX_POLL_GUARD_MS); + const uint32_t tx_steps = timeoutMsToRtcSteps(TX_TIMEOUT_MS); + if (!wcmd(OC_SET_TX, { + uint8_t(tx_steps >> 16), + uint8_t(tx_steps >> 8), + uint8_t(tx_steps), + })) return false; + + const auto deadline = std::chrono::steady_clock::now() + + std::chrono::milliseconds(TX_TIMEOUT_MS + TX_POLL_GUARD_MS); + + bool lbd_retry = false; while (std::chrono::steady_clock::now() < deadline) { - if (getGpioLine(dio9_fd_) == 0) { + if (readGpio(dio9_fd_) == 0) { std::this_thread::sleep_for(std::chrono::milliseconds(1)); continue; } @@ -519,69 +553,66 @@ inline bool Radio::send(const uint8_t *data, uint8_t n) if (irq & IRQ_TX_DONE) { (void)clearIrq(); (void)setIrqMask(0, 0); + last_err_ = Error::Ok; 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; + return fail(Error::TxTimeout); } if (irq & IRQ_LBD) { (void)clearIrq(); (void)setIrqMask(0, 0); - if (pa_sel == 0x01) { - pa_sel = 0x00; + if (pa_sel == PA_HP) { + pa_sel = PA_LP; if (tx_dbm > 10) tx_dbm = 10; } else if (tx_dbm > MIN_TX_DBM_FALLBACK) { - tx_dbm = static_cast(tx_dbm - 2); + tx_dbm = int8_t(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)); + return fail(Error::TxLbd); } + lbd_retry = true; 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()); + if (!lbd_retry) { (void)clearIrq(); (void)setIrqMask(0, 0); - return false; + return fail(Error::TxTimeout); } } - return false; + return fail(Error::TxLbd); } -inline int Radio::receive(uint8_t *buf, uint8_t cap, uint32_t timeout_ms, RxInfo *rx_info) +inline int Radio::receive(uint8_t *buf, uint8_t cap, uint32_t timeout_ms, + RxInfo *rx_info) { if (!setStandbyXosc()) return -1; - if (!clearIrq()) return -1; - if (!setIrqMask(IRQ_RX_DONE | IRQ_CRC_ERR | IRQ_TIMEOUT, IRQ_RX_DONE | IRQ_CRC_ERR | IRQ_TIMEOUT)) return -1; + if (!clearIrq()) return -1; + const uint32_t mask = IRQ_RX_DONE | IRQ_CRC_ERR | IRQ_TIMEOUT; + if (!setIrqMask(mask, mask)) return -1; const uint32_t steps = timeoutMsToRtcSteps(timeout_ms); - { - const uint8_t p[] = { - static_cast(steps >> 16), - static_cast(steps >> 8), - static_cast(steps), - }; - if (!wcmd(OC_SET_RX, p, sizeof(p))) return -1; - } + if (!wcmd(OC_SET_RX, { + uint8_t(steps >> 16), + uint8_t(steps >> 8), + uint8_t(steps), + })) return -1; + + const auto deadline = std::chrono::steady_clock::now() + + std::chrono::milliseconds(timeout_ms == 0 ? 60000 : timeout_ms + 500); - const auto deadline = std::chrono::steady_clock::now() + std::chrono::milliseconds(timeout_ms == 0 ? 60000 : timeout_ms + 500); for (;;) { if (std::chrono::steady_clock::now() >= deadline) { (void)clearIrq(); (void)setIrqMask(0, 0); + last_err_ = Error::RxTimeout; return -1; } - if (getGpioLine(dio9_fd_) == 0) { + if (readGpio(dio9_fd_) == 0) { std::this_thread::sleep_for(std::chrono::milliseconds(1)); continue; } @@ -589,6 +620,7 @@ inline int Radio::receive(uint8_t *buf, uint8_t cap, uint32_t timeout_ms, RxInfo if (irq & IRQ_TIMEOUT) { (void)clearIrq(); (void)setIrqMask(0, 0); + last_err_ = Error::RxTimeout; return -1; } if ((irq & (IRQ_RX_DONE | IRQ_CRC_ERR)) == 0) { @@ -599,22 +631,24 @@ inline int Radio::receive(uint8_t *buf, uint8_t cap, uint32_t timeout_ms, RxInfo if (rx_info) { uint8_t p[3]{}; if (rcmd(OC_GET_PKT_STATUS, nullptr, 0, p, sizeof(p))) { - rx_info->rssi_dbm = static_cast(-(int)p[0] / 2); - rx_info->snr_db = static_cast(static_cast(p[1]) / 4); - rx_info->signal_rssi_dbm = static_cast(-(int)p[2] / 2); + rx_info->rssi_dbm = int8_t(-int(p[0]) / 2); + rx_info->snr_db = int8_t(int8_t(p[1]) / 4); + rx_info->signal_rssi_dbm = int8_t(-int(p[2]) / 2); } } uint8_t st[2]{}; if (!rcmd(OC_GET_RXBUF_STA, nullptr, 0, st, sizeof(st))) return -1; - const uint8_t len = (st[0] < cap) ? st[0] : cap; - const uint8_t p[] = {st[1], len}; - if (len > 0 && !rcmd(OC_READ_BUF8, p, sizeof(p), buf, len)) return -1; + const uint8_t len = st[0] < cap ? st[0] : cap; + const uint8_t rp[] = {st[1], len}; + if (len > 0 && !rcmd(OC_READ_BUF8, rp, sizeof(rp), buf, len)) return -1; - (void)wcmd(OC_CLEAR_RXBUF, nullptr, 0); + (void)wcmd(OC_CLEAR_RXBUF); (void)clearIrq(); (void)setIrqMask(0, 0); - return (irq & IRQ_CRC_ERR) ? -2 : static_cast(len); + if (irq & IRQ_CRC_ERR) { last_err_ = Error::RxCrc; return -2; } + last_err_ = Error::Ok; + return int(len); } }