diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..caa4cb0 --- /dev/null +++ b/.clang-format @@ -0,0 +1,25 @@ +# C++ specific configuration (akin to Google's C++ style) +# https://clang.llvm.org/docs/ClangFormatStyleOptions.html#adding-additional-style-options +--- +Language: Cpp +BasedOnStyle: LLVM +UseTab: ForContinuationAndIndentation +IndentWidth: 4 +TabWidth: 4 +AccessModifierOffset: -4 +ColumnLimit: 0 +NamespaceIndentation: Inner +FixNamespaceComments: false +AllowShortIfStatementsOnASingleLine: WithoutElse +AllowShortLoopsOnASingleLine: true +AllowShortBlocksOnASingleLine: Empty +IndentCaseLabels: false +SortIncludes: Never +AlignConsecutiveMacros: AcrossEmptyLines +AlignConsecutiveAssignments: Consecutive +BreakStringLiterals: true +LineEnding: LF +MaxEmptyLinesToKeep: 2 +BreakBeforeBraces: Attach +InsertBraces: true +BreakAfterAttributes: Always diff --git a/include/llcc68.hpp b/include/llcc68.hpp index 489974e..f98d24e 100644 --- a/include/llcc68.hpp +++ b/include/llcc68.hpp @@ -44,10 +44,10 @@ constexpr auto DEFAULT_BUSY_TIMEOUT_MS = 100; * @param ldro_on Low data rate optimization setting sent to the radio */ 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, bool ldro_on); + LoRaCodingRate cr, + uint16_t preamble_length, + LoRaHeaderType header_type, + LoRaCrcType crc_type, bool ldro_on); struct LLCC68 { /** trivial getter */ @@ -253,7 +253,7 @@ struct LLCC68 { expected set_rx_duty_cycle(uint32_t rx_period, uint32_t sleep_period); expected set_rx_duty_cycle_ms(uint32_t rx_period_ms, - uint32_t sleep_period_ms); + uint32_t sleep_period_ms); expected set_sleep(sleep_config_t config); expected set_tx_continuous_wave(); expected set_tx_infinite_preamble(); diff --git a/include/llcc68_definitions.hpp b/include/llcc68_definitions.hpp index a70263a..07694df 100644 --- a/include/llcc68_definitions.hpp +++ b/include/llcc68_definitions.hpp @@ -14,11 +14,11 @@ namespace app::driver::llcc68 { struct LLCC68; -using airtime_t = std::chrono::microseconds; +using airtime_t = std::chrono::microseconds; using error_code = std::error_code; -constexpr uint32_t RX_DUTY_CYCLE_PERIOD_MAX = 0x00FFFFFFU; -constexpr uint32_t RX_DUTY_CYCLE_PERIOD_UNIT_US_NUMERATOR = 125U; +constexpr uint32_t RX_DUTY_CYCLE_PERIOD_MAX = 0x00FFFFFFU; +constexpr uint32_t RX_DUTY_CYCLE_PERIOD_UNIT_US_NUMERATOR = 125U; constexpr uint32_t RX_DUTY_CYCLE_PERIOD_UNIT_US_DENOMINATOR = 8U; /** @@ -28,16 +28,16 @@ constexpr uint32_t RX_DUTY_CYCLE_PERIOD_UNIT_US_DENOMINATOR = 8U; * 24-bit RTC periods, not milliseconds. One raw period is 15.625 us. */ constexpr std::optional rx_duty_cycle_period_from_us(uint64_t us) { - constexpr uint64_t scale = RX_DUTY_CYCLE_PERIOD_UNIT_US_DENOMINATOR; - constexpr uint64_t divisor = RX_DUTY_CYCLE_PERIOD_UNIT_US_NUMERATOR; - if (us > (UINT64_MAX - (divisor - 1U)) / scale) { - return std::nullopt; - } - const uint64_t raw = ((us * scale) + (divisor - 1U)) / divisor; - if (raw > RX_DUTY_CYCLE_PERIOD_MAX) { - return std::nullopt; - } - return static_cast(raw); + constexpr uint64_t scale = RX_DUTY_CYCLE_PERIOD_UNIT_US_DENOMINATOR; + constexpr uint64_t divisor = RX_DUTY_CYCLE_PERIOD_UNIT_US_NUMERATOR; + if (us > (UINT64_MAX - (divisor - 1U)) / scale) { + return std::nullopt; + } + const uint64_t raw = ((us * scale) + (divisor - 1U)) / divisor; + if (raw > RX_DUTY_CYCLE_PERIOD_MAX) { + return std::nullopt; + } + return static_cast(raw); } /** @@ -47,100 +47,100 @@ constexpr std::optional rx_duty_cycle_period_from_us(uint64_t us) { * 24-bit period field. */ constexpr std::optional rx_duty_cycle_period_from_ms(uint64_t ms) { - if (ms > UINT64_MAX / 1000U) { - return std::nullopt; - } - return rx_duty_cycle_period_from_us(ms * 1000U); + if (ms > UINT64_MAX / 1000U) { + return std::nullopt; + } + return rx_duty_cycle_period_from_us(ms * 1000U); } enum class Errc : uint8_t { - FailureToExecuteCommand = 1, - CommandTimeout = 2, - CommandProcessing = 3, - InvalidState = 4, + 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]] + 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"; - } - } + [[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; + static const Llcc68ErrorCategory category{}; + return category; } inline error_code make_error_code(Errc error) { - return {static_cast(error), error_category()}; + return {static_cast(error), error_category()}; } inline error_code make_zephyr_error_code(int zephyr_ret) { - if (zephyr_ret >= 0) { - return {}; - } + if (zephyr_ret >= 0) { + return {}; + } - return {static_cast(-zephyr_ret), std::generic_category()}; + 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, + 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, + 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, + 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, + CRC_OFF = 0x00, + CRC_ON = 0x01, }; enum LoRaIQType : uint8_t { - IQ_STANDARD = 0x00, - IQ_INVERTED = 0x01, + IQ_STANDARD = 0x00, + IQ_INVERTED = 0x01, }; // LoRa LDR (Low Data Rate) types enum LoRaLowDataRateType : uint8_t { - LDR_OFF = 0x00, - LDR_ON = 0x01, + LDR_OFF = 0x00, + LDR_ON = 0x01, }; // - RampTime Value RampTime (µs) @@ -153,917 +153,917 @@ enum LoRaLowDataRateType : uint8_t { // - 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, + 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, + 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 + 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"; - } + 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"; - } + 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; + if (sf < 6 || sf > 12) { + return std::nullopt; + } + return sf; } struct modulation_params_t { - LoRaBandwidth bw; - uint8_t sf; - LoRaCodingRate cr; - LoRaLowDataRateType ldr_optimize; + 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, - }; - } + 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; + 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, - }; - } + 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; + // 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 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 +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 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}; } + /** + * @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(); } + 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"; - } + 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; + /** + * @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; + 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 { + 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)); + 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) + "]"; + 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; + 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; + 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, + 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, + 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, }; inline uint32_t rx_bandwidth_hz(GfskRxBandwidth bandwidth) { - using enum GfskRxBandwidth; - switch (bandwidth) { - case Bw4800: - return 4800; - case Bw5800: - return 5800; - case Bw7300: - return 7300; - case Bw9700: - return 9700; - case Bw11700: - return 11700; - case Bw14600: - return 14600; - case Bw19500: - return 19500; - case Bw23400: - return 23400; - case Bw29300: - return 29300; - case Bw39000: - return 39000; - case Bw46900: - return 46900; - case Bw58600: - return 58600; - case Bw78200: - return 78200; - case Bw93800: - return 93800; - case Bw117300: - return 117300; - case Bw156200: - return 156200; - case Bw187200: - return 187200; - case Bw234300: - return 234300; - case Bw312000: - return 312000; - case Bw373600: - return 373600; - case Bw467000: - return 467000; - } - return 0; + using enum GfskRxBandwidth; + switch (bandwidth) { + case Bw4800: + return 4800; + case Bw5800: + return 5800; + case Bw7300: + return 7300; + case Bw9700: + return 9700; + case Bw11700: + return 11700; + case Bw14600: + return 14600; + case Bw19500: + return 19500; + case Bw23400: + return 23400; + case Bw29300: + return 29300; + case Bw39000: + return 39000; + case Bw46900: + return 46900; + case Bw58600: + return 58600; + case Bw78200: + return 78200; + case Bw93800: + return 93800; + case Bw117300: + return 117300; + case Bw156200: + return 156200; + case Bw187200: + return 187200; + case Bw234300: + return 234300; + case Bw312000: + return 312000; + case Bw373600: + return 373600; + case Bw467000: + return 467000; + } + return 0; } enum class GfskPreambleDetector : uint8_t { - Off = 0x00, - Bits8 = 0x04, - Bits16 = 0x05, - Bits24 = 0x06, - Bits32 = 0x07, + Off = 0x00, + Bits8 = 0x04, + Bits16 = 0x05, + Bits24 = 0x06, + Bits32 = 0x07, }; enum class GfskAddressFiltering : uint8_t { - Disabled = 0x00, - Node = 0x01, - NodeAndBroadcast = 0x02, + Disabled = 0x00, + Node = 0x01, + NodeAndBroadcast = 0x02, }; enum class GfskPacketLengthMode : uint8_t { - Fixed = 0x00, - Variable = 0x01, + Fixed = 0x00, + Variable = 0x01, }; enum class GfskCrcType : uint8_t { - Off = 0x01, - Crc1Byte = 0x00, - Crc2Byte = 0x02, - Crc1ByteInverted = 0x04, - Crc2ByteInverted = 0x06, + Off = 0x01, + Crc1Byte = 0x00, + Crc2Byte = 0x02, + Crc1ByteInverted = 0x04, + Crc2ByteInverted = 0x06, }; enum class GfskWhitening : uint8_t { - Off = 0x00, - On = 0x01, + Off = 0x00, + On = 0x01, }; struct gfsk_modulation_params_t { - uint32_t bitrate_bps; - uint32_t frequency_deviation_hz; - GfskPulseShape pulse_shape; - GfskRxBandwidth rx_bandwidth; + 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, - }; - } + 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; + 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, - }; - } + 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; + 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, - }; - } + 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, + }; + } - static constexpr gfsk_parameters_t - Gfsk100kFixed6(freq_t frequency_mhz = 434.18, - tx_params_t tx_params = tx_params_t::Default()) { - return { - .mod_params = - { - .bitrate_bps = 100'000, - .frequency_deviation_hz = 25'000, - .pulse_shape = GfskPulseShape::Gauss05, - .rx_bandwidth = GfskRxBandwidth::Bw187200, - }, - .packet_params = - { - .preamble_bits = 64, - .detector_length = GfskPreambleDetector::Bits32, - .sync_length_bits = 32, - .address_filtering = GfskAddressFiltering::Disabled, - .packet_length_mode = GfskPacketLengthMode::Fixed, - .payload_length = 6, - .crc_type = GfskCrcType::Off, - .whitening = GfskWhitening::On, - }, - .tx_params = tx_params, - .frequency_mhz = frequency_mhz, - .sync_word = {0x2D, 0xD4, 0xA1, 0x7E, 0, 0, 0, 0}, - .sync_word_length = 4, - .crc_seed = 0x1D0F, - .crc_polynomial = 0x1021, - .whitening_seed = 0x0100, - .node_address = std::nullopt, - .broadcast_address = std::nullopt, - }; - } - void log(const char *tag) const; + static constexpr gfsk_parameters_t + Gfsk100kFixed6(freq_t frequency_mhz = 434.18, + tx_params_t tx_params = tx_params_t::Default()) { + return { + .mod_params = + { + .bitrate_bps = 100'000, + .frequency_deviation_hz = 25'000, + .pulse_shape = GfskPulseShape::Gauss05, + .rx_bandwidth = GfskRxBandwidth::Bw187200, + }, + .packet_params = + { + .preamble_bits = 64, + .detector_length = GfskPreambleDetector::Bits32, + .sync_length_bits = 32, + .address_filtering = GfskAddressFiltering::Disabled, + .packet_length_mode = GfskPacketLengthMode::Fixed, + .payload_length = 6, + .crc_type = GfskCrcType::Off, + .whitening = GfskWhitening::On, + }, + .tx_params = tx_params, + .frequency_mhz = frequency_mhz, + .sync_word = {0x2D, 0xD4, 0xA1, 0x7E, 0, 0, 0, 0}, + .sync_word_length = 4, + .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, + 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"; - } + 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, + DBM_14, + DBM_17, + DBM_20, + DBM_22, }; /// @note suitable SX1261 enum class PaOptimalPresetLow : uint8_t { - DBM_10, - DBM_14, - DBM_15, + 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; + 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, + 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, + 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 + 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"; - } + 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 + 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"; - } + 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 + // 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; + // 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 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 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; - } + [[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; + // 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; - [[nodiscard]] - bool has_error() const { - return (rx_status & 0b11111100U) != 0; - } + [[nodiscard]] + bool has_error() const { + return (rx_status & 0b11111100U) != 0; + } - [[nodiscard]] - bool packet_received() const { - return (rx_status & 0b00000010U) != 0; - } + [[nodiscard]] + bool packet_received() const { + return (rx_status & 0b00000010U) != 0; + } - [[nodiscard]] - auto rssi_sync_dbm() const -> float { - return -static_cast(rssi_sync) / 2.0F; - } + [[nodiscard]] + auto rssi_sync_dbm() const -> float { + return -static_cast(rssi_sync) / 2.0F; + } - [[nodiscard]] - auto rssi_avg_dbm() const -> float { - return -static_cast(rssi_avg) / 2.0F; - } + [[nodiscard]] + auto rssi_avg_dbm() const -> float { + return -static_cast(rssi_avg) / 2.0F; + } }; union packet_status_t { - uint8_t raw[3]; - lora_packet_status_t lora; - fsk_packet_status_t fsk; + 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 + // 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 + 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 + // 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]] + 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; - } + [[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; + 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; - } + 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}; - } + 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; - } + 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 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}; - } + 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); - } + 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 >= -9 && power <= 22) { + return true; + } - if (power >= -17 && power <= 14) { - return true; - } + if (power >= -17 && power <= 14) { + return true; + } - return false; + 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; - } + 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}); + return in_range(freq, freq_t{150.0}, freq_t{960.0}); } struct irq_status_bits_t { - // LSB + // 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 + 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 + // MSB }; enum class RfSwitchState : uint8_t { - Idle, - TX, - RX, + Idle, + TX, + RX, }; struct irq_status_t { - union { - uint16_t raw; - irq_status_bits_t bits; - }; + union { + uint16_t raw; + irq_status_bits_t bits; + }; - operator uint16_t() const { return raw; } + operator uint16_t() const { return raw; } - [[nodiscard]] - uint8_t msb() const { - return static_cast((raw >> 8) & 0xFF); - } + [[nodiscard]] + uint8_t msb() const { + return static_cast((raw >> 8) & 0xFF); + } - [[nodiscard]] - uint8_t lsb() const { - return static_cast(raw & 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 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, + 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, + 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 @@ -1074,67 +1074,67 @@ enum class CAD_SYMB : uint8_t { // 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; + 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, + COLD_START = 0x00, + /** + * @brief device configuration is in retention + */ + WARM_START = 0x01, }; struct sleep_config_t { - // LSB + // 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 {}; + /** + * @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}; + 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(); + error_code post_action(); - LLCC68 *self{}; - airtime_t airtime_estimated; + LLCC68 *self{}; + airtime_t airtime_estimated; }; } // namespace app::driver::llcc68 @@ -1143,7 +1143,8 @@ namespace llcc68 = app::driver::llcc68; } namespace std { -template <> struct is_error_code_enum : true_type {}; +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 index 966c616..9fd1d60 100644 --- a/include/llcc68_raw.h +++ b/include/llcc68_raw.h @@ -15,9 +15,9 @@ extern "C" { typedef void (*llcc68_user_dio1_handler_t)(const struct device *dev, void *user_data); enum llcc68_rf_switch_mode { - LLCC68_RF_SWITCH_NONE = 0, + LLCC68_RF_SWITCH_NONE = 0, LLCC68_RF_SWITCH_GPIO_COMPLEMENTARY = 1, - LLCC68_RF_SWITCH_DIO2_SINGLE = 2, + LLCC68_RF_SWITCH_DIO2_SINGLE = 2, }; struct llcc68_config { diff --git a/src/llcc68.cpp b/src/llcc68.cpp index 85eb9fd..eeb41cd 100644 --- a/src/llcc68.cpp +++ b/src/llcc68.cpp @@ -16,47 +16,47 @@ LOG_MODULE_REGISTER(llcc68, CONFIG_LLCC68_LOG_LEVEL); namespace { -using error_code = app::driver::llcc68::error_code; -using ue_t = std::unexpected; +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)}; + 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)}; + 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); + 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) +#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) +#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 @@ -66,153 +66,150 @@ bool is_command_processing_error(const error_code &error) { * \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()); \ - } \ - } +#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); + return *static_cast(dev->config); } struct llcc68_data & LLCC68::data() { // NOLINT(readability-make-member-function-const) mutable - return *static_cast(dev->data); + 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; + 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; + 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 {}; + 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; + // Max payload the radio supports and buffer sizing helpers + constexpr size_t kMaxPayload = 32; -constexpr uint64_t ceil_div_u64(uint64_t numerator, uint64_t denominator) { - if (denominator == 0) { - return 0; - } - return numerator / denominator + (numerator % denominator == 0 ? 0 : 1); -} + constexpr uint64_t ceil_div_u64(uint64_t numerator, uint64_t denominator) { + if (denominator == 0) { + return 0; + } + return numerator / denominator + (numerator % denominator == 0 ? 0 : 1); + } -constexpr uint32_t lora_bw_hz(LoRaBandwidth bw) { - switch (bw) { - case LoRaBandwidth::BW_7_8: - return 7'810; - case LoRaBandwidth::BW_10_4: - return 10'420; - case LoRaBandwidth::BW_15_6: - return 15'630; - case LoRaBandwidth::BW_20_8: - return 20'830; - case LoRaBandwidth::BW_31_25: - return 31'250; - case LoRaBandwidth::BW_41_7: - return 41'670; - case LoRaBandwidth::BW_62_5: - return 62'500; - case LoRaBandwidth::BW_125_0: - return 125'000; - case LoRaBandwidth::BW_250_0: - return 250'000; - case LoRaBandwidth::BW_500_0: - return 500'000; - } - return 0; -} + constexpr uint32_t lora_bw_hz(LoRaBandwidth bw) { + switch (bw) { + case LoRaBandwidth::BW_7_8: + return 7'810; + case LoRaBandwidth::BW_10_4: + return 10'420; + case LoRaBandwidth::BW_15_6: + return 15'630; + case LoRaBandwidth::BW_20_8: + return 20'830; + case LoRaBandwidth::BW_31_25: + return 31'250; + case LoRaBandwidth::BW_41_7: + return 41'670; + case LoRaBandwidth::BW_62_5: + return 62'500; + case LoRaBandwidth::BW_125_0: + return 125'000; + case LoRaBandwidth::BW_250_0: + return 250'000; + case LoRaBandwidth::BW_500_0: + return 500'000; + } + return 0; + } -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 {}; - } -} + 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; -} + 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 expected LLCC68::set_rf_switch_state(RfSwitchState state) { - auto set_gpio = [](const gpio_dt_spec &gpio, int value) - -> expected { - if (not device_is_ready(gpio.port)) { - return ue(-ENODEV); - } - const int ret = gpio_pin_set_dt(&gpio, value); - if (ret < 0) { - return ue(ret); - } - return unit{}; - }; + auto set_gpio = [](const gpio_dt_spec &gpio, int value) + -> expected { + if (not device_is_ready(gpio.port)) { + return ue(-ENODEV); + } + const int ret = gpio_pin_set_dt(&gpio, value); + if (ret < 0) { + return ue(ret); + } + return unit{}; + }; - switch (config().rf_switch_mode) { - case LLCC68_RF_SWITCH_NONE: - return unit{}; - case LLCC68_RF_SWITCH_GPIO_COMPLEMENTARY: { - const auto tx = tx_enable_gpio(); - const auto rx = rx_enable_gpio(); - if (not tx or not rx) { - return ue(-ENODEV); - } + switch (config().rf_switch_mode) { + case LLCC68_RF_SWITCH_DIO2_SINGLE: + case LLCC68_RF_SWITCH_NONE: + return unit{}; + case LLCC68_RF_SWITCH_GPIO_COMPLEMENTARY: { + const auto tx = tx_enable_gpio(); + const auto rx = rx_enable_gpio(); + if (not tx or not rx) { + return ue(-ENODEV); + } - expected r; - if (state == RfSwitchState::TX) { - r = set_gpio(*rx, 0); - APP_RADIO_RETURN_ERR(r); - return set_gpio(*tx, 1); - } - r = set_gpio(*tx, 0); - APP_RADIO_RETURN_ERR(r); - return set_gpio(*rx, state == RfSwitchState::RX ? 1 : 0); - } - case LLCC68_RF_SWITCH_DIO2_SINGLE: - if (auto rx = rx_enable_gpio()) { - return set_gpio(*rx, 1); - } - return unit{}; - default: - return ue(-EINVAL); - } + expected r; + if (state == RfSwitchState::TX) { + r = set_gpio(*rx, 0); + APP_RADIO_RETURN_ERR(r); + return set_gpio(*tx, 1); + } + r = set_gpio(*tx, 0); + APP_RADIO_RETURN_ERR(r); + return set_gpio(*rx, state == RfSwitchState::RX ? 1 : 0); + } + return unit{}; + default: + return ue(-EINVAL); + } } /*! @@ -221,379 +218,379 @@ expected LLCC68::set_rf_switch_state(RfSwitchState state) { \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, bool ldro_on) { - // Everything is in microseconds to allow integer arithmetic. Some constants - // have .25, these are multiplied by 4 and have an _x4 postfix. - const uint32_t bw_hz = lora_bw_hz(bw); - if (bw_hz == 0) { - return airtime_t{0}; - } + LoRaCodingRate cr, + uint16_t preamble_length, + LoRaHeaderType header_type, + LoRaCrcType crc_type, bool ldro_on) { + // Everything is in microseconds to allow integer arithmetic. Some constants + // have .25, these are multiplied by 4 and have an _x4 postfix. + const uint32_t bw_hz = lora_bw_hz(bw); + if (bw_hz == 0) { + return airtime_t{0}; + } - 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; - } - const uint8_t ldro_sf_offset = ldro_on ? 2 : 0; - if (sf <= ldro_sf_offset) { - return airtime_t{0}; - } - const uint8_t sfDivisor = 4 * (sf - ldro_sf_offset); - const int16_t header_bits = - header_type == LoRaHeaderType::HEADER_EXPLICIT ? 20 : 0; + 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; + } + const uint8_t ldro_sf_offset = ldro_on ? 2 : 0; + if (sf <= ldro_sf_offset) { + return airtime_t{0}; + } + const uint8_t sfDivisor = 4 * (sf - ldro_sf_offset); + const int16_t header_bits = + header_type == LoRaHeaderType::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 == LoRaCrcType::CRC_ON ? 16 : 0) - - static_cast(4 * sf) + static_cast(sfCoeff2) + - header_bits); - 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); + // 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 == LoRaCrcType::CRC_ON ? 16 : 0) - + static_cast(4 * sf) + static_cast(sfCoeff2) + + header_bits); + 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 cr_denominator = 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 * cr_denominator * 4; + const auto cr_denominator = 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 * cr_denominator * 4; - const uint64_t time_us = ceil_div_u64( - (static_cast(1'000'000) << sf) * nSymbol_x4, - static_cast(bw_hz) * 4U); - return airtime_t{static_cast(time_us)}; + const uint64_t time_us = ceil_div_u64( + (static_cast(1'000'000) << sf) * nSymbol_x4, + static_cast(bw_hz) * 4U); + return airtime_t{static_cast(time_us)}; } static_assert(calc_time_on_air(6, 7, LoRaBandwidth::BW_125_0, - LoRaCodingRate::CR_4_5, 8, - LoRaHeaderType::HEADER_IMPLICIT, - LoRaCrcType::CRC_OFF, false) == - airtime_t{25'856}); + LoRaCodingRate::CR_4_5, 8, + LoRaHeaderType::HEADER_IMPLICIT, + LoRaCrcType::CRC_OFF, false) == + airtime_t{25'856}); // 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); - } + 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; + // 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}; + 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 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{}; + 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); - } + 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); + // 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); - } + 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{}; + // 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); - } + 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); + // 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); - } + 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{}; + 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); - } + 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); + // 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{}; + 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()); + 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); - } + 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; + // 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); + 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(); - return read_buffer_ref(ptr, sz, busy_timeout); + auto r = get_rx_buffer_status(busy_timeout); + APP_RADIO_RETURN_ERR_IGNORE_PROC_ERR(r); + const auto [sz, ptr] = r.value(); + return read_buffer_ref(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{}; + 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); - } + 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); - } + // 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{}; + 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); - } + 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); + // 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{}; + 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 @@ -601,1106 +598,1111 @@ LLCC68::write_buffer_immediate_mode(uint8_t offset, 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); + 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{}; + 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); + 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; + 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; + 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::get_rssi_inst_x2_neg() { - uint8_t raw = 0; - auto r = read_stream(RADIOLIB_SX126X_CMD_GET_RSSI_INST, - std::span{&raw, 1}); - APP_RADIO_RETURN_ERR_IGNORE_PROC_ERR(r); - return raw; + uint8_t raw = 0; + auto r = read_stream(RADIOLIB_SX126X_CMD_GET_RSSI_INST, + std::span{&raw, 1}); + APP_RADIO_RETURN_ERR_IGNORE_PROC_ERR(r); + return raw; } 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); + // 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); + // 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}; - auto r = write_stream(RADIOLIB_SX126X_CMD_SET_STANDBY, data); - APP_RADIO_RETURN_ERR(r); - return set_rf_switch_state(RfSwitchState::Idle); + const uint8_t data[] = {RADIOLIB_SX126X_STANDBY_RC}; + auto r = write_stream(RADIOLIB_SX126X_CMD_SET_STANDBY, data); + APP_RADIO_RETURN_ERR(r); + return set_rf_switch_state(RfSwitchState::Idle); } expected LLCC68::hal_get_chip_type() { - constexpr auto SX1262_CHIP_TYPE = "SX1262"; - constexpr auto LLCC68_CHIP_TYPE = "LLCC68"; - constexpr auto SX1261_CHIP_TYPE = "SX1261"; + 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; + 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); + 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; + 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); + 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); + 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); + 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; - } + 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 airtime_t 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; - if (params.mod_params.bitrate_bps == 0) { - return airtime_t{0}; - } - const uint64_t us = ceil_div_u64(static_cast(bits) * 1'000'000U, - params.mod_params.bitrate_bps); - return airtime_t{static_cast(us)}; + 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; + if (params.mod_params.bitrate_bps == 0) { + return airtime_t{0}; + } + const uint64_t us = ceil_div_u64(static_cast(bits) * 1'000'000U, + params.mod_params.bitrate_bps); + return airtime_t{static_cast(us)}; } 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}); + 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); + 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); + 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); + 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); + 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(); + 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); + // 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); + 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); + // 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); + 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); + 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), - hdr_type, - payload_length, - crc_type, - iq_type, - }; - auto r = write_stream(RADIOLIB_SX126X_CMD_SET_PACKET_PARAMS, data); - APP_RADIO_RETURN_ERR(r); - return fix_inverted_iq(iq_type); + 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), + hdr_type, + payload_length, + crc_type, + iq_type, + }; + auto r = write_stream(RADIOLIB_SX126X_CMD_SET_PACKET_PARAMS, data); + APP_RADIO_RETURN_ERR(r); + return fix_inverted_iq(iq_type); } 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); + 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); + 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); + 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); + 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); + 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}); + 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::calibrate_image(uint8_t freq1, - uint8_t freq2) { - const uint8_t data[] = {freq1, freq2}; - return write_stream(RADIOLIB_SX126X_CMD_CALIBRATE_IMAGE, data); + uint8_t freq2) { + const uint8_t data[] = {freq1, freq2}; + return write_stream(RADIOLIB_SX126X_CMD_CALIBRATE_IMAGE, data); } 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}); + 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); + 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); + 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); - } + 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; + int8_t pwr = params.power; + expected r; #if not(CONFIG_LLCC68_ALWAYS_USE_SX1262_HIGH_PA) - if (chip == ChipType::SX1262 || chip == ChipType::LLCC68) { + 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); - } + // 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); - } + } + // 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); + 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}); + 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); + 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); + return write_stream(RADIOLIB_SX126X_CMD_SET_CAD_PARAMS, data); } expected LLCC68::set_cad() { - auto dummy = std::span{}; - auto r = set_rf_switch_state(RfSwitchState::RX); - APP_RADIO_RETURN_ERR(r); - return write_stream(RADIOLIB_SX126X_CMD_SET_CAD, dummy); + auto dummy = std::span{}; + auto r = set_rf_switch_state(RfSwitchState::RX); + APP_RADIO_RETURN_ERR(r); + 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); + 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); + 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_rx_duty_cycle(uint32_t rx_period, - uint32_t sleep_period) { - if (rx_period > RX_DUTY_CYCLE_PERIOD_MAX || - sleep_period > RX_DUTY_CYCLE_PERIOD_MAX) { - return ue(-EINVAL); - } - auto r = set_rf_switch_state(RfSwitchState::RX); - APP_RADIO_RETURN_ERR(r); - const uint8_t data[] = { - static_cast((rx_period >> 16) & 0xFF), - static_cast((rx_period >> 8) & 0xFF), - static_cast(rx_period & 0xFF), - static_cast((sleep_period >> 16) & 0xFF), - static_cast((sleep_period >> 8) & 0xFF), - static_cast(sleep_period & 0xFF), - }; - return write_stream(RADIOLIB_SX126X_CMD_SET_RX_DUTY_CYCLE, data); + uint32_t sleep_period) { + if (rx_period > RX_DUTY_CYCLE_PERIOD_MAX || + sleep_period > RX_DUTY_CYCLE_PERIOD_MAX) { + return ue(-EINVAL); + } + auto r = set_rf_switch_state(RfSwitchState::RX); + APP_RADIO_RETURN_ERR(r); + const uint8_t data[] = { + static_cast((rx_period >> 16) & 0xFF), + static_cast((rx_period >> 8) & 0xFF), + static_cast(rx_period & 0xFF), + static_cast((sleep_period >> 16) & 0xFF), + static_cast((sleep_period >> 8) & 0xFF), + static_cast(sleep_period & 0xFF), + }; + return write_stream(RADIOLIB_SX126X_CMD_SET_RX_DUTY_CYCLE, data); } expected LLCC68::set_rx_duty_cycle_ms(uint32_t rx_period_ms, uint32_t sleep_period_ms) { - const auto rx_period = rx_duty_cycle_period_from_ms(rx_period_ms); - const auto sleep_period = rx_duty_cycle_period_from_ms(sleep_period_ms); - if (not rx_period or not sleep_period) { - return ue(-EINVAL); - } - return set_rx_duty_cycle(*rx_period, *sleep_period); + const auto rx_period = rx_duty_cycle_period_from_ms(rx_period_ms); + const auto sleep_period = rx_duty_cycle_period_from_ms(sleep_period_ms); + if (not rx_period or not sleep_period) { + return ue(-EINVAL); + } + return set_rx_duty_cycle(*rx_period, *sleep_period); } expected LLCC68::set_sleep(sleep_config_t config) { - auto c = *reinterpret_cast(&config); - const uint8_t data[] = {c}; - auto r = write_stream(RADIOLIB_SX126X_CMD_SET_SLEEP, data); - APP_RADIO_RETURN_ERR(r); - return set_rf_switch_state(RfSwitchState::Idle); + auto c = *reinterpret_cast(&config); + const uint8_t data[] = {c}; + auto r = write_stream(RADIOLIB_SX126X_CMD_SET_SLEEP, data); + APP_RADIO_RETURN_ERR(r); + return set_rf_switch_state(RfSwitchState::Idle); } expected LLCC68::set_tx_continuous_wave() { - auto dummy = std::span{}; - // const uint8_t dummy[] = {RADIOLIB_SX126X_CMD_NOP}; - auto r = set_rf_switch_state(RfSwitchState::TX); - APP_RADIO_RETURN_ERR(r); - return write_stream(RADIOLIB_SX126X_CMD_SET_TX_CONTINUOUS_WAVE, dummy); + auto dummy = std::span{}; + // const uint8_t dummy[] = {RADIOLIB_SX126X_CMD_NOP}; + auto r = set_rf_switch_state(RfSwitchState::TX); + APP_RADIO_RETURN_ERR(r); + 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}; - auto r = set_rf_switch_state(RfSwitchState::TX); - APP_RADIO_RETURN_ERR(r); - return write_stream(RADIOLIB_SX126X_CMD_SET_TX_INFINITE_PREAMBLE, dummy); + auto dummy = std::span{}; + // const uint8_t dummy[] = {RADIOLIB_SX126X_CMD_NOP}; + auto r = set_rf_switch_state(RfSwitchState::TX); + APP_RADIO_RETURN_ERR(r); + 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}); + uint8_t sensitivityConfig{}; + read_register(RADIOLIB_SX126X_REG_SENSITIVITY_CONFIG, + std::span{&sensitivityConfig, 1}); - // bit 2 - constexpr auto HACK_MASK = 0x04; + // 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}); + 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); + 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); + 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 == 1200) { + return update_register_bits(*this, RADIOLIB_SX126X_REG_GFSK_SYNC_CONFIG, + 0x10, 0x00); + } - if (bitrate_bps != 600) { - return unit{}; - } + 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); + 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); + 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]}; + 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; + 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( - config().rf_switch_mode == - LLCC68_RF_SWITCH_DIO2_SINGLE), - "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 {}; + 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( + config().rf_switch_mode == + LLCC68_RF_SWITCH_DIO2_SINGLE), + "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); - } + 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_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(calibrate_image(RADIOLIB_SX126X_CAL_IMG_430_MHZ_1, - RADIOLIB_SX126X_CAL_IMG_430_MHZ_2), - "gfsk_init::calibrate_image"); - 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( - config().rf_switch_mode == - LLCC68_RF_SWITCH_DIO2_SINGLE), - "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 {}; + APP_RADIO_RETURN_ERR_CTX(set_packet_type_gfsk(), + "gfsk_init::set_packet_type"); + APP_RADIO_RETURN_ERR_CTX(calibrate_image(RADIOLIB_SX126X_CAL_IMG_430_MHZ_1, + RADIOLIB_SX126X_CAL_IMG_430_MHZ_2), + "gfsk_init::calibrate_image"); + 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( + config().rf_switch_mode == + LLCC68_RF_SWITCH_DIO2_SINGLE), + "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_type_lora(), "tx::set_packet_type"); - APP_RADIO_RETURN_ERR_CTX(set_lora_sync_word(params.sync_word), - "tx::set_lora_sync_word"); - APP_RADIO_RETURN_ERR_CTX(set_rf_frequency(params.frequency_mhz), - "tx::set_rf_frequency"); - 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), - "tx::set_modulation_params"); - 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"); - auto chip_type_ = hal_get_chip_type(); - APP_RADIO_RETURN_ERR_CTX(chip_type_, "tx::get_chip_type"); - APP_RADIO_RETURN_ERR_CTX(hal_set_output_power(*chip_type_, params.tx_params), - "tx::hal_set_output_power"); - 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"); - APP_RADIO_RETURN_ERR_CTX(set_rf_switch_state(RfSwitchState::TX), - "tx::set_rf_switch"); - 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, - params.mod_params.ldr_optimize == LoRaLowDataRateType::LDR_ON); - return transmit_result{this, air}; + 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_type_lora(), "tx::set_packet_type"); + APP_RADIO_RETURN_ERR_CTX(set_lora_sync_word(params.sync_word), + "tx::set_lora_sync_word"); + APP_RADIO_RETURN_ERR_CTX(set_rf_frequency(params.frequency_mhz), + "tx::set_rf_frequency"); + 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), + "tx::set_modulation_params"); + 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"); + auto chip_type_ = hal_get_chip_type(); + APP_RADIO_RETURN_ERR_CTX(chip_type_, "tx::get_chip_type"); + APP_RADIO_RETURN_ERR_CTX(hal_set_output_power(*chip_type_, params.tx_params), + "tx::hal_set_output_power"); + 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"); + APP_RADIO_RETURN_ERR_CTX(set_rf_switch_state(RfSwitchState::TX), + "tx::set_rf_switch"); + 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, + params.mod_params.ldr_optimize == LoRaLowDataRateType::LDR_ON); + return transmit_result{this, air}; } 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); + 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); - } - if (params.sync_word_length > params.sync_word.size() || - params.packet_params.sync_length_bits > params.sync_word.size() * 8) { - return ue(-EINVAL); - } + 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); + } + 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_tx::standby"); - APP_RADIO_RETURN_ERR_CTX(set_packet_type_gfsk(), - "gfsk_tx::set_packet_type"); - APP_RADIO_RETURN_ERR_CTX(set_rf_frequency(params.frequency_mhz), - "gfsk_tx::set_rf_frequency"); - APP_RADIO_RETURN_ERR_CTX(set_gfsk_modulation_params(params.mod_params), - "gfsk_tx::set_modulation_params"); - APP_RADIO_RETURN_ERR_CTX( - set_gfsk_sync_word(std::span{params.sync_word.data(), - params.sync_word_length}), - "gfsk_tx::set_sync_word"); - APP_RADIO_RETURN_ERR_CTX(set_gfsk_crc_seed(params.crc_seed), - "gfsk_tx::set_crc_seed"); - APP_RADIO_RETURN_ERR_CTX(set_gfsk_crc_polynomial(params.crc_polynomial), - "gfsk_tx::set_crc_polynomial"); - APP_RADIO_RETURN_ERR_CTX(set_gfsk_whitening_seed(params.whitening_seed), - "gfsk_tx::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_tx::set_address_filtering"); - } - 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"); - auto chip_type_ = hal_get_chip_type(); - APP_RADIO_RETURN_ERR_CTX(chip_type_, "gfsk_tx::get_chip_type"); - APP_RADIO_RETURN_ERR_CTX(hal_set_output_power(*chip_type_, params.tx_params), - "gfsk_tx::hal_set_output_power"); - 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"); - APP_RADIO_RETURN_ERR_CTX(set_rf_switch_state(RfSwitchState::TX), - "gfsk_tx::set_rf_switch"); - 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}; + APP_RADIO_RETURN_ERR_CTX(set_standby(), "gfsk_tx::standby"); + APP_RADIO_RETURN_ERR_CTX(set_packet_type_gfsk(), + "gfsk_tx::set_packet_type"); + APP_RADIO_RETURN_ERR_CTX(set_rf_frequency(params.frequency_mhz), + "gfsk_tx::set_rf_frequency"); + APP_RADIO_RETURN_ERR_CTX(set_gfsk_modulation_params(params.mod_params), + "gfsk_tx::set_modulation_params"); + APP_RADIO_RETURN_ERR_CTX( + set_gfsk_sync_word(std::span{params.sync_word.data(), + params.sync_word_length}), + "gfsk_tx::set_sync_word"); + APP_RADIO_RETURN_ERR_CTX(set_gfsk_crc_seed(params.crc_seed), + "gfsk_tx::set_crc_seed"); + APP_RADIO_RETURN_ERR_CTX(set_gfsk_crc_polynomial(params.crc_polynomial), + "gfsk_tx::set_crc_polynomial"); + APP_RADIO_RETURN_ERR_CTX(set_gfsk_whitening_seed(params.whitening_seed), + "gfsk_tx::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_tx::set_address_filtering"); + } + 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"); + auto chip_type_ = hal_get_chip_type(); + APP_RADIO_RETURN_ERR_CTX(chip_type_, "gfsk_tx::get_chip_type"); + APP_RADIO_RETURN_ERR_CTX(hal_set_output_power(*chip_type_, params.tx_params), + "gfsk_tx::hal_set_output_power"); + 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"); + APP_RADIO_RETURN_ERR_CTX(set_rf_switch_state(RfSwitchState::TX), + "gfsk_tx::set_rf_switch"); + 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); + 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_packet_type_lora(), "rx::set_packet_type"); - APP_RADIO_RETURN_ERR_CTX(set_lora_sync_word(params.sync_word), - "rx::set_lora_sync_word"); - APP_RADIO_RETURN_ERR_CTX(set_rf_frequency(params.frequency_mhz), - "rx::set_rf_frequency"); - APP_RADIO_RETURN_ERR_CTX(set_buffer_base_address(), - "rx::set_buffer_base_address"); + APP_RADIO_RETURN_ERR_CTX(set_standby(), "rx::standby"); + APP_RADIO_RETURN_ERR_CTX(set_packet_type_lora(), "rx::set_packet_type"); + APP_RADIO_RETURN_ERR_CTX(set_lora_sync_word(params.sync_word), + "rx::set_lora_sync_word"); + APP_RADIO_RETURN_ERR_CTX(set_rf_frequency(params.frequency_mhz), + "rx::set_rf_frequency"); + 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"); + 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"); + 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"); - APP_RADIO_RETURN_ERR_CTX(set_rf_switch_state(RfSwitchState::RX), - "rx::set_rf_switch"); - APP_RADIO_RETURN_ERR_CTX(set_rx(), "rx::set_rx"); - return unit{}; + APP_RADIO_RETURN_ERR_CTX(set_rf_switch_state(RfSwitchState::RX), + "rx::set_rf_switch"); + APP_RADIO_RETURN_ERR_CTX(set_rx(), "rx::set_rx"); + return unit{}; } expected LLCC68::hal_gfsk_async_rx(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); - } + 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_rx::standby"); - APP_RADIO_RETURN_ERR_CTX(set_packet_type_gfsk(), - "gfsk_rx::set_packet_type"); - APP_RADIO_RETURN_ERR_CTX(set_rf_frequency(params.frequency_mhz), - "gfsk_rx::set_rf_frequency"); - 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_sync_word(std::span{params.sync_word.data(), - params.sync_word_length}), - "gfsk_rx::set_sync_word"); - APP_RADIO_RETURN_ERR_CTX(set_gfsk_crc_seed(params.crc_seed), - "gfsk_rx::set_crc_seed"); - APP_RADIO_RETURN_ERR_CTX(set_gfsk_crc_polynomial(params.crc_polynomial), - "gfsk_rx::set_crc_polynomial"); - APP_RADIO_RETURN_ERR_CTX(set_gfsk_whitening_seed(params.whitening_seed), - "gfsk_rx::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_rx::set_address_filtering"); - } - APP_RADIO_RETURN_ERR_CTX(set_gfsk_packet_params(params.packet_params), - "gfsk_rx::set_packet_params"); + APP_RADIO_RETURN_ERR_CTX(set_standby(), "gfsk_rx::standby"); + APP_RADIO_RETURN_ERR_CTX(set_packet_type_gfsk(), + "gfsk_rx::set_packet_type"); + APP_RADIO_RETURN_ERR_CTX(set_rf_frequency(params.frequency_mhz), + "gfsk_rx::set_rf_frequency"); + 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_sync_word(std::span{params.sync_word.data(), + params.sync_word_length}), + "gfsk_rx::set_sync_word"); + APP_RADIO_RETURN_ERR_CTX(set_gfsk_crc_seed(params.crc_seed), + "gfsk_rx::set_crc_seed"); + APP_RADIO_RETURN_ERR_CTX(set_gfsk_crc_polynomial(params.crc_polynomial), + "gfsk_rx::set_crc_polynomial"); + APP_RADIO_RETURN_ERR_CTX(set_gfsk_whitening_seed(params.whitening_seed), + "gfsk_rx::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_rx::set_address_filtering"); + } + 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"); + 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"); - APP_RADIO_RETURN_ERR_CTX(set_rf_switch_state(RfSwitchState::RX), - "gfsk_rx::set_rf_switch"); - APP_RADIO_RETURN_ERR_CTX(set_rx(), "gfsk_rx::set_rx"); - return unit{}; + APP_RADIO_RETURN_ERR_CTX(set_rf_switch_state(RfSwitchState::RX), + "gfsk_rx::set_rf_switch"); + 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(); + 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 {}; + 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 - } + 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)); + 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)); + 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