Home IO Control
ESPHome add-on for IO-Homecontrol devices
Loading...
Searching...
No Matches
radio_sx1276.h
Go to the documentation of this file.
1#pragma once
2
3/// @file radio_sx1276.h
4/// @brief SX1276 radio driver for IO-Homecontrol.
5/// @ingroup hioc_radio
6///
7/// Implements the RadioDriver interface for the Semtech SX1276 transceiver.
8/// Configures the chip in FSK mode with the IoHomeOn hardware feature that
9/// handles CRC and packet framing specific to the IO-Homecontrol protocol.
10
11#include "radio_interface.h"
12#include "radio_soft_phy.h" // io868_tx_air_time_us()
13#include "esphome/core/hal.h"
14
15namespace esphome {
16namespace home_io_control {
17
18/// `pa_pin` value that routes the output through the PA_BOOST pin; any other value selects the RFO
19/// pin. Doubles as the PaSelect bit of REG_PA_CONFIG.
20static constexpr uint8_t SX1276_PA_SELECT_PA_BOOST = 0x80;
21
22/// Wait after retuning to the TX channel before the clear-channel RSSI reading. The fast-hop PLL
23/// relocks in tens of µs, and with RssiSmoothing = 8 samples at the 50 kHz RX bandwidth one RSSI
24/// update takes about 40 µs; 250 µs covers both with margin and costs nothing next to a preamble.
25/// \todo Confirm on the bench (Heltec V2): time the retune and compare the reading against one
26/// taken after a long dwell on the same channel.
27static constexpr uint32_t SX1276_CCA_SETTLE_US = 250;
28
29// ============================================================================
30// SX1276 Register Addresses (subset needed for IO-Homecontrol)
31// Full register map: see Semtech SX1276 datasheet or sx1276Regs-Fsk.h
32// ============================================================================
33
34static constexpr uint8_t REG_FIFO = 0x00; ///< FIFO read/write access
35static constexpr uint8_t REG_OP_MODE = 0x01; ///< Operating mode (sleep/standby/tx/rx)
36static constexpr uint8_t REG_BITRATE_MSB = 0x02; ///< Bit rate MSB = FXOSC / bitrate
37static constexpr uint8_t REG_BITRATE_LSB = 0x03;
38static constexpr uint8_t REG_FDEV_MSB = 0x04; ///< Frequency deviation MSB
39static constexpr uint8_t REG_FDEV_LSB = 0x05;
40static constexpr uint8_t REG_FRF_MSB = 0x06; ///< Carrier frequency MSB (freq = FRF * FXOSC / 2^19)
41static constexpr uint8_t REG_FRF_MID = 0x07;
42static constexpr uint8_t REG_FRF_LSB = 0x08;
43static constexpr uint8_t REG_PA_CONFIG = 0x09; ///< Power amplifier config (pin select + power level)
44static constexpr uint8_t REG_PA_RAMP = 0x0A; ///< PA ramp time and modulation shaping
45static constexpr uint8_t REG_LNA = 0x0C; ///< Low noise amplifier gain and boost
46static constexpr uint8_t REG_RX_CONFIG = 0x0D; ///< Receiver configuration (AFC, AGC, trigger)
47static constexpr uint8_t REG_RSSI_CONFIG = 0x0E; ///< RSSI smoothing
48static constexpr uint8_t REG_RX_BW = 0x12; ///< Receiver bandwidth
49static constexpr uint8_t REG_AFC_BW = 0x13; ///< AFC bandwidth
50static constexpr uint8_t REG_AFC_FEI = 0x1A; ///< AFC auto clear
51static constexpr uint8_t REG_PREAMBLE_DETECT = 0x1F; ///< Preamble detector config
52static constexpr uint8_t REG_OSC = 0x24; ///< Oscillator / clock output
53static constexpr uint8_t REG_RSSI_VALUE = 0x11; ///< Instant RSSI value in FSK mode
54static constexpr uint8_t REG_PREAMBLE_MSB = 0x25; ///< TX preamble length MSB
55static constexpr uint8_t REG_PREAMBLE_LSB = 0x26;
56static constexpr uint8_t REG_SYNC_CONFIG = 0x27; ///< Sync word config (size, polarity, enable)
57static constexpr uint8_t REG_SYNC_VALUE1 = 0x28; ///< Sync word byte 1 (registers 0x28-0x2F for bytes 1-8)
58static constexpr uint8_t REG_PACKET_CONFIG1 = 0x30; ///< Packet format, CRC, encoding
59static constexpr uint8_t REG_PACKET_CONFIG2 = 0x31; ///< Packet mode, IoHomeOn, PowerFrame
60static constexpr uint8_t REG_PAYLOAD_LENGTH = 0x32; ///< Max payload length
61static constexpr uint8_t REG_FIFO_THRESH = 0x35; ///< FIFO threshold for TX start condition
62static constexpr uint8_t REG_IRQ_FLAGS1 = 0x3E; ///< IRQ flags: mode ready, preamble detect, etc.
63static constexpr uint8_t REG_IRQ_FLAGS2 = 0x3F; ///< IRQ flags: FIFO full/empty, payload ready, CRC ok
64static constexpr uint8_t REG_DIO_MAPPING1 = 0x40; ///< DIO0-DIO3 pin mapping
65static constexpr uint8_t REG_DIO_MAPPING2 = 0x41; ///< DIO4-DIO5 pin mapping
66static constexpr uint8_t REG_VERSION = 0x42; ///< Chip version (should read 0x12 for SX1276)
67static constexpr uint8_t REG_PLLHOP = 0x44; ///< PLL hop: fast frequency change without standby
68static constexpr uint8_t REG_IMAGE_CAL = 0x3B; ///< Image calibration
69
70// SX1276 operating modes (bits [2:0] of REG_OP_MODE)
71static constexpr uint8_t MODE_SLEEP = 0x00;
72static constexpr uint8_t MODE_STDBY = 0x01;
73static constexpr uint8_t MODE_TX = 0x03;
74static constexpr uint8_t MODE_RX = 0x05;
75static constexpr uint8_t MODE_MASK = 0x07;
76
77/// SX1276 crystal oscillator frequency (32 MHz). Used to calculate register values
78/// for bitrate, frequency deviation, and carrier frequency.
79static constexpr uint32_t FXOSC = 32000000U;
80
81// ============================================================================
82// SX1276 Radio Driver
83// ============================================================================
84
85/// @brief SX1276 implementation of RadioDriver.
86/// @ingroup hioc_radio
87///
88/// Manages the SX1276 via SPI using the SpiAccess interface. Configures the chip
89/// in FSK mode with IoHomeOn for hardware CRC and IO‑Homecontrol packet framing.
90class RadioSX1276 : public RadioDriver {
91 public:
92 RadioSX1276(SpiAccess *spi, InternalGPIOPin *rst_pin, InternalGPIOPin *dio0_pin, InternalGPIOPin *dio4_pin,
93 uint8_t tx_power, uint8_t pa_pin)
94 : RadioDriver(rst_pin),
95 spi_(spi),
96 dio0_pin_(dio0_pin),
97 dio4_pin_(dio4_pin),
98 tx_power_(tx_power),
99 pa_pin_(pa_pin) {}
100
101 /// @brief Initialize the SX1276 radio (reset, calibrate, configure registers).
102 /// @return true on success; false on failure (e.g., version check).
103 bool init() override;
104 /// @brief Transmit a frame with specified frequency and preamble.
105 /// @param data Pointer to payload bytes.
106 /// @param len Payload length.
107 /// @param tx_config Transmission config (frequency, preamble).
108 /// @return true if transmit succeeded.
109 TxResult send_packet(const uint8_t *data, uint8_t len, const RadioTxConfig &tx_config) override;
110 /// @brief Blocking wait for a packet with timeout.
111 /// @param packet Output: received packet (freq, len, data).
112 /// @param timeout_ms Maximum time to wait.
113 /// @return true if a packet was received; false on timeout.
114 bool wait_for_packet(RadioRxPacket &packet, uint32_t timeout_ms) override;
115 /// @brief Non‑blocking check for a received packet (called from loop).
116 /// @param packet Output: received packet if any.
117 /// @return true if a packet was read; false if no interrupt fired.
118 bool check_for_packet(RadioRxPacket &packet) override;
119 /// @brief Change RF frequency using fast hop (no standby needed).
120 /// @param freq_hz New frequency in Hz.
121 void change_frequency(uint32_t freq_hz) override;
122 /// @brief Read instantaneous RSSI (dBm) while in RX mode (used for LBT).
123 /// @return RSSI in dBm (negative).
124 int16_t read_rssi() override;
125 /// @copydoc RadioDriver::is_sync_detected
126 bool is_sync_detected() override;
127 /// @copydoc RadioDriver::is_preamble_detected
128 bool is_preamble_detected() override;
129 /// @copydoc RadioDriver::reception_in_progress
130 ///
131 /// Read live from REG_IRQ_FLAGS1 rather than recorded from check_for_packet(): DIO0 carries
132 /// PayloadReady only, so this driver's check_for_packet() returns before reading any register
133 /// while a frame is still arriving and has nothing to record (issue #81).
134 [[nodiscard]] bool reception_in_progress() override;
135 /// @brief Per-channel dwell for a rotating listen (SX1276).
136 ///
137 /// The SX1276 supports FastHop (PllHop), so frequency changes need no standby transition and a
138 /// short slice is enough. Governs discovery and the broadcast roll-call alike (see @ref
139 /// RadioDriver::hop_dwell_ms). The value comes from the user-facing
140 /// `sx1276_discovery_hop_slice_ms` tuning field.
141 [[nodiscard]] uint16_t hop_dwell_ms(const TuningConfig &tuning) const override {
142 return tuning.sx1276_discovery_hop_slice_ms;
143 }
144 /// @brief TX→RX turnaround capability (SX1276): fast.
145 ///
146 /// The IoHomeOn TX path returns to RX quickly enough that immediate replies
147 /// (including the pairing key-confirm 0x33) are caught through the standard
148 /// exchange wait — validated by real-hardware pairing on this driver.
149 [[nodiscard]] bool has_fast_tx_rx_turnaround() const override { return true; }
150 /// @brief Transmit time (SX1276): the preamble, sync word and the IoHomeOn-coded frame at the
151 /// protocol line rate — the same waveform the software PHY produces.
152 [[nodiscard]] uint32_t tx_air_time_us(uint8_t len, const RadioTxConfig &cfg) const override {
153 return io868_tx_air_time_us(cfg.preamble_len, len);
154 }
155 // `SX1276_RESPONSE_PREAMBLE` below is a code span, not \ref: doxygen 1.18 can't resolve \ref
156 // to it in a whole-project build (details in proto_sizes.h). Autolinking still links it.
157 /// @brief Preamble for response/continuation frames (SX1276).
158 ///
159 /// Runtime-tunable; the default and its hardware rationale are documented at
160 /// `SX1276_RESPONSE_PREAMBLE`.
161 [[nodiscard]] uint16_t response_preamble() const override { return this->response_preamble_; }
162 /// @brief Apply SX1276 runtime tuning: RX bandwidth and response preamble.
163 void apply_tuning(const TuningConfig &tuning) override {
166 }
167 /// @brief Switch radio into continuous receive mode.
168 void set_mode_rx() override;
169 /// @brief Switch radio into standby mode.
170 void set_mode_standby() override;
171 /// @copydoc RadioDriver::chip_name
172 [[nodiscard]] const char *chip_name() const override { return "sx1276"; }
173 /// @brief Dump radio‑specific debug info to log.
174 void dump_debug() override;
175
176 protected:
177 // --- Tuning helpers (applied via apply_tuning) ---
178 /// Apply the RX bandwidth selector to REG_RX_BW and REG_AFC_BW.
179 /// @param bandwidth Bandwidth selector enum (register byte written verbatim).
180 void set_rx_bandwidth_(SX1276RxBandwidth bandwidth);
181 /// Set the preamble length used for response/continuation frames within an exchange.
182 /// @param preamble Preamble length in bytes.
183 void set_response_preamble_(uint16_t preamble) { this->response_preamble_ = preamble; }
184
185 // --- SPI register access ---
186 /// Read an SX1276 register over SPI.
187 /// @param reg Register address (7 bits, MSB clear for read).
188 /// @return Register value.
189 uint8_t read_register_(uint8_t reg);
190 /// Write an SX1276 register over SPI.
191 /// @param reg Register address (7 bits, MSB set for write).
192 /// @param value Byte to write.
193 void write_register_(uint8_t reg, uint8_t value);
194
195 // --- Radio hardware control ---
196 /// Set the operating mode (sleep/standby/tx/rx) and wait for mode completion.
197 /// @param mode One of MODE_SLEEP, MODE_STDBY, MODE_TX, or MODE_RX.
198 void set_mode_(uint8_t mode);
199 /// Perform full radio configuration (called during init).
200 void configure_radio_();
201 /// Run image calibration routine (required after reset).
202 void run_image_cal_();
203 /// Wait until FIFO payload is ready (polling for TX/RX readiness).
204 /// @param timeout_ms How long to wait.
205 /// @param saw_dio0 Output: true if DIO0 fired.
206 /// @param irq1 Output: IRQ flags 1.
207 /// @param irq2 Output: IRQ flags 2.
208 /// @return true if payload ready before timeout; false otherwise.
209 bool poll_until_payload_ready_(uint32_t timeout_ms, bool &saw_dio0, uint8_t &irq1, uint8_t &irq2);
210 /// Read a packet from the RX FIFO into a buffer.
211 /// @param buf Output buffer.
212 /// @param buf_size Size of buf.
213 /// @return Number of bytes read.
214 uint8_t read_fifo_packet_(uint8_t *buf, uint8_t buf_size);
215 /// Populate last_capture_ from raw telemetry.
216 /// Note: `crc_error` is never set on this driver — in IoHomeOn mode the hardware
217 /// silently drops frames with a bad CRC before they reach the FIFO.
218 /// @param blocking_wait if this was a blocking receive.
219 /// @param irq1 IRQ flags 1.
220 /// @param irq2 IRQ flags 2.
221 /// @param rssi RSSI value.
222 /// @param raw Pointer to raw bytes (may be nullptr).
223 /// @param raw_len Length of raw buffer.
224 /// @param frame Pointer to parsed frame bytes (may be nullptr).
225 /// @param frame_len Length of parsed frame.
226 void fill_capture_info_(bool blocking_wait, uint8_t irq1, uint8_t irq2, uint8_t rssi, const uint8_t *raw,
227 uint8_t raw_len, const uint8_t *frame, uint8_t frame_len);
228
229 /// DIO0 ISR — sets dio_fired flag. Runs in interrupt context.
230 static void gpio_intr(RadioSX1276 *arg);
231
232 /// True when TX goes out through PA_BOOST rather than RFO; the PA config and the dump both key on it.
233 [[nodiscard]] bool uses_pa_boost_() const { return this->pa_pin_ == SX1276_PA_SELECT_PA_BOOST; }
234
236 InternalGPIOPin *dio0_pin_;
237 InternalGPIOPin *dio4_pin_;
238 uint8_t tx_power_;
239 uint8_t pa_pin_;
241 uint16_t response_preamble_{SX1276_RESPONSE_PREAMBLE}; ///< Runtime-tunable response preamble.
242 uint32_t sync_hold_since_us_{0}; ///< First-sighting timestamp for reception_in_progress()'s un-stick bound.
243};
244
245} // namespace home_io_control
246} // namespace esphome
RadioDriver(InternalGPIOPin *rst_pin=nullptr)
SX1276 implementation of RadioDriver.
void configure_radio_()
Perform full radio configuration (called during init).
uint32_t sync_hold_since_us_
First-sighting timestamp for reception_in_progress()'s un-stick bound.
int16_t read_rssi() override
Read instantaneous RSSI (dBm) while in RX mode (used for LBT).
bool wait_for_packet(RadioRxPacket &packet, uint32_t timeout_ms) override
Blocking wait for a packet with timeout.
bool has_fast_tx_rx_turnaround() const override
TX→RX turnaround capability (SX1276): fast.
void set_response_preamble_(uint16_t preamble)
Set the preamble length used for response/continuation frames within an exchange.
uint8_t read_register_(uint8_t reg)
Read an SX1276 register over SPI.
uint16_t hop_dwell_ms(const TuningConfig &tuning) const override
Per-channel dwell for a rotating listen (SX1276).
SX1276RxBandwidth rx_bandwidth_
Runtime-tunable RX bandwidth.
void fill_capture_info_(bool blocking_wait, uint8_t irq1, uint8_t irq2, uint8_t rssi, const uint8_t *raw, uint8_t raw_len, const uint8_t *frame, uint8_t frame_len)
Populate last_capture_ from raw telemetry.
uint16_t response_preamble_
Runtime-tunable response preamble.
void apply_tuning(const TuningConfig &tuning) override
Apply SX1276 runtime tuning: RX bandwidth and response preamble.
void set_rx_bandwidth_(SX1276RxBandwidth bandwidth)
Apply the RX bandwidth selector to REG_RX_BW and REG_AFC_BW.
void set_mode_(uint8_t mode)
Set the operating mode (sleep/standby/tx/rx) and wait for mode completion.
RadioSX1276(SpiAccess *spi, InternalGPIOPin *rst_pin, InternalGPIOPin *dio0_pin, InternalGPIOPin *dio4_pin, uint8_t tx_power, uint8_t pa_pin)
void set_mode_standby() override
Switch radio into standby mode.
static void gpio_intr(RadioSX1276 *arg)
DIO0 ISR — sets dio_fired flag. Runs in interrupt context.
bool poll_until_payload_ready_(uint32_t timeout_ms, bool &saw_dio0, uint8_t &irq1, uint8_t &irq2)
Wait until FIFO payload is ready (polling for TX/RX readiness).
void dump_debug() override
Dump radio‑specific debug info to log.
void change_frequency(uint32_t freq_hz) override
Change RF frequency using fast hop (no standby needed).
bool init() override
Initialize the SX1276 radio (reset, calibrate, configure registers).
bool reception_in_progress() override
True while a frame is arriving on the current channel and retuning would destroy it.
void write_register_(uint8_t reg, uint8_t value)
Write an SX1276 register over SPI.
const char * chip_name() const override
Get a human‑readable chip name.
bool uses_pa_boost_() const
True when TX goes out through PA_BOOST rather than RFO; the PA config and the dump both key on it.
void set_mode_rx() override
Switch radio into continuous receive mode.
bool is_sync_detected() override
Check if sync word has been detected (while in RX).
uint16_t response_preamble() const override
Preamble for response/continuation frames (SX1276).
uint8_t read_fifo_packet_(uint8_t *buf, uint8_t buf_size)
Read a packet from the RX FIFO into a buffer.
uint32_t tx_air_time_us(uint8_t len, const RadioTxConfig &cfg) const override
Transmit time (SX1276): the preamble, sync word and the IoHomeOn-coded frame at the protocol line rat...
bool check_for_packet(RadioRxPacket &packet) override
Non‑blocking check for a received packet (called from loop).
TxResult send_packet(const uint8_t *data, uint8_t len, const RadioTxConfig &tx_config) override
Transmit a frame with specified frequency and preamble.
void run_image_cal_()
Run image calibration routine (required after reset).
bool is_preamble_detected() override
Check if preamble has been detected (while in RX).
Interface for SPI bus access.
static constexpr uint8_t REG_RX_CONFIG
Receiver configuration (AFC, AGC, trigger).
static constexpr uint8_t REG_IRQ_FLAGS1
IRQ flags: mode ready, preamble detect, etc.
static constexpr uint8_t REG_FRF_MSB
Carrier frequency MSB (freq = FRF * FXOSC / 2^19).
static constexpr uint8_t REG_PACKET_CONFIG2
Packet mode, IoHomeOn, PowerFrame.
static constexpr uint8_t REG_PA_CONFIG
Power amplifier config (pin select + power level).
static constexpr uint8_t REG_IRQ_FLAGS2
IRQ flags: FIFO full/empty, payload ready, CRC ok.
static constexpr uint8_t MODE_RX
static constexpr uint8_t REG_DIO_MAPPING1
DIO0-DIO3 pin mapping.
static constexpr uint8_t REG_FDEV_LSB
static constexpr uint8_t REG_DIO_MAPPING2
DIO4-DIO5 pin mapping.
static constexpr uint8_t REG_FIFO_THRESH
FIFO threshold for TX start condition.
static constexpr uint8_t REG_FIFO
FIFO read/write access.
static constexpr uint8_t REG_OP_MODE
Operating mode (sleep/standby/tx/rx).
static constexpr uint8_t REG_FDEV_MSB
Frequency deviation MSB.
static constexpr uint8_t MODE_SLEEP
static constexpr uint8_t MODE_TX
static constexpr uint8_t REG_PA_RAMP
PA ramp time and modulation shaping.
static constexpr uint8_t MODE_STDBY
static constexpr uint8_t REG_PAYLOAD_LENGTH
Max payload length.
static constexpr uint8_t REG_AFC_BW
AFC bandwidth.
static constexpr uint8_t REG_RX_BW
Receiver bandwidth.
static constexpr uint8_t REG_PACKET_CONFIG1
Packet format, CRC, encoding.
static constexpr uint8_t REG_BITRATE_MSB
Bit rate MSB = FXOSC / bitrate.
static constexpr uint8_t REG_SYNC_CONFIG
Sync word config (size, polarity, enable).
static constexpr uint8_t REG_AFC_FEI
AFC auto clear.
static constexpr uint8_t REG_PREAMBLE_LSB
static constexpr uint8_t REG_PLLHOP
PLL hop: fast frequency change without standby.
uint32_t io868_tx_air_time_us(uint16_t preamble_len, uint8_t frame_len)
Time on air of one 868 MHz transmission: preamble, sync word, and the frame plus its CRC,...
static constexpr uint8_t REG_LNA
Low noise amplifier gain and boost.
static constexpr uint8_t MODE_MASK
static constexpr uint32_t FXOSC
SX1276 crystal oscillator frequency (32 MHz).
static constexpr uint8_t REG_IMAGE_CAL
Image calibration.
static constexpr uint8_t SX1276_PA_SELECT_PA_BOOST
pa_pin value that routes the output through the PA_BOOST pin; any other value selects the RFO pin.
static constexpr uint8_t REG_RSSI_CONFIG
RSSI smoothing.
static constexpr uint8_t REG_OSC
Oscillator / clock output.
static constexpr uint8_t REG_VERSION
Chip version (should read 0x12 for SX1276).
SX1276RxBandwidth
Valid SX1276 RX bandwidth options (RegRxBw register bytes).
@ BW_41_7_KHZ
41.7 kHz — default (validated against real devices).
static constexpr uint8_t REG_PREAMBLE_DETECT
Preamble detector config.
static constexpr uint8_t REG_SYNC_VALUE1
Sync word byte 1 (registers 0x28-0x2F for bytes 1-8).
static constexpr uint8_t REG_RSSI_VALUE
Instant RSSI value in FSK mode.
static constexpr uint8_t REG_FRF_MID
static constexpr uint8_t REG_PREAMBLE_MSB
TX preamble length MSB.
static constexpr uint32_t SX1276_CCA_SETTLE_US
Wait after retuning to the TX channel before the clear-channel RSSI reading.
static constexpr uint8_t REG_BITRATE_LSB
static constexpr uint8_t REG_FRF_LSB
Radio abstraction layer for IO-Homecontrol.
Software PHY for radios without IoHomeOn hardware framing.
Raw packet received from the radio.
Configuration for transmitting a packet: carrier frequency, preamble length, wake-up level and clear-...
uint16_t preamble_len
Preamble length in symbol periods (bytes).
All runtime tunable parameters for pairing and radio diagnostics.
SX1276RxBandwidth sx1276_rx_bandwidth
SX1276 RX bandwidth selector.
uint16_t sx1276_response_preamble
SX1276 response preamble in bytes.
uint16_t sx1276_discovery_hop_slice_ms
Per-channel dwell while SX1276 discovery hops.
What send_packet() did with a frame.