idfxx 1.0.0
Modern C++23 components for ESP-IDF
Loading...
Searching...
No Matches
sx126x.hpp
Go to the documentation of this file.
1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2026 Chris Leishman
3
4#pragma once
5
13#include <idfxx/cpu>
14#include <idfxx/error>
15#include <idfxx/event>
16#include <idfxx/flags>
17#include <idfxx/future>
18#include <idfxx/gpio>
21#include <idfxx/radio/types.hpp>
22#include <idfxx/spi/master>
23
24#include <chrono>
25#include <cstdint>
26#include <electro/decibel>
27#include <electro/electro>
28#include <frequency/frequency>
29#include <memory>
30#include <optional>
31#include <span>
32
33namespace idfxx::radio {
34
46enum class sx126x_irq_flag : uint16_t {
47 tx_done = 1 << 0,
48 rx_done = 1 << 1,
49 preamble_detected = 1 << 2,
50 sync_word_valid = 1 << 3,
51 header_valid = 1 << 4,
52 header_err = 1 << 5,
53 crc_err = 1 << 6,
54 cad_done = 1 << 7,
55 cad_detected = 1 << 8,
56 timeout = 1 << 9,
57};
58
59} // namespace idfxx::radio
60
61template<>
62inline constexpr bool idfxx::enable_flags_operators<idfxx::radio::sx126x_irq_flag> = true;
63
64namespace idfxx::radio {
65
79class sx126x final : public lora_transceiver {
80public:
83
91 enum class chip_variant : uint8_t {
92 sx1261,
93 sx1262,
94 sx1268,
95 };
96
101 enum class regulator : uint8_t {
102 ldo = 0x00,
103 dc_dc = 0x01,
104 };
105
114 enum class standby_clock : uint8_t {
115 rc,
116 xosc,
117 };
118
128 enum class sleep_mode : uint8_t {
129 warm,
130 cold,
131 };
132
141 struct tcxo_config {
142 electro::millivolts voltage{1700};
143 std::chrono::microseconds startup{5'000};
144
148 [[nodiscard]] constexpr bool operator==(const tcxo_config&) const noexcept = default;
149 };
150
158 enum class cad_symbols : uint8_t {
159 sym_1 = 0x00,
160 sym_2 = 0x01,
161 sym_4 = 0x02,
162 sym_8 = 0x03,
163 sym_16 = 0x04,
164 };
165
175 struct cad_params {
177 uint8_t det_peak = 22;
178 uint8_t det_min = 10;
179
183 [[nodiscard]] constexpr bool operator==(const cad_params&) const noexcept = default;
184 };
185
195 struct config {
201 freq::hertz clock_speed{10'000'000};
202 bool dio2_as_rf_switch = true;
205 std::optional<tcxo_config> tcxo = std::nullopt;
207 bool rx_boost = false;
208 std::chrono::milliseconds busy_timeout{100};
214 std::optional<flags<sx126x_irq_flag>> dio1_mask = std::nullopt;
222 bool warm_start = false;
223 event_loop* loop = nullptr;
225 size_t worker_stack_size = 4096;
226 std::optional<core_id> worker_core = std::nullopt;
227 };
228
229 // =========================================================================
230 // Construction / lifetime
231 // =========================================================================
232
233#ifdef CONFIG_COMPILER_CXX_EXCEPTIONS
245 [[nodiscard]] explicit sx126x(spi::master_bus& bus, config config);
246#endif
247
258 [[nodiscard]] static result<sx126x> make(spi::master_bus& bus, config config);
259
260 ~sx126x() override;
261
262 sx126x(const sx126x&) = delete;
263 sx126x& operator=(const sx126x&) = delete;
264 sx126x(sx126x&& other) noexcept;
265 sx126x& operator=(sx126x&& other) noexcept;
266
267 // =========================================================================
268 // Accessors
269 // =========================================================================
270
272 [[nodiscard]] chip_variant variant() const noexcept;
273
278 [[nodiscard]] spi::master_device& spi() noexcept;
279
280 // =========================================================================
281 // SX126x-specific mode control
282 //
283 // The chip-agnostic mode-control methods (standby, sleep, start_listening,
284 // start_channel_scan, ...) are inherited from radio::lora_transceiver. The overloads
285 // here expose SX126x-specific refinements of the same operations.
286 // =========================================================================
287
288#ifdef CONFIG_COMPILER_CXX_EXCEPTIONS
291#endif
294
295#ifdef CONFIG_COMPILER_CXX_EXCEPTIONS
307 void standby(standby_clock clock) { unwrap(try_standby(clock)); }
308
320 void sleep(sleep_mode mode) { unwrap(try_sleep(mode)); }
321#endif
322
329
336
337 // =========================================================================
338 // SX126x-specific surface
339 // =========================================================================
340
341#ifdef CONFIG_COMPILER_CXX_EXCEPTIONS
343#endif
345
346#ifdef CONFIG_COMPILER_CXX_EXCEPTIONS
358 void set_sync_word(uint16_t sync_word) { unwrap(try_set_sync_word(sync_word)); }
359
371
398 [[nodiscard]] std::optional<rx_info> adopt_pending() { return unwrap(try_adopt_pending()); }
399
406 [[nodiscard]] flags<irq_flag> irq_status() { return unwrap(try_irq_status()); }
407
415#endif
416
429
440 [[nodiscard]] result<void> try_set_sync_word(uint16_t sync_word);
441
448
454
461
462 // =========================================================================
463 // Low-level opcode escape hatch
464 // =========================================================================
465
466#ifdef CONFIG_COMPILER_CXX_EXCEPTIONS
474 void write_command(uint8_t opcode, std::span<const uint8_t> params) { unwrap(try_write_command(opcode, params)); }
475
483 void read_command(uint8_t opcode, std::span<uint8_t> response) { unwrap(try_read_command(opcode, response)); }
484
492 void write_register(uint16_t addr, std::span<const uint8_t> data) { unwrap(try_write_register(addr, data)); }
493
501 void read_register(uint16_t addr, std::span<uint8_t> data) { unwrap(try_read_register(addr, data)); }
502
510 void write_buffer(uint8_t offset, std::span<const uint8_t> data) { unwrap(try_write_buffer(offset, data)); }
511
519 void read_buffer(uint8_t offset, std::span<uint8_t> data) { unwrap(try_read_buffer(offset, data)); }
520#endif
521
523 [[nodiscard]] result<void> try_write_command(uint8_t opcode, std::span<const uint8_t> params);
525 [[nodiscard]] result<void> try_read_command(uint8_t opcode, std::span<uint8_t> response);
527 [[nodiscard]] result<void> try_write_register(uint16_t addr, std::span<const uint8_t> data);
529 [[nodiscard]] result<void> try_read_register(uint16_t addr, std::span<uint8_t> data);
531 [[nodiscard]] result<void> try_write_buffer(uint8_t offset, std::span<const uint8_t> data);
533 [[nodiscard]] result<void> try_read_buffer(uint8_t offset, std::span<uint8_t> data);
534
536 struct state;
538
539private:
540 explicit sx126x(std::unique_ptr<state> s) noexcept;
541
542 // radio::lora_transceiver customization hooks.
543 [[nodiscard]] chip_mode do_current_mode() const noexcept override;
544 [[nodiscard]] result<void> do_standby() override;
545 [[nodiscard]] result<void> do_sleep() override;
546 [[nodiscard]] result<void> do_start_listening() override;
547 [[nodiscard]] result<void>
548 do_start_listening(std::chrono::microseconds rx_period, std::chrono::microseconds sleep_period) override;
549 [[nodiscard]] result<idfxx::future<rx_info>> do_start_receive(std::span<uint8_t> buffer) override;
550 [[nodiscard]] result<idfxx::future<cad_info>> do_start_channel_scan() override;
551 [[nodiscard]] result<void> do_set_frequency(freq::hertz hz) override;
552 [[nodiscard]] result<void> do_set_output_power(electro::dbm power, ramp_time ramp) override;
553 [[nodiscard]] result<void> do_set_modulation(lora_modulation mod) override;
554 [[nodiscard]] result<void> do_set_packet_params(lora_packet_params params) override;
555 [[nodiscard]] result<void> do_set_sync_word(lora_network network) override;
556 [[nodiscard]] result<idfxx::future<void>> do_start_transmit(std::span<const uint8_t> data) override;
557 [[nodiscard]] result<rx_info> do_read_received(std::span<uint8_t> buffer) override;
558 [[nodiscard]] result<packet_status> do_last_packet_status() override;
559 [[nodiscard]] result<electro::centi_dbm> do_current_rssi() override;
560 [[nodiscard]] std::chrono::microseconds do_rx_duty_cycle_min_sleep() const noexcept override;
561
562 std::unique_ptr<state> _state;
563};
564
565} // namespace idfxx::radio
Base class for event loops.
Definition event.hpp:300
Type-safe set of flags from a scoped enum.
Definition flags.hpp:88
Async completion token.
Definition future.hpp:62
A GPIO pin.
Definition gpio.hpp:62
static constexpr gpio nc()
Returns a GPIO representing "not connected".
Definition gpio.hpp:255
Abstract base class for LoRa radio transceivers.
result< void > try_sleep()
Puts the radio in its lowest-power sleep mode.
void sleep()
Puts the radio in its lowest-power sleep mode.
void standby()
Puts the radio in standby mode.
result< void > try_standby()
Puts the radio in standby mode.
void set_sync_word(lora_network network)
Selects the LoRa network by setting the sync word.
result< void > try_set_sync_word(lora_network network)
Selects the LoRa network by setting the sync word.
Concrete LoRa radio driver for the Semtech SX126x family.
Definition sx126x.hpp:79
result< void > try_write_buffer(uint8_t offset, std::span< const uint8_t > data)
Result variant of write_buffer.
result< void > try_clear_irq_status(flags< irq_flag > mask)
Clears the specified IRQ bits.
result< std::optional< rx_info > > try_adopt_pending()
Drains an IRQ the chip latched while no driver was running.
flags< irq_flag > irq_status()
Returns the current IRQ-register status.
Definition sx126x.hpp:406
sx126x & operator=(const sx126x &)=delete
spi::master_device & spi() noexcept
Returns the underlying SPI device for advanced/diagnostic use.
sx126x(sx126x &&other) noexcept
result< void > try_set_sync_word(uint16_t sync_word)
Sets a raw 16-bit LoRa sync word.
result< void > try_sleep()
Puts the radio in its lowest-power sleep mode.
sx126x & operator=(sx126x &&other) noexcept
void standby(standby_clock clock)
Puts the radio in standby mode with the selected clock source.
Definition sx126x.hpp:307
sleep_mode
Sleep-mode configuration retention.
Definition sx126x.hpp:128
@ cold
Lowest-power sleep; all configuration is lost.
@ warm
Retain register configuration for fast wake-up.
void read_command(uint8_t opcode, std::span< uint8_t > response)
Sends a command opcode and reads the response bytes.
Definition sx126x.hpp:483
std::optional< rx_info > adopt_pending()
Drains an IRQ the chip latched while no driver was running.
Definition sx126x.hpp:398
result< void > try_set_cad_params(cad_params params)
Sets CAD detection parameters.
chip_variant
SX126x family chip variant.
Definition sx126x.hpp:91
@ sx1262
Sub-GHz, high-power PA, max +22 dBm.
@ sx1261
Sub-GHz, low-power PA, max +15 dBm.
@ sx1268
Sub-GHz, high-power PA tuned for 410–810 MHz, max +22 dBm.
void write_buffer(uint8_t offset, std::span< const uint8_t > data)
Writes the chip's data buffer at the specified offset.
Definition sx126x.hpp:510
void set_sync_word(uint16_t sync_word)
Sets a raw 16-bit LoRa sync word.
Definition sx126x.hpp:358
void sleep(sleep_mode mode)
Puts the radio in sleep mode with the selected retention.
Definition sx126x.hpp:320
result< void > try_standby()
Puts the radio in standby mode.
cad_symbols
Number of symbols a CAD (channel activity detection) operates on.
Definition sx126x.hpp:158
@ sym_1
CAD over 1 symbol (CAD_ON_1_SYMB).
@ sym_8
CAD over 8 symbols (CAD_ON_8_SYMB).
@ sym_4
CAD over 4 symbols (CAD_ON_4_SYMB).
@ sym_2
CAD over 2 symbols (CAD_ON_2_SYMB).
@ sym_16
CAD over 16 symbols (CAD_ON_16_SYMB).
sx126x(spi::master_bus &bus, config config)
Constructs a new SX126x driver on the given SPI bus.
void clear_irq_status(flags< irq_flag > mask)
Clears the specified IRQ bits.
Definition sx126x.hpp:414
result< void > try_write_command(uint8_t opcode, std::span< const uint8_t > params)
Result variant of write_command.
static result< sx126x > make(spi::master_bus &bus, config config)
Creates a new SX126x driver on the given SPI bus.
void set_cad_params(cad_params params)
Sets CAD detection parameters.
Definition sx126x.hpp:370
void read_register(uint16_t addr, std::span< uint8_t > data)
Reads one or more chip registers.
Definition sx126x.hpp:501
result< void > try_read_buffer(uint8_t offset, std::span< uint8_t > data)
Result variant of read_buffer.
regulator
Voltage regulator selection.
Definition sx126x.hpp:101
@ dc_dc
DC-DC + LDO (requires board support).
@ ldo
LDO regulator (universally safe).
sx126x(const sx126x &)=delete
result< flags< irq_flag > > try_irq_status()
Returns the current IRQ-register status.
result< void > try_standby(standby_clock clock)
Puts the radio in standby mode with the selected clock source.
void write_register(uint16_t addr, std::span< const uint8_t > data)
Writes one or more chip registers.
Definition sx126x.hpp:492
result< void > try_read_command(uint8_t opcode, std::span< uint8_t > response)
Result variant of read_command.
result< void > try_read_register(uint16_t addr, std::span< uint8_t > data)
Result variant of read_register.
result< void > try_sleep(sleep_mode mode)
Puts the radio in sleep mode with the selected retention.
standby_clock
Clock source kept running in standby mode.
Definition sx126x.hpp:114
@ xosc
Crystal oscillator (faster TX/RX transitions, higher current).
@ rc
13 MHz RC oscillator (lowest standby current).
chip_variant variant() const noexcept
Returns the configured chip variant.
void read_buffer(uint8_t offset, std::span< uint8_t > data)
Reads the chip's data buffer at the specified offset.
Definition sx126x.hpp:519
result< void > try_write_register(uint16_t addr, std::span< const uint8_t > data)
Result variant of write_register.
void write_command(uint8_t opcode, std::span< const uint8_t > params)
Sends a command opcode with parameter bytes.
Definition sx126x.hpp:474
A SPI master bus.
Definition master.hpp:256
Type-safe wrapper for FreeRTOS task priority values.
Definition cpu.hpp:84
Chip-agnostic radio event base and typed events.
Chip-agnostic LoRa modulation and packet types.
Abstract LoRa transceiver interface.
LoRa radio types and driver classes.
Definition airtime.hpp:26
constexpr event< event_id > tx_done
Transmit-complete event.
Definition events.hpp:47
constexpr event< event_id, cad_info > cad_done
Channel-scan-complete event, carrying cad_info.
Definition events.hpp:64
lora_network
LoRa network selection.
Definition types.hpp:100
constexpr event< event_id, rx_info > rx_done
Receive-complete event, carrying the packet's rx_info.
Definition events.hpp:58
ramp_time
Power-amplifier ramp-time bucket.
Definition types.hpp:120
sx126x_irq_flag
SX126x IRQ-register bitfield.
Definition sx126x.hpp:46
constexpr event< event_id > preamble_detected
Preamble-detected event.
Definition events.hpp:67
chip_mode
Top-level radio operating mode.
Definition types.hpp:32
Definition adc.hpp:48
T unwrap(result< T > result)
Throws a std::system_error if the result is an error.
Definition error.hpp:307
std::expected< T, std::error_code > result
result type wrapping a value or error code.
Definition error.hpp:120
Result of a channel-activity-detection operation.
Definition types.hpp:223
LoRa modulation parameters.
Definition types.hpp:132
LoRa packet framing parameters.
Definition types.hpp:149
Detailed status for the most recent packet.
Definition types.hpp:236
Information about a received packet.
Definition types.hpp:208
SX126x-specific channel-activity-detection parameters.
Definition sx126x.hpp:175
uint8_t det_peak
Detector peak threshold.
Definition sx126x.hpp:177
cad_symbols symbols
Number of symbols CAD listens on.
Definition sx126x.hpp:176
constexpr bool operator==(const cad_params &) const noexcept=default
Compares two CAD parameter sets for equality.
uint8_t det_min
Detector minimum threshold.
Definition sx126x.hpp:178
Configuration for an SX126x driver instance.
Definition sx126x.hpp:195
idfxx::gpio rxen
Optional external RF-switch RX-enable pin; nc() if unused.
Definition sx126x.hpp:203
freq::hertz clock_speed
SPI clock; chip max is 18 MHz.
Definition sx126x.hpp:201
idfxx::gpio dio1
DIO1 IRQ input from chip (required).
Definition sx126x.hpp:199
idfxx::gpio txen
Optional external RF-switch TX-enable pin; nc() if unused.
Definition sx126x.hpp:204
bool rx_boost
Use boosted-RX gain (more sensitive, higher current).
Definition sx126x.hpp:207
size_t worker_stack_size
Worker task stack size in bytes.
Definition sx126x.hpp:225
bool warm_start
Attach to an already configured chip instead of resetting and reconfiguring it.
Definition sx126x.hpp:222
chip_variant variant
Chip family member.
Definition sx126x.hpp:196
std::optional< core_id > worker_core
Worker task core affinity.
Definition sx126x.hpp:226
idfxx::gpio cs
SPI chip-select pin (required).
Definition sx126x.hpp:197
std::optional< tcxo_config > tcxo
Optional TCXO; leave unset for crystal.
Definition sx126x.hpp:205
event_loop * loop
Event loop to post radio events on; nullptr disables posting.
Definition sx126x.hpp:223
idfxx::gpio nreset
Active-low reset to chip; nc() skips the hardware reset.
Definition sx126x.hpp:200
task_priority worker_priority
Worker task priority.
Definition sx126x.hpp:224
std::optional< flags< sx126x_irq_flag > > dio1_mask
IRQ flags latched by the chip and routed to DIO1.
Definition sx126x.hpp:214
idfxx::gpio busy
BUSY input from chip (required).
Definition sx126x.hpp:198
std::chrono::milliseconds busy_timeout
Max wait for BUSY to go low.
Definition sx126x.hpp:208
bool dio2_as_rf_switch
Use DIO2 as the antenna T/R switch.
Definition sx126x.hpp:202
TCXO configuration applied through DIO3.
Definition sx126x.hpp:141
constexpr bool operator==(const tcxo_config &) const noexcept=default
Compares two TCXO configurations for equality.
electro::millivolts voltage
TCXO supply voltage (1600–3300 mV).
Definition sx126x.hpp:142
std::chrono::microseconds startup
TCXO startup time before the chip uses it.
Definition sx126x.hpp:143