Home IO Control
ESPHome add-on for IO-Homecontrol devices
Loading...
Searching...
No Matches
radio_sx1262.h
Go to the documentation of this file.
1#pragma once
2
3/// @file radio_sx1262.h
4/// @brief SX1262 radio driver for IO-Homecontrol.
5/// @ingroup hioc_radio
6///
7/// Implements the RadioDriver interface for the Semtech SX1262 transceiver.
8/// Unlike the SX1276, the SX1262 uses opcode-based SPI commands and requires a
9/// BUSY pin check before every SPI transaction. The capture path preserves the
10/// radio-reported RX metadata so offline analysis can work from trustworthy data.
11/// @todo Validate Heltec V4-family boards on real hardware, especially the assumed
12/// front-end module enable pins and the required TCXO voltage selection.
13
14#include "radio_interface.h"
15#include "esphome/core/hal.h"
16
17namespace esphome {
18namespace home_io_control {
19
20// ============================================================================
21// SX1262 Opcode Constants
22// ============================================================================
23
24static constexpr uint8_t SX1262_SET_STANDBY = 0x80;
25static constexpr uint8_t SX1262_SET_RX = 0x82;
26static constexpr uint8_t SX1262_SET_TX = 0x83;
27static constexpr uint8_t SX1262_SET_RF_FREQUENCY = 0x86;
28static constexpr uint8_t SX1262_SET_RX_TX_FALLBACK_MODE = 0x93;
29static constexpr uint8_t SX1262_WRITE_BUFFER = 0x0E;
30static constexpr uint8_t SX1262_READ_BUFFER = 0x1E;
31static constexpr uint8_t SX1262_SET_DIO_IRQ_PARAMS = 0x08;
32static constexpr uint8_t SX1262_GET_IRQ_STATUS = 0x12;
33static constexpr uint8_t SX1262_GET_PACKET_STATUS = 0x14;
34static constexpr uint8_t SX1262_GET_DEVICE_ERRORS = 0x17;
35static constexpr uint8_t SX1262_CLEAR_IRQ_STATUS = 0x02;
36static constexpr uint8_t SX1262_CLEAR_DEVICE_ERRORS = 0x07;
37static constexpr uint8_t SX1262_SET_PACKET_TYPE = 0x8A;
38static constexpr uint8_t SX1262_SET_MODULATION_PARAMS = 0x8B;
39static constexpr uint8_t SX1262_SET_PACKET_PARAMS = 0x8C;
40static constexpr uint8_t SX1262_SET_BUFFER_BASE_ADDRESS = 0x8F;
41static constexpr uint8_t SX1262_SET_PA_CONFIG = 0x95;
42static constexpr uint8_t SX1262_SET_TX_PARAMS = 0x8E;
43static constexpr uint8_t SX1262_SET_DIO2_AS_RF_SWITCH_CTRL = 0x9D;
44static constexpr uint8_t SX1262_SET_DIO3_AS_TCXO_CTRL = 0x97;
45static constexpr uint8_t SX1262_CALIBRATE = 0x89;
46static constexpr uint8_t SX1262_CALIBRATE_IMAGE = 0x98;
47static constexpr uint8_t SX1262_GET_RX_BUFFER_STATUS = 0x13;
48static constexpr uint8_t SX1262_GET_RSSI_INST = 0x15;
49static constexpr uint8_t SX1262_SET_REGULATOR_MODE = 0x96;
50static constexpr uint8_t SX1262_GET_STATUS = 0xC0;
51
52// SPI register access opcodes
53static constexpr uint8_t SX1262_WRITE_REGISTER = 0x0D;
54static constexpr uint8_t SX1262_READ_REGISTER = 0x1D;
55
56// ============================================================================
57// SX1262 IRQ Bit Masks
58// ============================================================================
59
60static constexpr uint16_t SX1262_IRQ_TX_DONE = 0x0001;
61static constexpr uint16_t SX1262_IRQ_RX_DONE = 0x0002;
62static constexpr uint16_t SX1262_IRQ_SYNC_WORD_VALID = 0x0008;
63static constexpr uint16_t SX1262_IRQ_CRC_ERR = 0x0040;
64// Sync word register base address
65static constexpr uint16_t SX1262_REG_SYNC_WORD = 0x06C0;
66static constexpr uint16_t SX1262_REG_RX_GAIN = 0x08AC;
67static constexpr uint16_t SX1262_REG_TX_CLAMP_CONFIG = 0x08D8;
68
69static constexpr uint8_t SX1262_GFSK_PACKET_TYPE_KNOWN_LENGTH = 0x00;
70static constexpr uint8_t SX1262_GFSK_CRC_OFF = 0x01;
71static constexpr uint8_t SX1262_FALLBACK_STDBY_XOSC = 0x30;
72
73// ============================================================================
74// SX1262 Radio Driver
75// ============================================================================
76
77/// @brief SX1262 implementation of RadioDriver.
78/// @ingroup hioc_radio
79///
80/// Manages the SX1262 via opcode‑based SPI using the SpiAccess interface.
81/// Configures the chip in FSK mode with software CRC‑CCITT to match the
82/// IO‑Homecontrol protocol (the SX1262 lacks the SX1276's IoHomeOn mode).
83/// @todo Confirm whether additional SX1262 board variants need board-specific FEM
84/// defaults beyond the currently documented Heltec V3/V4 assumptions.
85class RadioSX1262 : public RadioDriver {
86 public:
87 RadioSX1262(SpiAccess *spi, InternalGPIOPin *rst_pin, InternalGPIOPin *dio1_pin, InternalGPIOPin *busy_pin,
88 uint8_t tx_power, uint8_t tcxo_voltage, InternalGPIOPin *fem_en_pin = nullptr,
89 InternalGPIOPin *vfem_pin = nullptr, InternalGPIOPin *fem_pa_pin = nullptr)
90 : RadioDriver(rst_pin),
91 spi_(spi),
92 dio1_pin_(dio1_pin),
93 busy_pin_(busy_pin),
94 tx_power_(tx_power),
95 tcxo_voltage_(tcxo_voltage),
96 fem_en_pin_(fem_en_pin),
97 vfem_pin_(vfem_pin),
98 fem_pa_pin_(fem_pa_pin) {}
99
100 /// @copydoc RadioDriver::init
101 bool init() override;
102 /// @copydoc RadioDriver::send_packet
103 bool send_packet(const uint8_t *data, uint8_t len, const RadioTxConfig &tx_config) override;
104 /// @copydoc RadioDriver::wait_for_packet
105 bool wait_for_packet(RadioRxPacket &packet, uint32_t timeout_ms) override;
106 /// @copydoc RadioDriver::check_for_packet
107 bool check_for_packet(RadioRxPacket &packet) override;
108 /// @copydoc RadioDriver::change_frequency
109 void change_frequency(uint32_t freq_hz) override;
110 /// @copydoc RadioDriver::read_rssi
111 int16_t read_rssi() override;
112 /// @copydoc RadioDriver::set_mode_rx
113 void set_mode_rx() override;
114 /// @copydoc RadioDriver::set_mode_standby
115 void set_mode_standby() override;
116 [[nodiscard]] bool is_failed() const override { return this->failed_; }
117 [[nodiscard]] const char *chip_name() const override { return "sx1262"; }
118 /// @brief Dump SX1262‑specific debug info.
119 void dump_debug() override;
120
121 protected:
122 // --- SPI communication (opcode‑based) ---
123 /// Wait until BUSY pin is low before any SPI transaction.
124 void wait_busy_();
125 /// Write an opcode with optional parameter bytes.
126 /// @param opcode SX1262 opcode.
127 /// @param params Pointer to parameter buffer (may be nullptr).
128 /// @param len Parameter length.
129 void write_opcode_(uint8_t opcode, const uint8_t *params, uint8_t len);
130 /// Read response from an opcode.
131 /// @param opcode Opcode that was previously written.
132 /// @param data Output buffer.
133 /// @param len Expected number of bytes to read.
134 void read_opcode_(uint8_t opcode, uint8_t *data, uint8_t len);
135 /// Write to a register (SX1262 uses opcodes for register access).
136 /// @param addr 16‑bit register address.
137 /// @param data Pointer to data bytes.
138 /// @param len Number of bytes.
139 void write_register_(uint16_t addr, const uint8_t *data, uint8_t len);
140 /// Read from a register.
141 /// @param addr 16‑bit register address.
142 /// @param data Output buffer.
143 /// @param len Number of bytes to read.
144 void read_register_(uint16_t addr, uint8_t *data, uint8_t len);
145 /// Write into the SX1262 TX/RX buffer at a given offset.
146 /// @param offset Buffer offset.
147 /// @param data Payload bytes.
148 /// @param len Payload length.
149 void write_buffer_(uint8_t offset, const uint8_t *data, uint8_t len);
150 /// Read from the SX1262 RX buffer.
151 /// @param offset Buffer offset.
152 /// @param data Output buffer.
153 /// @param len Number of bytes to read.
154 void read_buffer_(uint8_t offset, uint8_t *data, uint8_t len);
155
156 // --- Radio configuration ---
157 /// Full radio initialization (called from init()).
158 void configure_radio_();
159 /// Set RF frequency via the frequency register.
160 /// @param freq_hz Frequency in Hz.
161 void set_frequency_register_(uint32_t freq_hz);
162 /// Configure packet parameters (preamble, payload length, CRC).
163 /// @param preamble_len Preamble length in symbols.
164 /// @param payload_len Expected payload length.
165 /// @param packet_type Fixed for GFSK.
166 /// @param crc_type CRC configuration (off or on).
167 void set_packet_params_(uint16_t preamble_len, uint8_t payload_len, uint8_t packet_type, uint8_t crc_type);
168 /// Apply RX‑specific packet parameters (calls set_packet_params_ for RX).
170 /// Clear all IRQ status bits.
171 /// @param irq_mask Bitmask of IRQs to clear.
172 void clear_irq_status_(uint16_t irq_mask);
173 /// @brief Read device error flags (and clear them).
174 /// @return Error bitmask.
175 uint16_t get_device_errors_();
176 /// @brief Clear device error flags.
178 /// Reset RX state machine and buffer. Optionally force standby first.
179 /// @param force_standby If true, switch to standby before reset.
180 void reset_rx_state_(bool force_standby = true);
181 /// Populate the RadioCaptureInfo from SX1262‑specific telemetry.
182 /// @param blocking_wait true if this was a blocking wait.
183 /// @param irq_status Raw IRQ status.
184 /// @param rx_offset Reported RX buffer offset.
185 /// @param reported_len Length reported by the radio.
186 /// @param raw Pointer to raw buffer bytes (may be nullptr).
187 /// @param raw_len Length of raw buffer.
188 /// @param frame Pointer to parsed frame bytes (may be nullptr).
189 /// @param frame_len Length of parsed frame.
190 void fill_capture_info_(bool blocking_wait, uint16_t irq_status, uint8_t rx_offset, uint8_t reported_len,
191 const uint8_t *raw, uint8_t raw_len, const uint8_t *frame, uint8_t frame_len);
192
193 /// Read a received packet from the buffer and return the raw bytes reported by the chip.
194 /// This is virtual to allow test doubles.
195 /// @param packet Output RadioRxPacket.
196 /// @param blocking_wait true if called from a blocking wait path.
197 /// @param irq_status Raw IRQ status word.
198 /// @return true if a valid packet was extracted; false otherwise.
199 virtual bool read_rx_packet(RadioRxPacket &packet, bool blocking_wait, uint16_t irq_status);
200
201 /// Software CRC helper kept for transmit framing parity with the current implementation.
202 /// @return Number of encoded bytes, or 0 if buffer too small.
203 static uint8_t uart_encode_packet(const uint8_t *data, uint8_t len, uint8_t *encoded, uint8_t encoded_max_len);
204
205 /// DIO1 ISR — sets dio_fired flag. Runs in interrupt context.
206 static void gpio_intr(RadioSX1262 *arg);
207
208 /// @brief Read the raw IRQ status from the radio.
209 /// @return 16‑bit IRQ status word.
210 virtual uint16_t read_irq_status_raw();
211
212 private:
213 // === wait_for_packet helpers (private) ===
214 /// Poll until any radio activity (DIO1) or timeout.
215 /// @param start Starting timestamp.
216 /// @param timeout_ms Maximum wait.
217 /// @param saw_dio1 Output: true if DIO1 fired.
218 /// @param irq Output: captured IRQ status.
219 /// @return true if activity detected before timeout; false otherwise.
220 bool poll_until_activity_(uint32_t start, uint32_t timeout_ms, bool &saw_dio1, uint16_t &irq);
221 /// Resolve the race between SYNC word detection and payload‑ready by peeking
222 /// the RX buffer status and potentially restarting RX.
223 /// @param start Starting timestamp.
224 /// @param timeout_ms Maximum wait.
225 /// @param irq Output: final IRQ status when success is determined.
226 /// @return true if a frame boundary is resolved; false on timeout or error.
227 bool resolve_sync_race_(uint32_t start, uint32_t timeout_ms, uint16_t &irq);
228 /// Finalize receive: populate packet from the RX buffer and telemetry.
229 /// @param packet Output RadioRxPacket.
230 /// @param irq IRQ status at completion.
231 /// @return true if packet extraction succeeded; false otherwise.
232 bool finalize_receive_(RadioRxPacket &packet, uint16_t irq);
233
234 SpiAccess *spi_;
235 InternalGPIOPin *dio1_pin_;
236 InternalGPIOPin *busy_pin_;
237 InternalGPIOPin *fem_en_pin_;
238 InternalGPIOPin *vfem_pin_;
239 InternalGPIOPin *fem_pa_pin_;
240 uint8_t tx_power_;
241 uint8_t tcxo_voltage_;
242 bool failed_{false};
243};
244
245} // namespace home_io_control
246} // namespace esphome
RadioDriver(InternalGPIOPin *rst_pin=nullptr)
SX1262 implementation of RadioDriver.
void wait_busy_()
Wait until BUSY pin is low before any SPI transaction.
void set_mode_rx() override
Switch to continuous receive mode.
int16_t read_rssi() override
Read instantaneous RSSI (in dBm) while in RX mode.
void write_opcode_(uint8_t opcode, const uint8_t *params, uint8_t len)
Write an opcode with optional parameter bytes.
void set_mode_standby() override
Switch to standby mode.
virtual bool read_rx_packet(RadioRxPacket &packet, bool blocking_wait, uint16_t irq_status)
Read a received packet from the buffer and return the raw bytes reported by the chip.
RadioSX1262(SpiAccess *spi, InternalGPIOPin *rst_pin, InternalGPIOPin *dio1_pin, InternalGPIOPin *busy_pin, uint8_t tx_power, uint8_t tcxo_voltage, InternalGPIOPin *fem_en_pin=nullptr, InternalGPIOPin *vfem_pin=nullptr, InternalGPIOPin *fem_pa_pin=nullptr)
void change_frequency(uint32_t freq_hz) override
Change the carrier frequency using fast hop (no standby transition needed).
void write_buffer_(uint8_t offset, const uint8_t *data, uint8_t len)
Write into the SX1262 TX/RX buffer at a given offset.
const char * chip_name() const override
Get a human‑readable chip name.
void reset_rx_state_(bool force_standby=true)
Reset RX state machine and buffer.
void fill_capture_info_(bool blocking_wait, uint16_t irq_status, uint8_t rx_offset, uint8_t reported_len, const uint8_t *raw, uint8_t raw_len, const uint8_t *frame, uint8_t frame_len)
Populate the RadioCaptureInfo from SX1262‑specific telemetry.
void set_frequency_register_(uint32_t freq_hz)
Set RF frequency via the frequency register.
void clear_device_errors_()
Clear device error flags.
void write_register_(uint16_t addr, const uint8_t *data, uint8_t len)
Write to a register (SX1262 uses opcodes for register access).
void set_packet_params_(uint16_t preamble_len, uint8_t payload_len, uint8_t packet_type, uint8_t crc_type)
Configure packet parameters (preamble, payload length, CRC).
void dump_debug() override
Dump SX1262‑specific debug info.
void configure_radio_()
Full radio initialization (called from init()).
virtual uint16_t read_irq_status_raw()
Read the raw IRQ status from the radio.
static void gpio_intr(RadioSX1262 *arg)
DIO1 ISR — sets dio_fired flag. Runs in interrupt context.
bool check_for_packet(RadioRxPacket &packet) override
Non-blocking check for a received packet.
static uint8_t uart_encode_packet(const uint8_t *data, uint8_t len, uint8_t *encoded, uint8_t encoded_max_len)
Software CRC helper kept for transmit framing parity with the current implementation.
void set_rx_packet_params_()
Apply RX‑specific packet parameters (calls set_packet_params_ for RX).
void read_opcode_(uint8_t opcode, uint8_t *data, uint8_t len)
Read response from an opcode.
bool send_packet(const uint8_t *data, uint8_t len, const RadioTxConfig &tx_config) override
Send a packet using the specified carrier frequency and preamble settings.
bool init() override
Initialize the radio hardware. Returns true on success.
void clear_irq_status_(uint16_t irq_mask)
Clear all IRQ status bits.
void read_buffer_(uint8_t offset, uint8_t *data, uint8_t len)
Read from the SX1262 RX buffer.
void read_register_(uint16_t addr, uint8_t *data, uint8_t len)
Read from a register.
bool is_failed() const override
Returns true if the radio failed to initialize or encountered a fatal error.
uint16_t get_device_errors_()
Read device error flags (and clear them).
bool wait_for_packet(RadioRxPacket &packet, uint32_t timeout_ms) override
Wait (blocking) for a packet with timeout.
Interface for SPI bus access.
static constexpr uint8_t SX1262_SET_DIO3_AS_TCXO_CTRL
static constexpr uint8_t SX1262_CALIBRATE
static constexpr uint16_t SX1262_REG_RX_GAIN
static constexpr uint8_t SX1262_SET_BUFFER_BASE_ADDRESS
static constexpr uint8_t SX1262_CLEAR_DEVICE_ERRORS
static constexpr uint8_t SX1262_GFSK_CRC_OFF
static constexpr uint8_t SX1262_READ_REGISTER
static constexpr uint8_t SX1262_SET_PACKET_TYPE
static constexpr uint16_t SX1262_IRQ_SYNC_WORD_VALID
static constexpr uint8_t SX1262_GET_DEVICE_ERRORS
static constexpr uint16_t SX1262_IRQ_CRC_ERR
static constexpr uint8_t SX1262_SET_MODULATION_PARAMS
static constexpr uint8_t SX1262_GET_RX_BUFFER_STATUS
static constexpr uint8_t SX1262_CLEAR_IRQ_STATUS
static constexpr uint8_t SX1262_SET_RX
static constexpr uint8_t SX1262_SET_TX
static constexpr uint8_t SX1262_GFSK_PACKET_TYPE_KNOWN_LENGTH
static constexpr uint8_t SX1262_CALIBRATE_IMAGE
static constexpr uint8_t SX1262_SET_DIO2_AS_RF_SWITCH_CTRL
static constexpr uint8_t SX1262_WRITE_BUFFER
static constexpr uint8_t SX1262_READ_BUFFER
static constexpr uint16_t SX1262_IRQ_RX_DONE
static constexpr uint8_t SX1262_SET_PA_CONFIG
static constexpr uint8_t SX1262_SET_TX_PARAMS
static constexpr uint16_t SX1262_IRQ_TX_DONE
static constexpr uint8_t SX1262_SET_DIO_IRQ_PARAMS
static constexpr uint8_t SX1262_SET_PACKET_PARAMS
static constexpr uint8_t SX1262_GET_STATUS
static constexpr uint16_t SX1262_REG_TX_CLAMP_CONFIG
static constexpr uint8_t SX1262_SET_RX_TX_FALLBACK_MODE
static constexpr uint8_t SX1262_GET_IRQ_STATUS
static constexpr uint8_t SX1262_SET_REGULATOR_MODE
static constexpr uint8_t SX1262_GET_PACKET_STATUS
static constexpr uint16_t SX1262_REG_SYNC_WORD
static constexpr uint8_t SX1262_FALLBACK_STDBY_XOSC
static constexpr uint8_t SX1262_SET_RF_FREQUENCY
static constexpr uint8_t SX1262_SET_STANDBY
static constexpr uint8_t SX1262_WRITE_REGISTER
static constexpr uint8_t SX1262_GET_RSSI_INST
Radio abstraction layer for IO-Homecontrol.
Raw packet received from the radio.
Configuration for transmitting a packet: carrier frequency and preamble length.