Compare commits

...

10 Commits

Author SHA1 Message Date
crosstyan e6f96ea0e3 fix(llcc68): cache detected chip type
Store the detected chip type after the first successful version-register read so repeated radio operations do not keep querying the chip identity.

This preserves Unknown as uncached so transient read failures or unsupported responses can still be retried on a later call.
2026-06-16 19:36:45 +08:00
crosstyan b125dd33b9 style(llcc68): apply shared clang-format
Add a .clang-format file to the LLCC68 submodule so it can be formatted consistently when edited or checked out independently from the parent repository.

Reformat the tracked C and C++ driver sources with the shared style configuration.
2026-06-16 19:36:45 +08:00
crosstyan d382bdfd1e fix(llcc68): apply full radio profile before tx and rx
Reapply packet type, sync word, RF frequency, modulation, packet, CRC, and whitening settings on async TX/RX entry points.

This lets callers switch between preconfigured profiles immediately before a send or listen operation instead of relying on modem-init state.
2026-06-16 19:35:59 +08:00
crosstyan efc6e17ed0 fix(llcc68): align spi binding with zephyr
Add a module-local .gitignore entry for macOS .DS_Store artifacts so generated Finder metadata does not dirty the submodule.

Declare the SPI chip-select delay binding properties as integers so Zephyr devicetree validation accepts the custom LLCC68 binding.

Pass the configured setup delay into SPI_DT_SPEC_INST_GET using the Zephyr 4 three-argument API, converting the nanosecond devicetree value to the microsecond spi_cs_control delay field.
2026-06-02 10:40:17 +08:00
crosstyan 1c3626d58b feat(llcc68): add configurable RF switch modes
Add an explicit rf-switch-mode devicetree property for LLCC68 instances, covering no switch handling, TXEN/RXEN complementary GPIO control, and DIO2 single-pin control for PE4259-style RF switches. Preserve the existing default behavior with an auto Kconfig default that only enables complementary GPIO handling when both TXEN and RXEN GPIOs are present.

Resolve the RF switch mode into llcc68_config at build time and validate incompatible devicetree combinations with BUILD_ASSERT checks. Configure optional RXEN GPIO handling for DIO2 single-pin mode and keep DIO2 RF switch control disabled unless that mode is selected.

Replace the old fire-and-forget TX/RX GPIO helper with a result-returning mode-aware RF switch state helper, and apply it across standby, sleep, CAD, TX, RX, continuous wave, infinite preamble, and modem init paths.

Add SetRxDutyCycle support with explicit raw 24-bit LLCC68 period units, plus helpers and a millisecond wrapper for callers that work in time units. Select the RX RF path before issuing the duty-cycle command so RXEN stays valid for duty-cycle listen windows.
2026-06-01 18:05:31 +08:00
crosstyan 4fb41a3211 feat(llcc68): honor directional TX settings
Apply the SX126x IQ config register fix after each LoRa packet parameter update so standard and inverted IQ changes take effect.

Reapply TX power settings in LoRa and GFSK async flush paths so a TX profile can differ from the RX/init profile.
2026-05-29 19:40:56 +08:00
crosstyan d4709da971 fix(llcc68): encode LoRa packet params in datasheet order
Program SetPacketParams as preamble, header type, payload length, CRC type, and IQ type.

The previous order swapped packet fields, which made gateway LoRa TX frames decode as corrupted fixed-length payloads on the CH32 LLCC68 receiver.
2026-05-29 19:40:55 +08:00
crosstyan 32a0c80ab0 fix(llcc68): align airtime estimate with radio config
Calculate LoRa airtime from the exact LDRO value supplied to SetModulationParams instead of inferring it from symbol time.

Keep transmit_result airtime in microseconds for both LoRa and GFSK, using integer Hz math and 64-bit ceil division to avoid truncation errors.

Reapply modulation parameters during async TX flush so the estimator and the bytes sent to the radio stay in sync, and add a compile-time check for the intended SF7/BW125 implicit 6-byte airtime.
2026-05-25 12:54:15 +08:00
crosstyan d8db9e1eb0 fix(llcc68): move SPI CS delay to devicetree
Zephyr deprecates passing the chip-select delay as the variadic delay argument to SPI_DT_SPEC_INST_GET. Remove that deprecated macro argument from the raw LLCC68 device initializer.

Add spi-cs-setup-delay-ns and spi-cs-hold-delay-ns defaults to the custom LLCC68 devicetree binding. Both defaults are 100000 ns, preserving the previous 100 us delay behavior while using the current Zephyr SPI devicetree properties.

Verified with cmake -S . -B build and cmake --build build from the parent application.
2026-05-25 10:12:53 +08:00
crosstyan ce56757dac feat(llcc68): make payload limit configurable
Add CONFIG_LLCC68_MAX_PAYLOAD_LENGTH with a default of 128 bytes and a hardware-bounded range of 1..255.

Wire both the C++ and raw C LLCC68 payload buffer constants to the Kconfig value so application builds can tune radio buffer RAM without editing headers.

