From 8d193126316c99f48a4e5afb0bdba61f9d7b57ed Mon Sep 17 00:00:00 2001 From: crosstyan Date: Tue, 19 May 2026 10:30:34 +0800 Subject: [PATCH] feat: add shared LLCC68 Zephyr module --- CMakeLists.txt | 10 + Kconfig | 1 + drivers/llcc68/Kconfig | 27 + drivers/llcc68/llcc68_raw.c | 69 ++ dts/bindings/semtech,llcc68.yaml | 46 + include/llcc68.hpp | 295 ++++++ include/llcc68_definitions.hpp | 1010 +++++++++++++++++++ include/llcc68_raw.h | 42 + src/llcc68.cpp | 1499 +++++++++++++++++++++++++++++ src/priv_radiolib_definitions.hpp | 445 +++++++++ zephyr/module.yml | 6 + 11 files changed, 3450 insertions(+) create mode 100644 CMakeLists.txt create mode 100644 Kconfig create mode 100644 drivers/llcc68/Kconfig create mode 100644 drivers/llcc68/llcc68_raw.c create mode 100644 dts/bindings/semtech,llcc68.yaml create mode 100644 include/llcc68.hpp create mode 100644 include/llcc68_definitions.hpp create mode 100644 include/llcc68_raw.h create mode 100644 src/llcc68.cpp create mode 100644 src/priv_radiolib_definitions.hpp create mode 100644 zephyr/module.yml diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..699521d --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,10 @@ +if(CONFIG_LLCC68) + zephyr_library_named(app_driver_llcc68) + zephyr_library_sources(src/llcc68.cpp drivers/llcc68/llcc68_raw.c) + zephyr_library_include_directories(include src) + set_target_properties(app_driver_llcc68 PROPERTIES CXX_STANDARD 23 CXX_STANDARD_REQUIRED ON CXX_EXTENSIONS ON) + target_compile_options(app_driver_llcc68 PRIVATE $<$:-std=gnu++23>) + target_include_directories(app_driver_llcc68 PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include) + target_link_libraries(app_driver_llcc68 PUBLIC zephyr_interface) + add_dependencies(app_driver_llcc68 offsets_h) +endif() diff --git a/Kconfig b/Kconfig new file mode 100644 index 0000000..21e2241 --- /dev/null +++ b/Kconfig @@ -0,0 +1 @@ +rsource "drivers/llcc68/Kconfig" diff --git a/drivers/llcc68/Kconfig b/drivers/llcc68/Kconfig new file mode 100644 index 0000000..4999105 --- /dev/null +++ b/drivers/llcc68/Kconfig @@ -0,0 +1,27 @@ +DT_COMPAT_SEMTECH_LLCC68 := "semtech,llcc68" + +config LLCC68 + bool "Semtech LLCC68 LoRa Radio Driver" + depends on SPI && GPIO + default $(dt_compat_enabled,$(DT_COMPAT_SEMTECH_LLCC68)) + help + Enable the Semtech LLCC68 LoRa Radio Driver. + +config LLCC68_INIT_PRIORITY + int "LLCC68 Initialization Priority" + default 90 + help + The priority of the LLCC68 initialization. Lower numbers indicate + higher priority. + +config LLCC68_ALWAYS_USE_SX1262_HIGH_PA + bool "LLCC68 Always Use SX1262 High Power Amplifier" + default y + help + When enabled, the LLCC68/SX1262/SX1261 driver always chooses high + power amplifier settings instead of selecting them from chip version. + +module = LLCC68 +module-str = llcc68 + +source "subsys/logging/Kconfig.template.log_config" diff --git a/drivers/llcc68/llcc68_raw.c b/drivers/llcc68/llcc68_raw.c new file mode 100644 index 0000000..7606d29 --- /dev/null +++ b/drivers/llcc68/llcc68_raw.c @@ -0,0 +1,69 @@ +#include "llcc68_raw.h" + +#include +#include + +#define DT_DRV_COMPAT semtech_llcc68 + +static void dio1_irq_trampoline(const struct device *port, struct gpio_callback *cb, uint32_t pins) { + ARG_UNUSED(port); + + struct llcc68_data *data = CONTAINER_OF(cb, struct llcc68_data, dio1_irq_callback); + const struct llcc68_config *config = data->self->config; + + if ((pins & BIT(config->dio1_gpio.pin)) != 0U && data->dio1_user_handler != NULL) { + data->dio1_user_handler(data->self, data->dio1_user_data); + } +} + +int llcc68_init(const struct device *dev) { + const struct llcc68_config *config = dev->config; + struct llcc68_data *data = dev->data; + + if (config->tx_enable_gpio.port != NULL) { + gpio_pin_configure_dt(&config->tx_enable_gpio, GPIO_OUTPUT_INACTIVE); + } + + if (config->rx_enable_gpio.port != NULL) { + gpio_pin_configure_dt(&config->rx_enable_gpio, GPIO_OUTPUT_INACTIVE); + } + + gpio_pin_configure_dt(&config->reset_gpio, GPIO_OUTPUT_INACTIVE); + gpio_pin_configure_dt(&config->busy_gpio, GPIO_INPUT); + gpio_pin_configure_dt(&config->dio1_gpio, GPIO_INPUT); + + data->self = dev; + gpio_init_callback(&data->dio1_irq_callback, dio1_irq_trampoline, BIT(config->dio1_gpio.pin)); + if (gpio_add_callback(config->dio1_gpio.port, &data->dio1_irq_callback) < 0) { + return -EIO; + } + + gpio_pin_interrupt_configure_dt(&config->dio1_gpio, GPIO_INT_DISABLE); + return 0; +} + +#define LLCC68_DEFINE(inst) \ + static struct llcc68_data llcc68_data_##inst; \ + static const struct llcc68_config llcc68_config_##inst = \ + { \ + .spi = SPI_DT_SPEC_INST_GET(inst, SPI_WORD_SET(8) | SPI_TRANSFER_MSB, 100), \ + .reset_gpio = GPIO_DT_SPEC_INST_GET(inst, reset_gpios), \ + .busy_gpio = GPIO_DT_SPEC_INST_GET(inst, busy_gpios), \ + .dio1_gpio = GPIO_DT_SPEC_INST_GET(inst, dio1_gpios), \ + .tx_enable_gpio = GPIO_DT_SPEC_INST_GET_OR(inst, tx_enable_gpios, {.port = NULL}), \ + .rx_enable_gpio = GPIO_DT_SPEC_INST_GET_OR(inst, rx_enable_gpios, {.port = NULL}), \ + }; \ + DEVICE_DT_INST_DEFINE(inst, \ + llcc68_init, \ + NULL, \ + &llcc68_data_##inst, \ + &llcc68_config_##inst, \ + POST_KERNEL, \ + CONFIG_LLCC68_INIT_PRIORITY, \ + NULL) + +BUILD_ASSERT(DT_HAS_COMPAT_STATUS_OKAY(DT_DRV_COMPAT), + "No status=okay nodes for DT_DRV_COMPAT; check compatible/status"); + +DT_INST_FOREACH_STATUS_OKAY(LLCC68_DEFINE) + diff --git a/dts/bindings/semtech,llcc68.yaml b/dts/bindings/semtech,llcc68.yaml new file mode 100644 index 0000000..5903b67 --- /dev/null +++ b/dts/bindings/semtech,llcc68.yaml @@ -0,0 +1,46 @@ +# Copyright (c) 2019 Manivannan Sadhasivam +# Copyright (c) 2020 Andreas Sandberg +# SPDX-License-Identifier: Apache-2.0 + + +description: | + Semtech LLCC68 LoRa transceiver + +compatible: "semtech,llcc68" + +include: spi-device.yaml + +properties: + reset-gpios: + type: phandle-array + required: true + description: | + GPIO connected to the modem's NRESET signal. + + This signal is open-drain, active-low as interpreted by the + modem. + + busy-gpios: + type: phandle-array + required: true + description: | + GPIO connected to the modem's BUSY signal. + + dio1-gpios: + type: phandle-array + required: true + description: | + GPIO connected to DIO1. This GPIO will be used as a generic + IRQ line from the chip. + + tx-enable-gpios: + type: phandle-array + description: | + Antenna switch TX enable GPIO. If set, the driver tracks the + state of the radio and controls the RF switch. + + rx-enable-gpios: + type: phandle-array + description: | + Antenna switch RX enable GPIO. If set, the driver tracks the + state of the radio and controls the RF switch. diff --git a/include/llcc68.hpp b/include/llcc68.hpp new file mode 100644 index 0000000..bf25676 --- /dev/null +++ b/include/llcc68.hpp @@ -0,0 +1,295 @@ +#ifndef AA1F69D0_32E3_44B6_BFD7_2013E1E86A6B +#define AA1F69D0_32E3_44B6_BFD7_2013E1E86A6B + +#include "llcc68_definitions.hpp" +#include "llcc68_raw.h" +#include +#include +#include +#include +#include +#include + +namespace app::driver::llcc68 { +constexpr size_t MAX_BUFFER_PAYLOAD = 128; +constexpr uint32_t TIMEOUT_NONE = 0; +constexpr uint32_t TIMEOUT_INF = 0xffffffff; + +constexpr auto DEFAULT_TX_BUFFER_ADDRESS = 0x00; +constexpr auto DEFAULT_RX_BUFFER_ADDRESS = 0x80; + +using user_dio1_handler_t = void (*)(const struct device *dev, void *user_data); +} // namespace app::driver::llcc68 + +namespace app::driver::llcc68 { + +template +using expected = std::expected; +template +using unexpected = std::unexpected; +using unit = std::monostate; + +constexpr auto DEFAULT_BUSY_TIMEOUT_MS = 100; + +/** + * @brief calculate time-on-air for a LoRa packet + * + * @param len Payload length in bytes + * @param sf Spreading factor (7-12) + * @param bw Bandwidth (125, 250, 500 kHz) + * @param cr Coding rate (4/5, 4/6, 4/7, 4/8) + * @param preamble_length Preamble length in symbols + * @param header_type Header type (implicit or explicit). + * @param crc_type CRC type (none or 16-bit) + */ +constexpr airtime_t calc_time_on_air(uint8_t len, uint8_t sf, LoRaBandwidth bw, + LoRaCodingRate cr, + uint16_t preamble_length, + LoRaHeaderType header_type, + LoRaCrcType crc_type); + +struct LLCC68 { + /** trivial getter */ + [[nodiscard]] + const struct llcc68_config &config() const; + struct llcc68_data &data(); + [[nodiscard]] + std::optional tx_enable_gpio() const; + [[nodiscard]] + std::optional rx_enable_gpio() const; + + using timeout_ms_t = uint16_t; + + /** SPI operation API */ + + expected + read_stream(uint8_t cmd, std::span data_to_host, + timeout_ms_t busy_timeout = DEFAULT_BUSY_TIMEOUT_MS); + + expected + write_stream(uint8_t cmd, std::span data_from_host, + timeout_ms_t busy_timeout = DEFAULT_BUSY_TIMEOUT_MS); + + expected + read_register(uint16_t reg, std::span data_to_host, + timeout_ms_t busy_timeout = DEFAULT_BUSY_TIMEOUT_MS); + + expected + write_register(uint16_t reg, std::span data_from_host, + timeout_ms_t busy_timeout = DEFAULT_BUSY_TIMEOUT_MS); + + /** READ BUFFER */ + + /** + * @brief read buffer of any offset and length, copying to user buffer + * @param[in] offset offset in the LLCC68 buffer to read from + * @param[out] data_to_host buffer to copy data into, the read length is + * determined by the size of this span + * @returns number of bytes read + */ + expected + read_buffer_copy(uint8_t offset, std::span data_to_host, + timeout_ms_t busy_timeout = DEFAULT_BUSY_TIMEOUT_MS); + + /** + * @brief read buffer of any offset and length + * @returns span referencing internal buffer + */ + expected, error_code> + read_buffer_ref(uint8_t offset, uint8_t n, + timeout_ms_t busy_timeout = DEFAULT_BUSY_TIMEOUT_MS); + + /** + * @brief read the RX buffer (whose offset would be DEFAULT_RX_BUFFER_ADDRESS) + * with all its current available data + * @returns span referencing internal buffer + */ + expected, error_code> + read_rx_buffer_ref(timeout_ms_t busy_timeout = DEFAULT_BUSY_TIMEOUT_MS); + + /** WRITE BUFFER */ + + expected + write_tx_buffer(std::span data_from_host); + + expected + flush_tx_buffer(timeout_ms_t busy_timeout = DEFAULT_BUSY_TIMEOUT_MS); + + expected write_buffer_immediate_mode( + uint8_t offset, std::span data_from_host, + timeout_ms_t busy_timeout = DEFAULT_BUSY_TIMEOUT_MS); + + void tx_rx_en_pin_set(TxRxPinState state); + + /** LLCC68 DataSheet Function */ + + expected get_status(); + expected get_packet_status(); + expected reset(); + expected set_standby(); + expected hal_get_chip_type(); + expected set_dio_irq_params(irq_params_t params); + expected get_irq_status(); + expected + clear_irq_status(irq_status_t irq = irq_status_t::all()); + + expected set_packet_type_lora(); + expected set_packet_type_gfsk(); + expected + set_modulation_params(modulation_params_t mod_params); + expected set_modulation_params(uint8_t sf, uint8_t bw, + uint8_t cr, uint8_t ldro); + expected + set_gfsk_modulation_params(gfsk_modulation_params_t mod_params); + /*! + \brief Sets LoRa sync word. + \param sync_word LoRa sync word to be set. + \note Differentiate the LoRa signal for Public or Private Network + Set to 0x3444 for Public Network + Set to 0x1424 for Private Network + */ + expected set_lora_sync_word(uint16_t sync_word); + expected set_dio2_as_rf_switch(bool en); + expected set_rf_frequency(freq_t freq_mhz); + expected set_rf_frequency(uint32_t freq_raw); + expected + set_packet_params(uint16_t preamble_length, uint8_t payload_length, + uint8_t crc_type, uint8_t hdr_type, uint8_t iq_type); + expected + set_gfsk_packet_params(gfsk_packet_params_t packet_params); + expected + set_gfsk_sync_word(std::span sync_word); + expected + set_gfsk_address_filtering(uint8_t node_address, uint8_t broadcast_address); + expected set_gfsk_crc_seed(uint16_t seed); + expected set_gfsk_crc_polynomial(uint16_t polynomial); + expected set_gfsk_whitening_seed(uint16_t seed); + expected set_tx_params(tx_params_t params); + expected set_tx_params(int8_t pwr, + LoRaTxRampTime ramp_time); + expected set_pa_config(pa_setting_t settings); + /** + * @brief fixes overly eager PA clamping + * + * fixes overly eager PA clamping + * see SX1262/SX1268 datasheet, chapter 15 Known Limitations, section 15.2 for + * details + * + * The LLCC68 platform embeds a Power Amplifier (PA) clamping mechanism, + * backing-off the power when over-voltage conditions are detected internally. + * This method is put in place to protect the internal devices and ensure + * long-term reliability of the chip. Considering a high-power operation of + * the LLCC68 (supporting +22dBm on-chip), these "clamping" devices are overly + * protective, causing the chip to back-down its output power when even a + * reasonable mismatch is detected at the PA output. The observation is + * typically 5 to 6 dB less output power than the expected. + * + * @param enable + * @return Result + */ + expected fix_pa_clamping(bool enable = true); + /** + * \brief fixes inverted IQ on SX1262 rev. B + * \param iq_config RADIOLIB_SX126X_LORA_IQ_STANDARD or + * RADIOLIB_SX126X_LORA_IQ_INVERTED + * \see SX1262/SX1268 datasheet, chapter 15 Known Limitations, section 15.4 + * for details + */ + expected fix_inverted_iq(uint8_t iq_config); + /** + * \brief Some sensitivity degradation may be observed on any LoRa device, + when receiving signals transmitted by the LLCC68 with a LoRa BW of 500 kHz. + * \note should be used before each packet transmission, to properly configure + the chip + * \param bw the bandwidth + * \sa SX1262/SX1268 datasheet, chapter 15 Known Limitations, section 15.1 for + details + */ + expected fix_sensitivity(LoRaBandwidth bw); + expected fix_gfsk_tx_modulation(); + expected fix_gfsk_low_bitrate(uint32_t bitrate_bps); + + /** + * \brief Channel Activity Detection (CAD) params + */ + expected set_cad_params(cad_params_t params); + + /** + * \brief Set the CAD mode + * + * The command SetCAD() can be used only in LoRa packet type. The Channel + * Activity Detection is a LoRa specific mode of operation where the device + * searches for the presence of a LoRa preamble signal. After the search has + * completed, the device returns in STDBY_RC mode. The length of the search is + * configured via the command SetCadParams(...). At the end of the search + * period, the device triggers the IRQ CAD DONE if it has been enabled. If a + * valid signal has been detected it also generates the IRQ CAD DETECTED. + * + * The time taken for the channel activity detection is dependent upon the + * LoRa® modulation settings used. For a given configuration (SF/BW) the + * typical CAD detection time can be selected to be either 1, 2, 4, 8 or 16 + * symbols. Once the duration of the selected number of symbols has been done, + * the radio will remains for around half a symbol in Rx to post-process the + * measurement. + */ + expected set_cad(); + + expected set_tx(uint32_t timeout = TIMEOUT_NONE); + expected set_rx(uint32_t timeout = TIMEOUT_INF); + expected set_sleep(sleep_config_t config); + expected set_tx_continuous_wave(); + expected set_tx_infinite_preamble(); + expected + set_buffer_base_address(uint8_t tx_base_addr = DEFAULT_TX_BUFFER_ADDRESS, + uint8_t rx_base_addr = DEFAULT_RX_BUFFER_ADDRESS); + + struct rx_buffer_status_t { + uint8_t payload_length; + uint8_t start_address; + }; + expected + get_rx_buffer_status(timeout_ms_t busy_timeout = DEFAULT_BUSY_TIMEOUT_MS); + + /** + * @brief Random Number Generator (RNG) + */ + expected random_number_gen(); + + /** DIO1 IRQ registration helpers (like sx126x) */ + expected dio1_irq_enable(); + void dio1_irq_disable(); + void set_dio1_irq_handler(user_dio1_handler_t handler, void *user_data); + + /** high level API that wraps the raw function call */ + + expected hal_modem_init(lora_parameters_t params); + expected hal_gfsk_modem_init(gfsk_parameters_t params); + expected + hal_async_transmit(std::span data, lora_parameters_t params); + expected + hal_async_flush(lora_parameters_t params); + + expected hal_async_rx(lora_parameters_t params); + expected + hal_gfsk_async_transmit(std::span data, + gfsk_parameters_t params); + expected + hal_gfsk_async_flush(gfsk_parameters_t params); + + expected hal_gfsk_async_rx(gfsk_parameters_t params); + + expected hal_set_output_power(ChipType chip, + tx_params_t params); + + error_code init(); + + /** properties */ + const struct device *dev; +}; +} // namespace app::driver::llcc68 + +namespace app::radio { +namespace llcc68 = app::driver::llcc68; +} + +#endif /* AA1F69D0_32E3_44B6_BFD7_2013E1E86A6B */ diff --git a/include/llcc68_definitions.hpp b/include/llcc68_definitions.hpp new file mode 100644 index 0000000..5cf534d --- /dev/null +++ b/include/llcc68_definitions.hpp @@ -0,0 +1,1010 @@ +#ifndef ECC594CF_EDF0_42B5_8518_0EB3B3583727 +#define ECC594CF_EDF0_42B5_8518_0EB3B3583727 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace app::driver::llcc68 { +struct LLCC68; +using airtime_t = std::chrono::duration; +using error_code = std::error_code; + +enum class Errc : uint8_t { + FailureToExecuteCommand = 1, + CommandTimeout = 2, + CommandProcessing = 3, + InvalidState = 4, +}; + +class Llcc68ErrorCategory : public std::error_category { +public: + [[nodiscard]] + const char *name() const noexcept override { + return "llcc68"; + } + + [[nodiscard]] + std::string message(int condition) const override { + switch (static_cast(condition)) { + case Errc::FailureToExecuteCommand: + return "failure to execute command"; + case Errc::CommandTimeout: + return "command timeout"; + case Errc::CommandProcessing: + return "command processing error"; + case Errc::InvalidState: + return "invalid state"; + default: + return "unknown llcc68 error"; + } + } +}; + +inline const std::error_category &error_category() { + static const Llcc68ErrorCategory category{}; + return category; +} + +inline error_code make_error_code(Errc error) { + return {static_cast(error), error_category()}; +} + +inline error_code make_zephyr_error_code(int zephyr_ret) { + if (zephyr_ret >= 0) { + return {}; + } + + return {static_cast(-zephyr_ret), std::generic_category()}; +} + +using freq_t = float; +enum LoRaBandwidth : uint8_t { + BW_7_8 = 0x00, + BW_10_4 = 0x08, + BW_15_6 = 0x01, + BW_20_8 = 0x09, + BW_31_25 = 0x02, + BW_41_7 = 0x0A, + BW_62_5 = 0x03, + BW_125_0 = 0x04, + BW_250_0 = 0x05, + BW_500_0 = 0x06, +}; + +enum LoRaHeaderType : uint8_t { + HEADER_EXPLICIT = 0x00, + HEADER_IMPLICIT = 0x01, +}; + +enum LoRaCodingRate : uint8_t { + CR_4_5 = 0x01, + CR_4_6 = 0x02, + CR_4_7 = 0x03, + CR_4_8 = 0x04, +}; + +enum LoRaCrcType : uint8_t { + CRC_OFF = 0x00, + CRC_ON = 0x01, +}; + +enum LoRaIQType : uint8_t { + IQ_STANDARD = 0x00, + IQ_INVERTED = 0x01, +}; + +// LoRa LDR (Low Data Rate) types +enum LoRaLowDataRateType : uint8_t { + LDR_OFF = 0x00, + LDR_ON = 0x01, +}; + +// - RampTime Value RampTime (µs) +// - SET_RAMP_10U 0x00 10 +// - SET_RAMP_20U 0x01 20 +// - SET_RAMP_ 40U 0x02 40 +// - SET_RAMP_80U 0x03 80 +// - SET_RAMP_200U 0x04 200 +// - SET_RAMP_800U 0x05 800 +// - SET_RAMP_1700U 0x06 1700 +// - SET_RAMP_3400U 0x07 3400 +enum LoRaTxRampTime : uint8_t { + SET_RAMP_10U = 0x00, + SET_RAMP_20U = 0x01, + SET_RAMP_40U = 0x02, + SET_RAMP_80U = 0x03, + SET_RAMP_200U = 0x04, + SET_RAMP_800U = 0x05, + SET_RAMP_1700U = 0x06, + SET_RAMP_3400U = 0x07, +}; + +enum RegulatorMode : uint8_t { + REGULATOR_LDO = 0x00, + REGULATOR_DC_DC = 0x01, +}; + +enum LoRaSyncWord : uint16_t { + SYNC_WORD_PRIVATE = 0x1424, // Private sync word + SYNC_WORD_PUBLIC = 0x3444, // Public sync word +}; + +inline const char *to_str(LoRaBandwidth bw) { + switch (bw) { + case LoRaBandwidth::BW_7_8: + return "7.8kHz"; + case LoRaBandwidth::BW_10_4: + return "10.4kHz"; + case LoRaBandwidth::BW_15_6: + return "15.6kHz"; + case LoRaBandwidth::BW_20_8: + return "20.8kHz"; + case LoRaBandwidth::BW_31_25: + return "31.25kHz"; + case LoRaBandwidth::BW_41_7: + return "41.7kHz"; + case LoRaBandwidth::BW_62_5: + return "62.5kHz"; + case LoRaBandwidth::BW_125_0: + return "125.0kHz"; + case LoRaBandwidth::BW_250_0: + return "250.0kHz"; + case LoRaBandwidth::BW_500_0: + return "500.0kHz"; + default: + return "Unknown"; + } +} + +inline const char *to_str(LoRaCodingRate cr) { + switch (cr) { + case LoRaCodingRate::CR_4_5: + return "4/5"; + case LoRaCodingRate::CR_4_6: + return "4/6"; + case LoRaCodingRate::CR_4_7: + return "4/7"; + case LoRaCodingRate::CR_4_8: + return "4/8"; + default: + return "Unknown"; + } +} + +inline std::optional sf_from_uint8_t(const uint8_t sf) { + if (sf < 6 || sf > 12) { + return std::nullopt; + } + return sf; +} + +struct modulation_params_t { + LoRaBandwidth bw; + uint8_t sf; + LoRaCodingRate cr; + LoRaLowDataRateType ldr_optimize; + + static modulation_params_t Default() { + return modulation_params_t{ + .bw = LoRaBandwidth::BW_250_0, + .sf = 7, + .cr = LoRaCodingRate::CR_4_7, + .ldr_optimize = LoRaLowDataRateType::LDR_OFF, + }; + } +}; + +struct packet_params_t { + uint16_t preamble_len; + uint8_t payload_len; + LoRaCrcType crc_type; + LoRaHeaderType hdr_type; + LoRaIQType iq_type; + + static packet_params_t Default() { + return packet_params_t{ + .preamble_len = 8, + .payload_len = 0xff, + .crc_type = LoRaCrcType::CRC_OFF, + .hdr_type = LoRaHeaderType::HEADER_IMPLICIT, + .iq_type = LoRaIQType::IQ_STANDARD, + }; + } +}; + +struct pa_setting_t { + // paDutyCycle controls the duty cycle (conduction angle) of both PAs (SX1261 + // and SX1262). The maximum output power, the power consumption, and the + // harmonics will drastically change with paDutyCycle. The values given across + // this datasheet are the recommended settings to achieve the best efficiency + // of the PA. Changing the paDutyCycle will affect the distribution of the + // power in the harmonics and should thus be selected to work in conjunction + // of a given matching network. + uint8_t pa_duty_cycle; + // hpMax selects the size of the PA in the SX1262, this value has no influence + // on the SX1261. The maximum output power can be reduced by reducing the + // value of hpMax. The valid range is between 0x00 and 0x07 and 0x07 is the + // maximum supported value for the SX1262 to achieve +22 dBm output power. + // Increasing hpMax above 0x07 could cause early aging of the device of could + // damage the device when used in extreme temperatures. + uint8_t hp_max; + uint8_t device_sel; + + /** + * @note SetTxParams should be set to +22dBm + */ + static constexpr pa_setting_t Default22dBmHp() { return {0x04, 0x07, 0x00}; } + + /** + * @note SetTxParams should be set to +22dBm + */ + static constexpr pa_setting_t Default20dBmHp() { return {0x03, 0x05, 0x00}; } + /** + * @note SetTxParams should be set to +22dBm + */ + static constexpr pa_setting_t Default17dBmHp() { return {0x02, 0x03, 0x00}; } + /** + * @note SetTxParams should be set to +22dBm + * @note High Power; SX1262/LLCC68 + */ + static constexpr pa_setting_t Default14dBmHp() { return {0x02, 0x02, 0x00}; } + + /** + * @note SetTxParams should be set to +14dBm + * @note Low Power; SX1261 + */ + static constexpr pa_setting_t Default14dBmLp() { return {0x04, 0x00, 0x01}; } + + /** + * @note SetTxParams should be set to +14dBm + * @note Low Power; SX1261 + */ + static constexpr pa_setting_t Default10dBmLp() { return {0x01, 0x00, 0x01}; } + + static constexpr pa_setting_t DefaultHp() { return Default22dBmHp(); } + static constexpr pa_setting_t DefaultLp() { return Default14dBmLp(); } +}; + +inline const char *to_str(llcc68::LoRaTxRampTime ramp_time) { + switch (ramp_time) { + case llcc68::SET_RAMP_10U: + return "10us"; + case llcc68::SET_RAMP_20U: + return "20us"; + case llcc68::SET_RAMP_40U: + return "40us"; + case llcc68::SET_RAMP_80U: + return "80us"; + case llcc68::SET_RAMP_200U: + return "200us"; + case llcc68::SET_RAMP_800U: + return "800us"; + case llcc68::SET_RAMP_1700U: + return "1700us"; + case llcc68::SET_RAMP_3400U: + return "3400us"; + default: + return "Unknown"; + } +} + +struct tx_params_t { + /** + * @brief magic number for automatic max TX power depending on + * chip type + */ + static constexpr auto MAX_POWER_HIGH_PA = 22; + static constexpr auto MAX_POWER_LOW_PA = 14; + + int8_t power; + LoRaTxRampTime ramp_time; + + static constexpr tx_params_t Default() { + return tx_params_t{ + .power = MAX_POWER_HIGH_PA, + .ramp_time = SET_RAMP_200U, + }; + } + [[nodiscard]] + std::string to_string() const { +#ifdef __cpp_lib_format + return std::format("tx_params[power={}, ramp_time={}]", power, + to_str(ramp_time)); +#else + return "tx_params[power=" + std::to_string(power) + + ", ramp_time=" + to_str(ramp_time) + "]"; +#endif + } +}; + +struct lora_parameters_t { + modulation_params_t mod_params; + packet_params_t packet_params; + tx_params_t tx_params; + freq_t frequency_mhz; + LoRaSyncWord sync_word; + + static lora_parameters_t Default() { + return lora_parameters_t{ + .mod_params = modulation_params_t::Default(), + .packet_params = packet_params_t::Default(), + .tx_params = tx_params_t::Default(), + .frequency_mhz = 434.75, + .sync_word = LoRaSyncWord::SYNC_WORD_PRIVATE, + }; + } + void log(const char *tag) const; +}; + +enum class GfskPulseShape : uint8_t { + Off = 0x00, + Gauss03 = 0x08, + Gauss05 = 0x09, + Gauss07 = 0x0A, + Gauss1 = 0x0B, +}; + +enum class GfskRxBandwidth : uint8_t { + Bw4800 = 0x1F, + Bw5800 = 0x17, + Bw7300 = 0x0F, + Bw9700 = 0x1E, + Bw11700 = 0x16, + Bw14600 = 0x0E, + Bw19500 = 0x1D, + Bw23400 = 0x15, + Bw29300 = 0x0D, + Bw39000 = 0x1C, + Bw46900 = 0x14, + Bw58600 = 0x0C, + Bw78200 = 0x1B, + Bw93800 = 0x13, + Bw117300 = 0x0B, + Bw156200 = 0x1A, + Bw187200 = 0x12, + Bw234300 = 0x0A, + Bw312000 = 0x19, + Bw373600 = 0x11, + Bw467000 = 0x09, +}; + +enum class GfskPreambleDetector : uint8_t { + Off = 0x00, + Bits8 = 0x04, + Bits16 = 0x05, + Bits24 = 0x06, + Bits32 = 0x07, +}; + +enum class GfskAddressFiltering : uint8_t { + Disabled = 0x00, + Node = 0x01, + NodeAndBroadcast = 0x02, +}; + +enum class GfskPacketLengthMode : uint8_t { + Fixed = 0x00, + Variable = 0x01, +}; + +enum class GfskCrcType : uint8_t { + Off = 0x01, + Crc1Byte = 0x00, + Crc2Byte = 0x02, + Crc1ByteInverted = 0x04, + Crc2ByteInverted = 0x06, +}; + +enum class GfskWhitening : uint8_t { + Off = 0x00, + On = 0x01, +}; + +struct gfsk_modulation_params_t { + uint32_t bitrate_bps; + uint32_t frequency_deviation_hz; + GfskPulseShape pulse_shape; + GfskRxBandwidth rx_bandwidth; + + static constexpr gfsk_modulation_params_t Default() { + return { + .bitrate_bps = 4800, + .frequency_deviation_hz = 5000, + .pulse_shape = GfskPulseShape::Gauss05, + .rx_bandwidth = GfskRxBandwidth::Bw23400, + }; + } +}; + +struct gfsk_packet_params_t { + uint16_t preamble_bits; + GfskPreambleDetector detector_length; + uint8_t sync_length_bits; + GfskAddressFiltering address_filtering; + GfskPacketLengthMode packet_length_mode; + uint8_t payload_length; + GfskCrcType crc_type; + GfskWhitening whitening; + + static constexpr gfsk_packet_params_t Default() { + return { + .preamble_bits = 32, + .detector_length = GfskPreambleDetector::Bits16, + .sync_length_bits = 32, + .address_filtering = GfskAddressFiltering::Disabled, + .packet_length_mode = GfskPacketLengthMode::Variable, + .payload_length = 0xff, + .crc_type = GfskCrcType::Crc2Byte, + .whitening = GfskWhitening::On, + }; + } +}; + +struct gfsk_parameters_t { + gfsk_modulation_params_t mod_params; + gfsk_packet_params_t packet_params; + tx_params_t tx_params; + freq_t frequency_mhz; + std::array sync_word; + uint8_t sync_word_length; + uint16_t crc_seed; + uint16_t crc_polynomial; + uint16_t whitening_seed; + std::optional node_address; + std::optional broadcast_address; + + static constexpr gfsk_parameters_t Default() { + return { + .mod_params = gfsk_modulation_params_t::Default(), + .packet_params = gfsk_packet_params_t::Default(), + .tx_params = tx_params_t::Default(), + .frequency_mhz = 434.75, + .sync_word = {0x2D, 0xD4, 0, 0, 0, 0, 0, 0}, + .sync_word_length = 2, + .crc_seed = 0x1D0F, + .crc_polynomial = 0x1021, + .whitening_seed = 0x0100, + .node_address = std::nullopt, + .broadcast_address = std::nullopt, + }; + } + void log(const char *tag) const; +}; + +enum class ChipType : std::uint8_t { + Unknown, + LLCC68, + SX1261, + SX1262, +}; + +inline const char *to_str(ChipType chip) { + switch (chip) { + case ChipType::LLCC68: + return "LLCC68"; + case ChipType::SX1261: + return "SX1261"; + case ChipType::SX1262: + return "SX1262"; + default: + return "Unknown"; + } +} + +/// @note suitable SX1262/LLCC68 +enum class PaOptimalPresetHigh : uint8_t { + DBM_14, + DBM_17, + DBM_20, + DBM_22, +}; + +/// @note suitable SX1261 +enum class PaOptimalPresetLow : uint8_t { + DBM_10, + DBM_14, + DBM_15, +}; + +using PaOptimalPreset = std::variant; + +struct pa_config_t { + uint8_t pa_duty_cycle; + uint8_t hp_max; + uint8_t device_sel; +}; + +enum class PacketType : uint8_t { + FSK = 0x00, + LORA = 0x01, + LR_FHSS = 0x03, +}; + +enum class TcxoVoltage : uint8_t { + V_1_6 = 0x00, + V_1_7 = 0x01, + V_1_8 = 0x02, + V_2_2 = 0x03, + V_2_4 = 0x04, + V_2_7 = 0x05, + V_3_0 = 0x06, + V_3_3 = 0x07, +}; + +enum class CommandStatus : uint8_t { + RESERVED = 0x00, // 0b000 + RFU = 0x01, // 0b001 + DATA_AVAILABLE = 0x02, // 0b010 + COMMAND_TIMEOUT = 0x03, // 0b011 i.e. TIMEOUT + COMMAND_PROCESSING_ERROR = 0x04, // 0b100 i.e. INVALID + FAILURE_TO_EXECUTE_COMMAND = 0x05, // 0b101 i.e. FAILED + COMMAND_TX_DONE = 0x06, // 0b110 +}; + +inline const char *to_str(const CommandStatus &status) { + switch (status) { + case CommandStatus::RESERVED: + return "RESERVED"; + case CommandStatus::RFU: + return "RFU"; + case CommandStatus::DATA_AVAILABLE: + return "DATA_AVAILABLE"; + case CommandStatus::COMMAND_TIMEOUT: + return "COMMAND_TIMEOUT"; + case CommandStatus::COMMAND_PROCESSING_ERROR: + return "COMMAND_PROCESSING_ERROR"; + case CommandStatus::FAILURE_TO_EXECUTE_COMMAND: + return "FAILURE_TO_EXECUTE_COMMAND"; + case CommandStatus::COMMAND_TX_DONE: + return "COMMAND_TX_DONE"; + default: + return "Unknown"; + } +} + +enum class ChipMode : uint8_t { + UNUSED = 0x00, // 0b000 + RFU = 0x01, // 0b001 + STBY_RC = 0x02, // 0b010 + STBY_XOSC = 0x03, // 0b011 + FS = 0x04, // 0b100 + RX = 0x05, // 0b101 + TX = 0x06, // 0b110 +}; + +inline const char *to_str(const ChipMode &mode) { + switch (mode) { + case ChipMode::UNUSED: + return "UNUSED"; + case ChipMode::RFU: + return "RFU"; + case ChipMode::STBY_RC: + return "STBY_RC"; + case ChipMode::STBY_XOSC: + return "STBY_XOSC"; + case ChipMode::FS: + return "FS"; + case ChipMode::RX: + return "RX"; + case ChipMode::TX: + return "TX"; + default: + return "Unknown"; + } +} + +struct __attribute__((packed)) status_t { + // LSB + uint8_t reserved_1 : 1; + CommandStatus command_status : 3; + ChipMode chip_mode : 3; + uint8_t reserved_2 : 1; + // MSB +}; +static_assert(sizeof(status_t) == 1); + +struct __attribute__((packed)) lora_packet_status_t { + // Average over last packet received of RSSI + // Actual signal power is –RssiPkt/2 (dBm) + uint8_t rssi_pkt; + // Estimation of SNR on last packet received in two’s compliment format + // multiplied by 4. Actual SNR in dB =SnrPkt/4 + int8_t snr_pkt; + // Estimation of RSSI of the LoRa® signal (after despreading) on last packet + // received. Actual Rssi in dB = -SignalRssiPkt/2 + uint8_t signal_rssi_pkt; + + [[nodiscard]] + auto rssi_pkt_dbm() const -> float { + return -static_cast(rssi_pkt) / 2.0F; + } + + [[nodiscard]] + auto snr_pkt_db() const -> float { + return static_cast(snr_pkt) / 4.0F; + } + + [[nodiscard]] + auto signal_rssi_pkt_dbm() const -> float { + return -static_cast(signal_rssi_pkt) / 2.0F; + } +}; + +struct __attribute__((packed)) fsk_packet_status_t { + // bit 7: preamble err + // bit 6: sync err + // bit 5: adrs err + // bit 4: crc err + // bit 3: length err + // bit 2: abort err + // bit 1: pkt received + // bit 0: pkt sent + uint8_t rx_status; + // RSSI value latched upon the detection of the sync address. + // [negated, dBm, fixdt(0,8,1)] + // Actual signal power is –RssiSync/2 (dBm) + uint8_t rssi_sync; + // RSSI average value over the payload of the received packet. Latched upon + // the pkt_done IRQ. [negated, dBm, fixdt(0,8,1)] Actual signal power is + // –RssiAvg/2 (dBm) + uint8_t rssi_avg; +}; + +union packet_status_t { + uint8_t raw[3]; + lora_packet_status_t lora; + fsk_packet_status_t fsk; +}; +static_assert(sizeof(packet_status_t) == 3); + +struct __attribute__((packed)) DeviceErrors { + // LSB + + bool RC64K_CALIB_ERR : 1; // [0] RC64k calibration failed + bool RC13M_CALIB_ERR : 1; // [1] RkC13M calibration failed + bool PLL_CALIB_ERR : 1; // [2] PLL calibration failed + bool ADC_CALIB_ERR : 1; // [3] ADC calibration failed + bool IMG_CALIB_ERR : 1; // [4] IMG calibration failed + bool XOSC_START_ERR : 1; // [5] XOSC failed to start + bool PLL_LOCK_ERR : 1; // [6] PLL failed to lock + uint8_t rfu_1 : 1; // [7] RFU + bool PA_RAMP_ERR : 1; // [8] PA ramping failed + uint8_t rfu_2 : 7; // [15:9] RFU + + // MSB + + [[nodiscard]] + std::string to_string() const { + auto ss = std::stringstream(); + ss << "DeviceErrors{"; + if (RC64K_CALIB_ERR) { + ss << "RC64K_CALIB_ERR(0) "; + } + if (RC13M_CALIB_ERR) { + ss << "RC13M_CALIB_ERR(1) "; + } + if (PLL_CALIB_ERR) { + ss << "PLL_CALIB_ERR(2) "; + } + if (ADC_CALIB_ERR) { + ss << "ADC_CALIB_ERR(3) "; + } + if (IMG_CALIB_ERR) { + ss << "IMG_CALIB_ERR(4) "; + } + if (XOSC_START_ERR) { + ss << "XOSC_START_ERR(5) "; + } + if (PLL_LOCK_ERR) { + ss << "PLL_LOCK_ERR(6) "; + } + if (PA_RAMP_ERR) { + ss << "PA_RAMP_ERR(8) "; + } + ss << "}"; + return ss.str(); + } + + [[nodiscard]] + bool has_any_error() const { + return RC64K_CALIB_ERR || RC13M_CALIB_ERR || PLL_CALIB_ERR || + ADC_CALIB_ERR || IMG_CALIB_ERR || XOSC_START_ERR || PLL_LOCK_ERR || + PA_RAMP_ERR; + } +}; +static_assert(sizeof(DeviceErrors) == 2); + +constexpr bool in_range(const auto v, const auto min, const auto max) { + return v >= min && v <= max; +} + +constexpr bool valid_cr(const uint8_t cr) { + using enum LoRaCodingRate; + switch (cr) { + case CR_4_5: + case CR_4_6: + case CR_4_7: + case CR_4_8: + return true; + default: + return false; + } +} + +constexpr std::tuple cr_to_ratio(const LoRaCodingRate cr) { + using enum LoRaCodingRate; + switch (cr) { + case CR_4_5: + return {4, 5}; + case CR_4_6: + return {4, 6}; + case CR_4_7: + return {4, 7}; + case CR_4_8: + return {4, 8}; + default: + return {0, 1}; + } +} + +constexpr bool valid_ldr_optimize(const uint8_t ldr_optimize) { + using enum LoRaLowDataRateType; + switch (ldr_optimize) { + case LDR_ON: + case LDR_OFF: + return true; + default: + return false; + } +} + +constexpr bool valid_bw(const uint8_t bw) { + using enum LoRaBandwidth; + switch (bw) { + case BW_7_8: + case BW_10_4: + case BW_15_6: + case BW_20_8: + case BW_31_25: + case BW_41_7: + case BW_62_5: + case BW_125_0: + case BW_250_0: + case BW_500_0: + return true; + default: + return false; + } +} + +using bw_t = float; +constexpr bw_t bw_khz(const uint8_t bw) { + using enum LoRaBandwidth; + switch (bw) { + case BW_7_8: + return bw_t{7.8F}; + case BW_10_4: + return bw_t{10.4F}; + case BW_15_6: + return bw_t{15.6F}; + case BW_20_8: + return bw_t{20.8F}; + case BW_31_25: + return bw_t{31.25F}; + case BW_41_7: + return bw_t{41.7F}; + case BW_62_5: + return bw_t{62.5F}; + case BW_125_0: + return bw_t{125.0F}; + case BW_250_0: + return bw_t{250.0F}; + case BW_500_0: + return bw_t{500.0F}; + default: + return bw_t{0}; + } +} + +constexpr bool valid_sf(const uint8_t bw, const uint8_t sf) { + if (const bool ok = valid_bw(bw); not ok) { + return false; + } + switch (bw) { + case BW_125_0: + return in_range(sf, 5, 9); + case BW_250_0: + return in_range(sf, 5, 10); + case BW_500_0: + return in_range(sf, 5, 11); + default: + return in_range(sf, 5, 12); + } +} + +constexpr bool valid_tx_power(int8_t power) { + if (power >= -9 && power <= 22) { + return true; + } + + if (power >= -17 && power <= 14) { + return true; + } + + return false; +} + +constexpr bool valid_ramp_time(const uint8_t ramp_time) { + switch (ramp_time) { + case llcc68::SET_RAMP_10U: + case llcc68::SET_RAMP_20U: + case llcc68::SET_RAMP_40U: + case llcc68::SET_RAMP_80U: + case llcc68::SET_RAMP_200U: + case llcc68::SET_RAMP_800U: + case llcc68::SET_RAMP_1700U: + case llcc68::SET_RAMP_3400U: + return true; + default: + return false; + } +} + +constexpr bool valid_freq(const freq_t freq) { + return in_range(freq, freq_t{150.0}, freq_t{960.0}); +} + +struct irq_status_bits_t { + // LSB + + bool tx_done : 1; // 0 + bool rx_done : 1; // 1 + bool preamble_detected : 1; // 2 + bool sync_word_valid : 1; // 3 + bool header_valid : 1; // 4 + bool header_err : 1; // 5 + bool crc_err : 1; // 6 + bool cad_done : 1; // 7 + bool cad_detected : 1; // 8 + bool timeout : 1; // 9 + uint8_t reserved_0 : 4; // 10-13 + bool lr_fhss_hop : 1; // 14 + uint8_t reserved_1 : 1; // 15 + + // MSB +}; + +enum class TxRxPinState : uint8_t { + TX, + RX, +}; + +struct irq_status_t { + union { + uint16_t raw; + irq_status_bits_t bits; + }; + + operator uint16_t() const { return raw; } + + [[nodiscard]] + uint8_t msb() const { + return static_cast((raw >> 8) & 0xFF); + } + + [[nodiscard]] + uint8_t lsb() const { + return static_cast(raw & 0xFF); + } + + static constexpr irq_status_t all() { + return irq_status_t{.raw = 0b0100001111111111}; + } + [[nodiscard]] + std::string to_string() const; +}; +static_assert(sizeof(irq_status_t) == 2, "irq_status_t must be 2 bytes"); + +enum class CAD_EXIT_MODE : uint8_t { + CAD_ONLY = 0x00, + CAD_RX = 0x01, +}; + +enum class CAD_SYMB : uint8_t { + CAD_ON_1_SYMB = 0x00, + CAD_ON_2_SYMB = 0x01, + CAD_ON_4_SYMB = 0x02, + CAD_ON_8_SYMB = 0x03, + CAD_ON_16_SYMB = 0x04, +}; + +// Parameters cadDetPeak and cadDetMin define the sensitivity of the LoRa modem +// when trying to correlate to actual LoRa preamble symbols. These two settings +// depend on the LoRa spreading factor and Bandwidth, but also depend on the +// number of symbol used to validate or not the detection. Choosing the right +// value is not easy and the values selected must be carefully tested to ensure +// a good detection at sensitivity level, and also to limit the number of false +// detections. +struct cad_params_t { + CAD_SYMB symbol_num; + uint8_t det_peak; + uint8_t det_min; + CAD_EXIT_MODE exit_mode; + // 24-bit + // + // The parameter cadTimeout is only used when the CAD is performed with + // cadExitMode = CAD_RX. Here, the cadTimeout indicates the time the device + // will stay in Rx following a successful CAD. + // Rx Timeout = cadTimeout * 15.625us + uint32_t timeout; +}; + +enum StartMode : uint8_t { + COLD_START = 0x00, + /** + * @brief device configuration is in retention + */ + WARM_START = 0x01, +}; + +struct sleep_config_t { + // LSB + + /** + * @brief + * 0: RTC timeout disable + * 1: wake-up on RTC timeout + */ + bool rtc_timeout_en : 1 {false}; + uint8_t _rfu_0 : 1 {}; + StartMode start_mode : 1 {StartMode::COLD_START}; + uint8_t _rfu_1 : 5 {}; +}; +static_assert(sizeof(sleep_config_t) == 1, "sleep_config_t must be 1 byte"); + +struct irq_params_t { + static constexpr uint16_t IRQ_MASK_NONE = 0; + // The IrqMask masks or unmasks the IRQ which can be triggered by the device. + // By default, all IRQ are masked (all ‘0’) and the user can enable them one + // by one (or several at a time) by setting the corresponding mask to ‘1’. + irq_status_t irqMask{IRQ_MASK_NONE}; + // The interrupt causes a DIO to be set if the corresponding bit in DioxMask + // and the IrqMask are set. As an example, if bit 0 of IrqMask is set to 1 + // and bit 0 of DIO1Mask is set to 1 then, a rising edge of IRQ source + // TxDone will be logged in the IRQ register and will appear at the same + // time on DIO1. + // + // One IRQ can be mapped to all DIOs, one DIO can be mapped to all IRQs (an + // OR operation is done) but some IRQ sources will be available only on + // certain modes of operation and frames. + irq_status_t dio1Mask{IRQ_MASK_NONE}; + irq_status_t dio2Mask{IRQ_MASK_NONE}; + irq_status_t dio3Mask{IRQ_MASK_NONE}; +}; + +struct transmit_result { + error_code post_action(); + + LLCC68 *self{}; + std::chrono::milliseconds airtime_estimated; +}; +} // namespace app::driver::llcc68 + +namespace app::radio { +namespace llcc68 = app::driver::llcc68; +} + +namespace std { +template <> +struct is_error_code_enum : true_type {}; +} // namespace std + +#endif /* ECC594CF_EDF0_42B5_8518_0EB3B3583727 */ diff --git a/include/llcc68_raw.h b/include/llcc68_raw.h new file mode 100644 index 0000000..f865728 --- /dev/null +++ b/include/llcc68_raw.h @@ -0,0 +1,42 @@ +#ifndef BD6F9621_EF52_430C_86CB_3BC688233342 +#define BD6F9621_EF52_430C_86CB_3BC688233342 + +#include +#include +#include +#include + +#define LLCC68_MAX_BUFFER_PAYLOAD 128 + +#ifdef __cplusplus +extern "C" { +#endif + +typedef void (*llcc68_user_dio1_handler_t)(const struct device *dev, void *user_data); + +struct llcc68_config { + struct spi_dt_spec spi; + struct gpio_dt_spec reset_gpio; + struct gpio_dt_spec busy_gpio; + struct gpio_dt_spec dio1_gpio; + struct gpio_dt_spec tx_enable_gpio; + struct gpio_dt_spec rx_enable_gpio; +}; + +struct llcc68_data { + struct gpio_callback dio1_irq_callback; + const struct device *self; + uint8_t tx_buffer[LLCC68_MAX_BUFFER_PAYLOAD + 4]; + uint8_t rx_buffer[LLCC68_MAX_BUFFER_PAYLOAD + 4]; + size_t tx_xfer_size; + llcc68_user_dio1_handler_t dio1_user_handler; + void *dio1_user_data; +}; + +int llcc68_init(const struct device *dev); + +#ifdef __cplusplus +} +#endif + +#endif /* BD6F9621_EF52_430C_86CB_3BC688233342 */ diff --git a/src/llcc68.cpp b/src/llcc68.cpp new file mode 100644 index 0000000..13e0e4f --- /dev/null +++ b/src/llcc68.cpp @@ -0,0 +1,1499 @@ +#include "llcc68.hpp" +#include "llcc68_definitions.hpp" +#include "priv_radiolib_definitions.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +LOG_MODULE_REGISTER(llcc68, CONFIG_LLCC68_LOG_LEVEL); + +namespace { +using error_code = app::driver::llcc68::error_code; +using ue_t = std::unexpected; +using llcc68_errc = app::driver::llcc68::Errc; + +ue_t ue(int zephyr_ret) { + return ue_t{app::driver::llcc68::make_zephyr_error_code(zephyr_ret)}; +} + +ue_t ue(const error_code &error) { return ue_t{error}; } + +ue_t ue(llcc68_errc error) { + return ue_t{app::driver::llcc68::make_error_code(error)}; +} + +bool is_command_processing_error(const error_code &error) { + return error == + app::driver::llcc68::make_error_code(llcc68_errc::CommandProcessing); +} +} // namespace + +// Helper macros for propagating std::expected errors +// Usage: APP_RADIO_RETURN_ERR(res); +#define APP_RADIO_RETURN_ERR(STATEVAR) \ + do { \ + if (!(STATEVAR).has_value()) { \ + return ue((STATEVAR).error()); \ + } \ + } while (0) + +// Usage: APP_RADIO_RETURN_ERR_IGNORE_PROC_ERR(res); +// If error is COMMAND_PROCESSING, ignore it and continue; otherwise return the +// error +#define APP_RADIO_RETURN_ERR_IGNORE_PROC_ERR(STATEVAR) \ + do { \ + if (!(STATEVAR).has_value()) { \ + auto __app_radio_err = (STATEVAR).error(); \ + if (!is_command_processing_error(__app_radio_err)) { \ + return ue(__app_radio_err); \ + } \ + } \ + } while (0) + +/** + * \brief return if STATEVAR has value, otherwise return the error + * \param STATEVAR the variable to check + * \param ctx the context of the error + * \param ... the arguments to pass to the error message + * \note this macro is used to return the error from a function and log the + * error message + */ +#define APP_RADIO_RETURN_ERR_CTX(STATEVAR, ...) \ + { \ + if (!(STATEVAR.has_value())) { \ + LOG_ERR(__VA_ARGS__); \ + return ue(STATEVAR.error()); \ + } \ + } + +namespace app::driver::llcc68 { + +const struct llcc68_config &LLCC68::config() const { + return *static_cast(dev->config); +} + +struct llcc68_data & +LLCC68::data() { // NOLINT(readability-make-member-function-const) mutable + return *static_cast(dev->data); +} + +std::optional LLCC68::tx_enable_gpio() const { + if (config().tx_enable_gpio.port != nullptr) { + return config().tx_enable_gpio; + } + return std::nullopt; +} + +std::optional LLCC68::rx_enable_gpio() const { + if (config().rx_enable_gpio.port != nullptr) { + return config().rx_enable_gpio; + } + return std::nullopt; +} + +error_code LLCC68::init() { + if (!device_is_ready(dev)) { + return make_zephyr_error_code(-ENODEV); + } + return {}; +} + +// Internal helpers (TU-local) +namespace { + // Max payload the radio supports and buffer sizing helpers + constexpr size_t kMaxPayload = 32; + + error_code command_status_to_error(status_t st) { + switch (st.command_status) { + case CommandStatus::FAILURE_TO_EXECUTE_COMMAND: + return make_error_code(Errc::FailureToExecuteCommand); + case CommandStatus::COMMAND_TIMEOUT: + return make_error_code(Errc::CommandTimeout); + case CommandStatus::COMMAND_PROCESSING_ERROR: + return make_error_code(Errc::CommandProcessing); + default: + return {}; + } + } + + int wait_for_not_busy(const gpio_dt_spec &busy_gpio, uint16_t timeout_ms) { + if (not device_is_ready(busy_gpio.port)) { + return -ENODEV; + } + const int64_t start = k_uptime_get(); + while (gpio_pin_get_dt(&busy_gpio) > 0) { + if ((k_uptime_get() - start) >= timeout_ms) { + return -ETIMEDOUT; + } + k_busy_wait(50); // ~50 us poll interval + } + return 0; + } +} // namespace + +void LLCC68::tx_rx_en_pin_set(TxRxPinState state) { + if (not tx_enable_gpio() or (not rx_enable_gpio())) { + return; + } + auto t = *tx_enable_gpio(); + auto r = *rx_enable_gpio(); + if (state == TxRxPinState::TX) { + gpio_pin_set_dt(&t, 1); + gpio_pin_set_dt(&r, 0); + } else if (state == TxRxPinState::RX) { + gpio_pin_set_dt(&t, 0); + gpio_pin_set_dt(&r, 1); + } +} + +/*! + \brief Get expected time-on-air for a given size of payload + \param len Payload length in bytes. + \returns Expected time-on-air in microseconds. +*/ +constexpr airtime_t calc_time_on_air(uint8_t len, uint8_t sf, LoRaBandwidth bw, + LoRaCodingRate cr, + uint16_t preamble_length, + LoRaHeaderType header_type, + LoRaCrcType crc_type) { + // everything is in microseconds to allow integer arithmetic + // some constants have .25, these are multiplied by 4, and have _x4 postfix to + // indicate that fact + const auto bw_ = bw_khz(bw); + const auto ubw = static_cast(bw_ * 10); + const uint32_t symbolLength_us = + (static_cast(1000 * 10) << sf) / ubw; + uint8_t sfCoeff1_x4 = 17; // (4.25 * 4) + uint8_t sfCoeff2 = 8; + if (sf == 5 || sf == 6) { + sfCoeff1_x4 = 25; // 6.25 * 4 + sfCoeff2 = 0; + } + uint8_t sfDivisor = 4 * sf; + if (symbolLength_us >= 16000) { + sfDivisor = 4 * (sf - 2); + } + constexpr int8_t bitsPerCrc = 16; + int8_t N_symbol_header = + header_type == RADIOLIB_SX126X_LORA_HEADER_EXPLICIT ? 20 : 0; + + // numerator of equation in section 6.1.4 of SX1268 datasheet v1.1 (might + // not actually be bitcount, but it has len * 8) + auto bit_count = static_cast( + static_cast(8) * static_cast(len) + + static_cast(crc_type * bitsPerCrc) - + static_cast(4 * sf) + static_cast(sfCoeff2) + + static_cast(N_symbol_header)); + bit_count = std::max(bit_count, 0); + // add (sfDivisor) - 1 to the numerator to give integer CEIL(...) + const uint16_t nPreCodedSymbols = (bit_count + (sfDivisor - 1)) / (sfDivisor); + + const auto de = std::get<1>(cr_to_ratio(cr)); + // preamble can be 65k, therefore nSymbol_x4 needs to be 32 bit + const uint32_t nSymbol_x4 = + (preamble_length + 8) * 4 + sfCoeff1_x4 + nPreCodedSymbols * de * 4; + + return airtime_t{symbolLength_us * nSymbol_x4 / 4}; +} + +// SPI helpers implementing the LLCC68 wire protocol +expected LLCC68::read_stream(uint8_t cmd, + std::span data, + timeout_ms_t busy_timeout) { + if (!spi_is_ready_dt(&config().spi)) { + return ue(-ENODEV); + } + if (data.size() > + kMaxPayload) { // protocol limit (payload up to 255 bytes typical) + return ue(-EINVAL); + } + if (int err = wait_for_not_busy(config().busy_gpio, busy_timeout); err) { + return ue(err); + } + + // TX: [cmd][dummy for status][dummy * data_len] + constexpr size_t kHdr = 1U + 1U; + std::array tx{}; + std::array rx{}; + const size_t xfer_len = kHdr + data.size(); + tx[0] = cmd; + + spi_buf tx_bufs[] = { + {.buf = tx.data(), .len = xfer_len}, + }; + spi_buf rx_bufs[] = { + {.buf = rx.data(), .len = xfer_len}, + }; + spi_buf_set tx_set{.buffers = tx_bufs, .count = 1}; + spi_buf_set rx_set{.buffers = rx_bufs, .count = 1}; + + const int ret = spi_transceive_dt(&config().spi, &tx_set, &rx_set); + if (ret != 0) { + return ue(ret); + } + + const size_t status_idx = 1U; // after cmd + const size_t data_idx = status_idx + 1; + const uint8_t status = rx[status_idx]; + std::copy(rx.begin() + static_cast(data_idx), + rx.begin() + static_cast(xfer_len), data.begin()); + if (auto e = command_status_to_error(std::bit_cast(status)); e) { + return ue(e); + } + return unit{}; +} + +expected +LLCC68::write_stream(uint8_t cmd, std::span data_from_host, + timeout_ms_t busy_timeout) { + if (!spi_is_ready_dt(&config().spi)) { + return ue(-ENODEV); + } + if (data_from_host.size() > kMaxPayload) { + return ue(-EINVAL); + } + if (int err = wait_for_not_busy(config().busy_gpio, busy_timeout); err) { + return ue(err); + } + + // TX: [cmd][payload...] + std::array tx{}; + std::array rx{}; + const size_t xfer_len = 1U + data_from_host.size(); + tx[0] = cmd; + std::ranges::copy(data_from_host, tx.begin() + 1); + + spi_buf tx_bufs[] = {{.buf = tx.data(), .len = xfer_len}}; + spi_buf rx_bufs[] = {{.buf = rx.data(), .len = xfer_len}}; + spi_buf_set tx_set{.buffers = tx_bufs, .count = 1}; + spi_buf_set rx_set{.buffers = rx_bufs, .count = 1}; + const int ret = spi_transceive_dt(&config().spi, &tx_set, &rx_set); + if (ret != 0) { + return ue(ret); + } + + // Status appears right after command, i.e., in rx[1] (if at least one more + // byte clocked) + if (xfer_len >= 2U) { + const uint8_t status = rx[1U]; + if (auto e = command_status_to_error(std::bit_cast(status)); e) { + return ue(e); + } + } + return unit{}; +} + +expected +LLCC68::read_register(uint16_t reg, std::span data_to_host, + timeout_ms_t busy_timeout) { + if (not spi_is_ready_dt(&config().spi)) { + return ue(-ENODEV); + } + if (data_to_host.size() > kMaxPayload) { + return ue(-EINVAL); + } + if (int err = wait_for_not_busy(config().busy_gpio, busy_timeout); err) { + return ue(err); + } + + // TX: [READ_REG][addrMSB][addrLSB][dummy for status][dummy * data_len] + constexpr size_t kHdr = 1U + 2U + 1U; + std::array tx{}; + std::array rx{}; + const size_t xfer_len = kHdr + data_to_host.size(); + tx[0] = RADIOLIB_SX126X_CMD_READ_REGISTER; + tx[1] = static_cast((reg >> 8) & 0xFF); + tx[2] = static_cast(reg & 0xFF); + + spi_buf tx_bufs[] = {{.buf = tx.data(), .len = xfer_len}}; + spi_buf rx_bufs[] = {{.buf = rx.data(), .len = xfer_len}}; + spi_buf_set tx_set{.buffers = tx_bufs, .count = 1}; + spi_buf_set rx_set{.buffers = rx_bufs, .count = 1}; + const int ret = spi_transceive_dt(&config().spi, &tx_set, &rx_set); + if (ret != 0) { + return ue(ret); + } + + const size_t status_idx = 1U + 2U; // after cmd+addr + const size_t data_idx = status_idx + 1; + const uint8_t status = rx[status_idx]; + std::copy(rx.begin() + static_cast(data_idx), + rx.begin() + static_cast(xfer_len), + data_to_host.begin()); + if (auto e = command_status_to_error(std::bit_cast(status)); e) { + return ue(e); + } + return unit{}; +} + +expected +LLCC68::write_register(uint16_t reg, std::span data_from_host, + timeout_ms_t busy_timeout) { + if (!spi_is_ready_dt(&config().spi)) { + return ue(-ENODEV); + } + if (data_from_host.size() > kMaxPayload) { + return ue(-EINVAL); + } + if (int err = wait_for_not_busy(config().busy_gpio, busy_timeout); err) { + return ue(err); + } + + // TX: [WRITE_REG][addrMSB][addrLSB][payload...] + std::array tx{}; + std::array rx{}; + const size_t xfer_len = 1U + 2U + data_from_host.size(); + tx[0] = RADIOLIB_SX126X_CMD_WRITE_REGISTER; + tx[1] = static_cast((reg >> 8) & 0xFF); + tx[2] = static_cast(reg & 0xFF); + std::ranges::copy(data_from_host, tx.begin() + 3); + + spi_buf tx_bufs[] = {{.buf = tx.data(), .len = xfer_len}}; + spi_buf rx_bufs[] = {{.buf = rx.data(), .len = xfer_len}}; + spi_buf_set tx_set{.buffers = tx_bufs, .count = 1}; + spi_buf_set rx_set{.buffers = rx_bufs, .count = 1}; + const int ret = spi_transceive_dt(&config().spi, &tx_set, &rx_set); + if (ret != 0) { + return ue(ret); + } + // Check status after address phase (first data byte time slot) + if (xfer_len >= 4U) { + const uint8_t status = rx[3U]; + if (auto e = command_status_to_error(std::bit_cast(status)); e) { + return ue(e); + } + } + return unit{}; +} + +expected +LLCC68::read_buffer_copy(uint8_t offset, std::span data_to_host, + timeout_ms_t busy_timeout) { + if (data_to_host.size() > MAX_BUFFER_PAYLOAD) { + return ue(-EINVAL); + } + auto rx = read_buffer_ref(offset, static_cast(data_to_host.size()), + busy_timeout); + APP_RADIO_RETURN_ERR_IGNORE_PROC_ERR(rx); + std::copy(rx->begin(), rx->end(), data_to_host.begin()); + return static_cast(rx->size()); +} + +expected, error_code> +LLCC68::read_buffer_ref(uint8_t offset, uint8_t n, timeout_ms_t busy_timeout) { + if (not spi_is_ready_dt(&config().spi)) { + return ue(-ENODEV); + } + if (n > MAX_BUFFER_PAYLOAD) { + return ue(-EINVAL); + } + if (int err = wait_for_not_busy(config().busy_gpio, busy_timeout); err) { + return ue(err); + } + + // TX: [READ_BUFFER][offset][dummy for status][dummy * data_len] + constexpr size_t kHdr = 1U + 1U + 1U; + auto &tx = data().tx_buffer; + auto &rx = data().rx_buffer; + const size_t xfer_len = kHdr + n; + tx[0] = RADIOLIB_SX126X_CMD_READ_BUFFER; + tx[1] = offset; + + spi_buf tx_bufs[] = {{.buf = tx, .len = xfer_len}}; + spi_buf rx_bufs[] = {{.buf = rx, .len = xfer_len}}; + spi_buf_set tx_set{.buffers = tx_bufs, .count = 1}; + spi_buf_set rx_set{.buffers = rx_bufs, .count = 1}; + const int ret = spi_transceive_dt(&config().spi, &tx_set, &rx_set); + if (ret != 0) { + return ue(ret); + } + const size_t status_idx = 1U + 1U; // after cmd+offset + const size_t data_idx = status_idx + 1; + const uint8_t status = rx[status_idx]; + if (auto e = command_status_to_error(std::bit_cast(status)); e) { + return ue(e); + } + // return only the data part + return std::span{rx}.subspan(data_idx, n); +} + +expected, error_code> +LLCC68::read_rx_buffer_ref(timeout_ms_t busy_timeout) { + auto r = get_rx_buffer_status(busy_timeout); + APP_RADIO_RETURN_ERR_IGNORE_PROC_ERR(r); + const auto [sz, ptr] = r.value(); + // the rx pointer would become 0x80 at first, so we need to adapt that + // behavior + const auto r_ptr = ptr - sz < DEFAULT_RX_BUFFER_ADDRESS ? ptr : ptr - sz; + return read_buffer_ref(r_ptr, sz, busy_timeout); +} + +expected +LLCC68::write_tx_buffer(std::span data_from_host) { + if (data_from_host.size() > MAX_BUFFER_PAYLOAD) { + return ue(-EINVAL); + } + auto &tx = data().tx_buffer; + tx[0] = RADIOLIB_SX126X_CMD_WRITE_BUFFER; + tx[1] = DEFAULT_TX_BUFFER_ADDRESS; + std::ranges::copy(data_from_host, tx + 2); + data().tx_xfer_size = data_from_host.size(); + return unit{}; +} + +expected LLCC68::flush_tx_buffer(timeout_ms_t busy_timeout) { + if (not spi_is_ready_dt(&config().spi)) { + return ue(-ENODEV); + } + if (int err = wait_for_not_busy(config().busy_gpio, busy_timeout); err) { + return ue(err); + } + + // TX: [WRITE_BUFFER][offset][payload...] + auto &tx = data().tx_buffer; + auto &rx = data().rx_buffer; + const size_t xfer_len = 1U + 1U + data().tx_xfer_size; + // sanity checks + if (data().tx_xfer_size == 0) { + return ue(-EINVAL); + } + if (tx[0] != RADIOLIB_SX126X_CMD_WRITE_BUFFER || + tx[1] != DEFAULT_TX_BUFFER_ADDRESS) { + return ue(-EINVAL); + } + + spi_buf tx_bufs[] = {{.buf = tx, .len = xfer_len}}; + spi_buf rx_bufs[] = {{.buf = rx, .len = xfer_len}}; + spi_buf_set tx_set{.buffers = tx_bufs, .count = 1}; + spi_buf_set rx_set{.buffers = rx_bufs, .count = 1}; + const int ret = spi_transceive_dt(&config().spi, &tx_set, &rx_set); + if (ret != 0) { + return ue(ret); + } + // Status appears after offset (at first payload slot) + if (xfer_len >= 3U) { + const uint8_t status = rx[2U]; + if (auto e = command_status_to_error(std::bit_cast(status)); e) { + return ue(e); + } + } + return unit{}; +} + +expected +LLCC68::write_buffer_immediate_mode(uint8_t offset, + std::span data_from_host, + timeout_ms_t busy_timeout) { + if (not spi_is_ready_dt(&config().spi)) { + return ue(-ENODEV); + } + if (data_from_host.size() > MAX_BUFFER_PAYLOAD) { + return ue(-EINVAL); + } + if (int err = wait_for_not_busy(config().busy_gpio, busy_timeout); err) { + return ue(err); + } + + // TX: [WRITE_BUFFER][offset][payload...] + auto &tx = data().tx_buffer; + auto &rx = data().rx_buffer; + const size_t xfer_len = 1U + 1U + data_from_host.size(); + tx[0] = RADIOLIB_SX126X_CMD_WRITE_BUFFER; + tx[1] = offset; + std::ranges::copy(data_from_host, tx + 2); + + spi_buf tx_bufs[] = {{.buf = tx, .len = xfer_len}}; + spi_buf rx_bufs[] = {{.buf = rx, .len = xfer_len}}; + spi_buf_set tx_set{.buffers = tx_bufs, .count = 1}; + spi_buf_set rx_set{.buffers = rx_bufs, .count = 1}; + const int ret = spi_transceive_dt(&config().spi, &tx_set, &rx_set); + if (ret != 0) { + return ue(ret); + } + // Status appears after offset (at first payload slot) + if (xfer_len >= 3U) { + const uint8_t status = rx[2U]; + if (auto e = command_status_to_error(std::bit_cast(status)); e) { + return ue(e); + } + } + return unit{}; +} +} // namespace app::driver::llcc68 + +// High-level helpers +namespace app::driver::llcc68 { + +expected LLCC68::get_status() { + uint8_t raw = 0; + auto r = + read_stream(RADIOLIB_SX126X_CMD_GET_STATUS, std::span{&raw, 1}); + APP_RADIO_RETURN_ERR_IGNORE_PROC_ERR(r); + return std::bit_cast(raw); +} + +expected LLCC68::dio1_irq_enable() { + int ret = gpio_pin_interrupt_configure_dt(&config().dio1_gpio, + GPIO_INT_EDGE_TO_ACTIVE); + if (ret != 0) { + return ue(ret); + } + return unit{}; +} + +void LLCC68::dio1_irq_disable() { + gpio_pin_interrupt_configure_dt(&config().dio1_gpio, GPIO_INT_DISABLE); +} + +void LLCC68::set_dio1_irq_handler(user_dio1_handler_t handler, + void *user_data) { + auto &d = data(); + d.dio1_user_handler = handler; + d.dio1_user_data = user_data; +} + +expected LLCC68::get_packet_status() { + uint8_t buf[3]{}; + auto r = read_stream(RADIOLIB_SX126X_CMD_GET_PACKET_STATUS, buf); + APP_RADIO_RETURN_ERR_IGNORE_PROC_ERR(r); + packet_status_t ps{}; + std::copy(buf, buf + 3, ps.raw); + return ps; +} + +expected LLCC68::reset() { + // Drive RESET low-high with delays per datasheet, then poll standby + if (not device_is_ready(config().reset_gpio.port)) { + return ue(-ENODEV); + } + // NOTE: low active + gpio_pin_configure_dt(&config().reset_gpio, GPIO_OUTPUT_ACTIVE); + k_msleep(2); // hold low + gpio_pin_set_dt(&config().reset_gpio, 0); + k_msleep(10); + + // try standby to verify + const int64_t start = k_uptime_get(); + const int64_t interval_ms = 500; + while ((k_uptime_get() - start) < interval_ms) { + auto r = set_standby(); + if (r.has_value()) { + return unit{}; + } + k_msleep(20); + } + return ue(-ETIMEDOUT); +} + +expected LLCC68::set_standby() { + const uint8_t data[] = {RADIOLIB_SX126X_STANDBY_RC}; + return write_stream(RADIOLIB_SX126X_CMD_SET_STANDBY, data); +} + +expected LLCC68::hal_get_chip_type() { + constexpr auto SX1262_CHIP_TYPE = "SX1262"; + constexpr auto LLCC68_CHIP_TYPE = "LLCC68"; + constexpr auto SX1261_CHIP_TYPE = "SX1261"; + + char version[16]{}; + auto version_buf = std::span{reinterpret_cast(version), + std::size(version)}; + auto r = read_register(RADIOLIB_SX126X_REG_VERSION_STRING, version_buf); + APP_RADIO_RETURN_ERR(r); + LOG_HEXDUMP_DBG(version, sizeof(version), "version dump"); + if (strncmp(version, LLCC68_CHIP_TYPE, 6) == 0) { + return ChipType::LLCC68; + } + if (strncmp(version, SX1261_CHIP_TYPE, 6) == 0) { + return ChipType::SX1261; + } + if (strncmp(version, SX1262_CHIP_TYPE, 6) == 0) { + return ChipType::SX1262; + } + return ChipType::Unknown; +} + +expected LLCC68::set_dio_irq_params(irq_params_t params) { + const uint8_t data[8] = { + params.irqMask.msb(), + params.irqMask.lsb(), + params.dio1Mask.msb(), + params.dio1Mask.lsb(), + params.dio2Mask.msb(), + params.dio2Mask.lsb(), + params.dio3Mask.msb(), + params.dio3Mask.lsb(), + }; + return write_stream(RADIOLIB_SX126X_CMD_SET_DIO_IRQ_PARAMS, data); +} + +expected LLCC68::get_irq_status() { + uint8_t buf[2]{}; + auto r = read_stream(RADIOLIB_SX126X_CMD_GET_IRQ_STATUS, buf); + APP_RADIO_RETURN_ERR_IGNORE_PROC_ERR(r); + irq_status_t st{static_cast((buf[0] << 8) | buf[1])}; + return st; +} + +expected LLCC68::clear_irq_status(irq_status_t irq) { + const uint8_t data[] = {irq.msb(), irq.lsb()}; + return write_stream(RADIOLIB_SX126X_CMD_CLEAR_IRQ_STATUS, data); +} + +static constexpr uint32_t gfsk_bitrate_to_raw(uint32_t bitrate_bps) { + constexpr uint64_t xtal_hz = 32'000'000ULL; + return static_cast((32ULL * xtal_hz) / bitrate_bps); +} + +static constexpr uint32_t freq_hz_to_raw(uint32_t freq_hz) { + return static_cast(static_cast(freq_hz) / + RADIOLIB_SX126X_FREQUENCY_STEP_SIZE); +} + +static constexpr uint8_t gfsk_crc_len_bytes(GfskCrcType crc_type) { + switch (crc_type) { + case GfskCrcType::Crc1Byte: + case GfskCrcType::Crc1ByteInverted: + return 1; + case GfskCrcType::Crc2Byte: + case GfskCrcType::Crc2ByteInverted: + return 2; + case GfskCrcType::Off: + default: + return 0; + } +} + +static constexpr std::chrono::milliseconds +calc_gfsk_time_on_air(gfsk_parameters_t params, uint8_t payload_len) { + const auto address_bits = + params.packet_params.address_filtering == GfskAddressFiltering::Disabled + ? 0U + : 8U; + const auto length_bits = + params.packet_params.packet_length_mode == GfskPacketLengthMode::Variable + ? 8U + : 0U; + const uint32_t bits = params.packet_params.preamble_bits + + params.packet_params.sync_length_bits + length_bits + + address_bits + + (static_cast(payload_len) + + gfsk_crc_len_bytes(params.packet_params.crc_type)) * + 8U; + const uint32_t ms = (bits * 1000U + params.mod_params.bitrate_bps - 1U) / + params.mod_params.bitrate_bps; + return std::chrono::milliseconds{ms}; +} + +static expected +update_register_bits(LLCC68 &radio, uint16_t reg, uint8_t mask, uint8_t value) { + uint8_t reg_value = 0; + auto r = radio.read_register(reg, std::span{®_value, 1}); + APP_RADIO_RETURN_ERR(r); + reg_value = static_cast((reg_value & ~mask) | (value & mask)); + return radio.write_register(reg, std::span{®_value, 1}); +} + +// Convenience APIs ported from ESP version +expected LLCC68::set_packet_type_lora() { + const uint8_t data[] = {RADIOLIB_SX126X_PACKET_TYPE_LORA}; + // Set packet type + auto r = write_stream(RADIOLIB_SX126X_CMD_SET_PACKET_TYPE, data); + APP_RADIO_RETURN_ERR(r); + // Set Rx/Tx fallback to STDBY_RC per reference implementation + const uint8_t fallback[] = {RADIOLIB_SX126X_RX_TX_FALLBACK_MODE_STDBY_RC}; + return write_stream(RADIOLIB_SX126X_CMD_SET_RX_TX_FALLBACK_MODE, fallback); +} + +expected LLCC68::set_packet_type_gfsk() { + const uint8_t data[] = {RADIOLIB_SX126X_PACKET_TYPE_GFSK}; + auto r = write_stream(RADIOLIB_SX126X_CMD_SET_PACKET_TYPE, data); + APP_RADIO_RETURN_ERR(r); + const uint8_t fallback[] = {RADIOLIB_SX126X_RX_TX_FALLBACK_MODE_STDBY_RC}; + return write_stream(RADIOLIB_SX126X_CMD_SET_RX_TX_FALLBACK_MODE, fallback); +} + +expected LLCC68::set_modulation_params(uint8_t sf, uint8_t bw, + uint8_t cr, + uint8_t ldro) { + if (!valid_bw(bw) || !valid_sf(bw, sf) || + !valid_cr(static_cast(cr)) || !valid_ldr_optimize(ldro)) { + return ue(-EINVAL); + } + const uint8_t data[] = {sf, bw, cr, ldro}; + return write_stream(RADIOLIB_SX126X_CMD_SET_MODULATION_PARAMS, data); +} + +expected +LLCC68::set_modulation_params(modulation_params_t mod_params) { + const uint8_t data[] = {mod_params.sf, mod_params.bw, mod_params.cr, + mod_params.ldr_optimize}; + return write_stream(RADIOLIB_SX126X_CMD_SET_MODULATION_PARAMS, data); +} + +expected +LLCC68::set_gfsk_modulation_params(gfsk_modulation_params_t mod_params) { + if (mod_params.bitrate_bps < 600 || mod_params.bitrate_bps > 300'000 || + mod_params.frequency_deviation_hz > 200'000 || + mod_params.frequency_deviation_hz + (mod_params.bitrate_bps / 2U) > + 250'000) { + return ue(-EINVAL); + } + const uint32_t bitrate = gfsk_bitrate_to_raw(mod_params.bitrate_bps); + const uint32_t fdev = freq_hz_to_raw(mod_params.frequency_deviation_hz); + const uint8_t data[] = { + static_cast((bitrate >> 16) & 0xFF), + static_cast((bitrate >> 8) & 0xFF), + static_cast(bitrate & 0xFF), + static_cast(mod_params.pulse_shape), + static_cast(mod_params.rx_bandwidth), + static_cast((fdev >> 16) & 0xFF), + static_cast((fdev >> 8) & 0xFF), + static_cast(fdev & 0xFF), + }; + auto r = write_stream(RADIOLIB_SX126X_CMD_SET_MODULATION_PARAMS, data); + APP_RADIO_RETURN_ERR(r); + r = fix_gfsk_low_bitrate(mod_params.bitrate_bps); + APP_RADIO_RETURN_ERR(r); + return fix_gfsk_tx_modulation(); +} + +expected LLCC68::set_lora_sync_word(uint16_t sync_word) { + // Ensure LoRa packet type set + uint8_t pkt_type = 0; + auto g = read_stream(RADIOLIB_SX126X_CMD_GET_PACKET_TYPE, + std::span{&pkt_type, 1}); + APP_RADIO_RETURN_ERR(g); + if (pkt_type != RADIOLIB_SX126X_PACKET_TYPE_LORA) { + return ue(-EINVAL); + } + const uint8_t data[2] = { + // MSB + static_cast((sync_word & 0xFF00) >> 8), + static_cast(sync_word & 0x00FF), + // LSB + }; + return write_register(RADIOLIB_SX126X_REG_LORA_SYNC_WORD_MSB, data); +} + +expected LLCC68::set_dio2_as_rf_switch(bool en) { + const uint8_t data[] = { + en ? static_cast(RADIOLIB_SX126X_DIO2_AS_RF_SWITCH) + : static_cast(RADIOLIB_SX126X_DIO2_AS_IRQ)}; + return write_stream(RADIOLIB_SX126X_CMD_SET_DIO2_AS_RF_SWITCH_CTRL, data); +} + +static constexpr uint32_t freq_mhz_to_raw(freq_t fmhz) { + // value = (f_xtal / 2^25) * f_rf + const auto scale = static_cast(1U << RADIOLIB_SX126X_DIV_EXPONENT); + const auto fxtal = static_cast(RADIOLIB_SX126X_CRYSTAL_FREQ); + return static_cast((fmhz * scale) / fxtal); +} + +expected LLCC68::set_rf_frequency(freq_t freq_mhz) { + if (not valid_freq(freq_mhz)) { + return ue(-EINVAL); + } + const uint32_t frf = freq_mhz_to_raw(freq_mhz); + const uint8_t data[] = { + static_cast((frf >> 24) & 0xFF), + static_cast((frf >> 16) & 0xFF), + static_cast((frf >> 8) & 0xFF), + static_cast(frf & 0xFF), + }; + return write_stream(RADIOLIB_SX126X_CMD_SET_RF_FREQUENCY, data); +} + +expected LLCC68::set_rf_frequency(uint32_t freq_raw) { + const uint8_t data[] = { + static_cast((freq_raw >> 24) & 0xFF), + static_cast((freq_raw >> 16) & 0xFF), + static_cast((freq_raw >> 8) & 0xFF), + static_cast(freq_raw & 0xFF), + }; + return write_stream(RADIOLIB_SX126X_CMD_SET_RF_FREQUENCY, data); +} + +expected +LLCC68::set_packet_params(uint16_t preamble_length, uint8_t payload_length, + uint8_t crc_type, uint8_t hdr_type, uint8_t iq_type) { + const uint8_t data[] = { + static_cast((preamble_length >> 8) & 0xFF), + static_cast(preamble_length & 0xFF), + crc_type, + payload_length, + hdr_type, + iq_type, + }; + return write_stream(RADIOLIB_SX126X_CMD_SET_PACKET_PARAMS, data); +} + +expected +LLCC68::set_gfsk_packet_params(gfsk_packet_params_t packet_params) { + if (packet_params.preamble_bits == 0 || packet_params.sync_length_bits > 64) { + return ue(-EINVAL); + } + const uint8_t data[] = { + static_cast((packet_params.preamble_bits >> 8) & 0xFF), + static_cast(packet_params.preamble_bits & 0xFF), + static_cast(packet_params.detector_length), + packet_params.sync_length_bits, + static_cast(packet_params.address_filtering), + static_cast(packet_params.packet_length_mode), + packet_params.payload_length, + static_cast(packet_params.crc_type), + static_cast(packet_params.whitening), + }; + return write_stream(RADIOLIB_SX126X_CMD_SET_PACKET_PARAMS, data); +} + +expected +LLCC68::set_gfsk_sync_word(std::span sync_word) { + if (sync_word.size() > 8) { + return ue(-EINVAL); + } + std::array data{}; + std::ranges::copy(sync_word, data.begin()); + return write_register(RADIOLIB_SX126X_REG_SYNC_WORD_0, data); +} + +expected +LLCC68::set_gfsk_address_filtering(uint8_t node_address, + uint8_t broadcast_address) { + const uint8_t data[] = {node_address, broadcast_address}; + return write_register(RADIOLIB_SX126X_REG_NODE_ADDRESS, data); +} + +expected LLCC68::set_gfsk_crc_seed(uint16_t seed) { + const uint8_t data[] = {static_cast((seed >> 8) & 0xFF), + static_cast(seed & 0xFF)}; + return write_register(RADIOLIB_SX126X_REG_CRC_INITIAL_MSB, data); +} + +expected +LLCC68::set_gfsk_crc_polynomial(uint16_t polynomial) { + const uint8_t data[] = {static_cast((polynomial >> 8) & 0xFF), + static_cast(polynomial & 0xFF)}; + return write_register(RADIOLIB_SX126X_REG_CRC_POLYNOMIAL_MSB, data); +} + +expected LLCC68::set_gfsk_whitening_seed(uint16_t seed) { + uint8_t msb = 0; + auto r = read_register(RADIOLIB_SX126X_REG_WHITENING_INITIAL_MSB, + std::span{&msb, 1}); + APP_RADIO_RETURN_ERR(r); + msb = static_cast((msb & 0xFE) | ((seed >> 8) & 0x01)); + r = write_register(RADIOLIB_SX126X_REG_WHITENING_INITIAL_MSB, + std::span{&msb, 1}); + APP_RADIO_RETURN_ERR(r); + const uint8_t lsb = static_cast(seed & 0xFF); + return write_register(RADIOLIB_SX126X_REG_WHITENING_INITIAL_LSB, + std::span{&lsb, 1}); +} + +expected LLCC68::fix_inverted_iq(uint8_t iq_config) { + uint8_t iqCfg = 0; + auto r = read_register(RADIOLIB_SX126X_REG_IQ_CONFIG, + std::span{&iqCfg, 1}); + APP_RADIO_RETURN_ERR(r); + if (iq_config == LoRaIQType::IQ_INVERTED) { + iqCfg &= ~0x04; + } else { + iqCfg |= 0x04; + } + return write_register(RADIOLIB_SX126X_REG_IQ_CONFIG, + std::span{&iqCfg, 1}); +} + +expected LLCC68::set_tx_params(int8_t pwr, + LoRaTxRampTime ramp_time) { + const uint8_t data[] = {static_cast(pwr), + static_cast(ramp_time)}; + return write_stream(RADIOLIB_SX126X_CMD_SET_TX_PARAMS, data); +} + +expected LLCC68::set_tx_params(tx_params_t params) { + return set_tx_params(params.power, params.ramp_time); +} + +expected LLCC68::hal_set_output_power(ChipType chip, + tx_params_t params) { + if (not valid_ramp_time(params.ramp_time)) { + return ue(-EINVAL); + } + if (not valid_tx_power(params.power)) { + return ue(-EINVAL); + } + if (chip == ChipType::Unknown) { + return ue(-ENODEV); + } + + int8_t pwr = params.power; + expected r; +#if not(CONFIG_LLCC68_ALWAYS_USE_SX1262_HIGH_PA) + if (chip == ChipType::SX1262 || chip == ChipType::LLCC68) { +#endif + // SX1262/LLCC68 High Power profile + if (pwr == 22) { + r = set_pa_config(pa_setting_t::Default22dBmHp()); + APP_RADIO_RETURN_ERR(r); + return set_tx_params(22, params.ramp_time); + } + if (pwr == 20) { + r = set_pa_config(pa_setting_t::Default20dBmHp()); + APP_RADIO_RETURN_ERR(r); + return set_tx_params(22, params.ramp_time); + } + if (pwr == 17) { + r = set_pa_config(pa_setting_t::Default17dBmHp()); + APP_RADIO_RETURN_ERR(r); + return set_tx_params(22, params.ramp_time); + } + if (pwr == 14) { + r = set_pa_config(pa_setting_t::Default14dBmHp()); + APP_RADIO_RETURN_ERR(r); + return set_tx_params(22, params.ramp_time); + } + { + // default + r = set_pa_config(pa_setting_t::DefaultHp()); + APP_RADIO_RETURN_ERR(r); + return set_tx_params(params); + } +#if not(CONFIG_LLCC68_ALWAYS_USE_SX1262_HIGH_PA) + } + // SX1261 Low Power + pwr = std::min(pwr, 14); + if (pwr == 14) { + r = set_pa_config(pa_setting_t::Default14dBmLp()); + APP_RADIO_RETURN_ERR(r); + return set_tx_params(14, params.ramp_time); + } + if (pwr == 10) { + r = set_pa_config(pa_setting_t::Default10dBmLp()); + APP_RADIO_RETURN_ERR(r); + return set_tx_params(13, params.ramp_time); + } + { + r = set_pa_config(pa_setting_t::DefaultLp()); + APP_RADIO_RETURN_ERR(r); + return set_tx_params(pwr, params.ramp_time); + } +#endif +} + +expected LLCC68::set_pa_config(pa_setting_t settings) { + const uint8_t data[] = {settings.pa_duty_cycle, settings.hp_max, + settings.device_sel, + RADIOLIB_SX126X_PA_CONFIG_PA_LUT}; + return write_stream(RADIOLIB_SX126X_CMD_SET_PA_CONFIG, data); +} + +expected LLCC68::fix_pa_clamping(bool enable) { + uint8_t clamp = 0; + auto r = read_register(RADIOLIB_SX126X_REG_TX_CLAMP_CONFIG, + std::span{&clamp, 1}); + APP_RADIO_RETURN_ERR(r); + if (enable) { + clamp |= 0x1E; + } else { + clamp = (clamp & ~0x1E) | 0x08; + } + return write_register(RADIOLIB_SX126X_REG_TX_CLAMP_CONFIG, + std::span{&clamp, 1}); +} + +expected LLCC68::set_cad_params(cad_params_t params) { + uint8_t data[7]; + data[0] = static_cast(params.symbol_num); + data[1] = params.det_peak; + data[2] = params.det_min; + data[3] = static_cast(params.exit_mode); + data[4] = static_cast((params.timeout >> 16) & 0xFF); + data[5] = static_cast((params.timeout >> 8) & 0xFF); + data[6] = static_cast(params.timeout & 0xFF); + + return write_stream(RADIOLIB_SX126X_CMD_SET_CAD_PARAMS, data); +} + +expected LLCC68::set_cad() { + auto dummy = std::span{}; + return write_stream(RADIOLIB_SX126X_CMD_SET_CAD, dummy); +} + +expected LLCC68::set_tx(uint32_t timeout) { + const uint8_t data[] = { + static_cast((timeout >> 16) & 0xFF), + static_cast((timeout >> 8) & 0xFF), + static_cast(timeout & 0xFF), + }; + return write_stream(RADIOLIB_SX126X_CMD_SET_TX, data); +} + +expected LLCC68::set_rx(uint32_t timeout) { + const uint8_t data[] = { + static_cast((timeout >> 16) & 0xFF), + static_cast((timeout >> 8) & 0xFF), + static_cast(timeout & 0xFF), + }; + return write_stream(RADIOLIB_SX126X_CMD_SET_RX, data); +} + +expected LLCC68::set_sleep(sleep_config_t config) { + auto c = *reinterpret_cast(&config); + const uint8_t data[] = {c}; + return write_stream(RADIOLIB_SX126X_CMD_SET_SLEEP, data); +} + +expected LLCC68::set_tx_continuous_wave() { + auto dummy = std::span{}; + // const uint8_t dummy[] = {RADIOLIB_SX126X_CMD_NOP}; + return write_stream(RADIOLIB_SX126X_CMD_SET_TX_CONTINUOUS_WAVE, dummy); +} + +expected LLCC68::set_tx_infinite_preamble() { + auto dummy = std::span{}; + // const uint8_t dummy[] = {RADIOLIB_SX126X_CMD_NOP}; + return write_stream(RADIOLIB_SX126X_CMD_SET_TX_INFINITE_PREAMBLE, dummy); +} + +expected LLCC68::fix_sensitivity(LoRaBandwidth bw) { + uint8_t sensitivityConfig{}; + read_register(RADIOLIB_SX126X_REG_SENSITIVITY_CONFIG, + std::span{&sensitivityConfig, 1}); + + // bit 2 + constexpr auto HACK_MASK = 0x04; + + if (bw == LoRaBandwidth::BW_500_0) { + sensitivityConfig &= ~HACK_MASK; + } else { + sensitivityConfig |= HACK_MASK; + } + return write_register(RADIOLIB_SX126X_REG_SENSITIVITY_CONFIG, + std::span{&sensitivityConfig, 1}); +} + +expected LLCC68::fix_gfsk_tx_modulation() { + return update_register_bits(*this, RADIOLIB_SX126X_REG_SENSITIVITY_CONFIG, + 0x04, 0x04); +} + +expected LLCC68::fix_gfsk_low_bitrate(uint32_t bitrate_bps) { + auto r = update_register_bits(*this, RADIOLIB_SX126X_REG_GFSK_BITRATE_CONFIG, + 0x18, 0x08); + APP_RADIO_RETURN_ERR(r); + r = update_register_bits(*this, RADIOLIB_SX126X_REG_RSSI_AVG_WINDOW, 0x1C, + 0x00); + APP_RADIO_RETURN_ERR(r); + r = update_register_bits(*this, RADIOLIB_SX126X_REG_GFSK_SYNC_CONFIG, 0x10, + 0x10); + APP_RADIO_RETURN_ERR(r); + r = update_register_bits(*this, RADIOLIB_SX126X_REG_GFSK_LOW_DATA_RATE_CONFIG, + 0x70, 0x00); + APP_RADIO_RETURN_ERR(r); + + if (bitrate_bps == 1200) { + return update_register_bits(*this, RADIOLIB_SX126X_REG_GFSK_SYNC_CONFIG, + 0x10, 0x00); + } + + if (bitrate_bps != 600) { + return unit{}; + } + + r = update_register_bits(*this, RADIOLIB_SX126X_REG_GFSK_BITRATE_CONFIG, 0x18, + 0x18); + APP_RADIO_RETURN_ERR(r); + r = update_register_bits(*this, RADIOLIB_SX126X_REG_RSSI_AVG_WINDOW, 0x1C, + 0x04); + APP_RADIO_RETURN_ERR(r); + r = update_register_bits(*this, RADIOLIB_SX126X_REG_GFSK_SYNC_CONFIG, 0x10, + 0x00); + APP_RADIO_RETURN_ERR(r); + return update_register_bits( + *this, RADIOLIB_SX126X_REG_GFSK_LOW_DATA_RATE_CONFIG, 0x70, 0x50); +} + +expected +LLCC68::set_buffer_base_address(uint8_t tx_base_addr, uint8_t rx_base_addr) { + const uint8_t data[] = {tx_base_addr, rx_base_addr}; + return write_stream(RADIOLIB_SX126X_CMD_SET_BUFFER_BASE_ADDRESS, data); +} + +expected +LLCC68::get_rx_buffer_status(LLCC68::timeout_ms_t busy_timeout) { + uint8_t rx_buf_status[] = {0, 0}; + auto r = read_stream(RADIOLIB_SX126X_CMD_GET_RX_BUFFER_STATUS, + std::span{rx_buf_status, 2}, busy_timeout); + APP_RADIO_RETURN_ERR_IGNORE_PROC_ERR(r); + return LLCC68::rx_buffer_status_t{rx_buf_status[0], rx_buf_status[1]}; +} + +expected LLCC68::random_number_gen() { + uint8_t buf[4]{}; + auto r = read_register(RADIOLIB_SX126X_REG_RANDOM_NUMBER_0, + std::span{buf}); + APP_RADIO_RETURN_ERR_IGNORE_PROC_ERR(r); + uint32_t rn = (static_cast(buf[0]) << 24) | + (static_cast(buf[1]) << 16) | + (static_cast(buf[2]) << 8) | + (static_cast(buf[3])); + return rn; +} + +expected LLCC68::hal_modem_init(lora_parameters_t params) { + APP_RADIO_RETURN_ERR_CTX(set_standby(), "modem_init::standby"); + auto chip_type_ = hal_get_chip_type(); + APP_RADIO_RETURN_ERR_CTX(chip_type_, "modem_init::get_chip_type"); + auto chip_type = *chip_type_; + LOG_INF("chip type=%s(%d)", to_str(chip_type), static_cast(chip_type)); + if (chip_type == ChipType::Unknown) { + return ue(-ENODEV); + } + APP_RADIO_RETURN_ERR_CTX(set_packet_type_lora(), + "modem_init::set_packet_type"); + APP_RADIO_RETURN_ERR_CTX( + set_modulation_params(params.mod_params.sf, params.mod_params.bw, + params.mod_params.cr, + params.mod_params.ldr_optimize), + "modem_init::set_modulation_params"); + APP_RADIO_RETURN_ERR_CTX(set_lora_sync_word(params.sync_word), + "modem_init::set_lora_sync_word"); + APP_RADIO_RETURN_ERR_CTX(set_dio2_as_rf_switch(false), + "modem_init::set_dio2_as_rf_switch"); + APP_RADIO_RETURN_ERR_CTX(set_rf_frequency(params.frequency_mhz), + "modem_init::set_rf_frequency"); + APP_RADIO_RETURN_ERR_CTX(set_packet_params(params.packet_params.preamble_len, + params.packet_params.payload_len, + params.packet_params.crc_type, + params.packet_params.hdr_type, + params.packet_params.iq_type), + "modem_init::set_packet_params"); + APP_RADIO_RETURN_ERR_CTX(hal_set_output_power(chip_type, params.tx_params), + "modem_init::hal_set_output_power"); + APP_RADIO_RETURN_ERR_CTX(set_standby(), "modem_init::standby"); + return {}; +} + +expected +LLCC68::hal_gfsk_modem_init(gfsk_parameters_t params) { + if (params.sync_word_length > params.sync_word.size() || + params.packet_params.sync_length_bits > params.sync_word.size() * 8) { + return ue(-EINVAL); + } + + APP_RADIO_RETURN_ERR_CTX(set_standby(), "gfsk_init::standby"); + auto chip_type_ = hal_get_chip_type(); + APP_RADIO_RETURN_ERR_CTX(chip_type_, "gfsk_init::get_chip_type"); + auto chip_type = *chip_type_; + LOG_INF("chip type=%s(%d)", to_str(chip_type), static_cast(chip_type)); + if (chip_type == ChipType::Unknown) { + return ue(-ENODEV); + } + + APP_RADIO_RETURN_ERR_CTX(set_packet_type_gfsk(), + "gfsk_init::set_packet_type"); + APP_RADIO_RETURN_ERR_CTX(set_gfsk_modulation_params(params.mod_params), + "gfsk_init::set_modulation_params"); + APP_RADIO_RETURN_ERR_CTX( + set_gfsk_sync_word(std::span{params.sync_word.data(), + params.sync_word_length}), + "gfsk_init::set_sync_word"); + APP_RADIO_RETURN_ERR_CTX(set_gfsk_crc_seed(params.crc_seed), + "gfsk_init::set_crc_seed"); + APP_RADIO_RETURN_ERR_CTX(set_gfsk_crc_polynomial(params.crc_polynomial), + "gfsk_init::set_crc_polynomial"); + APP_RADIO_RETURN_ERR_CTX(set_gfsk_whitening_seed(params.whitening_seed), + "gfsk_init::set_whitening_seed"); + if (params.packet_params.address_filtering != + GfskAddressFiltering::Disabled) { + APP_RADIO_RETURN_ERR_CTX( + set_gfsk_address_filtering(params.node_address.value_or(0), + params.broadcast_address.value_or(0)), + "gfsk_init::set_address_filtering"); + } + APP_RADIO_RETURN_ERR_CTX(set_dio2_as_rf_switch(false), + "gfsk_init::set_dio2_as_rf_switch"); + APP_RADIO_RETURN_ERR_CTX(set_rf_frequency(params.frequency_mhz), + "gfsk_init::set_rf_frequency"); + APP_RADIO_RETURN_ERR_CTX(set_gfsk_packet_params(params.packet_params), + "gfsk_init::set_packet_params"); + APP_RADIO_RETURN_ERR_CTX(hal_set_output_power(chip_type, params.tx_params), + "gfsk_init::hal_set_output_power"); + APP_RADIO_RETURN_ERR_CTX(set_standby(), "gfsk_init::standby"); + return {}; +} + +expected +LLCC68::hal_async_flush(lora_parameters_t params) { + auto status = get_status(); + APP_RADIO_RETURN_ERR_CTX(status, "tx::status"); + if (status->chip_mode == ChipMode::TX) { + return ue(Errc::InvalidState); + } + APP_RADIO_RETURN_ERR_CTX(set_standby(), "tx::standby"); + APP_RADIO_RETURN_ERR_CTX(set_packet_params(params.packet_params.preamble_len, + data().tx_xfer_size, + params.packet_params.crc_type, + params.packet_params.hdr_type, + params.packet_params.iq_type), + "tx::set_packet_params"); + APP_RADIO_RETURN_ERR_CTX(set_buffer_base_address(), + "tx::set_buffer_base_address"); + APP_RADIO_RETURN_ERR_CTX(flush_tx_buffer(), "tx::flush_tx_buffer"); + APP_RADIO_RETURN_ERR_CTX(fix_sensitivity(params.mod_params.bw), + "tx::fix_sensitivity"); + constexpr auto irq_mask = + RADIOLIB_SX126X_IRQ_TX_DONE | RADIOLIB_SX126X_IRQ_TIMEOUT; + constexpr auto irq_params = irq_params_t{ + {irq_mask}, + {irq_mask}, // DIO1 + {RADIOLIB_SX126X_IRQ_NONE}, // DIO2 + {RADIOLIB_SX126X_IRQ_NONE}, // DIO3 + }; + APP_RADIO_RETURN_ERR_CTX(set_dio_irq_params(irq_params), + "tx::set_dio_irq_params"); + APP_RADIO_RETURN_ERR_CTX(clear_irq_status(irq_params.irqMask), + "tx::clear_irq_status"); + tx_rx_en_pin_set(TxRxPinState::TX); + APP_RADIO_RETURN_ERR_CTX(set_tx(), "tx::set_tx_params"); + auto air = calc_time_on_air( + data().tx_xfer_size, params.mod_params.sf, params.mod_params.bw, + params.mod_params.cr, params.packet_params.preamble_len, + params.packet_params.hdr_type, params.packet_params.crc_type); + auto air_estimated = + std::chrono::duration_cast(air); + return transmit_result{this, air_estimated}; +} + +expected +LLCC68::hal_async_transmit(std::span data, + lora_parameters_t params) { + auto r = write_tx_buffer(data); + APP_RADIO_RETURN_ERR_CTX(r, "transmit::write_tx_buffer"); + return hal_async_flush(params); +} + +expected +LLCC68::hal_gfsk_async_flush(gfsk_parameters_t params) { + auto status = get_status(); + APP_RADIO_RETURN_ERR_CTX(status, "gfsk_tx::status"); + if (status->chip_mode == ChipMode::TX) { + return ue(Errc::InvalidState); + } + if (data().tx_xfer_size == 0 || data().tx_xfer_size > MAX_BUFFER_PAYLOAD) { + return ue(-EINVAL); + } + + APP_RADIO_RETURN_ERR_CTX(set_standby(), "gfsk_tx::standby"); + auto packet_params = params.packet_params; + packet_params.payload_length = static_cast(data().tx_xfer_size); + APP_RADIO_RETURN_ERR_CTX(set_gfsk_packet_params(packet_params), + "gfsk_tx::set_packet_params"); + APP_RADIO_RETURN_ERR_CTX(set_buffer_base_address(), + "gfsk_tx::set_buffer_base_address"); + APP_RADIO_RETURN_ERR_CTX(flush_tx_buffer(), "gfsk_tx::flush_tx_buffer"); + APP_RADIO_RETURN_ERR_CTX(fix_gfsk_tx_modulation(), + "gfsk_tx::fix_tx_modulation"); + constexpr auto irq_mask = + RADIOLIB_SX126X_IRQ_TX_DONE | RADIOLIB_SX126X_IRQ_TIMEOUT; + constexpr auto irq_params = irq_params_t{ + {irq_mask}, + {irq_mask}, + {RADIOLIB_SX126X_IRQ_NONE}, + {RADIOLIB_SX126X_IRQ_NONE}, + }; + APP_RADIO_RETURN_ERR_CTX(set_dio_irq_params(irq_params), + "gfsk_tx::set_dio_irq_params"); + APP_RADIO_RETURN_ERR_CTX(clear_irq_status(irq_params.irqMask), + "gfsk_tx::clear_irq_status"); + tx_rx_en_pin_set(TxRxPinState::TX); + APP_RADIO_RETURN_ERR_CTX(set_tx(), "gfsk_tx::set_tx"); + auto air_estimated = + calc_gfsk_time_on_air(params, static_cast(data().tx_xfer_size)); + return transmit_result{this, air_estimated}; +} + +expected +LLCC68::hal_gfsk_async_transmit(std::span data, + gfsk_parameters_t params) { + auto r = write_tx_buffer(data); + APP_RADIO_RETURN_ERR_CTX(r, "gfsk_transmit::write_tx_buffer"); + return hal_gfsk_async_flush(params); +} + +expected LLCC68::hal_async_rx(lora_parameters_t params) { + APP_RADIO_RETURN_ERR_CTX(set_standby(), "rx::standby"); + APP_RADIO_RETURN_ERR_CTX(set_buffer_base_address(), + "rx::set_buffer_base_address"); + + APP_RADIO_RETURN_ERR_CTX( + set_modulation_params(params.mod_params.sf, params.mod_params.bw, + params.mod_params.cr, + params.mod_params.ldr_optimize), + "rx::set_modulation_params"); + APP_RADIO_RETURN_ERR_CTX(set_packet_params(params.packet_params.preamble_len, + params.packet_params.payload_len, + params.packet_params.crc_type, + params.packet_params.hdr_type, + params.packet_params.iq_type), + "rx::set_packet_params"); + + constexpr auto irq_mask = RADIOLIB_SX126X_IRQ_RX_DEFAULT; + constexpr auto irq_params = irq_params_t{ + {irq_mask}, + {irq_mask}, // DIO1 + {RADIOLIB_SX126X_IRQ_NONE}, // DIO2 + {RADIOLIB_SX126X_IRQ_NONE}, // DIO3 + }; + APP_RADIO_RETURN_ERR_CTX(set_dio_irq_params(irq_params), + "rx::set_dio_irq_params"); + APP_RADIO_RETURN_ERR_CTX(clear_irq_status(irq_params.irqMask), + "rx::clear_irq_status"); + + tx_rx_en_pin_set(TxRxPinState::RX); + APP_RADIO_RETURN_ERR_CTX(set_rx(), "rx::set_rx"); + return unit{}; +} + +expected LLCC68::hal_gfsk_async_rx(gfsk_parameters_t params) { + APP_RADIO_RETURN_ERR_CTX(set_standby(), "gfsk_rx::standby"); + APP_RADIO_RETURN_ERR_CTX(set_buffer_base_address(), + "gfsk_rx::set_buffer_base_address"); + APP_RADIO_RETURN_ERR_CTX(set_gfsk_modulation_params(params.mod_params), + "gfsk_rx::set_modulation_params"); + APP_RADIO_RETURN_ERR_CTX(set_gfsk_packet_params(params.packet_params), + "gfsk_rx::set_packet_params"); + + constexpr auto irq_mask = RADIOLIB_SX126X_IRQ_RX_DEFAULT; + constexpr auto irq_params = irq_params_t{ + {irq_mask}, + {irq_mask}, + {RADIOLIB_SX126X_IRQ_NONE}, + {RADIOLIB_SX126X_IRQ_NONE}, + }; + APP_RADIO_RETURN_ERR_CTX(set_dio_irq_params(irq_params), + "gfsk_rx::set_dio_irq_params"); + APP_RADIO_RETURN_ERR_CTX(clear_irq_status(irq_params.irqMask), + "gfsk_rx::clear_irq_status"); + + tx_rx_en_pin_set(TxRxPinState::RX); + APP_RADIO_RETURN_ERR_CTX(set_rx(), "gfsk_rx::set_rx"); + return unit{}; +} + +// utils member function + +std::string irq_status_t::to_string() const { + std::ostringstream oss; + if (bits.tx_done) { + oss << "t"; + } + if (bits.rx_done) { + oss << "r"; + } + if (bits.preamble_detected) { + oss << "p"; + } + if (bits.sync_word_valid) { + oss << "s"; + } + if (bits.header_valid) { + oss << "h"; + } + if (bits.header_err) { + oss << "H"; + } + if (bits.crc_err) { + oss << "C"; + } + if (bits.cad_done) { + oss << "c"; + } + if (bits.cad_detected) { + oss << "d"; + } + if (bits.timeout) { + oss << "T"; + } + if (bits.lr_fhss_hop) { + oss << "f"; + } + return oss.str(); +} + +error_code transmit_result::post_action() { + constexpr auto irq_mask = + RADIOLIB_SX126X_IRQ_TX_DONE | RADIOLIB_SX126X_IRQ_TIMEOUT; + auto r = self->clear_irq_status({irq_mask}); + if (not r) { + return r.error(); + } + return {}; +} + +void lora_parameters_t::log(const char *tag) const { + const char *ldr_str = + (mod_params.ldr_optimize == LoRaLowDataRateType::LDR_ON ? "ON" : "OFF"); + const char *crc_str = + (packet_params.crc_type == LoRaCrcType::CRC_ON ? "ON" : "OFF"); + const char *hdr_str = + (packet_params.hdr_type == LoRaHeaderType::HEADER_EXPLICIT ? "Explicit" + : "Implicit"); + const char *iq_str = + (packet_params.iq_type == LoRaIQType::IQ_INVERTED ? "Inverted" + : "Standard"); + const char *sync_str = "UNKNOWN"; // default + if (sync_word == LoRaSyncWord::SYNC_WORD_PRIVATE) { + sync_str = "PRIVATE"; + } else if (sync_word == LoRaSyncWord::SYNC_WORD_PUBLIC) { + sync_str = "PUBLIC"; + } else { + // keep default + } + + LOG_INF("lora_parameters_t(%s)" + "{" + "BW=%s SF=%u CR=%s LDR=%s " + "PREAMBLE=%u CRC=%s HDR=%s IQ=%s " + "POWER=%d RAMP=%s FREQ=%dkHz SYNC_WORD=%s(0x%04X)" + "}", + tag, to_str(mod_params.bw), mod_params.sf, to_str(mod_params.cr), + ldr_str, packet_params.preamble_len, crc_str, hdr_str, iq_str, + tx_params.power, to_str(tx_params.ramp_time), + static_cast(frequency_mhz * 1000), sync_str, + static_cast(sync_word)); +} + +void gfsk_parameters_t::log(const char *tag) const { + LOG_INF( + "gfsk_parameters_t(%s)" + "{" + "BR=%u FDEV=%u RXBW=0x%02X PULSE=0x%02X " + "PREAMBLE_BITS=%u DET=0x%02X SYNC_BITS=%u ADDR=0x%02X LEN_MODE=0x%02X " + "PAYLOAD=%u CRC=0x%02X WHITE=0x%02X POWER=%d RAMP=%s FREQ=%dkHz" + "}", + tag, static_cast(mod_params.bitrate_bps), + static_cast(mod_params.frequency_deviation_hz), + static_cast(mod_params.rx_bandwidth), + static_cast(mod_params.pulse_shape), + packet_params.preamble_bits, + static_cast(packet_params.detector_length), + packet_params.sync_length_bits, + static_cast(packet_params.address_filtering), + static_cast(packet_params.packet_length_mode), + packet_params.payload_length, + static_cast(packet_params.crc_type), + static_cast(packet_params.whitening), tx_params.power, + to_str(tx_params.ramp_time), static_cast(frequency_mhz * 1000)); +} +} // namespace app::driver::llcc68 diff --git a/src/priv_radiolib_definitions.hpp b/src/priv_radiolib_definitions.hpp new file mode 100644 index 0000000..6797fbd --- /dev/null +++ b/src/priv_radiolib_definitions.hpp @@ -0,0 +1,445 @@ +/** + * @file priv_radiolib_definitions.hpp + * @brief private definitions from the radiolib library; + * @note should only be included in the llcc68 driver implementation files + */ + +#ifndef ADC87656_291B_49BB_A611_52533D666023 +#define ADC87656_291B_49BB_A611_52533D666023 +#include + +// SX126X physical layer properties +constexpr auto RADIOLIB_SX126X_FREQUENCY_STEP_SIZE = 0.9536743164; +constexpr auto RADIOLIB_SX126X_MAX_PACKET_LENGTH = 255; +constexpr auto RADIOLIB_SX126X_CRYSTAL_FREQ = 32.0; +constexpr auto RADIOLIB_SX126X_DIV_EXPONENT = 25; + +// SX126X SPI commands +// operational modes commands +constexpr uint8_t RADIOLIB_SX126X_CMD_NOP = 0x00; +constexpr uint8_t RADIOLIB_SX126X_CMD_SET_SLEEP = 0x84; +constexpr uint8_t RADIOLIB_SX126X_CMD_SET_STANDBY = 0x80; +constexpr uint8_t RADIOLIB_SX126X_CMD_SET_FS = 0xC1; +constexpr uint8_t RADIOLIB_SX126X_CMD_SET_TX = 0x83; +constexpr uint8_t RADIOLIB_SX126X_CMD_SET_RX = 0x82; +constexpr uint8_t RADIOLIB_SX126X_CMD_STOP_TIMER_ON_PREAMBLE = 0x9F; +constexpr uint8_t RADIOLIB_SX126X_CMD_SET_RX_DUTY_CYCLE = 0x94; +constexpr uint8_t RADIOLIB_SX126X_CMD_SET_CAD = 0xC5; +constexpr uint8_t RADIOLIB_SX126X_CMD_SET_TX_CONTINUOUS_WAVE = 0xD1; +constexpr uint8_t RADIOLIB_SX126X_CMD_SET_TX_INFINITE_PREAMBLE = 0xD2; +constexpr uint8_t RADIOLIB_SX126X_CMD_SET_REGULATOR_MODE = 0x96; +constexpr uint8_t RADIOLIB_SX126X_CMD_CALIBRATE = 0x89; +constexpr uint8_t RADIOLIB_SX126X_CMD_CALIBRATE_IMAGE = 0x98; +constexpr uint8_t RADIOLIB_SX126X_CMD_SET_PA_CONFIG = 0x95; +constexpr uint8_t RADIOLIB_SX126X_CMD_SET_RX_TX_FALLBACK_MODE = 0x93; + +// register and buffer access commands +constexpr uint8_t RADIOLIB_SX126X_CMD_WRITE_REGISTER = 0x0D; +constexpr uint8_t RADIOLIB_SX126X_CMD_READ_REGISTER = 0x1D; +constexpr uint8_t RADIOLIB_SX126X_CMD_WRITE_BUFFER = 0x0E; +constexpr uint8_t RADIOLIB_SX126X_CMD_READ_BUFFER = 0x1E; + +// DIO and IRQ control +constexpr uint8_t RADIOLIB_SX126X_CMD_SET_DIO_IRQ_PARAMS = 0x08; +constexpr uint8_t RADIOLIB_SX126X_CMD_GET_IRQ_STATUS = 0x12; +constexpr uint8_t RADIOLIB_SX126X_CMD_CLEAR_IRQ_STATUS = 0x02; +constexpr uint8_t RADIOLIB_SX126X_CMD_SET_DIO2_AS_RF_SWITCH_CTRL = 0x9D; +constexpr uint8_t RADIOLIB_SX126X_CMD_SET_DIO3_AS_TCXO_CTRL = 0x97; + +// RF, modulation and packet commands +constexpr uint8_t RADIOLIB_SX126X_CMD_SET_RF_FREQUENCY = 0x86; +constexpr uint8_t RADIOLIB_SX126X_CMD_SET_PACKET_TYPE = 0x8A; +constexpr uint8_t RADIOLIB_SX126X_CMD_GET_PACKET_TYPE = 0x11; +constexpr uint8_t RADIOLIB_SX126X_CMD_SET_TX_PARAMS = 0x8E; +constexpr uint8_t RADIOLIB_SX126X_CMD_SET_MODULATION_PARAMS = 0x8B; +constexpr uint8_t RADIOLIB_SX126X_CMD_SET_PACKET_PARAMS = 0x8C; +constexpr uint8_t RADIOLIB_SX126X_CMD_SET_CAD_PARAMS = 0x88; +constexpr uint8_t RADIOLIB_SX126X_CMD_SET_BUFFER_BASE_ADDRESS = 0x8F; +constexpr uint8_t RADIOLIB_SX126X_CMD_SET_LORA_SYMB_NUM_TIMEOUT = 0x0A; + +// status commands +constexpr uint8_t RADIOLIB_SX126X_CMD_GET_STATUS = 0xC0; +constexpr uint8_t RADIOLIB_SX126X_CMD_GET_RSSI_INST = 0x15; +constexpr uint8_t RADIOLIB_SX126X_CMD_GET_RX_BUFFER_STATUS = 0x13; +constexpr uint8_t RADIOLIB_SX126X_CMD_GET_PACKET_STATUS = 0x14; +constexpr uint8_t RADIOLIB_SX126X_CMD_GET_DEVICE_ERRORS = 0x17; +constexpr uint8_t RADIOLIB_SX126X_CMD_CLEAR_DEVICE_ERRORS = 0x07; +constexpr uint8_t RADIOLIB_SX126X_CMD_GET_STATS = 0x10; +constexpr uint8_t RADIOLIB_SX126X_CMD_RESET_STATS = 0x00; + +constexpr uint8_t RADIOLIB_SX126X_CMD_PRAM_UPDATE = 0xD9; +constexpr uint8_t RADIOLIB_SX126X_CMD_SET_LBT_SCAN_PARAMS = 0x9A; +constexpr uint8_t RADIOLIB_SX126X_CMD_SET_SPECTR_SCAN_PARAMS = 0x9B; + +#define RADIOLIB_SX126X_REG_LR_FHSS_NUM_SYMBOLS_FREQX_MSB(X) (0x0388 + (X) * 6) +#define RADIOLIB_SX126X_REG_LR_FHSS_NUM_SYMBOLS_FREQX_LSB(X) (0x0389 + (X) * 6) +#define RADIOLIB_SX126X_REG_LR_FHSS_FREQX_0(X) (0x038A + (X) * 6) +#define RADIOLIB_SX126X_REG_LR_FHSS_FREQX_1(X) (0x038B + (X) * 6) +#define RADIOLIB_SX126X_REG_LR_FHSS_FREQX_2(X) (0x038C + (X) * 6) +#define RADIOLIB_SX126X_REG_LR_FHSS_FREQX_3(X) (0x038D + (X) * 6) + +// SX126X register map +constexpr uint16_t RADIOLIB_SX126X_REG_RX_GAIN_RETENTION_0 = 0x029F; // SX1268 datasheet v1.1, section 9.6 +constexpr uint16_t RADIOLIB_SX126X_REG_RX_GAIN_RETENTION_1 = 0x02A0; // SX1268 datasheet v1.1, section 9.6 +constexpr uint16_t RADIOLIB_SX126X_REG_RX_GAIN_RETENTION_2 = 0x02A1; // SX1268 datasheet v1.1, section 9.6 +constexpr uint16_t RADIOLIB_SX126X_REG_VERSION_STRING = 0x0320; +constexpr uint16_t RADIOLIB_SX126X_REG_HOPPING_ENABLE = 0x0385; +constexpr uint16_t RADIOLIB_SX126X_REG_LR_FHSS_PACKET_LENGTH = 0x0386; +constexpr uint16_t RADIOLIB_SX126X_REG_LR_FHSS_NUM_HOPPING_BLOCKS = 0x0387; +constexpr uint16_t RADIOLIB_SX126X_REG_SPECTRAL_SCAN_RESULT = 0x0401; +constexpr uint16_t RADIOLIB_SX126X_REG_DIOX_OUT_ENABLE = 0x0580; +constexpr uint16_t RADIOLIB_SX126X_REG_DIOX_DRIVE_STRENGTH = 0x0582; +constexpr uint16_t RADIOLIB_SX126X_REG_DIOX_IN_ENABLE = 0x0583; +constexpr uint16_t RADIOLIB_SX126X_REG_DIOX_PULL_UP_CTRL = 0x0584; +constexpr uint16_t RADIOLIB_SX126X_REG_DIOX_PULL_DOWN_CTRL = 0x0585; +constexpr uint16_t RADIOLIB_SX126X_REG_TX_BITBANG_ENABLE_0 = 0x0587; +constexpr uint16_t RADIOLIB_SX126X_REG_PATCH_UPDATE_ENABLE = 0x0610; +constexpr uint16_t RADIOLIB_SX126X_REG_TX_BITBANG_ENABLE_1 = 0x0680; +constexpr uint16_t RADIOLIB_SX126X_REG_GFSK_LOW_DATA_RATE_CONFIG = 0x06AC; +constexpr uint16_t RADIOLIB_SX126X_REG_WHITENING_INITIAL_MSB = 0x06B8; +constexpr uint16_t RADIOLIB_SX126X_REG_WHITENING_INITIAL_LSB = 0x06B9; +constexpr uint16_t RADIOLIB_SX126X_REG_RX_TX_PLD_LEN = 0x06BB; +constexpr uint16_t RADIOLIB_SX126X_REG_CRC_INITIAL_MSB = 0x06BC; +constexpr uint16_t RADIOLIB_SX126X_REG_CRC_INITIAL_LSB = 0x06BD; +constexpr uint16_t RADIOLIB_SX126X_REG_CRC_POLYNOMIAL_MSB = 0x06BE; +constexpr uint16_t RADIOLIB_SX126X_REG_CRC_POLYNOMIAL_LSB = 0x06BF; +// GFSK used +constexpr uint16_t RADIOLIB_SX126X_REG_SYNC_WORD_0 = 0x06C0; +constexpr uint16_t RADIOLIB_SX126X_REG_SYNC_WORD_1 = 0x06C1; +constexpr uint16_t RADIOLIB_SX126X_REG_SYNC_WORD_2 = 0x06C2; +constexpr uint16_t RADIOLIB_SX126X_REG_SYNC_WORD_3 = 0x06C3; +constexpr uint16_t RADIOLIB_SX126X_REG_SYNC_WORD_4 = 0x06C4; +constexpr uint16_t RADIOLIB_SX126X_REG_SYNC_WORD_5 = 0x06C5; +constexpr uint16_t RADIOLIB_SX126X_REG_SYNC_WORD_6 = 0x06C6; +constexpr uint16_t RADIOLIB_SX126X_REG_SYNC_WORD_7 = 0x06C7; +constexpr uint16_t RADIOLIB_SX126X_REG_NODE_ADDRESS = 0x06CD; +constexpr uint16_t RADIOLIB_SX126X_REG_BROADCAST_ADDRESS = 0x06CE; +constexpr uint16_t RADIOLIB_SX126X_REG_GFSK_BITRATE_CONFIG = 0x06D1; +constexpr uint16_t RADIOLIB_SX126X_REG_PAYLOAD_LENGTH = 0x0702; +constexpr uint16_t RADIOLIB_SX126X_REG_PACKET_PARAMS = 0x0704; +constexpr uint16_t RADIOLIB_SX126X_REG_LORA_SYNC_TIMEOUT = 0x0706; +constexpr uint16_t RADIOLIB_SX126X_REG_IQ_CONFIG = 0x0736; +constexpr uint16_t RADIOLIB_SX126X_REG_LORA_SYNC_WORD_MSB = 0x0740; +constexpr uint16_t RADIOLIB_SX126X_REG_LORA_SYNC_WORD_LSB = 0x0741; +constexpr uint16_t RADIOLIB_SX126X_REG_FREQ_ERROR = 0x076B; +constexpr uint16_t RADIOLIB_SX126X_REG_SPECTRAL_SCAN_STATUS = 0x07CD; +constexpr uint16_t RADIOLIB_SX126X_REG_RX_ADDR_PTR = 0x0803; +constexpr uint16_t RADIOLIB_SX126X_REG_RANDOM_NUMBER_0 = 0x0819; +constexpr uint16_t RADIOLIB_SX126X_REG_RANDOM_NUMBER_1 = 0x081A; +constexpr uint16_t RADIOLIB_SX126X_REG_RANDOM_NUMBER_2 = 0x081B; +constexpr uint16_t RADIOLIB_SX126X_REG_RANDOM_NUMBER_3 = 0x081C; +constexpr uint16_t RADIOLIB_SX126X_REG_SENSITIVITY_CONFIG = 0x0889; // SX1268 datasheet v1.1, section 15.1 +constexpr uint16_t RADIOLIB_SX126X_REG_RF_FREQUENCY_0 = 0x088B; +constexpr uint16_t RADIOLIB_SX126X_REG_RF_FREQUENCY_1 = 0x088C; +constexpr uint16_t RADIOLIB_SX126X_REG_RF_FREQUENCY_2 = 0x088D; +constexpr uint16_t RADIOLIB_SX126X_REG_RF_FREQUENCY_3 = 0x088E; +constexpr uint16_t RADIOLIB_SX126X_REG_RSSI_AVG_WINDOW = 0x089B; +constexpr uint16_t RADIOLIB_SX126X_REG_GFSK_SYNC_CONFIG = 0x08B8; +constexpr uint16_t RADIOLIB_SX126X_REG_RX_GAIN = 0x08AC; +constexpr uint16_t RADIOLIB_SX126X_REG_TX_CLAMP_CONFIG = 0x08D8; +constexpr uint16_t RADIOLIB_SX126X_REG_ANA_LNA = 0x08E2; +constexpr uint16_t RADIOLIB_SX126X_REG_LNA_CAP_TUNE_N = 0x08E3; +constexpr uint16_t RADIOLIB_SX126X_REG_LNA_CAP_TUNE_P = 0x08E4; +constexpr uint16_t RADIOLIB_SX126X_REG_ANA_MIXER = 0x08E5; +constexpr uint16_t RADIOLIB_SX126X_REG_OCP_CONFIGURATION = 0x08E7; +constexpr uint16_t RADIOLIB_SX126X_REG_RTC_CTRL = 0x0902; +constexpr uint16_t RADIOLIB_SX126X_REG_XTA_TRIM = 0x0911; +constexpr uint16_t RADIOLIB_SX126X_REG_XTB_TRIM = 0x0912; +constexpr uint16_t RADIOLIB_SX126X_REG_DIO3_OUT_VOLTAGE_CTRL = 0x0920; +constexpr uint16_t RADIOLIB_SX126X_REG_EVENT_MASK = 0x0944; +constexpr uint16_t RADIOLIB_SX126X_REG_PATCH_MEMORY_BASE = 0x8000; + +// SX126X SPI command variables +// RADIOLIB_SX126X_CMD_SET_SLEEP MSB LSB DESCRIPTION +constexpr uint8_t RADIOLIB_SX126X_SLEEP_START_COLD = 0b00000000; // 2 2 sleep mode: cold start, configuration is lost (default) +constexpr uint8_t RADIOLIB_SX126X_SLEEP_START_WARM = 0b00000100; // 2 2 warm start, configuration is retained +constexpr uint8_t RADIOLIB_SX126X_SLEEP_RTC_OFF = 0b00000000; // 0 0 wake on RTC timeout: disabled +constexpr uint8_t RADIOLIB_SX126X_SLEEP_RTC_ON = 0b00000001; // 0 0 enabled + +// RADIOLIB_SX126X_CMD_SET_STANDBY +constexpr uint8_t RADIOLIB_SX126X_STANDBY_RC = 0x00; // 7 0 standby mode: 13 MHz RC oscillator +constexpr uint8_t RADIOLIB_SX126X_STANDBY_XOSC = 0x01; // 7 0 32 MHz crystal oscillator + +// RADIOLIB_SX126X_CMD_SET_RX +constexpr uint32_t RADIOLIB_SX126X_RX_TIMEOUT_NONE = 0x000000; // 23 0 Rx timeout duration: no timeout (Rx single mode) +constexpr uint32_t RADIOLIB_SX126X_RX_TIMEOUT_INF = 0xFFFFFF; // 23 0 infinite (Rx continuous mode) + +// RADIOLIB_SX126X_CMD_SET_TX +constexpr uint32_t RADIOLIB_SX126X_TX_TIMEOUT_NONE = 0x000000; // 23 0 Tx timeout duration: no timeout (Tx single mode) + +// RADIOLIB_SX126X_CMD_STOP_TIMER_ON_PREAMBLE +constexpr uint8_t RADIOLIB_SX126X_STOP_ON_PREAMBLE_OFF = 0x00; // 7 0 stop timer on: sync word or header (default) +constexpr uint8_t RADIOLIB_SX126X_STOP_ON_PREAMBLE_ON = 0x01; // 7 0 preamble detection + +// RADIOLIB_SX126X_CMD_SET_REGULATOR_MODE +constexpr uint8_t RADIOLIB_SX126X_REGULATOR_LDO = 0x00; // 7 0 set regulator mode: LDO (default) +constexpr uint8_t RADIOLIB_SX126X_REGULATOR_DC_DC = 0x01; // 7 0 DC-DC + +// RADIOLIB_SX126X_CMD_CALIBRATE +constexpr uint8_t RADIOLIB_SX126X_CALIBRATE_IMAGE_OFF = 0b00000000; // 6 6 image calibration: disabled +constexpr uint8_t RADIOLIB_SX126X_CALIBRATE_IMAGE_ON = 0b01000000; // 6 6 enabled +constexpr uint8_t RADIOLIB_SX126X_CALIBRATE_ADC_BULK_P_OFF = 0b00000000; // 5 5 ADC bulk P calibration: disabled +constexpr uint8_t RADIOLIB_SX126X_CALIBRATE_ADC_BULK_P_ON = 0b00100000; // 5 5 enabled +constexpr uint8_t RADIOLIB_SX126X_CALIBRATE_ADC_BULK_N_OFF = 0b00000000; // 4 4 ADC bulk N calibration: disabled +constexpr uint8_t RADIOLIB_SX126X_CALIBRATE_ADC_BULK_N_ON = 0b00010000; // 4 4 enabled +constexpr uint8_t RADIOLIB_SX126X_CALIBRATE_ADC_PULSE_OFF = 0b00000000; // 3 3 ADC pulse calibration: disabled +constexpr uint8_t RADIOLIB_SX126X_CALIBRATE_ADC_PULSE_ON = 0b00001000; // 3 3 enabled +constexpr uint8_t RADIOLIB_SX126X_CALIBRATE_PLL_OFF = 0b00000000; // 2 2 PLL calibration: disabled +constexpr uint8_t RADIOLIB_SX126X_CALIBRATE_PLL_ON = 0b00000100; // 2 2 enabled +constexpr uint8_t RADIOLIB_SX126X_CALIBRATE_RC13M_OFF = 0b00000000; // 1 1 13 MHz RC osc. calibration: disabled +constexpr uint8_t RADIOLIB_SX126X_CALIBRATE_RC13M_ON = 0b00000010; // 1 1 enabled +constexpr uint8_t RADIOLIB_SX126X_CALIBRATE_RC64K_OFF = 0b00000000; // 0 0 64 kHz RC osc. calibration: disabled +constexpr uint8_t RADIOLIB_SX126X_CALIBRATE_RC64K_ON = 0b00000001; // 0 0 enabled +constexpr uint8_t RADIOLIB_SX126X_CALIBRATE_ALL = 0b01111111; // 6 0 calibrate all blocks + +// RADIOLIB_SX126X_CMD_CALIBRATE_IMAGE +constexpr uint8_t RADIOLIB_SX126X_CAL_IMG_430_MHZ_1 = 0x6B; +constexpr uint8_t RADIOLIB_SX126X_CAL_IMG_430_MHZ_2 = 0x6F; +constexpr uint8_t RADIOLIB_SX126X_CAL_IMG_470_MHZ_1 = 0x75; +constexpr uint8_t RADIOLIB_SX126X_CAL_IMG_470_MHZ_2 = 0x81; +constexpr uint8_t RADIOLIB_SX126X_CAL_IMG_779_MHZ_1 = 0xC1; +constexpr uint8_t RADIOLIB_SX126X_CAL_IMG_779_MHZ_2 = 0xC5; +constexpr uint8_t RADIOLIB_SX126X_CAL_IMG_863_MHZ_1 = 0xD7; +constexpr uint8_t RADIOLIB_SX126X_CAL_IMG_863_MHZ_2 = 0xDB; +constexpr uint8_t RADIOLIB_SX126X_CAL_IMG_902_MHZ_1 = 0xE1; +constexpr uint8_t RADIOLIB_SX126X_CAL_IMG_902_MHZ_2 = 0xE9; + +// RADIOLIB_SX126X_CMD_SET_PA_CONFIG +constexpr uint8_t RADIOLIB_SX126X_PA_CONFIG_HP_MAX = 0x07; +constexpr uint8_t RADIOLIB_SX126X_PA_CONFIG_PA_LUT = 0x01; +constexpr uint8_t RADIOLIB_SX126X_PA_CONFIG_SX1262_8 = 0x00; + +// RADIOLIB_SX126X_CMD_SET_RX_TX_FALLBACK_MODE +constexpr uint8_t RADIOLIB_SX126X_RX_TX_FALLBACK_MODE_FS = 0x40; // 7 0 after Rx/Tx go to: FS mode +constexpr uint8_t RADIOLIB_SX126X_RX_TX_FALLBACK_MODE_STDBY_XOSC = 0x30; // 7 0 standby with crystal oscillator +constexpr uint8_t RADIOLIB_SX126X_RX_TX_FALLBACK_MODE_STDBY_RC = 0x20; // 7 0 standby with RC oscillator (default) + +// RADIOLIB_SX126X_CMD_SET_DIO_IRQ_PARAMS +#define RADIOLIB_SX126X_IRQ_LR_FHSS_HOP 0b0100000000000000 // 14 14 PA ramped up during LR-FHSS hop +#define RADIOLIB_SX126X_IRQ_TIMEOUT 0b0000001000000000 // 9 9 Rx or Tx timeout +#define RADIOLIB_SX126X_IRQ_CAD_DETECTED 0b0000000100000000 // 8 8 channel activity detected +#define RADIOLIB_SX126X_IRQ_CAD_DONE 0b0000000010000000 // 7 7 channel activity detection finished +#define RADIOLIB_SX126X_IRQ_CRC_ERR 0b0000000001000000 // 6 6 wrong CRC received +#define RADIOLIB_SX126X_IRQ_HEADER_ERR 0b0000000000100000 // 5 5 LoRa header CRC error +#define RADIOLIB_SX126X_IRQ_HEADER_VALID 0b0000000000010000 // 4 4 valid LoRa header received +#define RADIOLIB_SX126X_IRQ_SYNC_WORD_VALID 0b0000000000001000 // 3 3 valid sync word detected +#define RADIOLIB_SX126X_IRQ_RADIOLIB_PREAMBLE_DETECTED 0b0000000000000100 // 2 2 preamble detected +#define RADIOLIB_SX126X_IRQ_RX_DONE 0b0000000000000010 // 1 1 packet received +#define RADIOLIB_SX126X_IRQ_TX_DONE 0b0000000000000001 // 0 0 packet transmission completed +#define RADIOLIB_SX126X_IRQ_RX_DEFAULT 0b0000001001100010 // 14 0 default for Rx (RX_DONE, TIMEOUT, CRC_ERR and HEADER_ERR) +#define RADIOLIB_SX126X_IRQ_ALL 0b0100001111111111 // 14 0 all interrupts +#define RADIOLIB_SX126X_IRQ_NONE 0b0000000000000000 // 14 0 no interrupts + +// RADIOLIB_SX126X_CMD_SET_DIO2_AS_RF_SWITCH_CTRL +#define RADIOLIB_SX126X_DIO2_AS_IRQ 0x00 // 7 0 DIO2 configuration: IRQ +#define RADIOLIB_SX126X_DIO2_AS_RF_SWITCH 0x01 // 7 0 RF switch control + +// RADIOLIB_SX126X_CMD_SET_DIO3_AS_TCXO_CTRL +#define RADIOLIB_SX126X_DIO3_OUTPUT_1_6 0x00 // 7 0 DIO3 voltage output for TCXO: 1.6 V +#define RADIOLIB_SX126X_DIO3_OUTPUT_1_7 0x01 // 7 0 1.7 V +#define RADIOLIB_SX126X_DIO3_OUTPUT_1_8 0x02 // 7 0 1.8 V +#define RADIOLIB_SX126X_DIO3_OUTPUT_2_2 0x03 // 7 0 2.2 V +#define RADIOLIB_SX126X_DIO3_OUTPUT_2_4 0x04 // 7 0 2.4 V +#define RADIOLIB_SX126X_DIO3_OUTPUT_2_7 0x05 // 7 0 2.7 V +#define RADIOLIB_SX126X_DIO3_OUTPUT_3_0 0x06 // 7 0 3.0 V +#define RADIOLIB_SX126X_DIO3_OUTPUT_3_3 0x07 // 7 0 3.3 V + +// RADIOLIB_SX126X_CMD_SET_PACKET_TYPE +constexpr uint8_t RADIOLIB_SX126X_PACKET_TYPE_GFSK = 0x00; // 7 0 packet type: GFSK +constexpr uint8_t RADIOLIB_SX126X_PACKET_TYPE_LORA = 0x01; // 7 0 LoRa +constexpr uint8_t RADIOLIB_SX126X_PACKET_TYPE_LR_FHSS = 0x03; // 7 0 LR-FHSS + +// RADIOLIB_SX126X_CMD_SET_TX_PARAMS +#define RADIOLIB_SX126X_PA_RAMP_10U 0x00 // 7 0 ramp time: 10 us +#define RADIOLIB_SX126X_PA_RAMP_20U 0x01 // 7 0 20 us +#define RADIOLIB_SX126X_PA_RAMP_40U 0x02 // 7 0 40 us +#define RADIOLIB_SX126X_PA_RAMP_80U 0x03 // 7 0 80 us +#define RADIOLIB_SX126X_PA_RAMP_200U 0x04 // 7 0 200 us +#define RADIOLIB_SX126X_PA_RAMP_800U 0x05 // 7 0 800 us +#define RADIOLIB_SX126X_PA_RAMP_1700U 0x06 // 7 0 1700 us +#define RADIOLIB_SX126X_PA_RAMP_3400U 0x07 // 7 0 3400 us + +// RADIOLIB_SX126X_CMD_SET_MODULATION_PARAMS +constexpr uint8_t RADIOLIB_SX126X_GFSK_FILTER_NONE = 0x00; // 7 0 GFSK filter: none +constexpr uint8_t RADIOLIB_SX126X_GFSK_FILTER_GAUSS_0_3 = 0x08; // 7 0 Gaussian, BT = 0.3 +constexpr uint8_t RADIOLIB_SX126X_GFSK_FILTER_GAUSS_0_5 = 0x09; // 7 0 Gaussian, BT = 0.5 +constexpr uint8_t RADIOLIB_SX126X_GFSK_FILTER_GAUSS_0_7 = 0x0A; // 7 0 Gaussian, BT = 0.7 +constexpr uint8_t RADIOLIB_SX126X_GFSK_FILTER_GAUSS_1 = 0x0B; // 7 0 Gaussian, BT = 1 +constexpr uint8_t RADIOLIB_SX126X_GFSK_RX_BW_4_8 = 0x1F; // 7 0 GFSK Rx bandwidth: 4.8 kHz +constexpr uint8_t RADIOLIB_SX126X_GFSK_RX_BW_5_8 = 0x17; // 7 0 5.8 kHz +constexpr uint8_t RADIOLIB_SX126X_GFSK_RX_BW_7_3 = 0x0F; // 7 0 7.3 kHz +constexpr uint8_t RADIOLIB_SX126X_GFSK_RX_BW_9_7 = 0x1E; // 7 0 9.7 kHz +constexpr uint8_t RADIOLIB_SX126X_GFSK_RX_BW_11_7 = 0x16; // 7 0 11.7 kHz +constexpr uint8_t RADIOLIB_SX126X_GFSK_RX_BW_14_6 = 0x0E; // 7 0 14.6 kHz +constexpr uint8_t RADIOLIB_SX126X_GFSK_RX_BW_19_5 = 0x1D; // 7 0 19.5 kHz +constexpr uint8_t RADIOLIB_SX126X_GFSK_RX_BW_23_4 = 0x15; // 7 0 23.4 kHz +constexpr uint8_t RADIOLIB_SX126X_GFSK_RX_BW_29_3 = 0x0D; // 7 0 29.3 kHz +constexpr uint8_t RADIOLIB_SX126X_GFSK_RX_BW_39_0 = 0x1C; // 7 0 39.0 kHz +constexpr uint8_t RADIOLIB_SX126X_GFSK_RX_BW_46_9 = 0x14; // 7 0 46.9 kHz +constexpr uint8_t RADIOLIB_SX126X_GFSK_RX_BW_58_6 = 0x0C; // 7 0 58.6 kHz +constexpr uint8_t RADIOLIB_SX126X_GFSK_RX_BW_78_2 = 0x1B; // 7 0 78.2 kHz +constexpr uint8_t RADIOLIB_SX126X_GFSK_RX_BW_93_8 = 0x13; // 7 0 93.8 kHz +constexpr uint8_t RADIOLIB_SX126X_GFSK_RX_BW_117_3 = 0x0B; // 7 0 117.3 kHz +constexpr uint8_t RADIOLIB_SX126X_GFSK_RX_BW_156_2 = 0x1A; // 7 0 156.2 kHz +constexpr uint8_t RADIOLIB_SX126X_GFSK_RX_BW_187_2 = 0x12; // 7 0 187.2 kHz +constexpr uint8_t RADIOLIB_SX126X_GFSK_RX_BW_234_3 = 0x0A; // 7 0 234.3 kHz +constexpr uint8_t RADIOLIB_SX126X_GFSK_RX_BW_312_0 = 0x19; // 7 0 312.0 kHz +constexpr uint8_t RADIOLIB_SX126X_GFSK_RX_BW_373_6 = 0x11; // 7 0 373.6 kHz +constexpr uint8_t RADIOLIB_SX126X_GFSK_RX_BW_467_0 = 0x09; // 7 0 467.0 kHz +constexpr uint8_t RADIOLIB_SX126X_LORA_BW_7_8 = 0x00; // 7 0 LoRa bandwidth: 7.8 kHz +constexpr uint8_t RADIOLIB_SX126X_LORA_BW_10_4 = 0x08; // 7 0 10.4 kHz +constexpr uint8_t RADIOLIB_SX126X_LORA_BW_15_6 = 0x01; // 7 0 15.6 kHz +constexpr uint8_t RADIOLIB_SX126X_LORA_BW_20_8 = 0x09; // 7 0 20.8 kHz +constexpr uint8_t RADIOLIB_SX126X_LORA_BW_31_25 = 0x02; // 7 0 31.25 kHz +constexpr uint8_t RADIOLIB_SX126X_LORA_BW_41_7 = 0x0A; // 7 0 41.7 kHz +constexpr uint8_t RADIOLIB_SX126X_LORA_BW_62_5 = 0x03; // 7 0 62.5 kHz +constexpr uint8_t RADIOLIB_SX126X_LORA_BW_125_0 = 0x04; // 7 0 125.0 kHz +constexpr uint8_t RADIOLIB_SX126X_LORA_BW_250_0 = 0x05; // 7 0 250.0 kHz +constexpr uint8_t RADIOLIB_SX126X_LORA_BW_500_0 = 0x06; // 7 0 500.0 kHz +constexpr uint8_t RADIOLIB_SX126X_LORA_CR_4_5 = 0x01; // 7 0 LoRa coding rate: 4/5 +constexpr uint8_t RADIOLIB_SX126X_LORA_CR_4_6 = 0x02; // 7 0 4/6 +constexpr uint8_t RADIOLIB_SX126X_LORA_CR_4_7 = 0x03; // 7 0 4/7 +constexpr uint8_t RADIOLIB_SX126X_LORA_CR_4_8 = 0x04; // 7 0 4/8 +constexpr uint8_t RADIOLIB_SX126X_LORA_LOW_DATA_RATE_OPTIMIZE_OFF = 0x00; // 7 0 LoRa low data rate optimization: disabled +constexpr uint8_t RADIOLIB_SX126X_LORA_LOW_DATA_RATE_OPTIMIZE_ON = 0x01; // 7 0 enabled + +// RADIOLIB_SX126X_CMD_SET_PACKET_PARAMS +constexpr uint8_t RADIOLIB_SX126X_GFSK_PREAMBLE_DETECT_OFF = 0x00; // 7 0 GFSK minimum preamble length before reception starts: detector disabled +constexpr uint8_t RADIOLIB_SX126X_GFSK_PREAMBLE_DETECT_8 = 0x04; // 7 0 8 bits +constexpr uint8_t RADIOLIB_SX126X_GFSK_PREAMBLE_DETECT_16 = 0x05; // 7 0 16 bits +constexpr uint8_t RADIOLIB_SX126X_GFSK_PREAMBLE_DETECT_24 = 0x06; // 7 0 24 bits +constexpr uint8_t RADIOLIB_SX126X_GFSK_PREAMBLE_DETECT_32 = 0x07; // 7 0 32 bits +constexpr uint8_t RADIOLIB_SX126X_GFSK_ADDRESS_FILT_OFF = 0x00; // 7 0 GFSK address filtering: disabled +constexpr uint8_t RADIOLIB_SX126X_GFSK_ADDRESS_FILT_NODE = 0x01; // 7 0 node only +constexpr uint8_t RADIOLIB_SX126X_GFSK_ADDRESS_FILT_NODE_BROADCAST = 0x02; // 7 0 node and broadcast +constexpr uint8_t RADIOLIB_SX126X_GFSK_PACKET_FIXED = 0x00; // 7 0 GFSK packet type: fixed (payload length known in advance to both sides) +constexpr uint8_t RADIOLIB_SX126X_GFSK_PACKET_VARIABLE = 0x01; // 7 0 variable (payload length added to packet) +constexpr uint8_t RADIOLIB_SX126X_GFSK_CRC_OFF = 0x01; // 7 0 GFSK packet CRC: disabled +constexpr uint8_t RADIOLIB_SX126X_GFSK_CRC_1_BYTE = 0x00; // 7 0 1 byte +constexpr uint8_t RADIOLIB_SX126X_GFSK_CRC_2_BYTE = 0x02; // 7 0 2 byte +constexpr uint8_t RADIOLIB_SX126X_GFSK_CRC_1_BYTE_INV = 0x04; // 7 0 1 byte, inverted +constexpr uint8_t RADIOLIB_SX126X_GFSK_CRC_2_BYTE_INV = 0x06; // 7 0 2 byte, inverted +constexpr uint8_t RADIOLIB_SX126X_GFSK_WHITENING_OFF = 0x00; // 7 0 GFSK data whitening: disabled +constexpr uint8_t RADIOLIB_SX126X_GFSK_WHITENING_ON = 0x01; // 7 0 enabled +constexpr uint8_t RADIOLIB_SX126X_LORA_HEADER_EXPLICIT = 0x00; // 7 0 LoRa header mode: explicit +constexpr uint8_t RADIOLIB_SX126X_LORA_HEADER_IMPLICIT = 0x01; // 7 0 implicit +constexpr uint8_t RADIOLIB_SX126X_LORA_CRC_OFF = 0x00; // 7 0 LoRa CRC mode: disabled +constexpr uint8_t RADIOLIB_SX126X_LORA_CRC_ON = 0x01; // 7 0 enabled +constexpr uint8_t RADIOLIB_SX126X_LORA_IQ_STANDARD = 0x00; // 7 0 LoRa IQ setup: standard +constexpr uint8_t RADIOLIB_SX126X_LORA_IQ_INVERTED = 0x01; // 7 0 inverted + +// RADIOLIB_SX126X_CMD_SET_CAD_PARAMS +#define RADIOLIB_SX126X_CAD_ON_1_SYMB 0x00 // 7 0 number of symbols used for CAD: 1 +#define RADIOLIB_SX126X_CAD_ON_2_SYMB 0x01 // 7 0 2 +#define RADIOLIB_SX126X_CAD_ON_4_SYMB 0x02 // 7 0 4 +#define RADIOLIB_SX126X_CAD_ON_8_SYMB 0x03 // 7 0 8 +#define RADIOLIB_SX126X_CAD_ON_16_SYMB 0x04 // 7 0 16 +#define RADIOLIB_SX126X_CAD_GOTO_STDBY 0x00 // 7 0 after CAD is done, always go to STDBY_RC mode +#define RADIOLIB_SX126X_CAD_GOTO_RX 0x01 // 7 0 after CAD is done, go to Rx mode if activity is detected +#define RADIOLIB_SX126X_CAD_PARAM_DEFAULT 0xFF // 7 0 used by the CAD methods to specify default parameter value +#define RADIOLIB_SX126X_CAD_PARAM_DET_MIN 10 // 7 0 default detMin CAD parameter + +// RADIOLIB_SX126X_CMD_GET_STATUS +#define RADIOLIB_SX126X_STATUS_MODE_STDBY_RC 0b00100000 // 6 4 current chip mode: STDBY_RC +#define RADIOLIB_SX126X_STATUS_MODE_STDBY_XOSC 0b00110000 // 6 4 STDBY_XOSC +#define RADIOLIB_SX126X_STATUS_MODE_FS 0b01000000 // 6 4 FS +#define RADIOLIB_SX126X_STATUS_MODE_RX 0b01010000 // 6 4 RX +#define RADIOLIB_SX126X_STATUS_MODE_TX 0b01100000 // 6 4 TX +#define RADIOLIB_SX126X_STATUS_DATA_AVAILABLE 0b00000100 // 3 1 command status: packet received and data can be retrieved +#define RADIOLIB_SX126X_STATUS_CMD_TIMEOUT 0b00000110 // 3 1 SPI command timed out +#define RADIOLIB_SX126X_STATUS_CMD_INVALID 0b00001000 // 3 1 invalid SPI command +#define RADIOLIB_SX126X_STATUS_CMD_FAILED 0b00001010 // 3 1 SPI command failed to execute +#define RADIOLIB_SX126X_STATUS_TX_DONE 0b00001100 // 3 1 packet transmission done +#define RADIOLIB_SX126X_STATUS_SPI_FAILED 0b11111111 // 7 0 SPI transaction failed + +// RADIOLIB_SX126X_CMD_GET_PACKET_STATUS +#define RADIOLIB_SX126X_GFSK_RX_STATUS_PREAMBLE_ERR 0b10000000 // 7 7 GFSK Rx status: preamble error +#define RADIOLIB_SX126X_GFSK_RX_STATUS_SYNC_ERR 0b01000000 // 6 6 sync word error +#define RADIOLIB_SX126X_GFSK_RX_STATUS_ADRS_ERR 0b00100000 // 5 5 address error +#define RADIOLIB_SX126X_GFSK_RX_STATUS_CRC_ERR 0b00010000 // 4 4 CRC error +#define RADIOLIB_SX126X_GFSK_RX_STATUS_LENGTH_ERR 0b00001000 // 3 3 length error +#define RADIOLIB_SX126X_GFSK_RX_STATUS_ABORT_ERR 0b00000100 // 2 2 abort error +#define RADIOLIB_SX126X_GFSK_RX_STATUS_PACKET_RECEIVED 0b00000010 // 2 2 packet received +#define RADIOLIB_SX126X_GFSK_RX_STATUS_PACKET_SENT 0b00000001 // 2 2 packet sent + +// RADIOLIB_SX126X_CMD_GET_DEVICE_ERRORS +#define RADIOLIB_SX126X_PA_RAMP_ERR 0b100000000 // 8 8 device errors: PA ramping failed +#define RADIOLIB_SX126X_PLL_LOCK_ERR 0b001000000 // 6 6 PLL failed to lock +#define RADIOLIB_SX126X_XOSC_START_ERR 0b000100000 // 5 5 crystal oscillator failed to start +#define RADIOLIB_SX126X_IMG_CALIB_ERR 0b000010000 // 4 4 image calibration failed +#define RADIOLIB_SX126X_ADC_CALIB_ERR 0b000001000 // 3 3 ADC calibration failed +#define RADIOLIB_SX126X_PLL_CALIB_ERR 0b000000100 // 2 2 PLL calibration failed +#define RADIOLIB_SX126X_RC13M_CALIB_ERR 0b000000010 // 1 1 RC13M calibration failed +#define RADIOLIB_SX126X_RC64K_CALIB_ERR 0b000000001 // 0 0 RC64K calibration failed + +// RADIOLIB_SX126X_CMD_SET_LBT_SCAN_PARAMS + RADIOLIB_SX126X_CMD_SET_SPECTR_SCAN_PARAMS +#define RADIOLIB_SX126X_SCAN_INTERVAL_7_68_US 10 // 7 0 RSSI reading interval: 7.68 us +#define RADIOLIB_SX126X_SCAN_INTERVAL_8_20_US 11 // 7 0 8.20 us +#define RADIOLIB_SX126X_SCAN_INTERVAL_8_68_US 12 // 7 0 8.68 us + +// SX126X SPI register variables +// RADIOLIB_SX126X_REG_HOPPING_ENABLE +#define RADIOLIB_SX126X_HOPPING_ENABLED 0b00000001 // 0 0 intra-packet hopping for LR-FHSS: enabled +#define RADIOLIB_SX126X_HOPPING_DISABLED 0b00000000 // 0 0 (disabled) + +// RADIOLIB_SX126X_REG_LORA_SYNC_WORD_MSB + LSB +constexpr uint8_t RADIOLIB_SX126X_SYNC_WORD_PUBLIC = 0x34; // actually 0x3444 NOTE: The low nibbles in each byte (0x_4_4) are masked out since apparently, they're reserved. +constexpr uint8_t RADIOLIB_SX126X_SYNC_WORD_PRIVATE = 0x12; // actually 0x1424 You couldn't make this up if you tried. + +// RADIOLIB_SX126X_REG_TX_BITBANG_ENABLE_1 +#define RADIOLIB_SX126X_TX_BITBANG_1_DISABLED 0b00000000 // 6 4 Tx bitbang: disabled (default) +#define RADIOLIB_SX126X_TX_BITBANG_1_ENABLED 0b00010000 // 6 4 enabled + +// RADIOLIB_SX126X_REG_TX_BITBANG_ENABLE_0 +#define RADIOLIB_SX126X_TX_BITBANG_0_DISABLED 0b00000000 // 3 0 Tx bitbang: disabled (default) +#define RADIOLIB_SX126X_TX_BITBANG_0_ENABLED 0b00001100 // 3 0 enabled + +// RADIOLIB_SX126X_REG_DIOX_OUT_ENABLE +#define RADIOLIB_SX126X_DIO1_OUT_DISABLED 0b00000010 // 1 1 DIO1 output: disabled +#define RADIOLIB_SX126X_DIO1_OUT_ENABLED 0b00000000 // 1 1 enabled +#define RADIOLIB_SX126X_DIO2_OUT_DISABLED 0b00000100 // 2 2 DIO2 output: disabled +#define RADIOLIB_SX126X_DIO2_OUT_ENABLED 0b00000000 // 2 2 enabled +#define RADIOLIB_SX126X_DIO3_OUT_DISABLED 0b00001000 // 3 3 DIO3 output: disabled +#define RADIOLIB_SX126X_DIO3_OUT_ENABLED 0b00000000 // 3 3 enabled + +// RADIOLIB_SX126X_REG_DIOX_IN_ENABLE +#define RADIOLIB_SX126X_DIO1_IN_DISABLED 0b00000000 // 1 1 DIO1 input: disabled +#define RADIOLIB_SX126X_DIO1_IN_ENABLED 0b00000010 // 1 1 enabled +#define RADIOLIB_SX126X_DIO2_IN_DISABLED 0b00000000 // 2 2 DIO2 input: disabled +#define RADIOLIB_SX126X_DIO2_IN_ENABLED 0b00000100 // 2 2 enabled +#define RADIOLIB_SX126X_DIO3_IN_DISABLED 0b00000000 // 3 3 DIO3 input: disabled +#define RADIOLIB_SX126X_DIO3_IN_ENABLED 0b00001000 // 3 3 enabled + +// RADIOLIB_SX126X_REG_RX_GAIN +#define RADIOLIB_SX126X_RX_GAIN_BOOSTED 0x96 // 7 0 Rx gain: boosted +#define RADIOLIB_SX126X_RX_GAIN_POWER_SAVING 0x94 // 7 0 power saving +#define RADIOLIB_SX126X_RX_GAIN_SPECTRAL_SCAN 0xCB // 7 0 spectral scan + +// RADIOLIB_SX126X_REG_PATCH_UPDATE_ENABLE +#define RADIOLIB_SX126X_PATCH_UPDATE_DISABLED 0b00000000 // 4 4 patch update: disabled +#define RADIOLIB_SX126X_PATCH_UPDATE_ENABLED 0b00010000 // 4 4 enabled + +// RADIOLIB_SX126X_REG_SPECTRAL_SCAN_STATUS +#define RADIOLIB_SX126X_SPECTRAL_SCAN_NONE 0x00 // 7 0 spectral scan status: none +#define RADIOLIB_SX126X_SPECTRAL_SCAN_ONGOING 0x0F // 7 0 ongoing +#define RADIOLIB_SX126X_SPECTRAL_SCAN_ABORTED 0xF0 // 7 0 aborted +#define RADIOLIB_SX126X_SPECTRAL_SCAN_COMPLETED 0xFF // 7 0 completed + +// RADIOLIB_SX126X_REG_RSSI_AVG_WINDOW +#define RADIOLIB_SX126X_SPECTRAL_SCAN_WINDOW_DEFAULT (0x05 << 2) // 7 0 default RSSI average window + +// RADIOLIB_SX126X_REG_ANA_LNA +#define RADIOLIB_SX126X_LNA_RNG_DISABLED 0b00000001 // 0 0 random number: disabled +#define RADIOLIB_SX126X_LNA_RNG_ENABLED 0b00000000 // 0 0 enabled + +// RADIOLIB_SX126X_REG_ANA_MIXER +#define RADIOLIB_SX126X_MIXER_RNG_DISABLED 0b00000001 // 7 7 random number: disabled +#define RADIOLIB_SX126X_MIXER_RNG_ENABLED 0b00000000 // 7 7 enabled + +// size of the spectral scan result +#define RADIOLIB_SX126X_SPECTRAL_SCAN_RES_SIZE (33) + +// RADIOLIB_SX126X_CMD_SET_PA_CONFIG +#define RADIOLIB_SX126X_PA_CONFIG_SX1262 0x00 + +#endif /* ADC87656_291B_49BB_A611_52533D666023 */ diff --git a/zephyr/module.yml b/zephyr/module.yml new file mode 100644 index 0000000..1314290 --- /dev/null +++ b/zephyr/module.yml @@ -0,0 +1,6 @@ +name: zephyr_llcc68_driver +build: + cmake: . + kconfig: Kconfig + settings: + dts_root: .