#ifndef ECC594CF_EDF0_42B5_8518_0EB3B3583727 #define ECC594CF_EDF0_42B5_8518_0EB3B3583727 #include #include #include #include #include #include #include #include #include #include namespace app::driver::llcc68 { struct LLCC68; using airtime_t = std::chrono::duration; using error_code = std::error_code; enum class Errc : uint8_t { FailureToExecuteCommand = 1, CommandTimeout = 2, CommandProcessing = 3, InvalidState = 4, }; class Llcc68ErrorCategory : public std::error_category { public: [[nodiscard]] const char *name() const noexcept override { return "llcc68"; } [[nodiscard]] std::string message(int condition) const override { switch (static_cast(condition)) { case Errc::FailureToExecuteCommand: return "failure to execute command"; case Errc::CommandTimeout: return "command timeout"; case Errc::CommandProcessing: return "command processing error"; case Errc::InvalidState: return "invalid state"; default: return "unknown llcc68 error"; } } }; inline const std::error_category &error_category() { static const Llcc68ErrorCategory category{}; return category; } inline error_code make_error_code(Errc error) { return {static_cast(error), error_category()}; } inline error_code make_zephyr_error_code(int zephyr_ret) { if (zephyr_ret >= 0) { return {}; } return {static_cast(-zephyr_ret), std::generic_category()}; } using freq_t = float; enum LoRaBandwidth : uint8_t { BW_7_8 = 0x00, BW_10_4 = 0x08, BW_15_6 = 0x01, BW_20_8 = 0x09, BW_31_25 = 0x02, BW_41_7 = 0x0A, BW_62_5 = 0x03, BW_125_0 = 0x04, BW_250_0 = 0x05, BW_500_0 = 0x06, }; enum LoRaHeaderType : uint8_t { HEADER_EXPLICIT = 0x00, HEADER_IMPLICIT = 0x01, }; enum LoRaCodingRate : uint8_t { CR_4_5 = 0x01, CR_4_6 = 0x02, CR_4_7 = 0x03, CR_4_8 = 0x04, }; enum LoRaCrcType : uint8_t { CRC_OFF = 0x00, CRC_ON = 0x01, }; enum LoRaIQType : uint8_t { IQ_STANDARD = 0x00, IQ_INVERTED = 0x01, }; // LoRa LDR (Low Data Rate) types enum LoRaLowDataRateType : uint8_t { LDR_OFF = 0x00, LDR_ON = 0x01, }; // - RampTime Value RampTime (µs) // - SET_RAMP_10U 0x00 10 // - SET_RAMP_20U 0x01 20 // - SET_RAMP_ 40U 0x02 40 // - SET_RAMP_80U 0x03 80 // - SET_RAMP_200U 0x04 200 // - SET_RAMP_800U 0x05 800 // - SET_RAMP_1700U 0x06 1700 // - SET_RAMP_3400U 0x07 3400 enum LoRaTxRampTime : uint8_t { SET_RAMP_10U = 0x00, SET_RAMP_20U = 0x01, SET_RAMP_40U = 0x02, SET_RAMP_80U = 0x03, SET_RAMP_200U = 0x04, SET_RAMP_800U = 0x05, SET_RAMP_1700U = 0x06, SET_RAMP_3400U = 0x07, }; enum RegulatorMode : uint8_t { REGULATOR_LDO = 0x00, REGULATOR_DC_DC = 0x01, }; enum LoRaSyncWord : uint16_t { SYNC_WORD_PRIVATE = 0x1424, // Private sync word SYNC_WORD_PUBLIC = 0x3444, // Public sync word }; inline const char *to_str(LoRaBandwidth bw) { switch (bw) { case LoRaBandwidth::BW_7_8: return "7.8kHz"; case LoRaBandwidth::BW_10_4: return "10.4kHz"; case LoRaBandwidth::BW_15_6: return "15.6kHz"; case LoRaBandwidth::BW_20_8: return "20.8kHz"; case LoRaBandwidth::BW_31_25: return "31.25kHz"; case LoRaBandwidth::BW_41_7: return "41.7kHz"; case LoRaBandwidth::BW_62_5: return "62.5kHz"; case LoRaBandwidth::BW_125_0: return "125.0kHz"; case LoRaBandwidth::BW_250_0: return "250.0kHz"; case LoRaBandwidth::BW_500_0: return "500.0kHz"; default: return "Unknown"; } } inline const char *to_str(LoRaCodingRate cr) { switch (cr) { case LoRaCodingRate::CR_4_5: return "4/5"; case LoRaCodingRate::CR_4_6: return "4/6"; case LoRaCodingRate::CR_4_7: return "4/7"; case LoRaCodingRate::CR_4_8: return "4/8"; default: return "Unknown"; } } inline std::optional sf_from_uint8_t(const uint8_t sf) { if (sf < 6 || sf > 12) { return std::nullopt; } return sf; } struct modulation_params_t { LoRaBandwidth bw; uint8_t sf; LoRaCodingRate cr; LoRaLowDataRateType ldr_optimize; static modulation_params_t Default() { return modulation_params_t{ .bw = LoRaBandwidth::BW_250_0, .sf = 7, .cr = LoRaCodingRate::CR_4_7, .ldr_optimize = LoRaLowDataRateType::LDR_OFF, }; } }; struct packet_params_t { uint16_t preamble_len; uint8_t payload_len; LoRaCrcType crc_type; LoRaHeaderType hdr_type; LoRaIQType iq_type; static packet_params_t Default() { return packet_params_t{ .preamble_len = 8, .payload_len = 0xff, .crc_type = LoRaCrcType::CRC_OFF, .hdr_type = LoRaHeaderType::HEADER_IMPLICIT, .iq_type = LoRaIQType::IQ_STANDARD, }; } }; struct pa_setting_t { // paDutyCycle controls the duty cycle (conduction angle) of both PAs (SX1261 // and SX1262). The maximum output power, the power consumption, and the // harmonics will drastically change with paDutyCycle. The values given across // this datasheet are the recommended settings to achieve the best efficiency // of the PA. Changing the paDutyCycle will affect the distribution of the // power in the harmonics and should thus be selected to work in conjunction // of a given matching network. uint8_t pa_duty_cycle; // hpMax selects the size of the PA in the SX1262, this value has no influence // on the SX1261. The maximum output power can be reduced by reducing the // value of hpMax. The valid range is between 0x00 and 0x07 and 0x07 is the // maximum supported value for the SX1262 to achieve +22 dBm output power. // Increasing hpMax above 0x07 could cause early aging of the device of could // damage the device when used in extreme temperatures. uint8_t hp_max; uint8_t device_sel; /** * @note SetTxParams should be set to +22dBm */ static constexpr pa_setting_t Default22dBmHp() { return {0x04, 0x07, 0x00}; } /** * @note SetTxParams should be set to +22dBm */ static constexpr pa_setting_t Default20dBmHp() { return {0x03, 0x05, 0x00}; } /** * @note SetTxParams should be set to +22dBm */ static constexpr pa_setting_t Default17dBmHp() { return {0x02, 0x03, 0x00}; } /** * @note SetTxParams should be set to +22dBm * @note High Power; SX1262/LLCC68 */ static constexpr pa_setting_t Default14dBmHp() { return {0x02, 0x02, 0x00}; } /** * @note SetTxParams should be set to +14dBm * @note Low Power; SX1261 */ static constexpr pa_setting_t Default14dBmLp() { return {0x04, 0x00, 0x01}; } /** * @note SetTxParams should be set to +14dBm * @note Low Power; SX1261 */ static constexpr pa_setting_t Default10dBmLp() { return {0x01, 0x00, 0x01}; } static constexpr pa_setting_t DefaultHp() { return Default22dBmHp(); } static constexpr pa_setting_t DefaultLp() { return Default14dBmLp(); } }; inline const char *to_str(llcc68::LoRaTxRampTime ramp_time) { switch (ramp_time) { case llcc68::SET_RAMP_10U: return "10us"; case llcc68::SET_RAMP_20U: return "20us"; case llcc68::SET_RAMP_40U: return "40us"; case llcc68::SET_RAMP_80U: return "80us"; case llcc68::SET_RAMP_200U: return "200us"; case llcc68::SET_RAMP_800U: return "800us"; case llcc68::SET_RAMP_1700U: return "1700us"; case llcc68::SET_RAMP_3400U: return "3400us"; default: return "Unknown"; } } struct tx_params_t { /** * @brief magic number for automatic max TX power depending on * chip type */ static constexpr auto MAX_POWER_HIGH_PA = 22; static constexpr auto MAX_POWER_LOW_PA = 14; int8_t power; LoRaTxRampTime ramp_time; static constexpr tx_params_t Default() { return tx_params_t{ .power = MAX_POWER_HIGH_PA, .ramp_time = SET_RAMP_200U, }; } [[nodiscard]] std::string to_string() const { #ifdef __cpp_lib_format return std::format("tx_params[power={}, ramp_time={}]", power, to_str(ramp_time)); #else return "tx_params[power=" + std::to_string(power) + ", ramp_time=" + to_str(ramp_time) + "]"; #endif } }; struct lora_parameters_t { modulation_params_t mod_params; packet_params_t packet_params; tx_params_t tx_params; freq_t frequency_mhz; LoRaSyncWord sync_word; static lora_parameters_t Default() { return lora_parameters_t{ .mod_params = modulation_params_t::Default(), .packet_params = packet_params_t::Default(), .tx_params = tx_params_t::Default(), .frequency_mhz = 434.75, .sync_word = LoRaSyncWord::SYNC_WORD_PRIVATE, }; } void log(const char *tag) const; }; enum class GfskPulseShape : uint8_t { Off = 0x00, Gauss03 = 0x08, Gauss05 = 0x09, Gauss07 = 0x0A, Gauss1 = 0x0B, }; enum class GfskRxBandwidth : uint8_t { Bw4800 = 0x1F, Bw5800 = 0x17, Bw7300 = 0x0F, Bw9700 = 0x1E, Bw11700 = 0x16, Bw14600 = 0x0E, Bw19500 = 0x1D, Bw23400 = 0x15, Bw29300 = 0x0D, Bw39000 = 0x1C, Bw46900 = 0x14, Bw58600 = 0x0C, Bw78200 = 0x1B, Bw93800 = 0x13, Bw117300 = 0x0B, Bw156200 = 0x1A, Bw187200 = 0x12, Bw234300 = 0x0A, Bw312000 = 0x19, Bw373600 = 0x11, Bw467000 = 0x09, }; enum class GfskPreambleDetector : uint8_t { Off = 0x00, Bits8 = 0x04, Bits16 = 0x05, Bits24 = 0x06, Bits32 = 0x07, }; enum class GfskAddressFiltering : uint8_t { Disabled = 0x00, Node = 0x01, NodeAndBroadcast = 0x02, }; enum class GfskPacketLengthMode : uint8_t { Fixed = 0x00, Variable = 0x01, }; enum class GfskCrcType : uint8_t { Off = 0x01, Crc1Byte = 0x00, Crc2Byte = 0x02, Crc1ByteInverted = 0x04, Crc2ByteInverted = 0x06, }; enum class GfskWhitening : uint8_t { Off = 0x00, On = 0x01, }; struct gfsk_modulation_params_t { uint32_t bitrate_bps; uint32_t frequency_deviation_hz; GfskPulseShape pulse_shape; GfskRxBandwidth rx_bandwidth; static constexpr gfsk_modulation_params_t Default() { return { .bitrate_bps = 4800, .frequency_deviation_hz = 5000, .pulse_shape = GfskPulseShape::Gauss05, .rx_bandwidth = GfskRxBandwidth::Bw23400, }; } }; struct gfsk_packet_params_t { uint16_t preamble_bits; GfskPreambleDetector detector_length; uint8_t sync_length_bits; GfskAddressFiltering address_filtering; GfskPacketLengthMode packet_length_mode; uint8_t payload_length; GfskCrcType crc_type; GfskWhitening whitening; static constexpr gfsk_packet_params_t Default() { return { .preamble_bits = 32, .detector_length = GfskPreambleDetector::Bits16, .sync_length_bits = 32, .address_filtering = GfskAddressFiltering::Disabled, .packet_length_mode = GfskPacketLengthMode::Variable, .payload_length = 0xff, .crc_type = GfskCrcType::Crc2Byte, .whitening = GfskWhitening::On, }; } }; struct gfsk_parameters_t { gfsk_modulation_params_t mod_params; gfsk_packet_params_t packet_params; tx_params_t tx_params; freq_t frequency_mhz; std::array sync_word; uint8_t sync_word_length; uint16_t crc_seed; uint16_t crc_polynomial; uint16_t whitening_seed; std::optional node_address; std::optional broadcast_address; static constexpr gfsk_parameters_t Default() { return { .mod_params = gfsk_modulation_params_t::Default(), .packet_params = gfsk_packet_params_t::Default(), .tx_params = tx_params_t::Default(), .frequency_mhz = 434.75, .sync_word = {0x2D, 0xD4, 0, 0, 0, 0, 0, 0}, .sync_word_length = 2, .crc_seed = 0x1D0F, .crc_polynomial = 0x1021, .whitening_seed = 0x0100, .node_address = std::nullopt, .broadcast_address = std::nullopt, }; } static constexpr gfsk_parameters_t Gmsk100kFixed6(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 = 32, .detector_length = GfskPreambleDetector::Bits16, .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, }; inline const char *to_str(ChipType chip) { switch (chip) { case ChipType::LLCC68: return "LLCC68"; case ChipType::SX1261: return "SX1261"; case ChipType::SX1262: return "SX1262"; default: return "Unknown"; } } /// @note suitable SX1262/LLCC68 enum class PaOptimalPresetHigh : uint8_t { DBM_14, DBM_17, DBM_20, DBM_22, }; /// @note suitable SX1261 enum class PaOptimalPresetLow : uint8_t { DBM_10, DBM_14, DBM_15, }; using PaOptimalPreset = std::variant; struct pa_config_t { uint8_t pa_duty_cycle; uint8_t hp_max; uint8_t device_sel; }; enum class PacketType : uint8_t { FSK = 0x00, LORA = 0x01, LR_FHSS = 0x03, }; enum class TcxoVoltage : uint8_t { V_1_6 = 0x00, V_1_7 = 0x01, V_1_8 = 0x02, V_2_2 = 0x03, V_2_4 = 0x04, V_2_7 = 0x05, V_3_0 = 0x06, V_3_3 = 0x07, }; enum class CommandStatus : uint8_t { RESERVED = 0x00, // 0b000 RFU = 0x01, // 0b001 DATA_AVAILABLE = 0x02, // 0b010 COMMAND_TIMEOUT = 0x03, // 0b011 i.e. TIMEOUT COMMAND_PROCESSING_ERROR = 0x04, // 0b100 i.e. INVALID FAILURE_TO_EXECUTE_COMMAND = 0x05, // 0b101 i.e. FAILED COMMAND_TX_DONE = 0x06, // 0b110 }; inline const char *to_str(const CommandStatus &status) { switch (status) { case CommandStatus::RESERVED: return "RESERVED"; case CommandStatus::RFU: return "RFU"; case CommandStatus::DATA_AVAILABLE: return "DATA_AVAILABLE"; case CommandStatus::COMMAND_TIMEOUT: return "COMMAND_TIMEOUT"; case CommandStatus::COMMAND_PROCESSING_ERROR: return "COMMAND_PROCESSING_ERROR"; case CommandStatus::FAILURE_TO_EXECUTE_COMMAND: return "FAILURE_TO_EXECUTE_COMMAND"; case CommandStatus::COMMAND_TX_DONE: return "COMMAND_TX_DONE"; default: return "Unknown"; } } enum class ChipMode : uint8_t { UNUSED = 0x00, // 0b000 RFU = 0x01, // 0b001 STBY_RC = 0x02, // 0b010 STBY_XOSC = 0x03, // 0b011 FS = 0x04, // 0b100 RX = 0x05, // 0b101 TX = 0x06, // 0b110 }; inline const char *to_str(const ChipMode &mode) { switch (mode) { case ChipMode::UNUSED: return "UNUSED"; case ChipMode::RFU: return "RFU"; case ChipMode::STBY_RC: return "STBY_RC"; case ChipMode::STBY_XOSC: return "STBY_XOSC"; case ChipMode::FS: return "FS"; case ChipMode::RX: return "RX"; case ChipMode::TX: return "TX"; default: return "Unknown"; } } struct __attribute__((packed)) status_t { // LSB uint8_t reserved_1 : 1; CommandStatus command_status : 3; ChipMode chip_mode : 3; uint8_t reserved_2 : 1; // MSB }; static_assert(sizeof(status_t) == 1); struct __attribute__((packed)) lora_packet_status_t { // Average over last packet received of RSSI // Actual signal power is –RssiPkt/2 (dBm) uint8_t rssi_pkt; // Estimation of SNR on last packet received in two’s compliment format // multiplied by 4. Actual SNR in dB =SnrPkt/4 int8_t snr_pkt; // Estimation of RSSI of the LoRa® signal (after despreading) on last packet // received. Actual Rssi in dB = -SignalRssiPkt/2 uint8_t signal_rssi_pkt; [[nodiscard]] auto rssi_pkt_dbm() const -> float { return -static_cast(rssi_pkt) / 2.0F; } [[nodiscard]] auto snr_pkt_db() const -> float { return static_cast(snr_pkt) / 4.0F; } [[nodiscard]] auto signal_rssi_pkt_dbm() const -> float { return -static_cast(signal_rssi_pkt) / 2.0F; } }; struct __attribute__((packed)) fsk_packet_status_t { // bit 7: preamble err // bit 6: sync err // bit 5: adrs err // bit 4: crc err // bit 3: length err // bit 2: abort err // bit 1: pkt received // bit 0: pkt sent uint8_t rx_status; // RSSI value latched upon the detection of the sync address. // [negated, dBm, fixdt(0,8,1)] // Actual signal power is –RssiSync/2 (dBm) uint8_t rssi_sync; // RSSI average value over the payload of the received packet. Latched upon // the pkt_done IRQ. [negated, dBm, fixdt(0,8,1)] Actual signal power is // –RssiAvg/2 (dBm) uint8_t rssi_avg; [[nodiscard]] bool has_error() const { return (rx_status & 0b11111100U) != 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_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; }; static_assert(sizeof(packet_status_t) == 3); struct __attribute__((packed)) DeviceErrors { // LSB bool RC64K_CALIB_ERR : 1; // [0] RC64k calibration failed bool RC13M_CALIB_ERR : 1; // [1] RkC13M calibration failed bool PLL_CALIB_ERR : 1; // [2] PLL calibration failed bool ADC_CALIB_ERR : 1; // [3] ADC calibration failed bool IMG_CALIB_ERR : 1; // [4] IMG calibration failed bool XOSC_START_ERR : 1; // [5] XOSC failed to start bool PLL_LOCK_ERR : 1; // [6] PLL failed to lock uint8_t rfu_1 : 1; // [7] RFU bool PA_RAMP_ERR : 1; // [8] PA ramping failed uint8_t rfu_2 : 7; // [15:9] RFU // MSB [[nodiscard]] std::string to_string() const { auto ss = std::stringstream(); ss << "DeviceErrors{"; if (RC64K_CALIB_ERR) { ss << "RC64K_CALIB_ERR(0) "; } if (RC13M_CALIB_ERR) { ss << "RC13M_CALIB_ERR(1) "; } if (PLL_CALIB_ERR) { ss << "PLL_CALIB_ERR(2) "; } if (ADC_CALIB_ERR) { ss << "ADC_CALIB_ERR(3) "; } if (IMG_CALIB_ERR) { ss << "IMG_CALIB_ERR(4) "; } if (XOSC_START_ERR) { ss << "XOSC_START_ERR(5) "; } if (PLL_LOCK_ERR) { ss << "PLL_LOCK_ERR(6) "; } if (PA_RAMP_ERR) { ss << "PA_RAMP_ERR(8) "; } ss << "}"; return ss.str(); } [[nodiscard]] bool has_any_error() const { return RC64K_CALIB_ERR || RC13M_CALIB_ERR || PLL_CALIB_ERR || ADC_CALIB_ERR || IMG_CALIB_ERR || XOSC_START_ERR || PLL_LOCK_ERR || PA_RAMP_ERR; } }; static_assert(sizeof(DeviceErrors) == 2); constexpr bool in_range(const auto v, const auto min, const auto max) { return v >= min && v <= max; } constexpr bool valid_cr(const uint8_t cr) { using enum LoRaCodingRate; switch (cr) { case CR_4_5: case CR_4_6: case CR_4_7: case CR_4_8: return true; default: return false; } } constexpr std::tuple cr_to_ratio(const LoRaCodingRate cr) { using enum LoRaCodingRate; switch (cr) { case CR_4_5: return {4, 5}; case CR_4_6: return {4, 6}; case CR_4_7: return {4, 7}; case CR_4_8: return {4, 8}; default: return {0, 1}; } } constexpr bool valid_ldr_optimize(const uint8_t ldr_optimize) { using enum LoRaLowDataRateType; switch (ldr_optimize) { case LDR_ON: case LDR_OFF: return true; default: return false; } } constexpr bool valid_bw(const uint8_t bw) { using enum LoRaBandwidth; switch (bw) { case BW_7_8: case BW_10_4: case BW_15_6: case BW_20_8: case BW_31_25: case BW_41_7: case BW_62_5: case BW_125_0: case BW_250_0: case BW_500_0: return true; default: return false; } } using bw_t = float; constexpr bw_t bw_khz(const uint8_t bw) { using enum LoRaBandwidth; switch (bw) { case BW_7_8: return bw_t{7.8F}; case BW_10_4: return bw_t{10.4F}; case BW_15_6: return bw_t{15.6F}; case BW_20_8: return bw_t{20.8F}; case BW_31_25: return bw_t{31.25F}; case BW_41_7: return bw_t{41.7F}; case BW_62_5: return bw_t{62.5F}; case BW_125_0: return bw_t{125.0F}; case BW_250_0: return bw_t{250.0F}; case BW_500_0: return bw_t{500.0F}; default: return bw_t{0}; } } constexpr bool valid_sf(const uint8_t bw, const uint8_t sf) { if (const bool ok = valid_bw(bw); not ok) { return false; } switch (bw) { case BW_125_0: return in_range(sf, 5, 9); case BW_250_0: return in_range(sf, 5, 10); case BW_500_0: return in_range(sf, 5, 11); default: return in_range(sf, 5, 12); } } constexpr bool valid_tx_power(int8_t power) { if (power >= -9 && power <= 22) { return true; } if (power >= -17 && power <= 14) { return true; } return false; } constexpr bool valid_ramp_time(const uint8_t ramp_time) { switch (ramp_time) { case llcc68::SET_RAMP_10U: case llcc68::SET_RAMP_20U: case llcc68::SET_RAMP_40U: case llcc68::SET_RAMP_80U: case llcc68::SET_RAMP_200U: case llcc68::SET_RAMP_800U: case llcc68::SET_RAMP_1700U: case llcc68::SET_RAMP_3400U: return true; default: return false; } } constexpr bool valid_freq(const freq_t freq) { return in_range(freq, freq_t{150.0}, freq_t{960.0}); } struct irq_status_bits_t { // LSB bool tx_done : 1; // 0 bool rx_done : 1; // 1 bool preamble_detected : 1; // 2 bool sync_word_valid : 1; // 3 bool header_valid : 1; // 4 bool header_err : 1; // 5 bool crc_err : 1; // 6 bool cad_done : 1; // 7 bool cad_detected : 1; // 8 bool timeout : 1; // 9 uint8_t reserved_0 : 4; // 10-13 bool lr_fhss_hop : 1; // 14 uint8_t reserved_1 : 1; // 15 // MSB }; enum class TxRxPinState : uint8_t { TX, RX, }; struct irq_status_t { union { uint16_t raw; irq_status_bits_t bits; }; operator uint16_t() const { return raw; } [[nodiscard]] uint8_t msb() const { return static_cast((raw >> 8) & 0xFF); } [[nodiscard]] uint8_t lsb() const { return static_cast(raw & 0xFF); } static constexpr irq_status_t all() { return irq_status_t{.raw = 0b0100001111111111}; } [[nodiscard]] std::string to_string() const; }; static_assert(sizeof(irq_status_t) == 2, "irq_status_t must be 2 bytes"); enum class CAD_EXIT_MODE : uint8_t { CAD_ONLY = 0x00, CAD_RX = 0x01, }; enum class CAD_SYMB : uint8_t { CAD_ON_1_SYMB = 0x00, CAD_ON_2_SYMB = 0x01, CAD_ON_4_SYMB = 0x02, CAD_ON_8_SYMB = 0x03, CAD_ON_16_SYMB = 0x04, }; // Parameters cadDetPeak and cadDetMin define the sensitivity of the LoRa modem // when trying to correlate to actual LoRa preamble symbols. These two settings // depend on the LoRa spreading factor and Bandwidth, but also depend on the // number of symbol used to validate or not the detection. Choosing the right // value is not easy and the values selected must be carefully tested to ensure // a good detection at sensitivity level, and also to limit the number of false // detections. struct cad_params_t { CAD_SYMB symbol_num; uint8_t det_peak; uint8_t det_min; CAD_EXIT_MODE exit_mode; // 24-bit // // The parameter cadTimeout is only used when the CAD is performed with // cadExitMode = CAD_RX. Here, the cadTimeout indicates the time the device // will stay in Rx following a successful CAD. // Rx Timeout = cadTimeout * 15.625us uint32_t timeout; }; enum StartMode : uint8_t { COLD_START = 0x00, /** * @brief device configuration is in retention */ WARM_START = 0x01, }; struct sleep_config_t { // LSB /** * @brief * 0: RTC timeout disable * 1: wake-up on RTC timeout */ bool rtc_timeout_en : 1 {false}; uint8_t _rfu_0 : 1 {}; StartMode start_mode : 1 {StartMode::COLD_START}; uint8_t _rfu_1 : 5 {}; }; static_assert(sizeof(sleep_config_t) == 1, "sleep_config_t must be 1 byte"); struct irq_params_t { static constexpr uint16_t IRQ_MASK_NONE = 0; // The IrqMask masks or unmasks the IRQ which can be triggered by the device. // By default, all IRQ are masked (all ‘0’) and the user can enable them one // by one (or several at a time) by setting the corresponding mask to ‘1’. irq_status_t irqMask{IRQ_MASK_NONE}; // The interrupt causes a DIO to be set if the corresponding bit in DioxMask // and the IrqMask are set. As an example, if bit 0 of IrqMask is set to 1 // and bit 0 of DIO1Mask is set to 1 then, a rising edge of IRQ source // TxDone will be logged in the IRQ register and will appear at the same // time on DIO1. // // One IRQ can be mapped to all DIOs, one DIO can be mapped to all IRQs (an // OR operation is done) but some IRQ sources will be available only on // certain modes of operation and frames. irq_status_t dio1Mask{IRQ_MASK_NONE}; irq_status_t dio2Mask{IRQ_MASK_NONE}; irq_status_t dio3Mask{IRQ_MASK_NONE}; }; struct transmit_result { error_code post_action(); LLCC68 *self{}; std::chrono::milliseconds airtime_estimated; }; } // namespace app::driver::llcc68 namespace app::radio { namespace llcc68 = app::driver::llcc68; } namespace std { template <> struct is_error_code_enum : true_type {}; } // namespace std #endif /* ECC594CF_EDF0_42B5_8518_0EB3B3583727 */