#include "llcc68.hpp" #include "llcc68_definitions.hpp" #include "priv_radiolib_definitions.hpp" #include #include #include #include #include #include #include #include #include #include #include LOG_MODULE_REGISTER(llcc68, CONFIG_LLCC68_LOG_LEVEL); namespace { using error_code = app::driver::llcc68::error_code; using ue_t = std::unexpected; using llcc68_errc = app::driver::llcc68::Errc; ue_t ue(int zephyr_ret) { return ue_t{app::driver::llcc68::make_zephyr_error_code(zephyr_ret)}; } ue_t ue(const error_code &error) { return ue_t{error}; } ue_t ue(llcc68_errc error) { return ue_t{app::driver::llcc68::make_error_code(error)}; } bool is_command_processing_error(const error_code &error) { return error == app::driver::llcc68::make_error_code(llcc68_errc::CommandProcessing); } } // namespace // Helper macros for propagating std::expected errors // Usage: APP_RADIO_RETURN_ERR(res); #define APP_RADIO_RETURN_ERR(STATEVAR) \ do { \ if (!(STATEVAR).has_value()) { \ return ue((STATEVAR).error()); \ } \ } while (0) // Usage: APP_RADIO_RETURN_ERR_IGNORE_PROC_ERR(res); // If error is COMMAND_PROCESSING, ignore it and continue; otherwise return the // error #define APP_RADIO_RETURN_ERR_IGNORE_PROC_ERR(STATEVAR) \ do { \ if (!(STATEVAR).has_value()) { \ auto __app_radio_err = (STATEVAR).error(); \ if (!is_command_processing_error(__app_radio_err)) { \ return ue(__app_radio_err); \ } \ } \ } while (0) /** * \brief return if STATEVAR has value, otherwise return the error * \param STATEVAR the variable to check * \param ctx the context of the error * \param ... the arguments to pass to the error message * \note this macro is used to return the error from a function and log the * error message */ #define APP_RADIO_RETURN_ERR_CTX(STATEVAR, ...) \ { \ if (!(STATEVAR.has_value())) { \ LOG_ERR(__VA_ARGS__); \ return ue(STATEVAR.error()); \ } \ } namespace app::driver::llcc68 { const struct llcc68_config &LLCC68::config() const { return *static_cast(dev->config); } struct llcc68_data & LLCC68::data() { // NOLINT(readability-make-member-function-const) mutable return *static_cast(dev->data); } std::optional LLCC68::tx_enable_gpio() const { if (config().tx_enable_gpio.port != nullptr) { return config().tx_enable_gpio; } return std::nullopt; } std::optional LLCC68::rx_enable_gpio() const { if (config().rx_enable_gpio.port != nullptr) { return config().rx_enable_gpio; } return std::nullopt; } error_code LLCC68::init() { if (!device_is_ready(dev)) { return make_zephyr_error_code(-ENODEV); } return {}; } // Internal helpers (TU-local) namespace { // Max payload the radio supports and buffer sizing helpers constexpr size_t kMaxPayload = 32; error_code command_status_to_error(status_t st) { switch (st.command_status) { case CommandStatus::FAILURE_TO_EXECUTE_COMMAND: return make_error_code(Errc::FailureToExecuteCommand); case CommandStatus::COMMAND_TIMEOUT: return make_error_code(Errc::CommandTimeout); case CommandStatus::COMMAND_PROCESSING_ERROR: return make_error_code(Errc::CommandProcessing); default: return {}; } } int wait_for_not_busy(const gpio_dt_spec &busy_gpio, uint16_t timeout_ms) { if (not device_is_ready(busy_gpio.port)) { return -ENODEV; } const int64_t start = k_uptime_get(); while (gpio_pin_get_dt(&busy_gpio) > 0) { if ((k_uptime_get() - start) >= timeout_ms) { return -ETIMEDOUT; } k_busy_wait(50); // ~50 us poll interval } return 0; } } // namespace void LLCC68::tx_rx_en_pin_set(TxRxPinState state) { if (not tx_enable_gpio() or (not rx_enable_gpio())) { return; } auto t = *tx_enable_gpio(); auto r = *rx_enable_gpio(); if (state == TxRxPinState::TX) { gpio_pin_set_dt(&t, 1); gpio_pin_set_dt(&r, 0); } else if (state == TxRxPinState::RX) { gpio_pin_set_dt(&t, 0); gpio_pin_set_dt(&r, 1); } } /*! \brief Get expected time-on-air for a given size of payload \param len Payload length in bytes. \returns Expected time-on-air in microseconds. */ constexpr airtime_t calc_time_on_air(uint8_t len, uint8_t sf, LoRaBandwidth bw, LoRaCodingRate cr, uint16_t preamble_length, LoRaHeaderType header_type, LoRaCrcType crc_type) { // everything is in microseconds to allow integer arithmetic // some constants have .25, these are multiplied by 4, and have _x4 postfix to // indicate that fact const auto bw_ = bw_khz(bw); const auto ubw = static_cast(bw_ * 10); const uint32_t symbolLength_us = (static_cast(1000 * 10) << sf) / ubw; uint8_t sfCoeff1_x4 = 17; // (4.25 * 4) uint8_t sfCoeff2 = 8; if (sf == 5 || sf == 6) { sfCoeff1_x4 = 25; // 6.25 * 4 sfCoeff2 = 0; } uint8_t sfDivisor = 4 * sf; if (symbolLength_us >= 16000) { sfDivisor = 4 * (sf - 2); } constexpr int8_t bitsPerCrc = 16; int8_t N_symbol_header = header_type == RADIOLIB_SX126X_LORA_HEADER_EXPLICIT ? 20 : 0; // numerator of equation in section 6.1.4 of SX1268 datasheet v1.1 (might // not actually be bitcount, but it has len * 8) auto bit_count = static_cast( static_cast(8) * static_cast(len) + static_cast(crc_type * bitsPerCrc) - static_cast(4 * sf) + static_cast(sfCoeff2) + static_cast(N_symbol_header)); bit_count = std::max(bit_count, 0); // add (sfDivisor) - 1 to the numerator to give integer CEIL(...) const uint16_t nPreCodedSymbols = (bit_count + (sfDivisor - 1)) / (sfDivisor); const auto de = std::get<1>(cr_to_ratio(cr)); // preamble can be 65k, therefore nSymbol_x4 needs to be 32 bit const uint32_t nSymbol_x4 = (preamble_length + 8) * 4 + sfCoeff1_x4 + nPreCodedSymbols * de * 4; return airtime_t{symbolLength_us * nSymbol_x4 / 4}; } // SPI helpers implementing the LLCC68 wire protocol expected LLCC68::read_stream(uint8_t cmd, std::span data, timeout_ms_t busy_timeout) { if (!spi_is_ready_dt(&config().spi)) { return ue(-ENODEV); } if (data.size() > kMaxPayload) { // protocol limit (payload up to 255 bytes typical) return ue(-EINVAL); } if (int err = wait_for_not_busy(config().busy_gpio, busy_timeout); err) { return ue(err); } // TX: [cmd][dummy for status][dummy * data_len] constexpr size_t kHdr = 1U + 1U; std::array tx{}; std::array rx{}; const size_t xfer_len = kHdr + data.size(); tx[0] = cmd; spi_buf tx_bufs[] = { {.buf = tx.data(), .len = xfer_len}, }; spi_buf rx_bufs[] = { {.buf = rx.data(), .len = xfer_len}, }; spi_buf_set tx_set{.buffers = tx_bufs, .count = 1}; spi_buf_set rx_set{.buffers = rx_bufs, .count = 1}; const int ret = spi_transceive_dt(&config().spi, &tx_set, &rx_set); if (ret != 0) { return ue(ret); } const size_t status_idx = 1U; // after cmd const size_t data_idx = status_idx + 1; const uint8_t status = rx[status_idx]; std::copy(rx.begin() + static_cast(data_idx), rx.begin() + static_cast(xfer_len), data.begin()); if (auto e = command_status_to_error(std::bit_cast(status)); e) { return ue(e); } return unit{}; } expected LLCC68::write_stream(uint8_t cmd, std::span data_from_host, timeout_ms_t busy_timeout) { if (!spi_is_ready_dt(&config().spi)) { return ue(-ENODEV); } if (data_from_host.size() > kMaxPayload) { return ue(-EINVAL); } if (int err = wait_for_not_busy(config().busy_gpio, busy_timeout); err) { return ue(err); } // TX: [cmd][payload...] std::array tx{}; std::array rx{}; const size_t xfer_len = 1U + data_from_host.size(); tx[0] = cmd; std::ranges::copy(data_from_host, tx.begin() + 1); spi_buf tx_bufs[] = {{.buf = tx.data(), .len = xfer_len}}; spi_buf rx_bufs[] = {{.buf = rx.data(), .len = xfer_len}}; spi_buf_set tx_set{.buffers = tx_bufs, .count = 1}; spi_buf_set rx_set{.buffers = rx_bufs, .count = 1}; const int ret = spi_transceive_dt(&config().spi, &tx_set, &rx_set); if (ret != 0) { return ue(ret); } // Status appears right after command, i.e., in rx[1] (if at least one more // byte clocked) if (xfer_len >= 2U) { const uint8_t status = rx[1U]; if (auto e = command_status_to_error(std::bit_cast(status)); e) { return ue(e); } } return unit{}; } expected LLCC68::read_register(uint16_t reg, std::span data_to_host, timeout_ms_t busy_timeout) { if (not spi_is_ready_dt(&config().spi)) { return ue(-ENODEV); } if (data_to_host.size() > kMaxPayload) { return ue(-EINVAL); } if (int err = wait_for_not_busy(config().busy_gpio, busy_timeout); err) { return ue(err); } // TX: [READ_REG][addrMSB][addrLSB][dummy for status][dummy * data_len] constexpr size_t kHdr = 1U + 2U + 1U; std::array tx{}; std::array rx{}; const size_t xfer_len = kHdr + data_to_host.size(); tx[0] = RADIOLIB_SX126X_CMD_READ_REGISTER; tx[1] = static_cast((reg >> 8) & 0xFF); tx[2] = static_cast(reg & 0xFF); spi_buf tx_bufs[] = {{.buf = tx.data(), .len = xfer_len}}; spi_buf rx_bufs[] = {{.buf = rx.data(), .len = xfer_len}}; spi_buf_set tx_set{.buffers = tx_bufs, .count = 1}; spi_buf_set rx_set{.buffers = rx_bufs, .count = 1}; const int ret = spi_transceive_dt(&config().spi, &tx_set, &rx_set); if (ret != 0) { return ue(ret); } const size_t status_idx = 1U + 2U; // after cmd+addr const size_t data_idx = status_idx + 1; const uint8_t status = rx[status_idx]; std::copy(rx.begin() + static_cast(data_idx), rx.begin() + static_cast(xfer_len), data_to_host.begin()); if (auto e = command_status_to_error(std::bit_cast(status)); e) { return ue(e); } return unit{}; } expected LLCC68::write_register(uint16_t reg, std::span data_from_host, timeout_ms_t busy_timeout) { if (!spi_is_ready_dt(&config().spi)) { return ue(-ENODEV); } if (data_from_host.size() > kMaxPayload) { return ue(-EINVAL); } if (int err = wait_for_not_busy(config().busy_gpio, busy_timeout); err) { return ue(err); } // TX: [WRITE_REG][addrMSB][addrLSB][payload...] std::array tx{}; std::array rx{}; const size_t xfer_len = 1U + 2U + data_from_host.size(); tx[0] = RADIOLIB_SX126X_CMD_WRITE_REGISTER; tx[1] = static_cast((reg >> 8) & 0xFF); tx[2] = static_cast(reg & 0xFF); std::ranges::copy(data_from_host, tx.begin() + 3); spi_buf tx_bufs[] = {{.buf = tx.data(), .len = xfer_len}}; spi_buf rx_bufs[] = {{.buf = rx.data(), .len = xfer_len}}; spi_buf_set tx_set{.buffers = tx_bufs, .count = 1}; spi_buf_set rx_set{.buffers = rx_bufs, .count = 1}; const int ret = spi_transceive_dt(&config().spi, &tx_set, &rx_set); if (ret != 0) { return ue(ret); } // Check status after address phase (first data byte time slot) if (xfer_len >= 4U) { const uint8_t status = rx[3U]; if (auto e = command_status_to_error(std::bit_cast(status)); e) { return ue(e); } } return unit{}; } expected LLCC68::read_buffer_copy(uint8_t offset, std::span data_to_host, timeout_ms_t busy_timeout) { if (data_to_host.size() > MAX_BUFFER_PAYLOAD) { return ue(-EINVAL); } auto rx = read_buffer_ref(offset, static_cast(data_to_host.size()), busy_timeout); APP_RADIO_RETURN_ERR_IGNORE_PROC_ERR(rx); std::copy(rx->begin(), rx->end(), data_to_host.begin()); return static_cast(rx->size()); } expected, error_code> LLCC68::read_buffer_ref(uint8_t offset, uint8_t n, timeout_ms_t busy_timeout) { if (not spi_is_ready_dt(&config().spi)) { return ue(-ENODEV); } if (n > MAX_BUFFER_PAYLOAD) { return ue(-EINVAL); } if (int err = wait_for_not_busy(config().busy_gpio, busy_timeout); err) { return ue(err); } // TX: [READ_BUFFER][offset][dummy for status][dummy * data_len] constexpr size_t kHdr = 1U + 1U + 1U; auto &tx = data().tx_buffer; auto &rx = data().rx_buffer; const size_t xfer_len = kHdr + n; tx[0] = RADIOLIB_SX126X_CMD_READ_BUFFER; tx[1] = offset; spi_buf tx_bufs[] = {{.buf = tx, .len = xfer_len}}; spi_buf rx_bufs[] = {{.buf = rx, .len = xfer_len}}; spi_buf_set tx_set{.buffers = tx_bufs, .count = 1}; spi_buf_set rx_set{.buffers = rx_bufs, .count = 1}; const int ret = spi_transceive_dt(&config().spi, &tx_set, &rx_set); if (ret != 0) { return ue(ret); } const size_t status_idx = 1U + 1U; // after cmd+offset const size_t data_idx = status_idx + 1; const uint8_t status = rx[status_idx]; if (auto e = command_status_to_error(std::bit_cast(status)); e) { return ue(e); } // return only the data part return std::span{rx}.subspan(data_idx, n); } expected, error_code> LLCC68::read_rx_buffer_ref(timeout_ms_t busy_timeout) { auto r = get_rx_buffer_status(busy_timeout); APP_RADIO_RETURN_ERR_IGNORE_PROC_ERR(r); const auto [sz, ptr] = r.value(); // the rx pointer would become 0x80 at first, so we need to adapt that // behavior const auto r_ptr = ptr - sz < DEFAULT_RX_BUFFER_ADDRESS ? ptr : ptr - sz; return read_buffer_ref(r_ptr, sz, busy_timeout); } expected LLCC68::write_tx_buffer(std::span data_from_host) { if (data_from_host.size() > MAX_BUFFER_PAYLOAD) { return ue(-EINVAL); } auto &tx = data().tx_buffer; tx[0] = RADIOLIB_SX126X_CMD_WRITE_BUFFER; tx[1] = DEFAULT_TX_BUFFER_ADDRESS; std::ranges::copy(data_from_host, tx + 2); data().tx_xfer_size = data_from_host.size(); return unit{}; } expected LLCC68::flush_tx_buffer(timeout_ms_t busy_timeout) { if (not spi_is_ready_dt(&config().spi)) { return ue(-ENODEV); } if (int err = wait_for_not_busy(config().busy_gpio, busy_timeout); err) { return ue(err); } // TX: [WRITE_BUFFER][offset][payload...] auto &tx = data().tx_buffer; auto &rx = data().rx_buffer; const size_t xfer_len = 1U + 1U + data().tx_xfer_size; // sanity checks if (data().tx_xfer_size == 0) { return ue(-EINVAL); } if (tx[0] != RADIOLIB_SX126X_CMD_WRITE_BUFFER || tx[1] != DEFAULT_TX_BUFFER_ADDRESS) { return ue(-EINVAL); } spi_buf tx_bufs[] = {{.buf = tx, .len = xfer_len}}; spi_buf rx_bufs[] = {{.buf = rx, .len = xfer_len}}; spi_buf_set tx_set{.buffers = tx_bufs, .count = 1}; spi_buf_set rx_set{.buffers = rx_bufs, .count = 1}; const int ret = spi_transceive_dt(&config().spi, &tx_set, &rx_set); if (ret != 0) { return ue(ret); } // Status appears after offset (at first payload slot) if (xfer_len >= 3U) { const uint8_t status = rx[2U]; if (auto e = command_status_to_error(std::bit_cast(status)); e) { return ue(e); } } return unit{}; } expected LLCC68::write_buffer_immediate_mode(uint8_t offset, std::span data_from_host, timeout_ms_t busy_timeout) { if (not spi_is_ready_dt(&config().spi)) { return ue(-ENODEV); } if (data_from_host.size() > MAX_BUFFER_PAYLOAD) { return ue(-EINVAL); } if (int err = wait_for_not_busy(config().busy_gpio, busy_timeout); err) { return ue(err); } // TX: [WRITE_BUFFER][offset][payload...] auto &tx = data().tx_buffer; auto &rx = data().rx_buffer; const size_t xfer_len = 1U + 1U + data_from_host.size(); tx[0] = RADIOLIB_SX126X_CMD_WRITE_BUFFER; tx[1] = offset; std::ranges::copy(data_from_host, tx + 2); spi_buf tx_bufs[] = {{.buf = tx, .len = xfer_len}}; spi_buf rx_bufs[] = {{.buf = rx, .len = xfer_len}}; spi_buf_set tx_set{.buffers = tx_bufs, .count = 1}; spi_buf_set rx_set{.buffers = rx_bufs, .count = 1}; const int ret = spi_transceive_dt(&config().spi, &tx_set, &rx_set); if (ret != 0) { return ue(ret); } // Status appears after offset (at first payload slot) if (xfer_len >= 3U) { const uint8_t status = rx[2U]; if (auto e = command_status_to_error(std::bit_cast(status)); e) { return ue(e); } } return unit{}; } } // namespace app::driver::llcc68 // High-level helpers namespace app::driver::llcc68 { expected LLCC68::get_status() { uint8_t raw = 0; auto r = read_stream(RADIOLIB_SX126X_CMD_GET_STATUS, std::span{&raw, 1}); APP_RADIO_RETURN_ERR_IGNORE_PROC_ERR(r); return std::bit_cast(raw); } expected LLCC68::dio1_irq_enable() { int ret = gpio_pin_interrupt_configure_dt(&config().dio1_gpio, GPIO_INT_EDGE_TO_ACTIVE); if (ret != 0) { return ue(ret); } return unit{}; } void LLCC68::dio1_irq_disable() { gpio_pin_interrupt_configure_dt(&config().dio1_gpio, GPIO_INT_DISABLE); } void LLCC68::set_dio1_irq_handler(user_dio1_handler_t handler, void *user_data) { auto &d = data(); d.dio1_user_handler = handler; d.dio1_user_data = user_data; } expected LLCC68::get_packet_status() { uint8_t buf[3]{}; auto r = read_stream(RADIOLIB_SX126X_CMD_GET_PACKET_STATUS, buf); APP_RADIO_RETURN_ERR_IGNORE_PROC_ERR(r); packet_status_t ps{}; std::copy(buf, buf + 3, ps.raw); return ps; } expected LLCC68::reset() { // Drive RESET low-high with delays per datasheet, then poll standby if (not device_is_ready(config().reset_gpio.port)) { return ue(-ENODEV); } // NOTE: low active gpio_pin_configure_dt(&config().reset_gpio, GPIO_OUTPUT_ACTIVE); k_msleep(2); // hold low gpio_pin_set_dt(&config().reset_gpio, 0); k_msleep(10); // try standby to verify const int64_t start = k_uptime_get(); const int64_t interval_ms = 500; while ((k_uptime_get() - start) < interval_ms) { auto r = set_standby(); if (r.has_value()) { return unit{}; } k_msleep(20); } return ue(-ETIMEDOUT); } expected LLCC68::set_standby() { const uint8_t data[] = {RADIOLIB_SX126X_STANDBY_RC}; return write_stream(RADIOLIB_SX126X_CMD_SET_STANDBY, data); } expected LLCC68::hal_get_chip_type() { constexpr auto SX1262_CHIP_TYPE = "SX1262"; constexpr auto LLCC68_CHIP_TYPE = "LLCC68"; constexpr auto SX1261_CHIP_TYPE = "SX1261"; char version[16]{}; auto version_buf = std::span{reinterpret_cast(version), std::size(version)}; auto r = read_register(RADIOLIB_SX126X_REG_VERSION_STRING, version_buf); APP_RADIO_RETURN_ERR(r); LOG_HEXDUMP_DBG(version, sizeof(version), "version dump"); if (strncmp(version, LLCC68_CHIP_TYPE, 6) == 0) { return ChipType::LLCC68; } if (strncmp(version, SX1261_CHIP_TYPE, 6) == 0) { return ChipType::SX1261; } if (strncmp(version, SX1262_CHIP_TYPE, 6) == 0) { return ChipType::SX1262; } return ChipType::Unknown; } expected LLCC68::set_dio_irq_params(irq_params_t params) { const uint8_t data[8] = { params.irqMask.msb(), params.irqMask.lsb(), params.dio1Mask.msb(), params.dio1Mask.lsb(), params.dio2Mask.msb(), params.dio2Mask.lsb(), params.dio3Mask.msb(), params.dio3Mask.lsb(), }; return write_stream(RADIOLIB_SX126X_CMD_SET_DIO_IRQ_PARAMS, data); } expected LLCC68::get_irq_status() { uint8_t buf[2]{}; auto r = read_stream(RADIOLIB_SX126X_CMD_GET_IRQ_STATUS, buf); APP_RADIO_RETURN_ERR_IGNORE_PROC_ERR(r); irq_status_t st{static_cast((buf[0] << 8) | buf[1])}; return st; } expected LLCC68::clear_irq_status(irq_status_t irq) { const uint8_t data[] = {irq.msb(), irq.lsb()}; return write_stream(RADIOLIB_SX126X_CMD_CLEAR_IRQ_STATUS, data); } static constexpr uint32_t gfsk_bitrate_to_raw(uint32_t bitrate_bps) { constexpr uint64_t xtal_hz = 32'000'000ULL; return static_cast((32ULL * xtal_hz) / bitrate_bps); } static constexpr uint32_t freq_hz_to_raw(uint32_t freq_hz) { return static_cast(static_cast(freq_hz) / RADIOLIB_SX126X_FREQUENCY_STEP_SIZE); } static constexpr uint8_t gfsk_crc_len_bytes(GfskCrcType crc_type) { switch (crc_type) { case GfskCrcType::Crc1Byte: case GfskCrcType::Crc1ByteInverted: return 1; case GfskCrcType::Crc2Byte: case GfskCrcType::Crc2ByteInverted: return 2; case GfskCrcType::Off: default: return 0; } } static constexpr std::chrono::milliseconds calc_gfsk_time_on_air(gfsk_parameters_t params, uint8_t payload_len) { const auto address_bits = params.packet_params.address_filtering == GfskAddressFiltering::Disabled ? 0U : 8U; const auto length_bits = params.packet_params.packet_length_mode == GfskPacketLengthMode::Variable ? 8U : 0U; const uint32_t bits = params.packet_params.preamble_bits + params.packet_params.sync_length_bits + length_bits + address_bits + (static_cast(payload_len) + gfsk_crc_len_bytes(params.packet_params.crc_type)) * 8U; const uint32_t ms = (bits * 1000U + params.mod_params.bitrate_bps - 1U) / params.mod_params.bitrate_bps; return std::chrono::milliseconds{ms}; } static expected update_register_bits(LLCC68 &radio, uint16_t reg, uint8_t mask, uint8_t value) { uint8_t reg_value = 0; auto r = radio.read_register(reg, std::span{®_value, 1}); APP_RADIO_RETURN_ERR(r); reg_value = static_cast((reg_value & ~mask) | (value & mask)); return radio.write_register(reg, std::span{®_value, 1}); } // Convenience APIs ported from ESP version expected LLCC68::set_packet_type_lora() { const uint8_t data[] = {RADIOLIB_SX126X_PACKET_TYPE_LORA}; // Set packet type auto r = write_stream(RADIOLIB_SX126X_CMD_SET_PACKET_TYPE, data); APP_RADIO_RETURN_ERR(r); // Set Rx/Tx fallback to STDBY_RC per reference implementation const uint8_t fallback[] = {RADIOLIB_SX126X_RX_TX_FALLBACK_MODE_STDBY_RC}; return write_stream(RADIOLIB_SX126X_CMD_SET_RX_TX_FALLBACK_MODE, fallback); } expected LLCC68::set_packet_type_gfsk() { const uint8_t data[] = {RADIOLIB_SX126X_PACKET_TYPE_GFSK}; auto r = write_stream(RADIOLIB_SX126X_CMD_SET_PACKET_TYPE, data); APP_RADIO_RETURN_ERR(r); const uint8_t fallback[] = {RADIOLIB_SX126X_RX_TX_FALLBACK_MODE_STDBY_RC}; return write_stream(RADIOLIB_SX126X_CMD_SET_RX_TX_FALLBACK_MODE, fallback); } expected LLCC68::set_modulation_params(uint8_t sf, uint8_t bw, uint8_t cr, uint8_t ldro) { if (!valid_bw(bw) || !valid_sf(bw, sf) || !valid_cr(static_cast(cr)) || !valid_ldr_optimize(ldro)) { return ue(-EINVAL); } const uint8_t data[] = {sf, bw, cr, ldro}; return write_stream(RADIOLIB_SX126X_CMD_SET_MODULATION_PARAMS, data); } expected LLCC68::set_modulation_params(modulation_params_t mod_params) { const uint8_t data[] = {mod_params.sf, mod_params.bw, mod_params.cr, mod_params.ldr_optimize}; return write_stream(RADIOLIB_SX126X_CMD_SET_MODULATION_PARAMS, data); } expected LLCC68::set_gfsk_modulation_params(gfsk_modulation_params_t mod_params) { if (mod_params.bitrate_bps < 600 || mod_params.bitrate_bps > 300'000 || mod_params.frequency_deviation_hz > 200'000 || mod_params.frequency_deviation_hz + (mod_params.bitrate_bps / 2U) > 250'000) { return ue(-EINVAL); } const uint32_t bitrate = gfsk_bitrate_to_raw(mod_params.bitrate_bps); const uint32_t fdev = freq_hz_to_raw(mod_params.frequency_deviation_hz); const uint8_t data[] = { static_cast((bitrate >> 16) & 0xFF), static_cast((bitrate >> 8) & 0xFF), static_cast(bitrate & 0xFF), static_cast(mod_params.pulse_shape), static_cast(mod_params.rx_bandwidth), static_cast((fdev >> 16) & 0xFF), static_cast((fdev >> 8) & 0xFF), static_cast(fdev & 0xFF), }; auto r = write_stream(RADIOLIB_SX126X_CMD_SET_MODULATION_PARAMS, data); APP_RADIO_RETURN_ERR(r); r = fix_gfsk_low_bitrate(mod_params.bitrate_bps); APP_RADIO_RETURN_ERR(r); return fix_gfsk_tx_modulation(); } expected LLCC68::set_lora_sync_word(uint16_t sync_word) { // Ensure LoRa packet type set uint8_t pkt_type = 0; auto g = read_stream(RADIOLIB_SX126X_CMD_GET_PACKET_TYPE, std::span{&pkt_type, 1}); APP_RADIO_RETURN_ERR(g); if (pkt_type != RADIOLIB_SX126X_PACKET_TYPE_LORA) { return ue(-EINVAL); } const uint8_t data[2] = { // MSB static_cast((sync_word & 0xFF00) >> 8), static_cast(sync_word & 0x00FF), // LSB }; return write_register(RADIOLIB_SX126X_REG_LORA_SYNC_WORD_MSB, data); } expected LLCC68::set_dio2_as_rf_switch(bool en) { const uint8_t data[] = { en ? static_cast(RADIOLIB_SX126X_DIO2_AS_RF_SWITCH) : static_cast(RADIOLIB_SX126X_DIO2_AS_IRQ)}; return write_stream(RADIOLIB_SX126X_CMD_SET_DIO2_AS_RF_SWITCH_CTRL, data); } static constexpr uint32_t freq_mhz_to_raw(freq_t fmhz) { // value = (f_xtal / 2^25) * f_rf const auto scale = static_cast(1U << RADIOLIB_SX126X_DIV_EXPONENT); const auto fxtal = static_cast(RADIOLIB_SX126X_CRYSTAL_FREQ); return static_cast((fmhz * scale) / fxtal); } expected LLCC68::set_rf_frequency(freq_t freq_mhz) { if (not valid_freq(freq_mhz)) { return ue(-EINVAL); } const uint32_t frf = freq_mhz_to_raw(freq_mhz); const uint8_t data[] = { static_cast((frf >> 24) & 0xFF), static_cast((frf >> 16) & 0xFF), static_cast((frf >> 8) & 0xFF), static_cast(frf & 0xFF), }; return write_stream(RADIOLIB_SX126X_CMD_SET_RF_FREQUENCY, data); } expected LLCC68::set_rf_frequency(uint32_t freq_raw) { const uint8_t data[] = { static_cast((freq_raw >> 24) & 0xFF), static_cast((freq_raw >> 16) & 0xFF), static_cast((freq_raw >> 8) & 0xFF), static_cast(freq_raw & 0xFF), }; return write_stream(RADIOLIB_SX126X_CMD_SET_RF_FREQUENCY, data); } expected LLCC68::set_packet_params(uint16_t preamble_length, uint8_t payload_length, uint8_t crc_type, uint8_t hdr_type, uint8_t iq_type) { const uint8_t data[] = { static_cast((preamble_length >> 8) & 0xFF), static_cast(preamble_length & 0xFF), crc_type, payload_length, hdr_type, iq_type, }; return write_stream(RADIOLIB_SX126X_CMD_SET_PACKET_PARAMS, data); } expected LLCC68::set_gfsk_packet_params(gfsk_packet_params_t packet_params) { if (packet_params.preamble_bits == 0 || packet_params.sync_length_bits > 64) { return ue(-EINVAL); } const uint8_t data[] = { static_cast((packet_params.preamble_bits >> 8) & 0xFF), static_cast(packet_params.preamble_bits & 0xFF), static_cast(packet_params.detector_length), packet_params.sync_length_bits, static_cast(packet_params.address_filtering), static_cast(packet_params.packet_length_mode), packet_params.payload_length, static_cast(packet_params.crc_type), static_cast(packet_params.whitening), }; return write_stream(RADIOLIB_SX126X_CMD_SET_PACKET_PARAMS, data); } expected LLCC68::set_gfsk_sync_word(std::span sync_word) { if (sync_word.size() > 8) { return ue(-EINVAL); } std::array data{}; std::ranges::copy(sync_word, data.begin()); return write_register(RADIOLIB_SX126X_REG_SYNC_WORD_0, data); } expected LLCC68::set_gfsk_address_filtering(uint8_t node_address, uint8_t broadcast_address) { const uint8_t data[] = {node_address, broadcast_address}; return write_register(RADIOLIB_SX126X_REG_NODE_ADDRESS, data); } expected LLCC68::set_gfsk_crc_seed(uint16_t seed) { const uint8_t data[] = {static_cast((seed >> 8) & 0xFF), static_cast(seed & 0xFF)}; return write_register(RADIOLIB_SX126X_REG_CRC_INITIAL_MSB, data); } expected LLCC68::set_gfsk_crc_polynomial(uint16_t polynomial) { const uint8_t data[] = {static_cast((polynomial >> 8) & 0xFF), static_cast(polynomial & 0xFF)}; return write_register(RADIOLIB_SX126X_REG_CRC_POLYNOMIAL_MSB, data); } expected LLCC68::set_gfsk_whitening_seed(uint16_t seed) { uint8_t msb = 0; auto r = read_register(RADIOLIB_SX126X_REG_WHITENING_INITIAL_MSB, std::span{&msb, 1}); APP_RADIO_RETURN_ERR(r); msb = static_cast((msb & 0xFE) | ((seed >> 8) & 0x01)); r = write_register(RADIOLIB_SX126X_REG_WHITENING_INITIAL_MSB, std::span{&msb, 1}); APP_RADIO_RETURN_ERR(r); const uint8_t lsb = static_cast(seed & 0xFF); return write_register(RADIOLIB_SX126X_REG_WHITENING_INITIAL_LSB, std::span{&lsb, 1}); } expected LLCC68::fix_inverted_iq(uint8_t iq_config) { uint8_t iqCfg = 0; auto r = read_register(RADIOLIB_SX126X_REG_IQ_CONFIG, std::span{&iqCfg, 1}); APP_RADIO_RETURN_ERR(r); if (iq_config == LoRaIQType::IQ_INVERTED) { iqCfg &= ~0x04; } else { iqCfg |= 0x04; } return write_register(RADIOLIB_SX126X_REG_IQ_CONFIG, std::span{&iqCfg, 1}); } expected LLCC68::set_tx_params(int8_t pwr, LoRaTxRampTime ramp_time) { const uint8_t data[] = {static_cast(pwr), static_cast(ramp_time)}; return write_stream(RADIOLIB_SX126X_CMD_SET_TX_PARAMS, data); } expected LLCC68::set_tx_params(tx_params_t params) { return set_tx_params(params.power, params.ramp_time); } expected LLCC68::hal_set_output_power(ChipType chip, tx_params_t params) { if (not valid_ramp_time(params.ramp_time)) { return ue(-EINVAL); } if (not valid_tx_power(params.power)) { return ue(-EINVAL); } if (chip == ChipType::Unknown) { return ue(-ENODEV); } int8_t pwr = params.power; expected r; #if not(CONFIG_LLCC68_ALWAYS_USE_SX1262_HIGH_PA) if (chip == ChipType::SX1262 || chip == ChipType::LLCC68) { #endif // SX1262/LLCC68 High Power profile if (pwr == 22) { r = set_pa_config(pa_setting_t::Default22dBmHp()); APP_RADIO_RETURN_ERR(r); return set_tx_params(22, params.ramp_time); } if (pwr == 20) { r = set_pa_config(pa_setting_t::Default20dBmHp()); APP_RADIO_RETURN_ERR(r); return set_tx_params(22, params.ramp_time); } if (pwr == 17) { r = set_pa_config(pa_setting_t::Default17dBmHp()); APP_RADIO_RETURN_ERR(r); return set_tx_params(22, params.ramp_time); } if (pwr == 14) { r = set_pa_config(pa_setting_t::Default14dBmHp()); APP_RADIO_RETURN_ERR(r); return set_tx_params(22, params.ramp_time); } { // default r = set_pa_config(pa_setting_t::DefaultHp()); APP_RADIO_RETURN_ERR(r); return set_tx_params(params); } #if not(CONFIG_LLCC68_ALWAYS_USE_SX1262_HIGH_PA) } // SX1261 Low Power pwr = std::min(pwr, 14); if (pwr == 14) { r = set_pa_config(pa_setting_t::Default14dBmLp()); APP_RADIO_RETURN_ERR(r); return set_tx_params(14, params.ramp_time); } if (pwr == 10) { r = set_pa_config(pa_setting_t::Default10dBmLp()); APP_RADIO_RETURN_ERR(r); return set_tx_params(13, params.ramp_time); } { r = set_pa_config(pa_setting_t::DefaultLp()); APP_RADIO_RETURN_ERR(r); return set_tx_params(pwr, params.ramp_time); } #endif } expected LLCC68::set_pa_config(pa_setting_t settings) { const uint8_t data[] = {settings.pa_duty_cycle, settings.hp_max, settings.device_sel, RADIOLIB_SX126X_PA_CONFIG_PA_LUT}; return write_stream(RADIOLIB_SX126X_CMD_SET_PA_CONFIG, data); } expected LLCC68::fix_pa_clamping(bool enable) { uint8_t clamp = 0; auto r = read_register(RADIOLIB_SX126X_REG_TX_CLAMP_CONFIG, std::span{&clamp, 1}); APP_RADIO_RETURN_ERR(r); if (enable) { clamp |= 0x1E; } else { clamp = (clamp & ~0x1E) | 0x08; } return write_register(RADIOLIB_SX126X_REG_TX_CLAMP_CONFIG, std::span{&clamp, 1}); } expected LLCC68::set_cad_params(cad_params_t params) { uint8_t data[7]; data[0] = static_cast(params.symbol_num); data[1] = params.det_peak; data[2] = params.det_min; data[3] = static_cast(params.exit_mode); data[4] = static_cast((params.timeout >> 16) & 0xFF); data[5] = static_cast((params.timeout >> 8) & 0xFF); data[6] = static_cast(params.timeout & 0xFF); return write_stream(RADIOLIB_SX126X_CMD_SET_CAD_PARAMS, data); } expected LLCC68::set_cad() { auto dummy = std::span{}; return write_stream(RADIOLIB_SX126X_CMD_SET_CAD, dummy); } expected LLCC68::set_tx(uint32_t timeout) { const uint8_t data[] = { static_cast((timeout >> 16) & 0xFF), static_cast((timeout >> 8) & 0xFF), static_cast(timeout & 0xFF), }; return write_stream(RADIOLIB_SX126X_CMD_SET_TX, data); } expected LLCC68::set_rx(uint32_t timeout) { const uint8_t data[] = { static_cast((timeout >> 16) & 0xFF), static_cast((timeout >> 8) & 0xFF), static_cast(timeout & 0xFF), }; return write_stream(RADIOLIB_SX126X_CMD_SET_RX, data); } expected LLCC68::set_sleep(sleep_config_t config) { auto c = *reinterpret_cast(&config); const uint8_t data[] = {c}; return write_stream(RADIOLIB_SX126X_CMD_SET_SLEEP, data); } expected LLCC68::set_tx_continuous_wave() { auto dummy = std::span{}; // const uint8_t dummy[] = {RADIOLIB_SX126X_CMD_NOP}; return write_stream(RADIOLIB_SX126X_CMD_SET_TX_CONTINUOUS_WAVE, dummy); } expected LLCC68::set_tx_infinite_preamble() { auto dummy = std::span{}; // const uint8_t dummy[] = {RADIOLIB_SX126X_CMD_NOP}; return write_stream(RADIOLIB_SX126X_CMD_SET_TX_INFINITE_PREAMBLE, dummy); } expected LLCC68::fix_sensitivity(LoRaBandwidth bw) { uint8_t sensitivityConfig{}; read_register(RADIOLIB_SX126X_REG_SENSITIVITY_CONFIG, std::span{&sensitivityConfig, 1}); // bit 2 constexpr auto HACK_MASK = 0x04; if (bw == LoRaBandwidth::BW_500_0) { sensitivityConfig &= ~HACK_MASK; } else { sensitivityConfig |= HACK_MASK; } return write_register(RADIOLIB_SX126X_REG_SENSITIVITY_CONFIG, std::span{&sensitivityConfig, 1}); } expected LLCC68::fix_gfsk_tx_modulation() { return update_register_bits(*this, RADIOLIB_SX126X_REG_SENSITIVITY_CONFIG, 0x04, 0x04); } expected LLCC68::fix_gfsk_low_bitrate(uint32_t bitrate_bps) { auto r = update_register_bits(*this, RADIOLIB_SX126X_REG_GFSK_BITRATE_CONFIG, 0x18, 0x08); APP_RADIO_RETURN_ERR(r); r = update_register_bits(*this, RADIOLIB_SX126X_REG_RSSI_AVG_WINDOW, 0x1C, 0x00); APP_RADIO_RETURN_ERR(r); r = update_register_bits(*this, RADIOLIB_SX126X_REG_GFSK_SYNC_CONFIG, 0x10, 0x10); APP_RADIO_RETURN_ERR(r); r = update_register_bits(*this, RADIOLIB_SX126X_REG_GFSK_LOW_DATA_RATE_CONFIG, 0x70, 0x00); APP_RADIO_RETURN_ERR(r); if (bitrate_bps == 1200) { return update_register_bits(*this, RADIOLIB_SX126X_REG_GFSK_SYNC_CONFIG, 0x10, 0x00); } if (bitrate_bps != 600) { return unit{}; } r = update_register_bits(*this, RADIOLIB_SX126X_REG_GFSK_BITRATE_CONFIG, 0x18, 0x18); APP_RADIO_RETURN_ERR(r); r = update_register_bits(*this, RADIOLIB_SX126X_REG_RSSI_AVG_WINDOW, 0x1C, 0x04); APP_RADIO_RETURN_ERR(r); r = update_register_bits(*this, RADIOLIB_SX126X_REG_GFSK_SYNC_CONFIG, 0x10, 0x00); APP_RADIO_RETURN_ERR(r); return update_register_bits( *this, RADIOLIB_SX126X_REG_GFSK_LOW_DATA_RATE_CONFIG, 0x70, 0x50); } expected LLCC68::set_buffer_base_address(uint8_t tx_base_addr, uint8_t rx_base_addr) { const uint8_t data[] = {tx_base_addr, rx_base_addr}; return write_stream(RADIOLIB_SX126X_CMD_SET_BUFFER_BASE_ADDRESS, data); } expected LLCC68::get_rx_buffer_status(LLCC68::timeout_ms_t busy_timeout) { uint8_t rx_buf_status[] = {0, 0}; auto r = read_stream(RADIOLIB_SX126X_CMD_GET_RX_BUFFER_STATUS, std::span{rx_buf_status, 2}, busy_timeout); APP_RADIO_RETURN_ERR_IGNORE_PROC_ERR(r); return LLCC68::rx_buffer_status_t{rx_buf_status[0], rx_buf_status[1]}; } expected LLCC68::random_number_gen() { uint8_t buf[4]{}; auto r = read_register(RADIOLIB_SX126X_REG_RANDOM_NUMBER_0, std::span{buf}); APP_RADIO_RETURN_ERR_IGNORE_PROC_ERR(r); uint32_t rn = (static_cast(buf[0]) << 24) | (static_cast(buf[1]) << 16) | (static_cast(buf[2]) << 8) | (static_cast(buf[3])); return rn; } expected LLCC68::hal_modem_init(lora_parameters_t params) { APP_RADIO_RETURN_ERR_CTX(set_standby(), "modem_init::standby"); auto chip_type_ = hal_get_chip_type(); APP_RADIO_RETURN_ERR_CTX(chip_type_, "modem_init::get_chip_type"); auto chip_type = *chip_type_; LOG_INF("chip type=%s(%d)", to_str(chip_type), static_cast(chip_type)); if (chip_type == ChipType::Unknown) { return ue(-ENODEV); } APP_RADIO_RETURN_ERR_CTX(set_packet_type_lora(), "modem_init::set_packet_type"); APP_RADIO_RETURN_ERR_CTX( set_modulation_params(params.mod_params.sf, params.mod_params.bw, params.mod_params.cr, params.mod_params.ldr_optimize), "modem_init::set_modulation_params"); APP_RADIO_RETURN_ERR_CTX(set_lora_sync_word(params.sync_word), "modem_init::set_lora_sync_word"); APP_RADIO_RETURN_ERR_CTX(set_dio2_as_rf_switch(false), "modem_init::set_dio2_as_rf_switch"); APP_RADIO_RETURN_ERR_CTX(set_rf_frequency(params.frequency_mhz), "modem_init::set_rf_frequency"); APP_RADIO_RETURN_ERR_CTX(set_packet_params(params.packet_params.preamble_len, params.packet_params.payload_len, params.packet_params.crc_type, params.packet_params.hdr_type, params.packet_params.iq_type), "modem_init::set_packet_params"); APP_RADIO_RETURN_ERR_CTX(hal_set_output_power(chip_type, params.tx_params), "modem_init::hal_set_output_power"); APP_RADIO_RETURN_ERR_CTX(set_standby(), "modem_init::standby"); return {}; } expected LLCC68::hal_gfsk_modem_init(gfsk_parameters_t params) { if (params.sync_word_length > params.sync_word.size() || params.packet_params.sync_length_bits > params.sync_word.size() * 8) { return ue(-EINVAL); } APP_RADIO_RETURN_ERR_CTX(set_standby(), "gfsk_init::standby"); auto chip_type_ = hal_get_chip_type(); APP_RADIO_RETURN_ERR_CTX(chip_type_, "gfsk_init::get_chip_type"); auto chip_type = *chip_type_; LOG_INF("chip type=%s(%d)", to_str(chip_type), static_cast(chip_type)); if (chip_type == ChipType::Unknown) { return ue(-ENODEV); } APP_RADIO_RETURN_ERR_CTX(set_packet_type_gfsk(), "gfsk_init::set_packet_type"); APP_RADIO_RETURN_ERR_CTX(set_gfsk_modulation_params(params.mod_params), "gfsk_init::set_modulation_params"); APP_RADIO_RETURN_ERR_CTX( set_gfsk_sync_word(std::span{params.sync_word.data(), params.sync_word_length}), "gfsk_init::set_sync_word"); APP_RADIO_RETURN_ERR_CTX(set_gfsk_crc_seed(params.crc_seed), "gfsk_init::set_crc_seed"); APP_RADIO_RETURN_ERR_CTX(set_gfsk_crc_polynomial(params.crc_polynomial), "gfsk_init::set_crc_polynomial"); APP_RADIO_RETURN_ERR_CTX(set_gfsk_whitening_seed(params.whitening_seed), "gfsk_init::set_whitening_seed"); if (params.packet_params.address_filtering != GfskAddressFiltering::Disabled) { APP_RADIO_RETURN_ERR_CTX( set_gfsk_address_filtering(params.node_address.value_or(0), params.broadcast_address.value_or(0)), "gfsk_init::set_address_filtering"); } APP_RADIO_RETURN_ERR_CTX(set_dio2_as_rf_switch(false), "gfsk_init::set_dio2_as_rf_switch"); APP_RADIO_RETURN_ERR_CTX(set_rf_frequency(params.frequency_mhz), "gfsk_init::set_rf_frequency"); APP_RADIO_RETURN_ERR_CTX(set_gfsk_packet_params(params.packet_params), "gfsk_init::set_packet_params"); APP_RADIO_RETURN_ERR_CTX(hal_set_output_power(chip_type, params.tx_params), "gfsk_init::hal_set_output_power"); APP_RADIO_RETURN_ERR_CTX(set_standby(), "gfsk_init::standby"); return {}; } expected LLCC68::hal_async_flush(lora_parameters_t params) { auto status = get_status(); APP_RADIO_RETURN_ERR_CTX(status, "tx::status"); if (status->chip_mode == ChipMode::TX) { return ue(Errc::InvalidState); } APP_RADIO_RETURN_ERR_CTX(set_standby(), "tx::standby"); APP_RADIO_RETURN_ERR_CTX(set_packet_params(params.packet_params.preamble_len, data().tx_xfer_size, params.packet_params.crc_type, params.packet_params.hdr_type, params.packet_params.iq_type), "tx::set_packet_params"); APP_RADIO_RETURN_ERR_CTX(set_buffer_base_address(), "tx::set_buffer_base_address"); APP_RADIO_RETURN_ERR_CTX(flush_tx_buffer(), "tx::flush_tx_buffer"); APP_RADIO_RETURN_ERR_CTX(fix_sensitivity(params.mod_params.bw), "tx::fix_sensitivity"); constexpr auto irq_mask = RADIOLIB_SX126X_IRQ_TX_DONE | RADIOLIB_SX126X_IRQ_TIMEOUT; constexpr auto irq_params = irq_params_t{ {irq_mask}, {irq_mask}, // DIO1 {RADIOLIB_SX126X_IRQ_NONE}, // DIO2 {RADIOLIB_SX126X_IRQ_NONE}, // DIO3 }; APP_RADIO_RETURN_ERR_CTX(set_dio_irq_params(irq_params), "tx::set_dio_irq_params"); APP_RADIO_RETURN_ERR_CTX(clear_irq_status(irq_params.irqMask), "tx::clear_irq_status"); tx_rx_en_pin_set(TxRxPinState::TX); APP_RADIO_RETURN_ERR_CTX(set_tx(), "tx::set_tx_params"); auto air = calc_time_on_air( data().tx_xfer_size, params.mod_params.sf, params.mod_params.bw, params.mod_params.cr, params.packet_params.preamble_len, params.packet_params.hdr_type, params.packet_params.crc_type); auto air_estimated = std::chrono::duration_cast(air); return transmit_result{this, air_estimated}; } expected LLCC68::hal_async_transmit(std::span data, lora_parameters_t params) { auto r = write_tx_buffer(data); APP_RADIO_RETURN_ERR_CTX(r, "transmit::write_tx_buffer"); return hal_async_flush(params); } expected LLCC68::hal_gfsk_async_flush(gfsk_parameters_t params) { auto status = get_status(); APP_RADIO_RETURN_ERR_CTX(status, "gfsk_tx::status"); if (status->chip_mode == ChipMode::TX) { return ue(Errc::InvalidState); } if (data().tx_xfer_size == 0 || data().tx_xfer_size > MAX_BUFFER_PAYLOAD) { return ue(-EINVAL); } APP_RADIO_RETURN_ERR_CTX(set_standby(), "gfsk_tx::standby"); auto packet_params = params.packet_params; packet_params.payload_length = static_cast(data().tx_xfer_size); APP_RADIO_RETURN_ERR_CTX(set_gfsk_packet_params(packet_params), "gfsk_tx::set_packet_params"); APP_RADIO_RETURN_ERR_CTX(set_buffer_base_address(), "gfsk_tx::set_buffer_base_address"); APP_RADIO_RETURN_ERR_CTX(flush_tx_buffer(), "gfsk_tx::flush_tx_buffer"); APP_RADIO_RETURN_ERR_CTX(fix_gfsk_tx_modulation(), "gfsk_tx::fix_tx_modulation"); constexpr auto irq_mask = RADIOLIB_SX126X_IRQ_TX_DONE | RADIOLIB_SX126X_IRQ_TIMEOUT; constexpr auto irq_params = irq_params_t{ {irq_mask}, {irq_mask}, {RADIOLIB_SX126X_IRQ_NONE}, {RADIOLIB_SX126X_IRQ_NONE}, }; APP_RADIO_RETURN_ERR_CTX(set_dio_irq_params(irq_params), "gfsk_tx::set_dio_irq_params"); APP_RADIO_RETURN_ERR_CTX(clear_irq_status(irq_params.irqMask), "gfsk_tx::clear_irq_status"); tx_rx_en_pin_set(TxRxPinState::TX); APP_RADIO_RETURN_ERR_CTX(set_tx(), "gfsk_tx::set_tx"); auto air_estimated = calc_gfsk_time_on_air(params, static_cast(data().tx_xfer_size)); return transmit_result{this, air_estimated}; } expected LLCC68::hal_gfsk_async_transmit(std::span data, gfsk_parameters_t params) { auto r = write_tx_buffer(data); APP_RADIO_RETURN_ERR_CTX(r, "gfsk_transmit::write_tx_buffer"); return hal_gfsk_async_flush(params); } expected LLCC68::hal_async_rx(lora_parameters_t params) { APP_RADIO_RETURN_ERR_CTX(set_standby(), "rx::standby"); APP_RADIO_RETURN_ERR_CTX(set_buffer_base_address(), "rx::set_buffer_base_address"); APP_RADIO_RETURN_ERR_CTX( set_modulation_params(params.mod_params.sf, params.mod_params.bw, params.mod_params.cr, params.mod_params.ldr_optimize), "rx::set_modulation_params"); APP_RADIO_RETURN_ERR_CTX(set_packet_params(params.packet_params.preamble_len, params.packet_params.payload_len, params.packet_params.crc_type, params.packet_params.hdr_type, params.packet_params.iq_type), "rx::set_packet_params"); constexpr auto irq_mask = RADIOLIB_SX126X_IRQ_RX_DEFAULT; constexpr auto irq_params = irq_params_t{ {irq_mask}, {irq_mask}, // DIO1 {RADIOLIB_SX126X_IRQ_NONE}, // DIO2 {RADIOLIB_SX126X_IRQ_NONE}, // DIO3 }; APP_RADIO_RETURN_ERR_CTX(set_dio_irq_params(irq_params), "rx::set_dio_irq_params"); APP_RADIO_RETURN_ERR_CTX(clear_irq_status(irq_params.irqMask), "rx::clear_irq_status"); tx_rx_en_pin_set(TxRxPinState::RX); APP_RADIO_RETURN_ERR_CTX(set_rx(), "rx::set_rx"); return unit{}; } expected LLCC68::hal_gfsk_async_rx(gfsk_parameters_t params) { APP_RADIO_RETURN_ERR_CTX(set_standby(), "gfsk_rx::standby"); APP_RADIO_RETURN_ERR_CTX(set_buffer_base_address(), "gfsk_rx::set_buffer_base_address"); APP_RADIO_RETURN_ERR_CTX(set_gfsk_modulation_params(params.mod_params), "gfsk_rx::set_modulation_params"); APP_RADIO_RETURN_ERR_CTX(set_gfsk_packet_params(params.packet_params), "gfsk_rx::set_packet_params"); constexpr auto irq_mask = RADIOLIB_SX126X_IRQ_RX_DEFAULT; constexpr auto irq_params = irq_params_t{ {irq_mask}, {irq_mask}, {RADIOLIB_SX126X_IRQ_NONE}, {RADIOLIB_SX126X_IRQ_NONE}, }; APP_RADIO_RETURN_ERR_CTX(set_dio_irq_params(irq_params), "gfsk_rx::set_dio_irq_params"); APP_RADIO_RETURN_ERR_CTX(clear_irq_status(irq_params.irqMask), "gfsk_rx::clear_irq_status"); tx_rx_en_pin_set(TxRxPinState::RX); APP_RADIO_RETURN_ERR_CTX(set_rx(), "gfsk_rx::set_rx"); return unit{}; } // utils member function std::string irq_status_t::to_string() const { std::ostringstream oss; if (bits.tx_done) { oss << "t"; } if (bits.rx_done) { oss << "r"; } if (bits.preamble_detected) { oss << "p"; } if (bits.sync_word_valid) { oss << "s"; } if (bits.header_valid) { oss << "h"; } if (bits.header_err) { oss << "H"; } if (bits.crc_err) { oss << "C"; } if (bits.cad_done) { oss << "c"; } if (bits.cad_detected) { oss << "d"; } if (bits.timeout) { oss << "T"; } if (bits.lr_fhss_hop) { oss << "f"; } return oss.str(); } error_code transmit_result::post_action() { constexpr auto irq_mask = RADIOLIB_SX126X_IRQ_TX_DONE | RADIOLIB_SX126X_IRQ_TIMEOUT; auto r = self->clear_irq_status({irq_mask}); if (not r) { return r.error(); } return {}; } void lora_parameters_t::log(const char *tag) const { const char *ldr_str = (mod_params.ldr_optimize == LoRaLowDataRateType::LDR_ON ? "ON" : "OFF"); const char *crc_str = (packet_params.crc_type == LoRaCrcType::CRC_ON ? "ON" : "OFF"); const char *hdr_str = (packet_params.hdr_type == LoRaHeaderType::HEADER_EXPLICIT ? "Explicit" : "Implicit"); const char *iq_str = (packet_params.iq_type == LoRaIQType::IQ_INVERTED ? "Inverted" : "Standard"); const char *sync_str = "UNKNOWN"; // default if (sync_word == LoRaSyncWord::SYNC_WORD_PRIVATE) { sync_str = "PRIVATE"; } else if (sync_word == LoRaSyncWord::SYNC_WORD_PUBLIC) { sync_str = "PUBLIC"; } else { // keep default } LOG_INF("lora_parameters_t(%s)" "{" "BW=%s SF=%u CR=%s LDR=%s " "PREAMBLE=%u CRC=%s HDR=%s IQ=%s " "POWER=%d RAMP=%s FREQ=%dkHz SYNC_WORD=%s(0x%04X)" "}", tag, to_str(mod_params.bw), mod_params.sf, to_str(mod_params.cr), ldr_str, packet_params.preamble_len, crc_str, hdr_str, iq_str, tx_params.power, to_str(tx_params.ramp_time), static_cast(frequency_mhz * 1000), sync_str, static_cast(sync_word)); } void gfsk_parameters_t::log(const char *tag) const { LOG_INF( "gfsk_parameters_t(%s)" "{" "BR=%u FDEV=%u RXBW=0x%02X PULSE=0x%02X " "PREAMBLE_BITS=%u DET=0x%02X SYNC_BITS=%u ADDR=0x%02X LEN_MODE=0x%02X " "PAYLOAD=%u CRC=0x%02X WHITE=0x%02X POWER=%d RAMP=%s FREQ=%dkHz" "}", tag, static_cast(mod_params.bitrate_bps), static_cast(mod_params.frequency_deviation_hz), static_cast(mod_params.rx_bandwidth), static_cast(mod_params.pulse_shape), packet_params.preamble_bits, static_cast(packet_params.detector_length), packet_params.sync_length_bits, static_cast(packet_params.address_filtering), static_cast(packet_params.packet_length_mode), packet_params.payload_length, static_cast(packet_params.crc_type), static_cast(packet_params.whitening), tx_params.power, to_str(tx_params.ramp_time), static_cast(frequency_mhz * 1000)); } } // namespace app::driver::llcc68