f8836dd1b1
Use a longer preamble and 32-bit detector for the 100 kbps fixed 6-byte GMSK profile. Read RX payloads from the RxStartBufferPointer returned by GetRxBufferStatus. The previous subtract-by-length adjustment could read the previous FIFO slot once the continuous RX buffer advanced, causing duplicate and missing packet IDs on the gateway.
1509 lines
54 KiB
C++
1509 lines
54 KiB
C++
#include "llcc68.hpp"
|
|
#include "llcc68_definitions.hpp"
|
|
#include "priv_radiolib_definitions.hpp"
|
|
#include <algorithm>
|
|
#include <array>
|
|
#include <bit>
|
|
#include <chrono>
|
|
#include <cstddef>
|
|
#include <cstdint>
|
|
#include <cstring>
|
|
#include <zephyr/drivers/gpio.h>
|
|
#include <zephyr/drivers/spi.h>
|
|
#include <zephyr/kernel.h>
|
|
#include <zephyr/logging/log.h>
|
|
|
|
LOG_MODULE_REGISTER(llcc68, CONFIG_LLCC68_LOG_LEVEL);
|
|
|
|
namespace {
|
|
using error_code = app::driver::llcc68::error_code;
|
|
using ue_t = std::unexpected<error_code>;
|
|
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<const struct llcc68_config *>(dev->config);
|
|
}
|
|
|
|
struct llcc68_data &
|
|
LLCC68::data() { // NOLINT(readability-make-member-function-const) mutable
|
|
return *static_cast<struct llcc68_data *>(dev->data);
|
|
}
|
|
|
|
std::optional<gpio_dt_spec> LLCC68::tx_enable_gpio() const {
|
|
if (config().tx_enable_gpio.port != nullptr) {
|
|
return config().tx_enable_gpio;
|
|
}
|
|
return std::nullopt;
|
|
}
|
|
|
|
std::optional<gpio_dt_spec> 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<uint32_t>(bw_ * 10);
|
|
const uint32_t symbolLength_us =
|
|
(static_cast<uint32_t>(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<int16_t>(
|
|
static_cast<int16_t>(8) * static_cast<int16_t>(len) +
|
|
static_cast<int16_t>(crc_type * bitsPerCrc) -
|
|
static_cast<int16_t>(4 * sf) + static_cast<int16_t>(sfCoeff2) +
|
|
static_cast<int16_t>(N_symbol_header));
|
|
bit_count = std::max<int16_t>(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<unit, error_code> LLCC68::read_stream(uint8_t cmd,
|
|
std::span<uint8_t> 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<uint8_t, kHdr + kMaxPayload> tx{};
|
|
std::array<uint8_t, kHdr + kMaxPayload> 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<std::ptrdiff_t>(data_idx),
|
|
rx.begin() + static_cast<std::ptrdiff_t>(xfer_len), data.begin());
|
|
if (auto e = command_status_to_error(std::bit_cast<status_t>(status)); e) {
|
|
return ue(e);
|
|
}
|
|
return unit{};
|
|
}
|
|
|
|
expected<unit, error_code>
|
|
LLCC68::write_stream(uint8_t cmd, std::span<const uint8_t> 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<uint8_t, 1U + kMaxPayload> tx{};
|
|
std::array<uint8_t, 1U + kMaxPayload> 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_t>(status)); e) {
|
|
return ue(e);
|
|
}
|
|
}
|
|
return unit{};
|
|
}
|
|
|
|
expected<unit, error_code>
|
|
LLCC68::read_register(uint16_t reg, std::span<uint8_t> 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<uint8_t, kHdr + kMaxPayload> tx{};
|
|
std::array<uint8_t, kHdr + kMaxPayload> rx{};
|
|
const size_t xfer_len = kHdr + data_to_host.size();
|
|
tx[0] = RADIOLIB_SX126X_CMD_READ_REGISTER;
|
|
tx[1] = static_cast<uint8_t>((reg >> 8) & 0xFF);
|
|
tx[2] = static_cast<uint8_t>(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<std::ptrdiff_t>(data_idx),
|
|
rx.begin() + static_cast<std::ptrdiff_t>(xfer_len),
|
|
data_to_host.begin());
|
|
if (auto e = command_status_to_error(std::bit_cast<status_t>(status)); e) {
|
|
return ue(e);
|
|
}
|
|
return unit{};
|
|
}
|
|
|
|
expected<unit, error_code>
|
|
LLCC68::write_register(uint16_t reg, std::span<const uint8_t> 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<uint8_t, 1U + 2U + kMaxPayload> tx{};
|
|
std::array<uint8_t, 1U + 2U + kMaxPayload> rx{};
|
|
const size_t xfer_len = 1U + 2U + data_from_host.size();
|
|
tx[0] = RADIOLIB_SX126X_CMD_WRITE_REGISTER;
|
|
tx[1] = static_cast<uint8_t>((reg >> 8) & 0xFF);
|
|
tx[2] = static_cast<uint8_t>(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_t>(status)); e) {
|
|
return ue(e);
|
|
}
|
|
}
|
|
return unit{};
|
|
}
|
|
|
|
expected<uint8_t, error_code>
|
|
LLCC68::read_buffer_copy(uint8_t offset, std::span<uint8_t> 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<uint8_t>(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<uint8_t>(rx->size());
|
|
}
|
|
|
|
expected<std::span<const uint8_t>, 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_t>(status)); e) {
|
|
return ue(e);
|
|
}
|
|
// return only the data part
|
|
return std::span<const uint8_t>{rx}.subspan(data_idx, n);
|
|
}
|
|
|
|
expected<std::span<const uint8_t>, 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);
|
|
}
|
|
|
|
expected<unit, error_code>
|
|
LLCC68::write_tx_buffer(std::span<const uint8_t> 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<unit, error_code> 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_t>(status)); e) {
|
|
return ue(e);
|
|
}
|
|
}
|
|
return unit{};
|
|
}
|
|
|
|
expected<unit, error_code>
|
|
LLCC68::write_buffer_immediate_mode(uint8_t offset,
|
|
std::span<const uint8_t> 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_t>(status)); e) {
|
|
return ue(e);
|
|
}
|
|
}
|
|
return unit{};
|
|
}
|
|
} // namespace app::driver::llcc68
|
|
|
|
// High-level helpers
|
|
namespace app::driver::llcc68 {
|
|
|
|
expected<status_t, error_code> LLCC68::get_status() {
|
|
uint8_t raw = 0;
|
|
auto r =
|
|
read_stream(RADIOLIB_SX126X_CMD_GET_STATUS, std::span<uint8_t>{&raw, 1});
|
|
APP_RADIO_RETURN_ERR_IGNORE_PROC_ERR(r);
|
|
return std::bit_cast<status_t>(raw);
|
|
}
|
|
|
|
expected<unit, error_code> 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<packet_status_t, error_code> 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<uint8_t, error_code> LLCC68::get_rssi_inst_x2_neg() {
|
|
uint8_t raw = 0;
|
|
auto r = read_stream(RADIOLIB_SX126X_CMD_GET_RSSI_INST,
|
|
std::span<uint8_t>{&raw, 1});
|
|
APP_RADIO_RETURN_ERR_IGNORE_PROC_ERR(r);
|
|
return raw;
|
|
}
|
|
|
|
expected<unit, error_code> 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<unit, error_code> LLCC68::set_standby() {
|
|
const uint8_t data[] = {RADIOLIB_SX126X_STANDBY_RC};
|
|
return write_stream(RADIOLIB_SX126X_CMD_SET_STANDBY, data);
|
|
}
|
|
|
|
expected<ChipType, error_code> 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<uint8_t>{reinterpret_cast<uint8_t *>(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<unit, error_code> 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<irq_status_t, error_code> 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<uint16_t>((buf[0] << 8) | buf[1])};
|
|
return st;
|
|
}
|
|
|
|
expected<unit, error_code> 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<uint32_t>((32ULL * xtal_hz) / bitrate_bps);
|
|
}
|
|
|
|
static constexpr uint32_t freq_hz_to_raw(uint32_t freq_hz) {
|
|
return static_cast<uint32_t>(static_cast<double>(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<uint32_t>(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<unit, error_code>
|
|
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<uint8_t>{®_value, 1});
|
|
APP_RADIO_RETURN_ERR(r);
|
|
reg_value = static_cast<uint8_t>((reg_value & ~mask) | (value & mask));
|
|
return radio.write_register(reg, std::span<const uint8_t>{®_value, 1});
|
|
}
|
|
|
|
// Convenience APIs ported from ESP version
|
|
expected<unit, error_code> 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<unit, error_code> 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<unit, error_code> 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<LoRaCodingRate>(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<unit, error_code>
|
|
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<unit, error_code>
|
|
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<uint8_t>((bitrate >> 16) & 0xFF),
|
|
static_cast<uint8_t>((bitrate >> 8) & 0xFF),
|
|
static_cast<uint8_t>(bitrate & 0xFF),
|
|
static_cast<uint8_t>(mod_params.pulse_shape),
|
|
static_cast<uint8_t>(mod_params.rx_bandwidth),
|
|
static_cast<uint8_t>((fdev >> 16) & 0xFF),
|
|
static_cast<uint8_t>((fdev >> 8) & 0xFF),
|
|
static_cast<uint8_t>(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<unit, error_code> 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<uint8_t>{&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<uint8_t>((sync_word & 0xFF00) >> 8),
|
|
static_cast<uint8_t>(sync_word & 0x00FF),
|
|
// LSB
|
|
};
|
|
return write_register(RADIOLIB_SX126X_REG_LORA_SYNC_WORD_MSB, data);
|
|
}
|
|
|
|
expected<unit, error_code> LLCC68::set_dio2_as_rf_switch(bool en) {
|
|
const uint8_t data[] = {
|
|
en ? static_cast<uint8_t>(RADIOLIB_SX126X_DIO2_AS_RF_SWITCH)
|
|
: static_cast<uint8_t>(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<freq_t>(1U << RADIOLIB_SX126X_DIV_EXPONENT);
|
|
const auto fxtal = static_cast<freq_t>(RADIOLIB_SX126X_CRYSTAL_FREQ);
|
|
return static_cast<uint32_t>((fmhz * scale) / fxtal);
|
|
}
|
|
|
|
expected<unit, error_code> 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<uint8_t>((frf >> 24) & 0xFF),
|
|
static_cast<uint8_t>((frf >> 16) & 0xFF),
|
|
static_cast<uint8_t>((frf >> 8) & 0xFF),
|
|
static_cast<uint8_t>(frf & 0xFF),
|
|
};
|
|
return write_stream(RADIOLIB_SX126X_CMD_SET_RF_FREQUENCY, data);
|
|
}
|
|
|
|
expected<unit, error_code> LLCC68::set_rf_frequency(uint32_t freq_raw) {
|
|
const uint8_t data[] = {
|
|
static_cast<uint8_t>((freq_raw >> 24) & 0xFF),
|
|
static_cast<uint8_t>((freq_raw >> 16) & 0xFF),
|
|
static_cast<uint8_t>((freq_raw >> 8) & 0xFF),
|
|
static_cast<uint8_t>(freq_raw & 0xFF),
|
|
};
|
|
return write_stream(RADIOLIB_SX126X_CMD_SET_RF_FREQUENCY, data);
|
|
}
|
|
|
|
expected<unit, error_code>
|
|
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<uint8_t>((preamble_length >> 8) & 0xFF),
|
|
static_cast<uint8_t>(preamble_length & 0xFF),
|
|
crc_type,
|
|
payload_length,
|
|
hdr_type,
|
|
iq_type,
|
|
};
|
|
return write_stream(RADIOLIB_SX126X_CMD_SET_PACKET_PARAMS, data);
|
|
}
|
|
|
|
expected<unit, error_code>
|
|
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<uint8_t>((packet_params.preamble_bits >> 8) & 0xFF),
|
|
static_cast<uint8_t>(packet_params.preamble_bits & 0xFF),
|
|
static_cast<uint8_t>(packet_params.detector_length),
|
|
packet_params.sync_length_bits,
|
|
static_cast<uint8_t>(packet_params.address_filtering),
|
|
static_cast<uint8_t>(packet_params.packet_length_mode),
|
|
packet_params.payload_length,
|
|
static_cast<uint8_t>(packet_params.crc_type),
|
|
static_cast<uint8_t>(packet_params.whitening),
|
|
};
|
|
return write_stream(RADIOLIB_SX126X_CMD_SET_PACKET_PARAMS, data);
|
|
}
|
|
|
|
expected<unit, error_code>
|
|
LLCC68::set_gfsk_sync_word(std::span<const uint8_t> sync_word) {
|
|
if (sync_word.size() > 8) {
|
|
return ue(-EINVAL);
|
|
}
|
|
std::array<uint8_t, 8> data{};
|
|
std::ranges::copy(sync_word, data.begin());
|
|
return write_register(RADIOLIB_SX126X_REG_SYNC_WORD_0, data);
|
|
}
|
|
|
|
expected<unit, error_code>
|
|
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<unit, error_code> LLCC68::set_gfsk_crc_seed(uint16_t seed) {
|
|
const uint8_t data[] = {static_cast<uint8_t>((seed >> 8) & 0xFF),
|
|
static_cast<uint8_t>(seed & 0xFF)};
|
|
return write_register(RADIOLIB_SX126X_REG_CRC_INITIAL_MSB, data);
|
|
}
|
|
|
|
expected<unit, error_code>
|
|
LLCC68::set_gfsk_crc_polynomial(uint16_t polynomial) {
|
|
const uint8_t data[] = {static_cast<uint8_t>((polynomial >> 8) & 0xFF),
|
|
static_cast<uint8_t>(polynomial & 0xFF)};
|
|
return write_register(RADIOLIB_SX126X_REG_CRC_POLYNOMIAL_MSB, data);
|
|
}
|
|
|
|
expected<unit, error_code> LLCC68::set_gfsk_whitening_seed(uint16_t seed) {
|
|
uint8_t msb = 0;
|
|
auto r = read_register(RADIOLIB_SX126X_REG_WHITENING_INITIAL_MSB,
|
|
std::span<uint8_t>{&msb, 1});
|
|
APP_RADIO_RETURN_ERR(r);
|
|
msb = static_cast<uint8_t>((msb & 0xFE) | ((seed >> 8) & 0x01));
|
|
r = write_register(RADIOLIB_SX126X_REG_WHITENING_INITIAL_MSB,
|
|
std::span<const uint8_t>{&msb, 1});
|
|
APP_RADIO_RETURN_ERR(r);
|
|
const uint8_t lsb = static_cast<uint8_t>(seed & 0xFF);
|
|
return write_register(RADIOLIB_SX126X_REG_WHITENING_INITIAL_LSB,
|
|
std::span<const uint8_t>{&lsb, 1});
|
|
}
|
|
|
|
expected<unit, error_code> LLCC68::calibrate_image(uint8_t freq1,
|
|
uint8_t freq2) {
|
|
const uint8_t data[] = {freq1, freq2};
|
|
return write_stream(RADIOLIB_SX126X_CMD_CALIBRATE_IMAGE, data);
|
|
}
|
|
|
|
expected<unit, error_code> LLCC68::fix_inverted_iq(uint8_t iq_config) {
|
|
uint8_t iqCfg = 0;
|
|
auto r = read_register(RADIOLIB_SX126X_REG_IQ_CONFIG,
|
|
std::span<uint8_t>{&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<const uint8_t>{&iqCfg, 1});
|
|
}
|
|
|
|
expected<unit, error_code> LLCC68::set_tx_params(int8_t pwr,
|
|
LoRaTxRampTime ramp_time) {
|
|
const uint8_t data[] = {static_cast<uint8_t>(pwr),
|
|
static_cast<uint8_t>(ramp_time)};
|
|
return write_stream(RADIOLIB_SX126X_CMD_SET_TX_PARAMS, data);
|
|
}
|
|
|
|
expected<unit, error_code> LLCC68::set_tx_params(tx_params_t params) {
|
|
return set_tx_params(params.power, params.ramp_time);
|
|
}
|
|
|
|
expected<unit, error_code> 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<unit, error_code> 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<int8_t>(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<unit, error_code> 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<unit, error_code> LLCC68::fix_pa_clamping(bool enable) {
|
|
uint8_t clamp = 0;
|
|
auto r = read_register(RADIOLIB_SX126X_REG_TX_CLAMP_CONFIG,
|
|
std::span<uint8_t>{&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<const uint8_t>{&clamp, 1});
|
|
}
|
|
|
|
expected<unit, error_code> LLCC68::set_cad_params(cad_params_t params) {
|
|
uint8_t data[7];
|
|
data[0] = static_cast<uint8_t>(params.symbol_num);
|
|
data[1] = params.det_peak;
|
|
data[2] = params.det_min;
|
|
data[3] = static_cast<uint8_t>(params.exit_mode);
|
|
data[4] = static_cast<uint8_t>((params.timeout >> 16) & 0xFF);
|
|
data[5] = static_cast<uint8_t>((params.timeout >> 8) & 0xFF);
|
|
data[6] = static_cast<uint8_t>(params.timeout & 0xFF);
|
|
|
|
return write_stream(RADIOLIB_SX126X_CMD_SET_CAD_PARAMS, data);
|
|
}
|
|
|
|
expected<unit, error_code> LLCC68::set_cad() {
|
|
auto dummy = std::span<uint8_t>{};
|
|
return write_stream(RADIOLIB_SX126X_CMD_SET_CAD, dummy);
|
|
}
|
|
|
|
expected<unit, error_code> LLCC68::set_tx(uint32_t timeout) {
|
|
const uint8_t data[] = {
|
|
static_cast<uint8_t>((timeout >> 16) & 0xFF),
|
|
static_cast<uint8_t>((timeout >> 8) & 0xFF),
|
|
static_cast<uint8_t>(timeout & 0xFF),
|
|
};
|
|
return write_stream(RADIOLIB_SX126X_CMD_SET_TX, data);
|
|
}
|
|
|
|
expected<unit, error_code> LLCC68::set_rx(uint32_t timeout) {
|
|
const uint8_t data[] = {
|
|
static_cast<uint8_t>((timeout >> 16) & 0xFF),
|
|
static_cast<uint8_t>((timeout >> 8) & 0xFF),
|
|
static_cast<uint8_t>(timeout & 0xFF),
|
|
};
|
|
return write_stream(RADIOLIB_SX126X_CMD_SET_RX, data);
|
|
}
|
|
|
|
expected<unit, error_code> LLCC68::set_sleep(sleep_config_t config) {
|
|
auto c = *reinterpret_cast<const uint8_t *>(&config);
|
|
const uint8_t data[] = {c};
|
|
return write_stream(RADIOLIB_SX126X_CMD_SET_SLEEP, data);
|
|
}
|
|
|
|
expected<unit, error_code> LLCC68::set_tx_continuous_wave() {
|
|
auto dummy = std::span<uint8_t>{};
|
|
// const uint8_t dummy[] = {RADIOLIB_SX126X_CMD_NOP};
|
|
return write_stream(RADIOLIB_SX126X_CMD_SET_TX_CONTINUOUS_WAVE, dummy);
|
|
}
|
|
|
|
expected<unit, error_code> LLCC68::set_tx_infinite_preamble() {
|
|
auto dummy = std::span<uint8_t>{};
|
|
// const uint8_t dummy[] = {RADIOLIB_SX126X_CMD_NOP};
|
|
return write_stream(RADIOLIB_SX126X_CMD_SET_TX_INFINITE_PREAMBLE, dummy);
|
|
}
|
|
|
|
expected<unit, error_code> LLCC68::fix_sensitivity(LoRaBandwidth bw) {
|
|
uint8_t sensitivityConfig{};
|
|
read_register(RADIOLIB_SX126X_REG_SENSITIVITY_CONFIG,
|
|
std::span<uint8_t>{&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<const uint8_t>{&sensitivityConfig, 1});
|
|
}
|
|
|
|
expected<unit, error_code> LLCC68::fix_gfsk_tx_modulation() {
|
|
return update_register_bits(*this, RADIOLIB_SX126X_REG_SENSITIVITY_CONFIG,
|
|
0x04, 0x04);
|
|
}
|
|
|
|
expected<unit, error_code> 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<unit, error_code>
|
|
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::rx_buffer_status_t, error_code>
|
|
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<uint8_t>{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<uint32_t, error_code> LLCC68::random_number_gen() {
|
|
uint8_t buf[4]{};
|
|
auto r = read_register(RADIOLIB_SX126X_REG_RANDOM_NUMBER_0,
|
|
std::span<uint8_t>{buf});
|
|
APP_RADIO_RETURN_ERR_IGNORE_PROC_ERR(r);
|
|
uint32_t rn = (static_cast<uint32_t>(buf[0]) << 24) |
|
|
(static_cast<uint32_t>(buf[1]) << 16) |
|
|
(static_cast<uint32_t>(buf[2]) << 8) |
|
|
(static_cast<uint32_t>(buf[3]));
|
|
return rn;
|
|
}
|
|
|
|
expected<unit, error_code> 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<int>(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<unit, error_code>
|
|
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<int>(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<const uint8_t>{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<transmit_result, error_code>
|
|
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<std::chrono::milliseconds>(air);
|
|
return transmit_result{this, air_estimated};
|
|
}
|
|
|
|
expected<transmit_result, error_code>
|
|
LLCC68::hal_async_transmit(std::span<const uint8_t> 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<transmit_result, error_code>
|
|
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<uint8_t>(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<uint8_t>(data().tx_xfer_size));
|
|
return transmit_result{this, air_estimated};
|
|
}
|
|
|
|
expected<transmit_result, error_code>
|
|
LLCC68::hal_gfsk_async_transmit(std::span<const uint8_t> 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<unit, error_code> 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<unit, error_code> 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<int>(frequency_mhz * 1000), sync_str,
|
|
static_cast<unsigned>(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<unsigned>(mod_params.bitrate_bps),
|
|
static_cast<unsigned>(mod_params.frequency_deviation_hz),
|
|
static_cast<unsigned>(mod_params.rx_bandwidth),
|
|
static_cast<unsigned>(mod_params.pulse_shape),
|
|
packet_params.preamble_bits,
|
|
static_cast<unsigned>(packet_params.detector_length),
|
|
packet_params.sync_length_bits,
|
|
static_cast<unsigned>(packet_params.address_filtering),
|
|
static_cast<unsigned>(packet_params.packet_length_mode),
|
|
packet_params.payload_length,
|
|
static_cast<unsigned>(packet_params.crc_type),
|
|
static_cast<unsigned>(packet_params.whitening), tx_params.power,
|
|
to_str(tx_params.ramp_time), static_cast<int>(frequency_mhz * 1000));
|
|
}
|
|
} // namespace app::driver::llcc68
|