19#include "esphome/core/log.h"
20#include "esphome/core/application.h"
27static const char *
const TAG =
"home_io_control.lr1121";
45 uint32_t
const start = millis();
46 while (this->busy_pin_->digital_read()) {
48 ESP_LOGE(
TAG,
"BUSY timeout");
61 this->spi_->spi_enable();
62 this->spi_->spi_write((opcode >> 8) & 0xFF);
63 this->spi_->spi_write(opcode & 0xFF);
64 for (uint8_t i = 0; i < len; i++)
65 this->spi_->spi_write(params[i]);
66 this->spi_->spi_disable();
77 this->spi_->spi_enable();
78 this->last_stat1_ = this->spi_->spi_read();
79 for (uint8_t i = 0; i < out_len; i++)
80 out[i] = this->spi_->spi_read();
81 this->spi_->spi_disable();
90 uint8_t
const cmd_status = (this->last_stat1_ >> 1) & 0x07;
91 if (cmd_status == 0x00 || cmd_status == 0x01) {
92 ESP_LOGW(
TAG,
"cmd 0x%04X rejected: stat1=0x%02X (%s)", opcode, this->last_stat1_,
93 cmd_status == 0x00 ?
"CMD_FAIL" :
"CMD_PERR");
95#ifdef IOHOME_FRAME_LOG
96 ESP_LOGV(
TAG,
"cmd 0x%04X stat1=0x%02X", opcode, this->last_stat1_);
101 uint8_t params[12] = {
102 (uint8_t) (addr >> 24), (uint8_t) (addr >> 16), (uint8_t) (addr >> 8), (uint8_t) addr,
103 (uint8_t) (mask >> 24), (uint8_t) (mask >> 16), (uint8_t) (mask >> 8), (uint8_t) mask,
104 (uint8_t) (value >> 24), (uint8_t) (value >> 16), (uint8_t) (value >> 8), (uint8_t) value,
133 uint8_t params[2] = {offset, len};
143 uint8_t resp[5] = {0};
145 return (
static_cast<uint32_t
>(resp[1]) << 24) | (
static_cast<uint32_t
>(resp[2]) << 16) |
146 (
static_cast<uint32_t
>(resp[3]) << 8) |
static_cast<uint32_t
>(resp[4]);
159 const uint16_t preamble_bits = preamble_len * 8;
160 uint8_t params[9] = {
161 (uint8_t) (preamble_bits >> 8),
162 (uint8_t) preamble_bits,
188 uint32_t
const bitrate_bps = 38400;
189 uint32_t
const fdev_hz = 19200;
190 uint8_t mod_params[10] = {
191 (uint8_t) (bitrate_bps >> 24),
192 (uint8_t) (bitrate_bps >> 16),
193 (uint8_t) (bitrate_bps >> 8),
194 (uint8_t) bitrate_bps,
196 static_cast<uint8_t
>(this->rx_bandwidth_),
197 (uint8_t) (fdev_hz >> 24),
198 (uint8_t) (fdev_hz >> 16),
199 (uint8_t) (fdev_hz >> 8),
210 uint8_t errors_raw[2] = {0};
212 return (
static_cast<uint16_t
>(errors_raw[0]) << 8) | errors_raw[1];
218 uint8_t clear_irq[4] = {
219 (uint8_t) ((irq_mask >> 24) & 0xFF),
220 (uint8_t) ((irq_mask >> 16) & 0xFF),
221 (uint8_t) ((irq_mask >> 8) & 0xFF),
222 (uint8_t) (irq_mask & 0xFF),
228 const uint8_t *raw, uint8_t raw_len,
const uint8_t *frame, uint8_t frame_len) {
236 uint8_t pkt_status[4] = {0};
245 this->
last_capture_.irq_status =
static_cast<uint16_t
>((irq_status >> 2) & 0x01FF);
259 uint8_t rx_status[2] = {0};
261 reported_len = rx_status[0];
262 rx_offset = rx_status[1];
269 uint8_t tx_timeout[3] = {0x03, 0xE8, 0x00};
282 this->irq_pin_->setup();
283 this->busy_pin_->setup();
295 ESP_LOGI(
TAG,
"LR1121 initialized");
300 uint8_t version[4] = {0};
305 ESP_LOGCONFIG(
TAG,
" LR1121 Diagnostic:");
307 ESP_LOGCONFIG(
TAG,
" HW version: 0x%02X, FW version: %u.%u", version[0], version[2], version[3]);
310 " Newer firmware available (%u.%u), see "
311 "https://github.com/Lora-net/radio_firmware_images/blob/master/lr1121/transceiver/README.md",
314 ESP_LOGCONFIG(
TAG,
" BUSY=%d IRQ=%d", this->busy_pin_->digital_read(), this->irq_pin_->digital_read());
315 ESP_LOGCONFIG(
TAG,
" IRQ status: 0x%08" PRIX32, irq);
316 ESP_LOGCONFIG(
TAG,
" Device errors: 0x%04X", errors);
317 ESP_LOGCONFIG(
TAG,
" Last Stat1: 0x%02X", this->last_stat1_);
324 uint8_t version[4] = {0};
327 ESP_LOGE(
TAG,
"Unexpected device type 0x%02X (expected 0x%02X) — not an LR1121?", version[1],
LR1121_DEVICE_TYPE);
328 this->failed_ =
true;
331 ESP_LOGI(
TAG,
"LR1121 detected: hw=0x%02X fw=%u.%u", version[0], version[2], version[3]);
337 auto const tcxo_code =
static_cast<uint8_t
>(this->tcxo_voltage_yaml_code_ - 1);
354 uint8_t rfswitch_params[8] = {
383 uint8_t sync_word[8] = {0x57, 0xFD, 0x99, 0x00, 0x00, 0x00, 0x00, 0x00};
393 bool const use_hp_pa = this->tx_power_ > 14;
394 uint8_t pa_config[4] = {
395 (uint8_t) (use_hp_pa ? 0x01 : 0x00),
396 (uint8_t) (use_hp_pa ? 0x01 : 0x00),
408 int8_t
const min_power = use_hp_pa ? -9 : -17;
409 int8_t
const max_power = use_hp_pa ? 22 : 14;
410 int8_t
const power = std::max(min_power, std::min(max_power, (int8_t) this->tx_power_));
411 uint8_t tx_params[2] = {(uint8_t) power, 0x0C};
422 uint8_t irq_params[8] = {
448 uint8_t
const stdby_xosc = 0x01;
456 uint8_t rx_continuous[3] = {0xFF, 0xFF, 0xFF};
463 uint8_t params[4] = {
464 (uint8_t) (freq_hz >> 24),
465 (uint8_t) (freq_hz >> 16),
466 (uint8_t) (freq_hz >> 8),
474 this->rx_bandwidth_ = bandwidth;
void populate_capture_base_(bool blocking_wait, uint32_t freq_hz, int16_t rssi_dbm, const uint8_t *raw, uint8_t raw_len, const uint8_t *frame, uint8_t frame_len)
Populate the common fields of RadioCaptureInfo from raw telemetry.
InternalGPIOPin * rst_pin_
void mark_dio_fired_from_isr()
void reset_hardware_()
Shared hardware reset sequence for chips with an active-low RST pin.
RadioCaptureInfo last_capture_
void write_reg_mem_mask32_(uint32_t addr, uint32_t mask, uint32_t value)
WriteRegMemMask32: read-modify-write a 32-bit register through mask/value.
uint8_t read_rssi_raw_byte() override
Read the single raw RSSI byte (chip-specific opcode); formula is shared, see read_rssi.
uint32_t read_irq_status_raw() override
Read the raw IRQ status word from the radio.
void fill_capture_info(bool blocking_wait, uint32_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) override
Populate the RadioCaptureInfo from chip-specific telemetry (RSSI opcode, packet-status byte,...
void configure_radio_()
Full radio initialization (called from init()).
void set_frequency_register(uint32_t freq_hz) override
Set RF frequency via the chip's own frequency register/opcode encoding, and update current_freq_.
void get_rx_buffer_status(uint8_t &reported_len, uint8_t &rx_offset) override
Read the chip-reported RX length and buffer offset (raw, before any clamping).
void clear_irq_status(uint32_t irq_mask) override
Clear IRQ status bits.
void set_rx_packet_params() override
Configure RX-specific packet parameters (preamble detector length, fixed probe length).
void set_rx_bandwidth_(LR1121RxBandwidth bandwidth)
Apply the RX bandwidth selector and rewrite the modulation parameters.
void start_tx() override
Issue the SetTx opcode with the fixed TX timeout — identical 3-byte payload on both chips,...
static void gpio_intr(RadioLR1121 *arg)
IRQ pin (DIO9) ISR — sets dio_fired flag. Runs in interrupt context.
void set_mode_standby() override
Switch to standby mode.
void apply_high_acp_workaround_()
Apply the Semtech high-ACP TX-quality erratum workaround (analysis §4.1).
void dump_debug() override
Dump LR1121-specific debug info.
RadioLR1121(SpiAccess *spi, InternalGPIOPin *rst_pin, InternalGPIOPin *irq_pin, InternalGPIOPin *busy_pin, uint8_t tx_power, uint8_t tcxo_voltage_yaml_code)
void write_buffer_(const uint8_t *data, uint8_t len)
Write into the LR1121 TX buffer (always from the chip's internal write pointer, which resets to the b...
void write_command_(uint16_t opcode, const uint8_t *params, uint8_t len)
Write-only command: opcode + params, single NSS cycle.
void set_mode_rx() override
Switch to continuous receive mode.
void write_modulation_params_()
Apply the runtime bandwidth setting to the LR1121 modulation parameters.
uint16_t get_errors_()
Read device error flags (for diagnostics only — see dump_debug()).
void wait_busy_()
Wait until BUSY pin is low before any SPI transaction.
void clear_errors_()
Clear device error flags.
void read_command_(uint16_t opcode, const uint8_t *params, uint8_t params_len, uint8_t *out, uint8_t out_len)
Read-type command: write transaction, wait BUSY, then a second NSS cycle clocks out a Stat1 status by...
void apply_gfsk_workaround_()
Apply the GFSK modulation workaround register trio (analysis §4.2).
void set_packet_params_(uint16_t preamble_len, uint8_t payload_len, uint8_t packet_type, uint8_t crc_type)
Configure GFSK packet parameters (preamble, payload length, CRC).
bool init() override
Initialize the radio hardware. Returns true on success.
void log_command_status_(uint16_t opcode) const
Log a warning if the most recently observed Stat1 command-status byte indicates the chip rejected the...
void read_buffer_(uint8_t offset, uint8_t len, uint8_t *data)
Read from the LR1121 RX buffer at a given offset (as reported by GetRxBufferStatus).
void calibrate_image_()
Issue a banded image calibration for the 868MHz operating range (analysis §4.3).
static constexpr uint16_t LR1121_CMD_SET_TX_PARAMS
cross-checked
static constexpr uint16_t LR1121_CMD_CALIBRATE_IMAGE
cross-checked (RadioLib calibrateImageRejection)
static constexpr uint16_t LR1121_CMD_SET_MODULATION_PARAMS
cross-checked
static constexpr uint16_t LR1121_CMD_GET_PKT_STATUS
cross-checked (Semtech SWDR001 lr11xx_radio.c :: lr11xx_radio_get_gfsk_pkt_status — 0 request params,...
static constexpr uint8_t SOFT_PHY_RX_PROBE_PACKET_LEN
Fixed raw-RX probe length: chosen from captures of 23-25 byte protocol frames after UART packing and ...
static constexpr uint32_t LR1121_REG_GFSK_WORKAROUND_2_MASK
static constexpr uint32_t LR1121_REG_HIGH_ACP_WORKAROUND_MASK
static constexpr uint16_t LR1121_CMD_SET_RX
cross-checked
static constexpr uint8_t LR1121_KNOWN_LATEST_FW_MAJOR
Newest LR1121 transceiver firmware known at the time this file was last updated, per the version-numb...
static constexpr uint8_t LR1121_KNOWN_LATEST_FW_MINOR
constexpr bool lr1121_firmware_is_outdated(uint8_t fw_major, uint8_t fw_minor)
Pure comparison against the known-latest constants above — no I/O, host-testable.
static constexpr uint8_t LR1121_SYNC_WORD_PARAM_24_BITS
Sync word length: 24 bits, encoded as a literal bit count — cross-checked, identical encoding to SX12...
static constexpr uint16_t LR1121_CMD_WRITE_REG_MEM_MASK32
cross-checked (Semtech SWDR001 lr11xx_radio.c / RadioLib LR11x0_commands.h)
static constexpr uint8_t LR1121_DEVICE_TYPE
cross-checked (design §1.2 "Identity" row)
static constexpr uint32_t LR1121_REG_GFSK_WORKAROUND_2_VALUE
static constexpr const char * TAG
static constexpr uint16_t LR1121_CMD_SET_DIO_AS_RF_SWITCH
cross-checked
static constexpr uint16_t LR1121_CMD_SET_DIO_IRQ_PARAMS
cross-checked
LR1121RxBandwidth
Valid LR1121 RX bandwidth options (register values).
static constexpr uint8_t LR1121_CALIBRATE_ALL_BLOCKS
Calibrate "all blocks" bitmask.
static constexpr uint16_t LR1121_CMD_SET_PACKET_TYPE
cross-checked
static constexpr uint32_t LR1121_REG_GFSK_WORKAROUND_2_ADDR
static constexpr uint8_t LR1121_TCXO_STARTUP_DELAY_TICKS_MSB
LR1121 TCXO voltage on the T3-S3 board — 3.0V, confirmed on real hardware.
static constexpr uint8_t LR1121_TCXO_STARTUP_DELAY_TICKS_MID
static constexpr uint16_t LR1121_CMD_SET_GFSK_SYNC_WORD
cross-checked
static constexpr uint8_t LR1121_RFSWITCH_STANDBY
Both low.
static constexpr uint16_t LR1121_CMD_SET_PACKET_PARAMS
cross-checked
static constexpr uint16_t LR1121_CMD_WRITE_BUFFER
cross-checked
static constexpr uint32_t LR1121_REG_GFSK_WORKAROUND_1_ADDR
GFSK modulation workaround register trio, standard (non-0.6/1.2kbps) values for our 38....
static constexpr uint16_t LR1121_CMD_SET_TX
cross-checked
static constexpr uint8_t LR1121_FALLBACK_STDBY_XOSC
SetRxTxFallbackMode value for STDBY_XOSC.
static constexpr uint8_t LR1121_RFSWITCH_ENABLE_DIO5_DIO6
DIO5 + DIO6 are switch pins.
static constexpr uint32_t LR1121_IRQ_CRC_ERR
static constexpr uint16_t LR1121_CMD_GET_VERSION
cross-checked
static constexpr uint16_t LR1121_CMD_GET_RX_BUFFER_STATUS
cross-checked
static constexpr uint32_t LR1121_REG_GFSK_WORKAROUND_3_ADDR
static constexpr uint8_t LR1121_RFSWITCH_TX_HP
Same as TX (LP PA only used today).
static constexpr uint8_t LR1121_RFSWITCH_WIFI
Unused; both low.
static constexpr uint16_t LR1121_CMD_CLEAR_ERRORS
hardware-verified (called on every init/RX cycle, never rejected — see log_command_status_())
static constexpr uint32_t LR1121_REG_GFSK_WORKAROUND_1_VALUE
static constexpr uint16_t LR1121_CMD_CALIBRATE
cross-checked
static constexpr uint32_t FREQ_CH2
Channel 2: 868.95 MHz (1W and 2W, TX channel).
static constexpr uint16_t LR1121_CMD_GET_ERRORS
cross-checked (2-byte response)
static constexpr uint8_t LR1121_GFSK_PACKET_FIXED_LENGTH
cross-checked (same encoding as SX126x)
static constexpr uint8_t LR1121_PACKET_TYPE_GFSK
cross-checked (design §3.2 step 5)
static constexpr uint8_t LR1121_RFSWITCH_TX_HF
2.4GHz path unused; both low.
static constexpr uint16_t LR1121_CMD_CLEAR_IRQ
cross-checked
static constexpr uint16_t LR1121_CMD_SET_STANDBY
cross-checked
static constexpr uint16_t LR1121_CMD_SET_RX_TX_FALLBACK_MODE
cross-checked
static constexpr uint8_t LR1121_RFSWITCH_GNSS
Unused; both low.
static constexpr uint8_t LR1121_RFSWITCH_TX
DIO6 high (both LP and HP PA).
static constexpr uint8_t LR1121_RFSWITCH_RX
DIO5 high.
static constexpr uint8_t LR1121_TCXO_STARTUP_DELAY_TICKS_LSB
0x140 ticks at 30.52us/tick (32.768kHz RTC) is ~9.8ms, not the ~5ms the SX1262-derived comment used t...
static constexpr uint32_t LR1121_REG_GFSK_WORKAROUND_3_VALUE
static constexpr uint8_t LR1121_PREAMBLE_DETECTOR_16_BIT
Preamble detector length selector: 16 bits.
static constexpr uint8_t LR1121_GFSK_CRC_OFF
cross-checked (same encoding as SX126x)
static const uint32_t LR1121_BUSY_TIMEOUT_MS
static constexpr uint16_t LR1121_CMD_SET_RF_FREQUENCY
cross-checked
static constexpr uint16_t LR1121_CMD_SET_PA_CONFIG
cross-checked
static constexpr uint32_t LR1121_IRQ_RX_DONE
static constexpr uint32_t LR1121_REG_GFSK_WORKAROUND_3_MASK
static constexpr uint32_t LR1121_REG_HIGH_ACP_WORKAROUND_ADDR
High-ACP (adjacent channel power) TX-quality erratum: clear bit 30 of this register before every SetR...
static constexpr uint16_t LR1121_CMD_GET_RSSI_INST
cross-checked
static constexpr uint32_t LR1121_REG_GFSK_WORKAROUND_1_MASK
static constexpr uint32_t LR1121_IRQ_DIO_ENABLE_MASK
DIO-routed IRQ enable mask: TxDone|RxDone|PreambleDetected|SyncWordValid|Timeout (design implementati...
static constexpr uint32_t LR1121_REG_HIGH_ACP_WORKAROUND_VALUE
static constexpr uint16_t LR1121_CMD_READ_BUFFER
cross-checked
static constexpr uint16_t LR1121_CMD_SET_TCXO_MODE
cross-checked
static constexpr uint16_t LR1121_CMD_GET_STATUS
cross-checked
static constexpr uint8_t LR1121_IMAGE_CAL_FREQ1
Banded image calibration for 868.25-869.85MHz +/-4MHz (~860-876MHz), matching RadioLib's setFrequency...
static constexpr uint8_t LR1121_IMAGE_CAL_FREQ2
LR1121 radio driver for IO-Homecontrol.