// // Created by Kurosu Chan on 2024/1/26. // #ifndef LLCC68_DEFINITIONS_H #define LLCC68_DEFINITIONS_H #include #include #include #include #include #include #include #include "radiolib_definitions.hpp" namespace app::driver::llcc68 { 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, }; // - 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 TxRampTime : 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 = RADIOLIB_SX126X_REGULATOR_LDO, REGULATOR_DC_DC = RADIOLIB_SX126X_REGULATOR_DC_DC, }; 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"; } } enum LoRaCodingRate : uint8_t { CR_4_5 = 0x01, CR_4_6 = 0x02, CR_4_7 = 0x03, CR_4_8 = 0x04, }; 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"; } } constexpr uint16_t DEFAULT_PREAMBLE_LEN = 8; constexpr uint8_t DEFAULT_SYNC_WORD = RADIOLIB_SX126X_SYNC_WORD_PRIVATE; constexpr uint8_t DEFAULT_CRC_TYPE = RADIOLIB_SX126X_LORA_CRC_OFF; constexpr uint8_t DEFAULT_HEADER_TYPE = RADIOLIB_SX126X_LORA_HEADER_EXPLICIT; constexpr uint8_t DEFAULT_IQ_TYPE = RADIOLIB_SX126X_LORA_IQ_STANDARD; constexpr auto DEFAULT_SF = 7; constexpr auto DEFAULT_FREQUENCY = freq_t{433.05}; constexpr auto DEFAULT_BW = BW_250_0; constexpr auto DEFAULT_CR = CR_4_5; constexpr auto DEFAULT_LDR_OPTIMIZE = RADIOLIB_SX126X_LORA_LOW_DATA_RATE_OPTIMIZE_OFF; 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 { uint8_t bw; uint8_t sf; uint8_t cr; uint8_t ldr_optimize; static modulation_params_t Default() { return modulation_params_t{ .bw = DEFAULT_BW, .sf = DEFAULT_SF, .cr = DEFAULT_CR, .ldr_optimize = DEFAULT_LDR_OPTIMIZE, }; } [[nodiscard]] auto to_string() const -> std::string { return std::format("mod_params: bw={}, sf={}, cr={}, ldr_optimize={}", to_str(static_cast(bw)), sf, to_str(static_cast(cr)), ldr_optimize ? "ON" : "OFF"); } }; struct packet_params_t { uint16_t preamble_len; uint8_t payload_len; uint8_t crc_type; uint8_t hdr_type; static packet_params_t Default() { return packet_params_t{ .preamble_len = DEFAULT_PREAMBLE_LEN, .payload_len = 0xff, .crc_type = DEFAULT_CRC_TYPE, .hdr_type = DEFAULT_HEADER_TYPE, }; } }; struct pa_setting_t { uint8_t pa_duty_cycle; uint8_t hp_max; uint8_t device_sel; static constexpr pa_setting_t Default22dBm() { return {0x04, 0x07, 0x00}; } /** * @note SetTxParams should be set to +22dBm */ static constexpr pa_setting_t Default20dBm() { return {0x03, 0x05, 0x00}; } /** * @note SetTxParams should be set to +22dBm */ static constexpr pa_setting_t Default17dBm() { return {0x02, 0x03, 0x00}; } /** * @note SetTxParams should be set to +14dBm */ static constexpr pa_setting_t Default14dBm() { return {0x02, 0x02, 0x00}; } static constexpr pa_setting_t Default() { return Default22dBm(); } }; inline const char *to_str(llcc68::TxRampTime 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 TX_POWER_AUTO = 0x7f; static constexpr auto MAX_POWER_HIGH_PA = 22; static constexpr auto MAX_POWER_LOW_PA = 14; int8_t power; TxRampTime ramp_time; static tx_params_t Default() { return tx_params_t{ .power = MAX_POWER_HIGH_PA, .ramp_time = SET_RAMP_200U, }; } [[nodiscard]] std::string to_string() const { if (power == TX_POWER_AUTO) { return "tx_params: power=auto, ramp_time=" + std::string(to_str(ramp_time)); } return std::format("tx_params: power={}, ramp_time={}", power, to_str(ramp_time)); } }; struct lora_parameters_t { modulation_params_t mod_params; packet_params_t packet_params; tx_params_t tx_params; freq_t frequency; uint8_t 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 = DEFAULT_FREQUENCY, .sync_word = DEFAULT_SYNC_WORD, }; } }; enum class ChipType { Unknown, LLCC68, SX1261, SX1262, }; inline const char *chip_type_str(const 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 = RADIOLIB_SX126X_PACKET_TYPE_GFSK, LORA = RADIOLIB_SX126X_PACKET_TYPE_LORA, LR_FHSS = RADIOLIB_SX126X_PACKET_TYPE_LR_FHSS, }; 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 uint8_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 -rssi_pkt / 2.0F; } [[nodiscard]] auto snr_pkt_db() const -> float { return snr_pkt / 4.0F; } [[nodiscard]] auto signal_rssi_pkt_dbm() const -> float { return -signal_rssi_pkt / 2.0F; } }; struct __attribute__((packed)) fsk_packet_status_t { // bit 7: preamble err // bit 6: sync err // bit 5: adrs err // bit 4: crc err // bit 3: length err // bit 2: abort err // bit 1: pkt received // bit 0: pkt sent uint8_t rx_status; // RSSI value latched upon the detection of the sync address. // [negated, dBm, fixdt(0,8,1)] // Actual signal power is –RssiSync/2 (dBm) uint8_t rssi_sync; // RSSI average value over the payload of the received packet. Latched upon the pkt_done IRQ. // [negated, dBm, fixdt(0,8,1)] // Actual signal power is –RssiAvg/2 (dBm) uint8_t rssi_avg; }; union packet_status_t { uint8_t raw[3]; lora_packet_status_t lora; fsk_packet_status_t fsk; }; 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) { switch (cr) { case RADIOLIB_SX126X_LORA_CR_4_5: case RADIOLIB_SX126X_LORA_CR_4_6: case RADIOLIB_SX126X_LORA_CR_4_7: case RADIOLIB_SX126X_LORA_CR_4_8: return true; default: return false; } } constexpr std::tuple cr_to_ratio(const uint8_t cr) { switch (cr) { case RADIOLIB_SX126X_LORA_CR_4_5: return {4, 5}; case RADIOLIB_SX126X_LORA_CR_4_6: return {4, 6}; case RADIOLIB_SX126X_LORA_CR_4_7: return {4, 7}; case RADIOLIB_SX126X_LORA_CR_4_8: return {4, 8}; default: return {0, 1}; } } constexpr bool valid_ldr_optimize(const uint8_t ldr_optimize) { return static_cast(ldr_optimize <= RADIOLIB_SX126X_LORA_LOW_DATA_RATE_OPTIMIZE_ON); } constexpr bool valid_bw(const uint8_t bw) { switch (bw) { case RADIOLIB_SX126X_LORA_BW_7_8: case RADIOLIB_SX126X_LORA_BW_10_4: case RADIOLIB_SX126X_LORA_BW_15_6: case RADIOLIB_SX126X_LORA_BW_20_8: case RADIOLIB_SX126X_LORA_BW_31_25: case RADIOLIB_SX126X_LORA_BW_41_7: case RADIOLIB_SX126X_LORA_BW_62_5: case RADIOLIB_SX126X_LORA_BW_125_0: case RADIOLIB_SX126X_LORA_BW_250_0: case RADIOLIB_SX126X_LORA_BW_500_0: return true; default: return false; } } using bw_t = float; constexpr bw_t bw_khz(const uint8_t bw) { switch (bw) { case RADIOLIB_SX126X_LORA_BW_7_8: return bw_t{7.8F}; case RADIOLIB_SX126X_LORA_BW_10_4: return bw_t{10.4F}; case RADIOLIB_SX126X_LORA_BW_15_6: return bw_t{15.6F}; case RADIOLIB_SX126X_LORA_BW_20_8: return bw_t{20.8F}; case RADIOLIB_SX126X_LORA_BW_31_25: return bw_t{31.25F}; case RADIOLIB_SX126X_LORA_BW_41_7: return bw_t{41.7F}; case RADIOLIB_SX126X_LORA_BW_62_5: return bw_t{62.5F}; case RADIOLIB_SX126X_LORA_BW_125_0: return bw_t{125.0F}; case RADIOLIB_SX126X_LORA_BW_250_0: return bw_t{250.0F}; case RADIOLIB_SX126X_LORA_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 RADIOLIB_SX126X_LORA_BW_125_0: return in_range(sf, 5, 9); case RADIOLIB_SX126X_LORA_BW_250_0: return in_range(sf, 5, 10); case RADIOLIB_SX126X_LORA_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 == tx_params_t::TX_POWER_AUTO) { return true; } if (power >= -9 && power <= 22) { return true; } if (power >= -17 && power <= 14) { return true; } return false; } constexpr bool valid_ramp_time(const llcc68::TxRampTime 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}); } } #endif // LLCC68_DEFINITIONS_H