Rename the fixed-length 100k preset from GMSK to GFSK and expose rx_bandwidth_hz() next to the GfskRxBandwidth enum so applications can report configured bandwidth without carrying driver-specific lookup tables.
2026-05-20 11:57:37 +08:00
9 changed files with 2370 additions and 1911 deletions
+25
View File
@@ -0,0 +1,25 @@
# C++ specific configuration (akin to Google's C++ style)
# https://clang.llvm.org/docs/ClangFormatStyleOptions.html#adding-additional-style-options
---
Language: Cpp
BasedOnStyle: LLVM
UseTab: ForContinuationAndIndentation
IndentWidth: 4
TabWidth: 4
AccessModifierOffset: -4
ColumnLimit: 0
NamespaceIndentation: Inner
FixNamespaceComments: false
AllowShortIfStatementsOnASingleLine: WithoutElse
AllowShortLoopsOnASingleLine: true
AllowShortBlocksOnASingleLine: Empty
IndentCaseLabels: false
SortIncludes: Never
AlignConsecutiveMacros: AcrossEmptyLines
AlignConsecutiveAssignments: Consecutive
BreakStringLiterals: true
LineEnding: LF
MaxEmptyLinesToKeep: 2
BreakBeforeBraces: Attach
InsertBraces: true
BreakAfterAttributes: Always
+1
View File
@@ -0,0 +1 @@
.DS_Store
+45 -2
View File
@@ -1,9 +1,9 @@
DT_COMPAT_SEMTECH_LLCC68 := "semtech,llcc68"
DT_COMPAT_SEMTECH_LLCC68_WEIHUA := "semtech,llcc68-weihua"
config LLCC68
bool "Semtech LLCC68 LoRa Radio Driver"
depends on SPI && GPIO
default $(dt_compat_enabled,$(DT_COMPAT_SEMTECH_LLCC68))
default $(dt_compat_enabled,$(DT_COMPAT_SEMTECH_LLCC68_WEIHUA))
help
Enable the Semtech LLCC68 LoRa Radio Driver.
@@ -14,6 +14,17 @@ config LLCC68_INIT_PRIORITY
The priority of the LLCC68 initialization. Lower numbers indicate
higher priority.
config LLCC68_MAX_PAYLOAD_LENGTH
int "LLCC68 maximum payload length"
default 128
range 1 255
help
Maximum radio payload length accepted by the LLCC68 driver.
The LLCC68 packet length fields support up to 255 bytes. Keep this
lower when the application has a known payload ceiling to reduce RAM
used by the driver's SPI staging buffers.
config LLCC68_ALWAYS_USE_SX1262_HIGH_PA
bool "LLCC68 Always Use SX1262 High Power Amplifier"
default y
@@ -21,6 +32,38 @@ config LLCC68_ALWAYS_USE_SX1262_HIGH_PA
When enabled, the LLCC68/SX1262/SX1261 driver always chooses high
power amplifier settings instead of selecting them from chip version.
choice LLCC68_RF_SWITCH_DEFAULT
prompt "Default LLCC68 RF switch mode"
default LLCC68_RF_SWITCH_DEFAULT_AUTO
help
Default RF switch mode for LLCC68 devicetree nodes that do not set
rf-switch-mode.
config LLCC68_RF_SWITCH_DEFAULT_AUTO
bool "Auto"
help
Preserve legacy behavior: use complementary GPIO control when both
tx-enable-gpios and rx-enable-gpios are present, otherwise disable RF
switch control.
config LLCC68_RF_SWITCH_DEFAULT_NONE
bool "None"
help
Disable RF switch control unless rf-switch-mode is set in devicetree.
config LLCC68_RF_SWITCH_DEFAULT_GPIO_COMPLEMENTARY
bool "TXEN/RXEN complementary GPIO"
help
Use MCU GPIOs for complementary TXEN/RXEN RF switch control by default.
config LLCC68_RF_SWITCH_DEFAULT_DIO2_SINGLE
bool "DIO2 single-pin"
help
Use LLCC68 DIO2 RF switch control by default. DIO2 drives TXEN, while
RXEN is held active externally or by rx-enable-gpios.
endchoice
module = LLCC68
module-str = llcc68
+45 -3
View File
@@ -1,10 +1,34 @@
#include "llcc68_raw.h"
#include <errno.h>
#include <zephyr/devicetree.h>
#include <zephyr/sys_clock.h>
#include <zephyr/sys/util.h>
#define DT_DRV_COMPAT semtech_llcc68_weihua
#define LLCC68_AUTO_RF_SWITCH_MODE(inst) \
COND_CODE_1(DT_INST_NODE_HAS_PROP(inst, tx_enable_gpios), \
(COND_CODE_1(DT_INST_NODE_HAS_PROP(inst, rx_enable_gpios), \
(LLCC68_RF_SWITCH_GPIO_COMPLEMENTARY), (LLCC68_RF_SWITCH_NONE))), \
(LLCC68_RF_SWITCH_NONE))
#if defined(CONFIG_LLCC68_RF_SWITCH_DEFAULT_GPIO_COMPLEMENTARY)
#define LLCC68_DEFAULT_RF_SWITCH_MODE(inst) LLCC68_RF_SWITCH_GPIO_COMPLEMENTARY
#elif defined(CONFIG_LLCC68_RF_SWITCH_DEFAULT_DIO2_SINGLE)
#define LLCC68_DEFAULT_RF_SWITCH_MODE(inst) LLCC68_RF_SWITCH_DIO2_SINGLE
#elif defined(CONFIG_LLCC68_RF_SWITCH_DEFAULT_NONE)
#define LLCC68_DEFAULT_RF_SWITCH_MODE(inst) LLCC68_RF_SWITCH_NONE
#else
#define LLCC68_DEFAULT_RF_SWITCH_MODE(inst) LLCC68_AUTO_RF_SWITCH_MODE(inst)
#endif
#define LLCC68_RF_SWITCH_MODE(inst) \
DT_ENUM_IDX_OR(DT_DRV_INST(inst), rf_switch_mode, LLCC68_DEFAULT_RF_SWITCH_MODE(inst))
#define LLCC68_SPI_CS_DELAY_US(inst) \
DIV_ROUND_UP(DT_INST_PROP(inst, spi_cs_setup_delay_ns), NSEC_PER_USEC)
static void dio1_irq_trampoline(const struct device *port, struct gpio_callback *cb, uint32_t pins) {
ARG_UNUSED(port);
@@ -20,14 +44,21 @@ int llcc68_init(const struct device *dev) {
const struct llcc68_config *config = dev->config;
struct llcc68_data *data = dev->data;
if (config->tx_enable_gpio.port != NULL) {
if (config->rf_switch_mode == LLCC68_RF_SWITCH_GPIO_COMPLEMENTARY &&
config->tx_enable_gpio.port != NULL) {
gpio_pin_configure_dt(&config->tx_enable_gpio, GPIO_OUTPUT_INACTIVE);
}
if (config->rx_enable_gpio.port != NULL) {
if (config->rf_switch_mode == LLCC68_RF_SWITCH_GPIO_COMPLEMENTARY &&
config->rx_enable_gpio.port != NULL) {
gpio_pin_configure_dt(&config->rx_enable_gpio, GPIO_OUTPUT_INACTIVE);
}
if (config->rf_switch_mode == LLCC68_RF_SWITCH_DIO2_SINGLE &&
config->rx_enable_gpio.port != NULL) {
gpio_pin_configure_dt(&config->rx_enable_gpio, GPIO_OUTPUT_ACTIVE);
}
gpio_pin_configure_dt(&config->reset_gpio, GPIO_OUTPUT_INACTIVE);
gpio_pin_configure_dt(&config->busy_gpio, GPIO_INPUT);
gpio_pin_configure_dt(&config->dio1_gpio, GPIO_INPUT);
@@ -43,15 +74,26 @@ int llcc68_init(const struct device *dev) {
}
#define LLCC68_DEFINE(inst) \
BUILD_ASSERT(LLCC68_RF_SWITCH_MODE(inst) != LLCC68_RF_SWITCH_GPIO_COMPLEMENTARY || \
(DT_INST_NODE_HAS_PROP(inst, tx_enable_gpios) && \
DT_INST_NODE_HAS_PROP(inst, rx_enable_gpios)), \
"LLCC68 gpio-complementary RF switch mode requires tx-enable-gpios " \
"and rx-enable-gpios"); \
BUILD_ASSERT(LLCC68_RF_SWITCH_MODE(inst) != LLCC68_RF_SWITCH_DIO2_SINGLE || \
!DT_INST_NODE_HAS_PROP(inst, tx_enable_gpios), \
"LLCC68 dio2-single RF switch mode uses DIO2 for TXEN and must not " \
"define tx-enable-gpios"); \
static struct llcc68_data llcc68_data_##inst; \
static const struct llcc68_config llcc68_config_##inst = \
{ \
.spi = SPI_DT_SPEC_INST_GET(inst, SPI_WORD_SET(8) | SPI_TRANSFER_MSB, 100), \
.spi = SPI_DT_SPEC_INST_GET( \
inst, SPI_WORD_SET(8) | SPI_TRANSFER_MSB, LLCC68_SPI_CS_DELAY_US(inst)), \
.reset_gpio = GPIO_DT_SPEC_INST_GET(inst, reset_gpios), \
.busy_gpio = GPIO_DT_SPEC_INST_GET(inst, busy_gpios), \
.dio1_gpio = GPIO_DT_SPEC_INST_GET(inst, dio1_gpios), \
.tx_enable_gpio = GPIO_DT_SPEC_INST_GET_OR(inst, tx_enable_gpios, {.port = NULL}), \
.rx_enable_gpio = GPIO_DT_SPEC_INST_GET_OR(inst, rx_enable_gpios, {.port = NULL}), \
.rf_switch_mode = LLCC68_RF_SWITCH_MODE(inst), \
}; \
DEVICE_DT_INST_DEFINE(inst, \
llcc68_init, \
+30
View File
@@ -47,3 +47,33 @@ properties:
description: |
Antenna switch RX enable GPIO. If set, the driver tracks the
state of the radio and controls the RF switch.
rf-switch-mode:
type: string
enum:
- "none"
- "gpio-complementary"
- "dio2-single"
description: |
Optional RF switch control mode.
"none" disables RF switch handling.
"gpio-complementary" controls TXEN/RXEN from MCU GPIOs using the
complementary table:
idle: TXEN=0, RXEN=0
RX: TXEN=0, RXEN=1
TX: TXEN=1, RXEN=0
This mode requires tx-enable-gpios and rx-enable-gpios.
"dio2-single" enables LLCC68 DIO2-as-RF-switch control for TXEN.
RXEN must be externally pulled active or supplied as rx-enable-gpios,
which the driver holds active. This mode must not use tx-enable-gpios.
spi-cs-setup-delay-ns:
type: int
default: 100000
spi-cs-hold-delay-ns:
type: int
default: 100000
+20 -3
View File
@@ -11,7 +11,7 @@
#include <zephyr/drivers/gpio.h>
namespace app::driver::llcc68 {
constexpr size_t MAX_BUFFER_PAYLOAD = 128;
constexpr size_t MAX_BUFFER_PAYLOAD = CONFIG_LLCC68_MAX_PAYLOAD_LENGTH;
constexpr uint32_t TIMEOUT_NONE = 0;
constexpr uint32_t TIMEOUT_INF = 0xffffffff;
@@ -41,12 +41,13 @@ constexpr auto DEFAULT_BUSY_TIMEOUT_MS = 100;
* @param preamble_length Preamble length in symbols
* @param header_type Header type (implicit or explicit).
* @param crc_type CRC type (none or 16-bit)
* @param ldro_on Low data rate optimization setting sent to the radio
*/
constexpr airtime_t calc_time_on_air(uint8_t len, uint8_t sf, LoRaBandwidth bw,
LoRaCodingRate cr,
uint16_t preamble_length,
LoRaHeaderType header_type,
LoRaCrcType crc_type);
LoRaCrcType crc_type, bool ldro_on);
struct LLCC68 {
/** trivial getter */
@@ -119,7 +120,7 @@ struct LLCC68 {
uint8_t offset, std::span<const uint8_t> data_from_host,
timeout_ms_t busy_timeout = DEFAULT_BUSY_TIMEOUT_MS);
void tx_rx_en_pin_set(TxRxPinState state);
expected<unit, error_code> set_rf_switch_state(RfSwitchState state);
/** LLCC68 DataSheet Function */
@@ -238,6 +239,21 @@ struct LLCC68 {
expected<unit, error_code> set_tx(uint32_t timeout = TIMEOUT_NONE);
expected<unit, error_code> set_rx(uint32_t timeout = TIMEOUT_INF);
/**
* @brief Start LLCC68 RX duty-cycle/listen mode.
*
* rx_period and sleep_period are raw 24-bit LLCC68 RTC periods, not
* milliseconds. Datasheet section 13.1.7 defines one period as 15.625 us:
* RX duration = rx_period * 15.625 us
* sleep duration = sleep_period * 15.625 us
*
* Use rx_duty_cycle_period_from_ms() or set_rx_duty_cycle_ms() when caller
* inputs are in milliseconds.
*/
expected<unit, error_code> set_rx_duty_cycle(uint32_t rx_period,
uint32_t sleep_period);
expected<unit, error_code> set_rx_duty_cycle_ms(uint32_t rx_period_ms,
uint32_t sleep_period_ms);
expected<unit, error_code> set_sleep(sleep_config_t config);
expected<unit, error_code> set_tx_continuous_wave();
expected<unit, error_code> set_tx_infinite_preamble();
@@ -287,6 +303,7 @@ struct LLCC68 {
/** properties */
const struct device *dev;
std::optional<ChipType> cached_chip_type{};
};
} // namespace app::driver::llcc68
+92 -5
View File
@@ -14,9 +14,45 @@
namespace app::driver::llcc68 {
struct LLCC68;
using airtime_t = std::chrono::duration<uint32_t, std::micro>;
using airtime_t = std::chrono::microseconds;
using error_code = std::error_code;
constexpr uint32_t RX_DUTY_CYCLE_PERIOD_MAX = 0x00FFFFFFU;
constexpr uint32_t RX_DUTY_CYCLE_PERIOD_UNIT_US_NUMERATOR = 125U;
constexpr uint32_t RX_DUTY_CYCLE_PERIOD_UNIT_US_DENOMINATOR = 8U;
/**
* @brief Convert microseconds to a raw SetRxDutyCycle period.
*
* LLCC68 datasheet section 13.1.7 defines rxPeriod and sleepPeriod as raw
* 24-bit RTC periods, not milliseconds. One raw period is 15.625 us.
*/
constexpr std::optional<uint32_t> rx_duty_cycle_period_from_us(uint64_t us) {
constexpr uint64_t scale = RX_DUTY_CYCLE_PERIOD_UNIT_US_DENOMINATOR;
constexpr uint64_t divisor = RX_DUTY_CYCLE_PERIOD_UNIT_US_NUMERATOR;
if (us > (UINT64_MAX - (divisor - 1U)) / scale) {
return std::nullopt;
}
const uint64_t raw = ((us * scale) + (divisor - 1U)) / divisor;
if (raw > RX_DUTY_CYCLE_PERIOD_MAX) {
return std::nullopt;
}
return static_cast<uint32_t>(raw);
}
/**
* @brief Convert milliseconds to a raw SetRxDutyCycle period.
*
* Returns std::nullopt if the requested duration does not fit the LLCC68
* 24-bit period field.
*/
constexpr std::optional<uint32_t> rx_duty_cycle_period_from_ms(uint64_t ms) {
if (ms > UINT64_MAX / 1000U) {
return std::nullopt;
}
return rx_duty_cycle_period_from_us(ms * 1000U);
}
enum class Errc : uint8_t {
FailureToExecuteCommand = 1,
CommandTimeout = 2,
@@ -376,6 +412,55 @@ enum class GfskRxBandwidth : uint8_t {
Bw467000 = 0x09,
};
inline uint32_t rx_bandwidth_hz(GfskRxBandwidth bandwidth) {
using enum GfskRxBandwidth;
switch (bandwidth) {
case Bw4800:
return 4800;
case Bw5800:
return 5800;
case Bw7300:
return 7300;
case Bw9700:
return 9700;
case Bw11700:
return 11700;
case Bw14600:
return 14600;
case Bw19500:
return 19500;
case Bw23400:
return 23400;
case Bw29300:
return 29300;
case Bw39000:
return 39000;
case Bw46900:
return 46900;
case Bw58600:
return 58600;
case Bw78200:
return 78200;
case Bw93800:
return 93800;
case Bw117300:
return 117300;
case Bw156200:
return 156200;
case Bw187200:
return 187200;
case Bw234300:
return 234300;
case Bw312000:
return 312000;
case Bw373600:
return 373600;
case Bw467000:
return 467000;
}
return 0;
}
enum class GfskPreambleDetector : uint8_t {
Off = 0x00,
Bits8 = 0x04,
@@ -478,7 +563,7 @@ struct gfsk_parameters_t {
}
static constexpr gfsk_parameters_t
Gmsk100kFixed6(freq_t frequency_mhz = 434.18,
Gfsk100kFixed6(freq_t frequency_mhz = 434.18,
tx_params_t tx_params = tx_params_t::Default()) {
return {
.mod_params =
@@ -936,7 +1021,8 @@ struct irq_status_bits_t {
// MSB
};
enum class TxRxPinState : uint8_t {
enum class RfSwitchState : uint8_t {
Idle,
TX,
RX,
};
@@ -1048,7 +1134,7 @@ struct transmit_result {
error_code post_action();
LLCC68 *self{};
std::chrono::milliseconds airtime_estimated;
airtime_t airtime_estimated;
};
} // namespace app::driver::llcc68
@@ -1057,7 +1143,8 @@ namespace llcc68 = app::driver::llcc68;
}
namespace std {
template <> struct is_error_code_enum<app::driver::llcc68::Errc> : true_type {};
template <>
struct is_error_code_enum<app::driver::llcc68::Errc> : true_type {};
} // namespace std
#endif /* ECC594CF_EDF0_42B5_8518_0EB3B3583727 */
+8 -1
View File
@@ -6,7 +6,7 @@
#include <zephyr/types.h>
#include <stddef.h>
#define LLCC68_MAX_BUFFER_PAYLOAD 128
#define LLCC68_MAX_BUFFER_PAYLOAD CONFIG_LLCC68_MAX_PAYLOAD_LENGTH
#ifdef __cplusplus
extern "C" {
@@ -14,6 +14,12 @@ extern "C" {
typedef void (*llcc68_user_dio1_handler_t)(const struct device *dev, void *user_data);
enum llcc68_rf_switch_mode {
LLCC68_RF_SWITCH_NONE = 0,
LLCC68_RF_SWITCH_GPIO_COMPLEMENTARY = 1,
LLCC68_RF_SWITCH_DIO2_SINGLE = 2,
};
struct llcc68_config {
struct spi_dt_spec spi;
struct gpio_dt_spec reset_gpio;
@@ -21,6 +27,7 @@ struct llcc68_config {
struct gpio_dt_spec dio1_gpio;
struct gpio_dt_spec tx_enable_gpio;
struct gpio_dt_spec rx_enable_gpio;
enum llcc68_rf_switch_mode rf_switch_mode;
};
struct llcc68_data {
+266 -59
View File
@@ -111,6 +111,39 @@ namespace {
// Max payload the radio supports and buffer sizing helpers
constexpr size_t kMaxPayload = 32;
constexpr uint64_t ceil_div_u64(uint64_t numerator, uint64_t denominator) {
if (denominator == 0) {
return 0;
}
return numerator / denominator + (numerator % denominator == 0 ? 0 : 1);
}
constexpr uint32_t lora_bw_hz(LoRaBandwidth bw) {
switch (bw) {
case LoRaBandwidth::BW_7_8:
return 7'810;
case LoRaBandwidth::BW_10_4:
return 10'420;
case LoRaBandwidth::BW_15_6:
return 15'630;
case LoRaBandwidth::BW_20_8:
return 20'830;
case LoRaBandwidth::BW_31_25:
return 31'250;
case LoRaBandwidth::BW_41_7:
return 41'670;
case LoRaBandwidth::BW_62_5:
return 62'500;
case LoRaBandwidth::BW_125_0:
return 125'000;
case LoRaBandwidth::BW_250_0:
return 250'000;
case LoRaBandwidth::BW_500_0:
return 500'000;
}
return 0;
}
error_code command_status_to_error(status_t st) {
switch (st.command_status) {
case CommandStatus::FAILURE_TO_EXECUTE_COMMAND:
@@ -139,18 +172,43 @@ int wait_for_not_busy(const gpio_dt_spec &busy_gpio, uint16_t timeout_ms) {
}
} // namespace
void LLCC68::tx_rx_en_pin_set(TxRxPinState state) {
if (not tx_enable_gpio() or (not rx_enable_gpio())) {
return;
expected<unit, error_code> LLCC68::set_rf_switch_state(RfSwitchState state) {
auto set_gpio = [](const gpio_dt_spec &gpio, int value)
-> expected<unit, error_code> {
if (not device_is_ready(gpio.port)) {
return ue(-ENODEV);
}
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);
const int ret = gpio_pin_set_dt(&gpio, value);
if (ret < 0) {
return ue(ret);
}
return unit{};
};
switch (config().rf_switch_mode) {
case LLCC68_RF_SWITCH_DIO2_SINGLE:
case LLCC68_RF_SWITCH_NONE:
return unit{};
case LLCC68_RF_SWITCH_GPIO_COMPLEMENTARY: {
const auto tx = tx_enable_gpio();
const auto rx = rx_enable_gpio();
if (not tx or not rx) {
return ue(-ENODEV);
}
expected<unit, error_code> r;
if (state == RfSwitchState::TX) {
r = set_gpio(*rx, 0);
APP_RADIO_RETURN_ERR(r);
return set_gpio(*tx, 1);
}
r = set_gpio(*tx, 0);
APP_RADIO_RETURN_ERR(r);
return set_gpio(*rx, state == RfSwitchState::RX ? 1 : 0);
}
return unit{};
default:
return ue(-EINVAL);
}
}
@@ -163,47 +221,57 @@ 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;
LoRaCrcType crc_type, bool ldro_on) {
// Everything is in microseconds to allow integer arithmetic. Some constants
// have .25, these are multiplied by 4 and have an _x4 postfix.
const uint32_t bw_hz = lora_bw_hz(bw);
if (bw_hz == 0) {
return airtime_t{0};
}
uint8_t sfCoeff1_x4 = 17; // (4.25 * 4)
uint8_t sfCoeff2 = 8;
if (sf == 5 || sf == 6) {
sfCoeff1_x4 = 25; // 6.25 * 4
sfCoeff2 = 0;
}
uint8_t sfDivisor = 4 * sf;
if (symbolLength_us >= 16000) {
sfDivisor = 4 * (sf - 2);
const uint8_t ldro_sf_offset = ldro_on ? 2 : 0;
if (sf <= ldro_sf_offset) {
return airtime_t{0};
}
constexpr int8_t bitsPerCrc = 16;
int8_t N_symbol_header =
header_type == RADIOLIB_SX126X_LORA_HEADER_EXPLICIT ? 20 : 0;
const uint8_t sfDivisor = 4 * (sf - ldro_sf_offset);
const int16_t header_bits =
header_type == LoRaHeaderType::HEADER_EXPLICIT ? 20 : 0;
// numerator of equation in section 6.1.4 of SX1268 datasheet v1.1 (might
// not actually be bitcount, but it has len * 8)
auto bit_count = static_cast<int16_t>(
static_cast<int16_t>(8) * static_cast<int16_t>(len) +
static_cast<int16_t>(crc_type * bitsPerCrc) -
static_cast<int16_t>(crc_type == LoRaCrcType::CRC_ON ? 16 : 0) -
static_cast<int16_t>(4 * sf) + static_cast<int16_t>(sfCoeff2) +
static_cast<int16_t>(N_symbol_header));
header_bits);
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));
const auto cr_denominator = std::get<1>(cr_to_ratio(cr));
// preamble can be 65k, therefore nSymbol_x4 needs to be 32 bit
const uint32_t nSymbol_x4 =
(preamble_length + 8) * 4 + sfCoeff1_x4 + nPreCodedSymbols * de * 4;
(preamble_length + 8) * 4 + sfCoeff1_x4 +
nPreCodedSymbols * cr_denominator * 4;
return airtime_t{symbolLength_us * nSymbol_x4 / 4};
const uint64_t time_us = ceil_div_u64(
(static_cast<uint64_t>(1'000'000) << sf) * nSymbol_x4,
static_cast<uint64_t>(bw_hz) * 4U);
return airtime_t{static_cast<airtime_t::rep>(time_us)};
}
static_assert(calc_time_on_air(6, 7, LoRaBandwidth::BW_125_0,
LoRaCodingRate::CR_4_5, 8,
LoRaHeaderType::HEADER_IMPLICIT,
LoRaCrcType::CRC_OFF, false) ==
airtime_t{25'856});
// SPI helpers implementing the LLCC68 wire protocol
expected<unit, error_code> LLCC68::read_stream(uint8_t cmd,
std::span<uint8_t> data,
@@ -600,10 +668,16 @@ expected<unit, error_code> LLCC68::reset() {
expected<unit, error_code> LLCC68::set_standby() {
const uint8_t data[] = {RADIOLIB_SX126X_STANDBY_RC};
return write_stream(RADIOLIB_SX126X_CMD_SET_STANDBY, data);
auto r = write_stream(RADIOLIB_SX126X_CMD_SET_STANDBY, data);
APP_RADIO_RETURN_ERR(r);
return set_rf_switch_state(RfSwitchState::Idle);
}
expected<ChipType, error_code> LLCC68::hal_get_chip_type() {
if (cached_chip_type.has_value()) {
return *cached_chip_type;
}
constexpr auto SX1262_CHIP_TYPE = "SX1262";
constexpr auto LLCC68_CHIP_TYPE = "LLCC68";
constexpr auto SX1261_CHIP_TYPE = "SX1261";
@@ -614,23 +688,31 @@ expected<ChipType, error_code> LLCC68::hal_get_chip_type() {
auto r = read_register(RADIOLIB_SX126X_REG_VERSION_STRING, version_buf);
APP_RADIO_RETURN_ERR(r);
LOG_HEXDUMP_DBG(version, sizeof(version), "version dump");
ChipType chip_type = ChipType::Unknown;
if (strncmp(version, LLCC68_CHIP_TYPE, 6) == 0) {
return ChipType::LLCC68;
chip_type = ChipType::LLCC68;
} else if (strncmp(version, SX1261_CHIP_TYPE, 6) == 0) {
chip_type = ChipType::SX1261;
} else if (strncmp(version, SX1262_CHIP_TYPE, 6) == 0) {
chip_type = ChipType::SX1262;
}
if (strncmp(version, SX1261_CHIP_TYPE, 6) == 0) {
return ChipType::SX1261;
if (chip_type != ChipType::Unknown) {
cached_chip_type = chip_type;
}
if (strncmp(version, SX1262_CHIP_TYPE, 6) == 0) {
return ChipType::SX1262;
}
return ChipType::Unknown;
return chip_type;
}
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(),
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);
}
@@ -672,7 +754,7 @@ static constexpr uint8_t gfsk_crc_len_bytes(GfskCrcType crc_type) {
}
}
static constexpr std::chrono::milliseconds
static constexpr airtime_t
calc_gfsk_time_on_air(gfsk_parameters_t params, uint8_t payload_len) {
const auto address_bits =
params.packet_params.address_filtering == GfskAddressFiltering::Disabled
@@ -688,9 +770,12 @@ calc_gfsk_time_on_air(gfsk_parameters_t params, uint8_t payload_len) {
(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};
if (params.mod_params.bitrate_bps == 0) {
return airtime_t{0};
}
const uint64_t us = ceil_div_u64(static_cast<uint64_t>(bits) * 1'000'000U,
params.mod_params.bitrate_bps);
return airtime_t{static_cast<airtime_t::rep>(us)};
}
static expected<unit, error_code>
@@ -828,12 +913,14 @@ LLCC68::set_packet_params(uint16_t preamble_length, uint8_t payload_length,
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,
payload_length,
crc_type,
iq_type,
};
return write_stream(RADIOLIB_SX126X_CMD_SET_PACKET_PARAMS, data);
auto r = write_stream(RADIOLIB_SX126X_CMD_SET_PACKET_PARAMS, data);
APP_RADIO_RETURN_ERR(r);
return fix_inverted_iq(iq_type);
}
expected<unit, error_code>
@@ -1032,6 +1119,8 @@ expected<unit, error_code> LLCC68::set_cad_params(cad_params_t params) {
expected<unit, error_code> LLCC68::set_cad() {
auto dummy = std::span<uint8_t>{};
auto r = set_rf_switch_state(RfSwitchState::RX);
APP_RADIO_RETURN_ERR(r);
return write_stream(RADIOLIB_SX126X_CMD_SET_CAD, dummy);
}
@@ -1053,21 +1142,56 @@ expected<unit, error_code> LLCC68::set_rx(uint32_t timeout) {
return write_stream(RADIOLIB_SX126X_CMD_SET_RX, data);
}
expected<unit, error_code> LLCC68::set_rx_duty_cycle(uint32_t rx_period,
uint32_t sleep_period) {
if (rx_period > RX_DUTY_CYCLE_PERIOD_MAX ||
sleep_period > RX_DUTY_CYCLE_PERIOD_MAX) {
return ue(-EINVAL);
}
auto r = set_rf_switch_state(RfSwitchState::RX);
APP_RADIO_RETURN_ERR(r);
const uint8_t data[] = {
static_cast<uint8_t>((rx_period >> 16) & 0xFF),
static_cast<uint8_t>((rx_period >> 8) & 0xFF),
static_cast<uint8_t>(rx_period & 0xFF),
static_cast<uint8_t>((sleep_period >> 16) & 0xFF),
static_cast<uint8_t>((sleep_period >> 8) & 0xFF),
static_cast<uint8_t>(sleep_period & 0xFF),
};
return write_stream(RADIOLIB_SX126X_CMD_SET_RX_DUTY_CYCLE, data);
}
expected<unit, error_code>
LLCC68::set_rx_duty_cycle_ms(uint32_t rx_period_ms, uint32_t sleep_period_ms) {
const auto rx_period = rx_duty_cycle_period_from_ms(rx_period_ms);
const auto sleep_period = rx_duty_cycle_period_from_ms(sleep_period_ms);
if (not rx_period or not sleep_period) {
return ue(-EINVAL);
}
return set_rx_duty_cycle(*rx_period, *sleep_period);
}
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);
auto r = write_stream(RADIOLIB_SX126X_CMD_SET_SLEEP, data);
APP_RADIO_RETURN_ERR(r);
return set_rf_switch_state(RfSwitchState::Idle);
}
expected<unit, error_code> LLCC68::set_tx_continuous_wave() {
auto dummy = std::span<uint8_t>{};
// const uint8_t dummy[] = {RADIOLIB_SX126X_CMD_NOP};
auto r = set_rf_switch_state(RfSwitchState::TX);
APP_RADIO_RETURN_ERR(r);
return write_stream(RADIOLIB_SX126X_CMD_SET_TX_CONTINUOUS_WAVE, dummy);
}
expected<unit, error_code> LLCC68::set_tx_infinite_preamble() {
auto dummy = std::span<uint8_t>{};
// const uint8_t dummy[] = {RADIOLIB_SX126X_CMD_NOP};
auto r = set_rf_switch_state(RfSwitchState::TX);
APP_RADIO_RETURN_ERR(r);
return write_stream(RADIOLIB_SX126X_CMD_SET_TX_INFINITE_PREAMBLE, dummy);
}
@@ -1174,7 +1298,9 @@ expected<unit, error_code> LLCC68::hal_modem_init(lora_parameters_t params) {
"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),
APP_RADIO_RETURN_ERR_CTX(set_dio2_as_rf_switch(
config().rf_switch_mode ==
LLCC68_RF_SWITCH_DIO2_SINGLE),
"modem_init::set_dio2_as_rf_switch");
APP_RADIO_RETURN_ERR_CTX(set_rf_frequency(params.frequency_mhz),
"modem_init::set_rf_frequency");
@@ -1230,7 +1356,9 @@ LLCC68::hal_gfsk_modem_init(gfsk_parameters_t params) {
params.broadcast_address.value_or(0)),
"gfsk_init::set_address_filtering");
}
APP_RADIO_RETURN_ERR_CTX(set_dio2_as_rf_switch(false),
APP_RADIO_RETURN_ERR_CTX(set_dio2_as_rf_switch(
config().rf_switch_mode ==
LLCC68_RF_SWITCH_DIO2_SINGLE),
"gfsk_init::set_dio2_as_rf_switch");
APP_RADIO_RETURN_ERR_CTX(set_rf_frequency(params.frequency_mhz),
"gfsk_init::set_rf_frequency");
@@ -1250,12 +1378,26 @@ LLCC68::hal_async_flush(lora_parameters_t params) {
return ue(Errc::InvalidState);
}
APP_RADIO_RETURN_ERR_CTX(set_standby(), "tx::standby");
APP_RADIO_RETURN_ERR_CTX(set_packet_type_lora(), "tx::set_packet_type");
APP_RADIO_RETURN_ERR_CTX(set_lora_sync_word(params.sync_word),
"tx::set_lora_sync_word");
APP_RADIO_RETURN_ERR_CTX(set_rf_frequency(params.frequency_mhz),
"tx::set_rf_frequency");
APP_RADIO_RETURN_ERR_CTX(
set_modulation_params(params.mod_params.sf, params.mod_params.bw,
params.mod_params.cr,
params.mod_params.ldr_optimize),
"tx::set_modulation_params");
APP_RADIO_RETURN_ERR_CTX(set_packet_params(params.packet_params.preamble_len,
data().tx_xfer_size,
params.packet_params.crc_type,
params.packet_params.hdr_type,
params.packet_params.iq_type),
"tx::set_packet_params");
auto chip_type_ = hal_get_chip_type();
APP_RADIO_RETURN_ERR_CTX(chip_type_, "tx::get_chip_type");
APP_RADIO_RETURN_ERR_CTX(hal_set_output_power(*chip_type_, params.tx_params),
"tx::hal_set_output_power");
APP_RADIO_RETURN_ERR_CTX(set_buffer_base_address(),
"tx::set_buffer_base_address");
APP_RADIO_RETURN_ERR_CTX(flush_tx_buffer(), "tx::flush_tx_buffer");
@@ -1273,15 +1415,15 @@ LLCC68::hal_async_flush(lora_parameters_t 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_rf_switch_state(RfSwitchState::TX),
"tx::set_rf_switch");
APP_RADIO_RETURN_ERR_CTX(set_tx(), "tx::set_tx_params");
auto air = calc_time_on_air(
data().tx_xfer_size, params.mod_params.sf, params.mod_params.bw,
params.mod_params.cr, params.packet_params.preamble_len,
params.packet_params.hdr_type, params.packet_params.crc_type);
auto air_estimated =
std::chrono::duration_cast<std::chrono::milliseconds>(air);
return transmit_result{this, air_estimated};
params.packet_params.hdr_type, params.packet_params.crc_type,
params.mod_params.ldr_optimize == LoRaLowDataRateType::LDR_ON);
return transmit_result{this, air};
}
expected<transmit_result, error_code>
@@ -1302,12 +1444,43 @@ LLCC68::hal_gfsk_async_flush(gfsk_parameters_t params) {
if (data().tx_xfer_size == 0 || data().tx_xfer_size > MAX_BUFFER_PAYLOAD) {
return ue(-EINVAL);
}
if (params.sync_word_length > params.sync_word.size() ||
params.packet_params.sync_length_bits > params.sync_word.size() * 8) {
return ue(-EINVAL);
}
APP_RADIO_RETURN_ERR_CTX(set_standby(), "gfsk_tx::standby");
APP_RADIO_RETURN_ERR_CTX(set_packet_type_gfsk(),
"gfsk_tx::set_packet_type");
APP_RADIO_RETURN_ERR_CTX(set_rf_frequency(params.frequency_mhz),
"gfsk_tx::set_rf_frequency");
APP_RADIO_RETURN_ERR_CTX(set_gfsk_modulation_params(params.mod_params),
"gfsk_tx::set_modulation_params");
APP_RADIO_RETURN_ERR_CTX(
set_gfsk_sync_word(std::span<const uint8_t>{params.sync_word.data(),
params.sync_word_length}),
"gfsk_tx::set_sync_word");
APP_RADIO_RETURN_ERR_CTX(set_gfsk_crc_seed(params.crc_seed),
"gfsk_tx::set_crc_seed");
APP_RADIO_RETURN_ERR_CTX(set_gfsk_crc_polynomial(params.crc_polynomial),
"gfsk_tx::set_crc_polynomial");
APP_RADIO_RETURN_ERR_CTX(set_gfsk_whitening_seed(params.whitening_seed),
"gfsk_tx::set_whitening_seed");
if (params.packet_params.address_filtering !=
GfskAddressFiltering::Disabled) {
APP_RADIO_RETURN_ERR_CTX(
set_gfsk_address_filtering(params.node_address.value_or(0),
params.broadcast_address.value_or(0)),
"gfsk_tx::set_address_filtering");
}
auto packet_params = params.packet_params;
packet_params.payload_length = static_cast<uint8_t>(data().tx_xfer_size);
APP_RADIO_RETURN_ERR_CTX(set_gfsk_packet_params(packet_params),
"gfsk_tx::set_packet_params");
auto chip_type_ = hal_get_chip_type();
APP_RADIO_RETURN_ERR_CTX(chip_type_, "gfsk_tx::get_chip_type");
APP_RADIO_RETURN_ERR_CTX(hal_set_output_power(*chip_type_, params.tx_params),
"gfsk_tx::hal_set_output_power");
APP_RADIO_RETURN_ERR_CTX(set_buffer_base_address(),
"gfsk_tx::set_buffer_base_address");
APP_RADIO_RETURN_ERR_CTX(flush_tx_buffer(), "gfsk_tx::flush_tx_buffer");
@@ -1325,7 +1498,8 @@ LLCC68::hal_gfsk_async_flush(gfsk_parameters_t 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_rf_switch_state(RfSwitchState::TX),
"gfsk_tx::set_rf_switch");
APP_RADIO_RETURN_ERR_CTX(set_tx(), "gfsk_tx::set_tx");
auto air_estimated =
calc_gfsk_time_on_air(params, static_cast<uint8_t>(data().tx_xfer_size));
@@ -1342,6 +1516,11 @@ LLCC68::hal_gfsk_async_transmit(std::span<const uint8_t> data,
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_packet_type_lora(), "rx::set_packet_type");
APP_RADIO_RETURN_ERR_CTX(set_lora_sync_word(params.sync_word),
"rx::set_lora_sync_word");
APP_RADIO_RETURN_ERR_CTX(set_rf_frequency(params.frequency_mhz),
"rx::set_rf_frequency");
APP_RADIO_RETURN_ERR_CTX(set_buffer_base_address(),
"rx::set_buffer_base_address");
@@ -1369,17 +1548,44 @@ expected<unit, error_code> LLCC68::hal_async_rx(lora_parameters_t 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_rf_switch_state(RfSwitchState::RX),
"rx::set_rf_switch");
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) {
if (params.sync_word_length > params.sync_word.size() ||
params.packet_params.sync_length_bits > params.sync_word.size() * 8) {
return ue(-EINVAL);
}
APP_RADIO_RETURN_ERR_CTX(set_standby(), "gfsk_rx::standby");
APP_RADIO_RETURN_ERR_CTX(set_packet_type_gfsk(),
"gfsk_rx::set_packet_type");
APP_RADIO_RETURN_ERR_CTX(set_rf_frequency(params.frequency_mhz),
"gfsk_rx::set_rf_frequency");
APP_RADIO_RETURN_ERR_CTX(set_buffer_base_address(),
"gfsk_rx::set_buffer_base_address");
APP_RADIO_RETURN_ERR_CTX(set_gfsk_modulation_params(params.mod_params),
"gfsk_rx::set_modulation_params");
APP_RADIO_RETURN_ERR_CTX(
set_gfsk_sync_word(std::span<const uint8_t>{params.sync_word.data(),
params.sync_word_length}),
"gfsk_rx::set_sync_word");
APP_RADIO_RETURN_ERR_CTX(set_gfsk_crc_seed(params.crc_seed),
"gfsk_rx::set_crc_seed");
APP_RADIO_RETURN_ERR_CTX(set_gfsk_crc_polynomial(params.crc_polynomial),
"gfsk_rx::set_crc_polynomial");
APP_RADIO_RETURN_ERR_CTX(set_gfsk_whitening_seed(params.whitening_seed),
"gfsk_rx::set_whitening_seed");
if (params.packet_params.address_filtering !=
GfskAddressFiltering::Disabled) {
APP_RADIO_RETURN_ERR_CTX(
set_gfsk_address_filtering(params.node_address.value_or(0),
params.broadcast_address.value_or(0)),
"gfsk_rx::set_address_filtering");
}
APP_RADIO_RETURN_ERR_CTX(set_gfsk_packet_params(params.packet_params),
"gfsk_rx::set_packet_params");
@@ -1395,7 +1601,8 @@ expected<unit, error_code> LLCC68::hal_gfsk_async_rx(gfsk_parameters_t 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_rf_switch_state(RfSwitchState::RX),
"gfsk_rx::set_rf_switch");
APP_RADIO_RETURN_ERR_CTX(set_rx(), "gfsk_rx::set_rx");
return unit{};
}