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"},
54 this->spi_->spi_enable();
55 this->spi_->spi_write((opcode >> 8) & 0xFF);
56 this->spi_->spi_write(opcode & 0xFF);
57 for (uint8_t i = 0; i < len; i++)
58 this->spi_->spi_write(params[i]);
59 this->spi_->spi_disable();
70 this->spi_->spi_enable();
71 this->last_stat1_ = this->spi_->spi_read();
72 for (uint8_t i = 0; i < out_len; i++)
73 out[i] = this->spi_->spi_read();
74 this->spi_->spi_disable();
83 uint8_t
const cmd_status = (this->last_stat1_ >> 1) & 0x07;
84 if (cmd_status == 0x00 || cmd_status == 0x01) {
85 ESP_LOGW(
TAG,
"cmd 0x%04X rejected: stat1=0x%02X (%s)", opcode, this->last_stat1_,
86 cmd_status == 0x00 ?
"CMD_FAIL" :
"CMD_PERR");
88#ifdef IOHOME_FRAME_LOG
89 ESP_LOGV(
TAG,
"cmd 0x%04X stat1=0x%02X", opcode, this->last_stat1_);
94 uint8_t params[12] = {
95 (uint8_t) (addr >> 24), (uint8_t) (addr >> 16), (uint8_t) (addr >> 8), (uint8_t) addr,
96 (uint8_t) (mask >> 24), (uint8_t) (mask >> 16), (uint8_t) (mask >> 8), (uint8_t) mask,
97 (uint8_t) (value >> 24), (uint8_t) (value >> 16), (uint8_t) (value >> 8), (uint8_t) value,
99 this->
write_command_(LR1121_CMD_WRITE_REG_MEM_MASK32, params,
sizeof(params));
103 this->
write_reg_mem_mask32_(LR1121_REG_HIGH_ACP_WORKAROUND_ADDR, LR1121_REG_HIGH_ACP_WORKAROUND_MASK,
104 LR1121_REG_HIGH_ACP_WORKAROUND_VALUE);
109 LR1121_REG_GFSK_WORKAROUND_1_VALUE);
111 LR1121_REG_GFSK_WORKAROUND_2_VALUE);
113 LR1121_REG_GFSK_WORKAROUND_3_VALUE);
117 uint8_t params[2] = {LR1121_IMAGE_CAL_FREQ1, LR1121_IMAGE_CAL_FREQ2};
118 this->
write_command_(LR1121_CMD_CALIBRATE_IMAGE, params,
sizeof(params));
126 uint8_t params[2] = {offset, len};
127 this->
read_command_(LR1121_CMD_READ_BUFFER, params,
sizeof(params), data, len);
136 uint8_t resp[5] = {0};
137 this->
read_command_(LR1121_CMD_GET_STATUS,
nullptr, 0, resp,
sizeof(resp));
138 return (
static_cast<uint32_t
>(resp[1]) << 24) | (
static_cast<uint32_t
>(resp[2]) << 16) |
139 (
static_cast<uint32_t
>(resp[3]) << 8) |
static_cast<uint32_t
>(resp[4]);
151 .preamble_detector = LR1121_PREAMBLE_DETECTOR_16_BIT,
152 .sync_word_param = LR1121_SYNC_WORD_PARAM_24_BITS,
153 .packet_type = packet_type,
154 .payload_len = payload_len,
155 .crc_type = crc_type},
157 this->
write_command_(LR1121_CMD_SET_PACKET_PARAMS, params,
sizeof(params));
165 this->
set_packet_params_(8, SOFT_PHY_RX_PROBE_PACKET_LEN, LR1121_GFSK_PACKET_FIXED_LENGTH, LR1121_GFSK_CRC_OFF);
173 uint32_t
const bitrate_bps = 38400;
174 uint32_t
const fdev_hz = 19200;
175 uint8_t mod_params[10] = {
176 (uint8_t) (bitrate_bps >> 24),
177 (uint8_t) (bitrate_bps >> 16),
178 (uint8_t) (bitrate_bps >> 8),
179 (uint8_t) bitrate_bps,
181 static_cast<uint8_t
>(this->rx_bandwidth_),
182 (uint8_t) (fdev_hz >> 24),
183 (uint8_t) (fdev_hz >> 16),
184 (uint8_t) (fdev_hz >> 8),
187 this->
write_command_(LR1121_CMD_SET_MODULATION_PARAMS, mod_params,
sizeof(mod_params));
195 uint8_t errors_raw[2] = {0};
196 this->
read_command_(LR1121_CMD_GET_ERRORS,
nullptr, 0, errors_raw,
sizeof(errors_raw));
197 return (
static_cast<uint16_t
>(errors_raw[0]) << 8) | errors_raw[1];
203 uint8_t clear_irq[4] = {
204 (uint8_t) ((irq_mask >> 24) & 0xFF),
205 (uint8_t) ((irq_mask >> 16) & 0xFF),
206 (uint8_t) ((irq_mask >> 8) & 0xFF),
207 (uint8_t) (irq_mask & 0xFF),
209 this->
write_command_(LR1121_CMD_CLEAR_IRQ, clear_irq,
sizeof(clear_irq));
213 const uint8_t *raw, uint8_t raw_len,
const uint8_t *frame, uint8_t frame_len) {
221 uint8_t pkt_status[4] = {0};
222 this->
read_command_(LR1121_CMD_GET_PKT_STATUS,
nullptr, 0, pkt_status,
sizeof(pkt_status));
238 this->
read_command_(LR1121_CMD_GET_RSSI_INST,
nullptr, 0, &raw, 1);
244 uint8_t rx_status[2] = {0};
245 this->
read_command_(LR1121_CMD_GET_RX_BUFFER_STATUS,
nullptr, 0, rx_status,
sizeof(rx_status));
246 reported_len = rx_status[0];
247 rx_offset = rx_status[1];
253 uint8_t tx_timeout[3] = {0x03, 0xE8, 0x00};
254 this->
write_command_(LR1121_CMD_SET_TX, tx_timeout,
sizeof(tx_timeout));
266 this->irq_pin_->setup();
279 ESP_LOGI(
TAG,
"LR1121 initialized");
284 uint8_t version[4] = {0};
285 this->
read_command_(LR1121_CMD_GET_VERSION,
nullptr, 0, version,
sizeof(version));
289 ESP_LOGCONFIG(
TAG,
" LR1121 Diagnostic:");
291 ESP_LOGCONFIG(
TAG,
" Device type: 0x%02X (expect 0x%02X)", version[1], LR1121_DEVICE_TYPE);
292 ESP_LOGCONFIG(
TAG,
" HW version: 0x%02X, FW version: %u.%u", version[0], version[2], version[3]);
295 " Newer firmware available (%u.%u), see "
296 "https://github.com/Lora-net/radio_firmware_images/blob/master/lr1121/transceiver/README.md",
297 LR1121_KNOWN_LATEST_FW_MAJOR, LR1121_KNOWN_LATEST_FW_MINOR);
299 ESP_LOGCONFIG(
TAG,
" BUSY=%d IRQ=%d", this->
busy_pin_->digital_read(), this->irq_pin_->digital_read());
300 ESP_LOGCONFIG(
TAG,
" IRQ status: 0x%08" PRIX32, irq);
301 char errbuf[DEVICE_ERROR_STR_SIZE];
303 ESP_LOGCONFIG(
TAG,
" Device errors: 0x%04X (%s)", errors, errbuf);
304 ESP_LOGCONFIG(
TAG,
" Last Stat1: 0x%02X", this->last_stat1_);
312 uint8_t version[4] = {0};
313 this->
read_command_(LR1121_CMD_GET_VERSION,
nullptr, 0, version,
sizeof(version));
314 if (version[1] != LR1121_DEVICE_TYPE) {
315 ESP_LOGE(
TAG,
"Unexpected device type 0x%02X (expected 0x%02X) — not an LR1121?", version[1], LR1121_DEVICE_TYPE);
316 this->
fail_(
"chip does not identify as an LR1121 -- check radio_type, SPI wiring and the board");
319 ESP_LOGI(
TAG,
"LR1121 detected: hw=0x%02X fw=%u.%u", version[0], version[2], version[3]);
327 uint8_t tcxo_params[4] = {this->tcxo_voltage_code_, LR1121_TCXO_STARTUP_DELAY_TICKS_MSB,
328 LR1121_TCXO_STARTUP_DELAY_TICKS_MID, LR1121_TCXO_STARTUP_DELAY_TICKS_LSB};
329 this->
write_command_(LR1121_CMD_SET_TCXO_MODE, tcxo_params,
sizeof(tcxo_params));
336 uint8_t
const cal_all = LR1121_CALIBRATE_ALL_BLOCKS;
345 uint8_t rfswitch_params[8] = {
346 LR1121_RFSWITCH_ENABLE_DIO5_DIO6,
347 LR1121_RFSWITCH_STANDBY,
350 LR1121_RFSWITCH_TX_HP,
351 LR1121_RFSWITCH_TX_HF,
352 LR1121_RFSWITCH_GNSS,
353 LR1121_RFSWITCH_WIFI,
355 this->
write_command_(LR1121_CMD_SET_DIO_AS_RF_SWITCH, rfswitch_params,
sizeof(rfswitch_params));
358 uint8_t
const pkt_type = LR1121_PACKET_TYPE_GFSK;
376 uint8_t sync_word[8] = {0x57, 0xFD, 0x99, 0x00, 0x00, 0x00, 0x00, 0x00};
377 this->
write_command_(LR1121_CMD_SET_GFSK_SYNC_WORD, sync_word,
sizeof(sync_word));
386 bool const use_hp_pa = this->tx_power_ > 14;
387 uint8_t pa_config[4] = {
388 (uint8_t) (use_hp_pa ? 0x01 : 0x00),
389 (uint8_t) (use_hp_pa ? 0x01 : 0x00),
393 this->
write_command_(LR1121_CMD_SET_PA_CONFIG, pa_config,
sizeof(pa_config));
401 int8_t
const min_power = use_hp_pa ? -9 : -17;
402 int8_t
const max_power = use_hp_pa ? 22 : 14;
403 int8_t
const power = std::max(min_power, std::min(max_power, (int8_t) this->tx_power_));
404 uint8_t tx_params[2] = {(uint8_t) power, 0x0C};
405 this->
write_command_(LR1121_CMD_SET_TX_PARAMS, tx_params,
sizeof(tx_params));
408 uint8_t
const fallback_mode = LR1121_FALLBACK_STDBY_XOSC;
409 this->
write_command_(LR1121_CMD_SET_RX_TX_FALLBACK_MODE, &fallback_mode, 1);
414 uint8_t irq_params[8] = {
415 (uint8_t) (LR1121_IRQ_DIO_ENABLE_MASK >> 24),
416 (uint8_t) (LR1121_IRQ_DIO_ENABLE_MASK >> 16),
417 (uint8_t) (LR1121_IRQ_DIO_ENABLE_MASK >> 8),
418 (uint8_t) LR1121_IRQ_DIO_ENABLE_MASK,
424 this->
write_command_(LR1121_CMD_SET_DIO_IRQ_PARAMS, irq_params,
sizeof(irq_params));
443 uint8_t
const stdby_xosc = 0x01;
451 uint8_t rx_continuous[3] = {0xFF, 0xFF, 0xFF};
452 this->
write_command_(LR1121_CMD_SET_RX, rx_continuous,
sizeof(rx_continuous));
458 uint8_t params[4] = {
459 (uint8_t) (freq_hz >> 24),
460 (uint8_t) (freq_hz >> 16),
461 (uint8_t) (freq_hz >> 8),
464 this->
write_command_(LR1121_CMD_SET_RF_FREQUENCY, params,
sizeof(params));
469 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 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 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.
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.
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).