19#include "esphome/core/log.h"
20#include "esphome/core/application.h"
27static const char *
const TAG =
"home_io_control.lr1121";
35 {LR1121_ERR_LF_RC_CALIB,
"LF_RC_CALIB_ERR"}, {LR1121_ERR_HF_RC_CALIB,
"HF_RC_CALIB_ERR"},
36 {LR1121_ERR_ADC_CALIB,
"ADC_CALIB_ERR"}, {LR1121_ERR_PLL_CALIB,
"PLL_CALIB_ERR"},
37 {LR1121_ERR_IMG_CALIB,
"IMG_CALIB_ERR"}, {LR1121_ERR_HF_XOSC_START,
"HF_XOSC_START_ERR"},
38 {LR1121_ERR_LF_XOSC_START,
"LF_XOSC_START_ERR"}, {LR1121_ERR_PLL_LOCK,
"PLL_LOCK_ERR"},
50 static constexpr const char *NAMES[] = {
"SLEEP",
"STDBY_RC",
"STDBY_XOSC",
"FS",
"RX",
"TX",
"LOC"};
51 uint8_t
const mode = (stat2 >> 1) & 0x07;
52 return mode <
sizeof(NAMES) /
sizeof(NAMES[0]) ? NAMES[mode] :
"?";
62 this->spi_->spi_enable();
63 this->spi_->spi_write((opcode >> 8) & 0xFF);
64 this->spi_->spi_write(opcode & 0xFF);
65 for (uint8_t i = 0; i < len; i++)
66 this->spi_->spi_write(params[i]);
67 this->spi_->spi_disable();
78 this->spi_->spi_enable();
79 this->last_stat1_ = this->spi_->spi_read();
80 for (uint8_t i = 0; i < out_len; i++)
81 out[i] = this->spi_->spi_read();
82 this->spi_->spi_disable();
91 uint8_t
const cmd_status = (this->last_stat1_ >> 1) & 0x07;
92 if (cmd_status == 0x00 || cmd_status == 0x01) {
93 ESP_LOGW(
TAG,
"cmd 0x%04X rejected: stat1=0x%02X (%s)", opcode, this->last_stat1_,
94 cmd_status == 0x00 ?
"CMD_FAIL" :
"CMD_PERR");
96#ifdef IOHOME_FRAME_LOG
97 ESP_LOGV(
TAG,
"cmd 0x%04X stat1=0x%02X", opcode, this->last_stat1_);
102 uint8_t params[12] = {
103 (uint8_t) (addr >> 24), (uint8_t) (addr >> 16), (uint8_t) (addr >> 8), (uint8_t) addr,
104 (uint8_t) (mask >> 24), (uint8_t) (mask >> 16), (uint8_t) (mask >> 8), (uint8_t) mask,
105 (uint8_t) (value >> 24), (uint8_t) (value >> 16), (uint8_t) (value >> 8), (uint8_t) value,
107 this->
write_command_(LR1121_CMD_WRITE_REG_MEM_MASK32, params,
sizeof(params));
111 this->
write_reg_mem_mask32_(LR1121_REG_HIGH_ACP_WORKAROUND_ADDR, LR1121_REG_HIGH_ACP_WORKAROUND_MASK,
112 LR1121_REG_HIGH_ACP_WORKAROUND_VALUE);
117 LR1121_REG_GFSK_WORKAROUND_1_VALUE);
119 LR1121_REG_GFSK_WORKAROUND_2_VALUE);
121 LR1121_REG_GFSK_WORKAROUND_3_VALUE);
125 uint8_t params[2] = {LR1121_IMAGE_CAL_FREQ1, LR1121_IMAGE_CAL_FREQ2};
126 this->
write_command_(LR1121_CMD_CALIBRATE_IMAGE, params,
sizeof(params));
134 uint8_t params[2] = {offset, len};
135 this->
read_command_(LR1121_CMD_READ_BUFFER, params,
sizeof(params), data, len);
150 uint8_t resp[5] = {0};
151 this->
read_command_(LR1121_CMD_GET_STATUS,
nullptr, 0, resp,
sizeof(resp));
153 return (
static_cast<uint32_t
>(resp[1]) << 24) | (
static_cast<uint32_t
>(resp[2]) << 16) |
154 (
static_cast<uint32_t
>(resp[3]) << 8) |
static_cast<uint32_t
>(resp[4]);
166 .preamble_detector = LR1121_PREAMBLE_DETECTOR_16_BIT,
167 .sync_word_param = LR1121_SYNC_WORD_PARAM_24_BITS,
168 .packet_type = packet_type,
169 .payload_len = payload_len,
170 .crc_type = crc_type},
172 this->
write_command_(LR1121_CMD_SET_PACKET_PARAMS, params,
sizeof(params));
180 this->
set_packet_params_(8, SOFT_PHY_RX_PROBE_PACKET_LEN, LR1121_GFSK_PACKET_FIXED_LENGTH, LR1121_GFSK_CRC_OFF);
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),
202 this->
write_command_(LR1121_CMD_SET_MODULATION_PARAMS, mod_params,
sizeof(mod_params));
210 uint8_t errors_raw[2] = {0};
211 this->
read_command_(LR1121_CMD_GET_ERRORS,
nullptr, 0, errors_raw,
sizeof(errors_raw));
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),
224 this->
write_command_(LR1121_CMD_CLEAR_IRQ, clear_irq,
sizeof(clear_irq));
228 const uint8_t *raw, uint8_t raw_len,
const uint8_t *frame, uint8_t frame_len) {
236 uint8_t pkt_status[4] = {0};
237 this->
read_command_(LR1121_CMD_GET_PKT_STATUS,
nullptr, 0, pkt_status,
sizeof(pkt_status));
253 this->
read_command_(LR1121_CMD_GET_RSSI_INST,
nullptr, 0, &raw, 1);
259 uint8_t rx_status[2] = {0};
260 this->
read_command_(LR1121_CMD_GET_RX_BUFFER_STATUS,
nullptr, 0, rx_status,
sizeof(rx_status));
261 reported_len = rx_status[0];
262 rx_offset = rx_status[1];
268 uint8_t tx_timeout[3] = {0x03, 0xE8, 0x00};
269 this->
write_command_(LR1121_CMD_SET_TX, tx_timeout,
sizeof(tx_timeout));
281 this->irq_pin_->setup();
294 ESP_LOGI(
TAG,
"LR1121 initialized");
302 char errbuf[DEVICE_ERROR_STR_SIZE];
310 ESP_LOGE(
TAG,
"TX timeout chip state: stat2 0x%02X (mode=%s), tx_done=%d, device errors 0x%04X (%s), DIO1=%d BUSY=%d",
312 this->irq_pin_->digital_read(), this->busy_pin_->digital_read());
316 uint8_t version[4] = {0};
317 this->
read_command_(LR1121_CMD_GET_VERSION,
nullptr, 0, version,
sizeof(version));
321 ESP_LOGCONFIG(
TAG,
" LR1121 Diagnostic:");
323 ESP_LOGCONFIG(
TAG,
" Device type: 0x%02X (expect 0x%02X)", version[1], LR1121_DEVICE_TYPE);
324 ESP_LOGCONFIG(
TAG,
" HW version: 0x%02X, FW version: %u.%u", version[0], version[2], version[3]);
327 " Newer firmware available (%u.%u), see "
328 "https://github.com/Lora-net/radio_firmware_images/blob/master/lr1121/transceiver/README.md",
329 LR1121_KNOWN_LATEST_FW_MAJOR, LR1121_KNOWN_LATEST_FW_MINOR);
331 ESP_LOGCONFIG(
TAG,
" BUSY=%d IRQ=%d", this->
busy_pin_->digital_read(), this->irq_pin_->digital_read());
332 ESP_LOGCONFIG(
TAG,
" BUSY wait overruns (task preempted, chip ready): %" PRIu32, this->
get_busy_overruns_());
333 ESP_LOGCONFIG(
TAG,
" IRQ status: 0x%08" PRIX32, irq);
334 char errbuf[DEVICE_ERROR_STR_SIZE];
336 ESP_LOGCONFIG(
TAG,
" Device errors: 0x%04X (%s)", errors, errbuf);
337 ESP_LOGCONFIG(
TAG,
" Last Stat1: 0x%02X", this->last_stat1_);
345 uint8_t version[4] = {0};
346 this->
read_command_(LR1121_CMD_GET_VERSION,
nullptr, 0, version,
sizeof(version));
347 if (version[1] != LR1121_DEVICE_TYPE) {
348 ESP_LOGE(
TAG,
"Unexpected device type 0x%02X (expected 0x%02X) — not an LR1121?", version[1], LR1121_DEVICE_TYPE);
349 this->
fail_(
"chip does not identify as an LR1121 -- check radio_type, SPI wiring and the board");
352 ESP_LOGI(
TAG,
"LR1121 detected: hw=0x%02X fw=%u.%u", version[0], version[2], version[3]);
360 uint8_t tcxo_params[4] = {this->tcxo_voltage_code_, LR1121_TCXO_STARTUP_DELAY_TICKS_MSB,
361 LR1121_TCXO_STARTUP_DELAY_TICKS_MID, LR1121_TCXO_STARTUP_DELAY_TICKS_LSB};
362 this->
write_command_(LR1121_CMD_SET_TCXO_MODE, tcxo_params,
sizeof(tcxo_params));
369 uint8_t
const cal_all = LR1121_CALIBRATE_ALL_BLOCKS;
378 uint8_t rfswitch_params[8] = {
379 LR1121_RFSWITCH_ENABLE_DIO5_DIO6,
380 LR1121_RFSWITCH_STANDBY,
383 LR1121_RFSWITCH_TX_HP,
384 LR1121_RFSWITCH_TX_HF,
385 LR1121_RFSWITCH_GNSS,
386 LR1121_RFSWITCH_WIFI,
388 this->
write_command_(LR1121_CMD_SET_DIO_AS_RF_SWITCH, rfswitch_params,
sizeof(rfswitch_params));
391 uint8_t
const pkt_type = LR1121_PACKET_TYPE_GFSK;
409 uint8_t sync_word[8] = {0x57, 0xFD, 0x99, 0x00, 0x00, 0x00, 0x00, 0x00};
410 this->
write_command_(LR1121_CMD_SET_GFSK_SYNC_WORD, sync_word,
sizeof(sync_word));
419 uint8_t pa_config[4] = {
425 this->
write_command_(LR1121_CMD_SET_PA_CONFIG, pa_config,
sizeof(pa_config));
433 int8_t
const min_power = -9;
434 int8_t
const max_power = 22;
435 int8_t
const power = std::max(min_power, std::min(max_power, (int8_t) this->tx_power_));
436 uint8_t tx_params[2] = {(uint8_t) power, 0x0C};
437 this->
write_command_(LR1121_CMD_SET_TX_PARAMS, tx_params,
sizeof(tx_params));
440 uint8_t
const fallback_mode = LR1121_FALLBACK_STDBY_XOSC;
441 this->
write_command_(LR1121_CMD_SET_RX_TX_FALLBACK_MODE, &fallback_mode, 1);
446 uint8_t irq_params[8] = {
447 (uint8_t) (LR1121_IRQ_DIO_ENABLE_MASK >> 24),
448 (uint8_t) (LR1121_IRQ_DIO_ENABLE_MASK >> 16),
449 (uint8_t) (LR1121_IRQ_DIO_ENABLE_MASK >> 8),
450 (uint8_t) LR1121_IRQ_DIO_ENABLE_MASK,
456 this->
write_command_(LR1121_CMD_SET_DIO_IRQ_PARAMS, irq_params,
sizeof(irq_params));
475 uint8_t
const stdby_xosc = 0x01;
483 uint8_t rx_continuous[3] = {0xFF, 0xFF, 0xFF};
484 this->
write_command_(LR1121_CMD_SET_RX, rx_continuous,
sizeof(rx_continuous));
490 uint8_t params[4] = {
491 (uint8_t) (freq_hz >> 24),
492 (uint8_t) (freq_hz >> 16),
493 (uint8_t) (freq_hz >> 8),
496 this->
write_command_(LR1121_CMD_SET_RF_FREQUENCY, params,
sizeof(params));
501 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.
void fail_(const char *reason)
Latch the failed state and record why, so is_failed and failure_reason can never disagree.
RadioCaptureInfo last_capture_
bool failed_
Latched by fail_; see is_failed.
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,...
RadioLR1121(SpiAccess *spi, InternalGPIOPin *rst_pin, InternalGPIOPin *irq_pin, InternalGPIOPin *busy_pin, uint8_t tx_power, uint8_t tcxo_voltage_code)
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.
void dump_debug() override
Dump LR1121-specific debug info.
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 log_tx_timeout_state(uint32_t irq_status) override
Log Stat2 with the chip mode, whether the IRQ word holds TX-done, the device errors and the DIO1/BUSY...
void write_command_(uint16_t opcode, const uint8_t *params, uint8_t len)
Write-only command: opcode + params, single NSS cycle.
uint32_t tx_done_bit() const override
void set_mode_rx() override
Switch to continuous receive mode.
uint32_t read_status_(uint8_t &stat2)
Read GetStatus.
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 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.
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.
void wait_busy_()
Wait until busy_pin_ reads low, feeding the watchdog while polling.
void dump_init_device_errors_(const char *tag, DeviceErrorFormatter format) const
Log the init-time device errors under tag, or nothing when there were none.
int16_t raw_rssi_to_dbm_(uint8_t raw) const
Convert a chip's raw RSSI byte to an antenna-referred dBm reading.
static void build_gfsk_packet_params(const SoftPhyPacketParams &p, uint8_t out[GFSK_PACKET_PARAMS_LEN])
Fill the nine-byte GFSK SetPacketParams payload shared by both chips.
InternalGPIOPin * busy_pin_
BUSY line, read directly by both concrete drivers' own dump_debug() in addition to wait_busy_,...
static constexpr uint8_t GFSK_PACKET_PARAMS_LEN
Byte count of the GFSK SetPacketParams payload — identical on both software-PHY chips.
void record_init_device_errors_(const char *tag, const char *chip_label, uint16_t errors, DeviceErrorFormatter format)
Keep the device-error word read at the end of configure_radio_() and warn if it is non-zero.
uint32_t get_busy_overruns_() const
Number of wait_busy_ calls that ran past their timeout and still found the chip ready.
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 const char * TAG
LR1121RxBandwidth
Valid LR1121 RX bandwidth options (register values).
const char * tcxo_voltage_label(uint8_t code)
Human-readable voltage for a tcxo_voltage code, for the config dump.
void format_device_error_bits(uint16_t errors, const DeviceErrorBit *bits, size_t bit_count, char *buf, size_t buf_size)
Expand a device-error word into a human-readable NAME|NAME|... string.
void lr1121_format_device_errors(uint16_t errors, char *buf, size_t buf_size)
Expand a GetErrors word into a human-readable NAME|NAME|... string.
const char * lr1121_chip_mode_name(uint8_t stat2)
Name of the chip mode in a Stat2 byte (bits 3:1, LR1121 user manual "Status Registers").
constexpr uint8_t TCXO_VOLTAGE_NONE
Sentinel tcxo_voltage code (YAML none) for a board with a bare crystal and no TCXO.
static const char *const TAG
LR1121 radio driver for IO-Homecontrol.
One named bit of a chip's device-error word.
uint16_t irq_status
Raw IRQ status register value.
uint8_t packet_status
Packet status byte (chip-specific).
bool crc_error
True if a CRC error was detected.
uint8_t reported_len
Length reported by the radio chip.
bool rx_done
True if RxDone IRQ fired.
uint8_t rx_offset
RX buffer offset where the frame starts (0 for chips without offset reporting).