ESPHome 2026.3.0
Loading...
Searching...
No Matches
sx126x.cpp
Go to the documentation of this file.
1#include "sx126x.h"
2#include "esphome/core/hal.h"
3#include "esphome/core/log.h"
4
5namespace esphome {
6namespace sx126x {
7
8static const char *const TAG = "sx126x";
9static const uint16_t RAMP[8] = {10, 20, 40, 80, 200, 800, 1700, 3400};
10static const uint32_t BW_HZ[31] = {4800, 5800, 7300, 9700, 11700, 14600, 19500, 23400, 29300, 39000, 46900,
11 58600, 78200, 93800, 117300, 156200, 187200, 234300, 312000, 373600, 467000, 7810,
12 10420, 15630, 20830, 31250, 41670, 62500, 125000, 250000, 500000};
13static const uint8_t BW_LORA[10] = {LORA_BW_7810, LORA_BW_10420, LORA_BW_15630, LORA_BW_20830, LORA_BW_31250,
15static const uint8_t BW_FSK[21] = {
19
20static constexpr uint32_t RESET_DELAY_HIGH_US = 5000;
21static constexpr uint32_t RESET_DELAY_LOW_US = 2000;
22static constexpr uint32_t SWITCHING_DELAY_US = 1;
23static constexpr uint32_t TRANSMIT_TIMEOUT_MS = 4000;
24static constexpr uint32_t BUSY_TIMEOUT_MS = 20;
25
26// OCP (Over Current Protection) values
27static constexpr uint8_t OCP_80MA = 0x18; // 80 mA max current
28static constexpr uint8_t OCP_140MA = 0x38; // 140 mA max current
29
30// LoRa low data rate optimization threshold
31static constexpr float LOW_DATA_RATE_OPTIMIZE_THRESHOLD = 16.38f; // 16.38 ms
32
33uint8_t SX126x::read_fifo_(uint8_t offset, std::vector<uint8_t> &packet) {
34 this->wait_busy_();
35 this->enable();
37 this->transfer_byte(offset);
38 uint8_t status = this->transfer_byte(0x00);
39 for (uint8_t &byte : packet) {
40 byte = this->transfer_byte(0x00);
41 }
42 this->disable();
43 return status;
44}
45
46void SX126x::write_fifo_(uint8_t offset, const std::vector<uint8_t> &packet) {
47 this->wait_busy_();
48 this->enable();
50 this->transfer_byte(offset);
51 for (const uint8_t &byte : packet) {
52 this->transfer_byte(byte);
53 }
54 this->disable();
55 delayMicroseconds(SWITCHING_DELAY_US);
56}
57
58uint8_t SX126x::read_opcode_(uint8_t opcode, uint8_t *data, uint8_t size) {
59 this->wait_busy_();
60 this->enable();
61 this->transfer_byte(opcode);
62 uint8_t status = this->transfer_byte(0x00);
63 for (int32_t i = 0; i < size; i++) {
64 data[i] = this->transfer_byte(0x00);
65 }
66 this->disable();
67 return status;
68}
69
70void SX126x::write_opcode_(uint8_t opcode, uint8_t *data, uint8_t size) {
71 this->wait_busy_();
72 this->enable();
73 this->transfer_byte(opcode);
74 for (int32_t i = 0; i < size; i++) {
75 this->transfer_byte(data[i]);
76 }
77 this->disable();
78 delayMicroseconds(SWITCHING_DELAY_US);
79}
80
81void SX126x::read_register_(uint16_t reg, uint8_t *data, uint8_t size) {
82 this->wait_busy_();
83 this->enable();
85 this->write_byte((reg >> 8) & 0xFF);
86 this->write_byte((reg >> 0) & 0xFF);
87 this->write_byte(0x00);
88 for (int32_t i = 0; i < size; i++) {
89 data[i] = this->transfer_byte(0x00);
90 }
91 this->disable();
92}
93
94void SX126x::write_register_(uint16_t reg, uint8_t *data, uint8_t size) {
95 this->wait_busy_();
96 this->enable();
98 this->write_byte((reg >> 8) & 0xFF);
99 this->write_byte((reg >> 0) & 0xFF);
100 for (int32_t i = 0; i < size; i++) {
101 this->transfer_byte(data[i]);
102 }
103 this->disable();
104 delayMicroseconds(SWITCHING_DELAY_US);
105}
106
108 // setup pins
109 this->busy_pin_->setup();
110 this->rst_pin_->setup();
111 this->dio1_pin_->setup();
112
113 // start spi
114 this->spi_setup();
115
116 // configure rf
117 this->configure();
118}
119
121 uint8_t buf[8];
122
123 // toggle chip reset
124 this->rst_pin_->digital_write(true);
125 delayMicroseconds(RESET_DELAY_HIGH_US);
126 this->rst_pin_->digital_write(false);
127 delayMicroseconds(RESET_DELAY_LOW_US);
128 this->rst_pin_->digital_write(true);
129 delayMicroseconds(RESET_DELAY_HIGH_US);
130
131 // wakeup
132 this->read_opcode_(RADIO_GET_STATUS, nullptr, 0);
133
134 // config tcxo
135 if (this->tcxo_voltage_ != TCXO_CTRL_NONE) {
136 uint32_t delay = this->tcxo_delay_ >> 6;
137 buf[0] = this->tcxo_voltage_;
138 buf[1] = (delay >> 16) & 0xFF;
139 buf[2] = (delay >> 8) & 0xFF;
140 buf[3] = (delay >> 0) & 0xFF;
141 this->write_opcode_(RADIO_SET_TCXOMODE, buf, 4);
142 buf[0] = 0x7F;
143 this->write_opcode_(RADIO_CALIBRATE, buf, 1);
144 }
145
146 // clear errors
147 buf[0] = 0x00;
148 buf[1] = 0x00;
149 this->write_opcode_(RADIO_CLR_ERROR, buf, 2);
150
151 // rf switch
152 if (this->rf_switch_) {
153 buf[0] = 0x01;
155 }
156
157 // check silicon version to make sure hw is ok
158 this->read_register_(REG_VERSION_STRING, (uint8_t *) this->version_, sizeof(this->version_));
159 this->version_[sizeof(this->version_) - 1] = '\0';
160 if (strncmp(this->version_, "SX126", 5) != 0 && strncmp(this->version_, "LLCC68", 6) != 0) {
161 this->mark_failed();
162 return;
163 }
164
165 // setup packet type
166 buf[0] = this->modulation_;
167 this->write_opcode_(RADIO_SET_PACKETTYPE, buf, 1);
168
169 // calibrate image
170 this->run_image_cal();
171
172 // set frequency
173 uint64_t freq = ((uint64_t) this->frequency_ << 25) / XTAL_FREQ;
174 buf[0] = (uint8_t) ((freq >> 24) & 0xFF);
175 buf[1] = (uint8_t) ((freq >> 16) & 0xFF);
176 buf[2] = (uint8_t) ((freq >> 8) & 0xFF);
177 buf[3] = (uint8_t) (freq & 0xFF);
179
180 // configure pa
181 int8_t pa_power = this->pa_power_;
182 if (this->hw_version_ == "sx1261") {
183 // the following values were taken from section 13.1.14.1 table 13-21
184 // in rev 2.1 of the datasheet
185 if (pa_power == 15) {
186 uint8_t cfg[4] = {0x06, 0x00, 0x01, 0x01};
187 this->write_opcode_(RADIO_SET_PACONFIG, cfg, 4);
188 } else {
189 uint8_t cfg[4] = {0x04, 0x00, 0x01, 0x01};
190 this->write_opcode_(RADIO_SET_PACONFIG, cfg, 4);
191 }
192 pa_power = std::max(pa_power, (int8_t) -3);
193 pa_power = std::min(pa_power, (int8_t) 14);
194 buf[0] = OCP_80MA;
195 this->write_register_(REG_OCP, buf, 1);
196 } else {
197 // the following values were taken from section 13.1.14.1 table 13-21
198 // in rev 2.1 of the datasheet
199 uint8_t cfg[4] = {0x04, 0x07, 0x00, 0x01};
200 this->write_opcode_(RADIO_SET_PACONFIG, cfg, 4);
201 pa_power = std::max(pa_power, (int8_t) -3);
202 pa_power = std::min(pa_power, (int8_t) 22);
203 buf[0] = OCP_140MA;
204 this->write_register_(REG_OCP, buf, 1);
205 }
206 buf[0] = pa_power;
207 buf[1] = this->pa_ramp_;
208 this->write_opcode_(RADIO_SET_TXPARAMS, buf, 2);
209
210 // configure modem
211 if (this->modulation_ == PACKET_TYPE_LORA) {
212 // set modulation params
213 float duration = 1000.0f * std::pow(2, this->spreading_factor_) / BW_HZ[this->bandwidth_];
214 buf[0] = this->spreading_factor_;
215 buf[1] = BW_LORA[this->bandwidth_ - SX126X_BW_7810];
216 buf[2] = this->coding_rate_;
217 buf[3] = (duration > LOW_DATA_RATE_OPTIMIZE_THRESHOLD) ? 0x01 : 0x00;
219
220 // set packet params and sync word
222 if (this->sync_value_.size() == 2) {
223 this->write_register_(REG_LORA_SYNCWORD, this->sync_value_.data(), this->sync_value_.size());
224 }
225 } else {
226 // set modulation params
227 uint32_t bitrate = ((uint64_t) XTAL_FREQ * 32) / this->bitrate_;
228 uint32_t fdev = ((uint64_t) this->deviation_ << 25) / XTAL_FREQ;
229 buf[0] = (bitrate >> 16) & 0xFF;
230 buf[1] = (bitrate >> 8) & 0xFF;
231 buf[2] = (bitrate >> 0) & 0xFF;
232 buf[3] = this->shaping_;
233 buf[4] = BW_FSK[this->bandwidth_ - SX126X_BW_4800];
234 buf[5] = (fdev >> 16) & 0xFF;
235 buf[6] = (fdev >> 8) & 0xFF;
236 buf[7] = (fdev >> 0) & 0xFF;
238
239 // set crc params
240 if (this->crc_enable_) {
241 buf[0] = this->crc_initial_ >> 8;
242 buf[1] = this->crc_initial_ & 0xFF;
243 this->write_register_(REG_CRC_INITIAL, buf, 2);
244 buf[0] = this->crc_polynomial_ >> 8;
245 buf[1] = this->crc_polynomial_ & 0xFF;
246 this->write_register_(REG_CRC_POLYNOMIAL, buf, 2);
247 }
248
249 // set packet params and sync word
251 if (!this->sync_value_.empty()) {
252 this->write_register_(REG_GFSK_SYNCWORD, this->sync_value_.data(), this->sync_value_.size());
253 }
254 }
255
256 // switch to rx or sleep
257 if (this->rx_start_) {
258 this->set_mode_rx();
259 } else {
260 this->set_mode_sleep();
261 }
262}
263
265 if (this->payload_length_ > 0) {
266 return this->payload_length_;
267 }
268 return 255;
269}
270
271void SX126x::set_packet_params_(uint8_t payload_length) {
272 uint8_t buf[9];
273 if (this->modulation_ == PACKET_TYPE_LORA) {
274 buf[0] = (this->preamble_size_ >> 8) & 0xFF;
275 buf[1] = (this->preamble_size_ >> 0) & 0xFF;
276 buf[2] = (this->payload_length_ > 0) ? 0x01 : 0x00;
277 buf[3] = payload_length;
278 buf[4] = (this->crc_enable_) ? 0x01 : 0x00;
279 buf[5] = 0x00;
281 } else {
282 uint16_t preamble_size = this->preamble_size_ * 8;
283 buf[0] = (preamble_size >> 8) & 0xFF;
284 buf[1] = (preamble_size >> 0) & 0xFF;
285 buf[2] = (this->preamble_detect_ > 0) ? ((this->preamble_detect_ - 1) | 0x04) : 0x00;
286 buf[3] = this->sync_value_.size() * 8;
287 buf[4] = 0x00;
288 buf[5] = (this->payload_length_ > 0) ? 0x00 : 0x01;
289 buf[6] = payload_length;
290 if (this->crc_enable_) {
291 buf[7] = (this->crc_inverted_ ? 0x04 : 0x00) + (this->crc_size_ & 0x02);
292 } else {
293 buf[7] = 0x01;
294 }
295 buf[8] = 0x00;
297 }
298}
299
300SX126xError SX126x::transmit_packet(const std::vector<uint8_t> &packet) {
301 if (this->payload_length_ > 0 && this->payload_length_ != packet.size()) {
302 ESP_LOGE(TAG, "Packet size does not match config");
304 }
305 if (packet.empty() || packet.size() > this->get_max_packet_size()) {
306 ESP_LOGE(TAG, "Packet size out of range");
308 }
309
312 if (this->payload_length_ == 0) {
313 this->set_packet_params_(packet.size());
314 }
315 this->write_fifo_(0x00, packet);
316 this->set_mode_tx();
317
318 // wait until transmit completes, typically the delay will be less than 100 ms
319 uint32_t start = millis();
320 while (!this->dio1_pin_->digital_read()) {
321 if (millis() - start > TRANSMIT_TIMEOUT_MS) {
322 ESP_LOGE(TAG, "Transmit packet failure");
324 break;
325 }
326 }
327
328 uint8_t buf[2];
329 buf[0] = 0xFF;
330 buf[1] = 0xFF;
331 this->write_opcode_(RADIO_CLR_IRQSTATUS, buf, 2);
332 if (this->payload_length_ == 0) {
334 }
335 if (this->rx_start_) {
336 this->set_mode_rx();
337 } else {
338 this->set_mode_sleep();
339 }
340 return ret;
341}
342
343void SX126x::call_listeners_(const std::vector<uint8_t> &packet, float rssi, float snr) {
344 for (auto &listener : this->listeners_) {
345 listener->on_packet(packet, rssi, snr);
346 }
347 this->packet_trigger_.trigger(packet, rssi, snr);
348}
349
351 if (!this->dio1_pin_->digital_read()) {
352 return;
353 }
354
355 uint16_t status;
356 uint8_t buf[3];
357 uint8_t rssi;
358 int8_t snr;
359 this->read_opcode_(RADIO_GET_IRQSTATUS, buf, 2);
360 this->write_opcode_(RADIO_CLR_IRQSTATUS, buf, 2);
361 status = (buf[0] << 8) | buf[1];
362 if ((status & IRQ_RX_DONE) == IRQ_RX_DONE) {
365 if (this->modulation_ == PACKET_TYPE_LORA) {
366 rssi = buf[0];
367 snr = buf[1];
368 } else {
369 rssi = buf[2];
370 snr = 0;
371 }
373 this->packet_.resize(buf[0]);
374 this->read_fifo_(buf[1], this->packet_);
375 this->call_listeners_(this->packet_, (float) rssi / -2.0f, (float) snr / 4.0f);
376 }
377 }
378}
379
381 // the following values were taken from section 9.2.1 table 9-2
382 // in rev 2.1 of the datasheet
383 uint8_t buf[2] = {0, 0};
384 if (this->frequency_ > 900000000) {
385 buf[0] = 0xE1;
386 buf[1] = 0xE9;
387 } else if (this->frequency_ > 850000000) {
388 buf[0] = 0xD7;
389 buf[1] = 0xD8;
390 } else if (this->frequency_ > 770000000) {
391 buf[0] = 0xC1;
392 buf[1] = 0xC5;
393 } else if (this->frequency_ > 460000000) {
394 buf[0] = 0x75;
395 buf[1] = 0x81;
396 } else if (this->frequency_ > 425000000) {
397 buf[0] = 0x6B;
398 buf[1] = 0x6F;
399 }
400 if (buf[0] > 0 && buf[1] > 0) {
401 this->write_opcode_(RADIO_CALIBRATEIMAGE, buf, 2);
402 }
403}
404
406 uint8_t buf[8];
407
408 // configure irq params
410 buf[0] = (irq >> 8) & 0xFF;
411 buf[1] = (irq >> 0) & 0xFF;
412 buf[2] = (irq >> 8) & 0xFF;
413 buf[3] = (irq >> 0) & 0xFF;
414 buf[4] = (IRQ_RADIO_NONE >> 8) & 0xFF;
415 buf[5] = (IRQ_RADIO_NONE >> 0) & 0xFF;
416 buf[6] = (IRQ_RADIO_NONE >> 8) & 0xFF;
417 buf[7] = (IRQ_RADIO_NONE >> 0) & 0xFF;
419
420 // set timeout to 0
421 buf[0] = 0x00;
423
424 // switch to continuous mode rx
425 buf[0] = 0xFF;
426 buf[1] = 0xFF;
427 buf[2] = 0xFF;
428 this->write_opcode_(RADIO_SET_RX, buf, 3);
429}
430
432 uint8_t buf[8];
433
434 // configure irq params
435 uint16_t irq = IRQ_TX_DONE | IRQ_RX_TX_TIMEOUT;
436 buf[0] = (irq >> 8) & 0xFF;
437 buf[1] = (irq >> 0) & 0xFF;
438 buf[2] = (irq >> 8) & 0xFF;
439 buf[3] = (irq >> 0) & 0xFF;
440 buf[4] = (IRQ_RADIO_NONE >> 8) & 0xFF;
441 buf[5] = (IRQ_RADIO_NONE >> 0) & 0xFF;
442 buf[6] = (IRQ_RADIO_NONE >> 8) & 0xFF;
443 buf[7] = (IRQ_RADIO_NONE >> 0) & 0xFF;
445
446 // switch to single mode tx
447 buf[0] = 0x00;
448 buf[1] = 0x00;
449 buf[2] = 0x00;
450 this->write_opcode_(RADIO_SET_TX, buf, 3);
451}
452
454 uint8_t buf[1];
455 buf[0] = 0x05;
456 this->write_opcode_(RADIO_SET_SLEEP, buf, 1);
457}
458
460 uint8_t buf[1];
461 buf[0] = mode;
462 this->write_opcode_(RADIO_SET_STANDBY, buf, 1);
463}
464
466 // wait if the device is busy, the maximum delay is only be a few ms
467 // with most commands taking only a few us
468 uint32_t start = millis();
469 while (this->busy_pin_->digital_read()) {
470 if (millis() - start > BUSY_TIMEOUT_MS) {
471 ESP_LOGE(TAG, "Wait busy timeout");
472 this->mark_failed();
473 break;
474 }
475 }
476}
477
479 ESP_LOGCONFIG(TAG, "SX126x:");
480 LOG_PIN(" CS Pin: ", this->cs_);
481 LOG_PIN(" BUSY Pin: ", this->busy_pin_);
482 LOG_PIN(" RST Pin: ", this->rst_pin_);
483 LOG_PIN(" DIO1 Pin: ", this->dio1_pin_);
484 ESP_LOGCONFIG(TAG,
485 " HW Version: %15s\n"
486 " Frequency: %" PRIu32 " Hz\n"
487 " Bandwidth: %" PRIu32 " Hz\n"
488 " PA Power: %" PRId8 " dBm\n"
489 " PA Ramp: %" PRIu16 " us\n"
490 " Payload Length: %" PRIu32 "\n"
491 " CRC Enable: %s\n"
492 " Rx Start: %s",
493 this->version_, this->frequency_, BW_HZ[this->bandwidth_], this->pa_power_, RAMP[this->pa_ramp_],
494 this->payload_length_, TRUEFALSE(this->crc_enable_), TRUEFALSE(this->rx_start_));
495 if (this->modulation_ == PACKET_TYPE_GFSK) {
496 const char *shaping = "NONE";
497 if (this->shaping_ == GAUSSIAN_BT_0_3) {
498 shaping = "GAUSSIAN_BT_0_3";
499 } else if (this->shaping_ == GAUSSIAN_BT_0_5) {
500 shaping = "GAUSSIAN_BT_0_5";
501 } else if (this->shaping_ == GAUSSIAN_BT_0_7) {
502 shaping = "GAUSSIAN_BT_0_7";
503 } else if (this->shaping_ == GAUSSIAN_BT_1_0) {
504 shaping = "GAUSSIAN_BT_1_0";
505 }
506 ESP_LOGCONFIG(TAG,
507 " Modulation: FSK\n"
508 " Deviation: %" PRIu32 " Hz\n"
509 " Shaping: %s\n"
510 " Preamble Size: %" PRIu16 "\n"
511 " Preamble Detect: %" PRIu16 "\n"
512 " Bitrate: %" PRIu32 "b/s",
513 this->deviation_, shaping, this->preamble_size_, this->preamble_detect_, this->bitrate_);
514 } else if (this->modulation_ == PACKET_TYPE_LORA) {
515 const char *cr = "4/8";
516 if (this->coding_rate_ == LORA_CR_4_5) {
517 cr = "4/5";
518 } else if (this->coding_rate_ == LORA_CR_4_6) {
519 cr = "4/6";
520 } else if (this->coding_rate_ == LORA_CR_4_7) {
521 cr = "4/7";
522 }
523 ESP_LOGCONFIG(TAG,
524 " Modulation: LORA\n"
525 " Spreading Factor: %" PRIu8 "\n"
526 " Coding Rate: %s\n"
527 " Preamble Size: %" PRIu16,
528 this->spreading_factor_, cr, this->preamble_size_);
529 }
530 if (!this->sync_value_.empty()) {
531 char hex_buf[17]; // 8 bytes max = 16 hex chars + null
532 ESP_LOGCONFIG(TAG, " Sync Value: 0x%s",
533 format_hex_to(hex_buf, this->sync_value_.data(), this->sync_value_.size()));
534 }
535 if (this->is_failed()) {
536 ESP_LOGE(TAG, "Configuring SX126x failed");
537 }
538}
539
540} // namespace sx126x
541} // namespace esphome
BedjetMode mode
BedJet operating mode.
uint8_t status
Definition bl0942.h:8
void mark_failed()
Mark this component as failed.
bool is_failed() const
Definition component.h:233
virtual void setup()=0
virtual void digital_write(bool value)=0
virtual bool digital_read()=0
void trigger(const Ts &...x)
Inform the parent automation that the event has triggered.
Definition automation.h:325
uint8_t read_fifo_(uint8_t offset, std::vector< uint8_t > &packet)
Definition sx126x.cpp:33
void write_opcode_(uint8_t opcode, uint8_t *data, uint8_t size)
Definition sx126x.cpp:70
std::vector< uint8_t > sync_value_
Definition sx126x.h:117
std::vector< SX126xListener * > listeners_
Definition sx126x.h:115
size_t get_max_packet_size()
Definition sx126x.cpp:264
void set_mode_standby(SX126xStandbyMode mode)
Definition sx126x.cpp:459
Trigger< std::vector< uint8_t >, float, float > packet_trigger_
Definition sx126x.h:114
uint8_t read_opcode_(uint8_t opcode, uint8_t *data, uint8_t size)
Definition sx126x.cpp:58
void dump_config() override
Definition sx126x.cpp:478
void read_register_(uint16_t reg, uint8_t *data, uint8_t size)
Definition sx126x.cpp:81
void write_register_(uint16_t reg, uint8_t *data, uint8_t size)
Definition sx126x.cpp:94
uint16_t crc_polynomial_
Definition sx126x.h:128
uint16_t preamble_size_
Definition sx126x.h:135
void loop() override
Definition sx126x.cpp:350
void set_packet_params_(uint8_t payload_length)
Definition sx126x.cpp:271
void call_listeners_(const std::vector< uint8_t > &packet, float rssi, float snr)
Definition sx126x.cpp:343
std::vector< uint8_t > packet_
Definition sx126x.h:116
uint16_t preamble_detect_
Definition sx126x.h:134
void setup() override
Definition sx126x.cpp:107
std::string hw_version_
Definition sx126x.h:121
uint32_t payload_length_
Definition sx126x.h:132
SX126xError transmit_packet(const std::vector< uint8_t > &packet)
Definition sx126x.cpp:300
uint8_t spreading_factor_
Definition sx126x.h:141
void write_fifo_(uint8_t offset, const std::vector< uint8_t > &packet)
Definition sx126x.cpp:46
uint16_t preamble_size
uint8_t duration
Definition msa3xx.h:0
const char *const TAG
Definition spi.cpp:7
@ RADIO_SET_MODULATIONPARAMS
Definition sx126x_reg.h:32
@ RADIO_SET_LORASYMBTIMEOUT
Definition sx126x_reg.h:51
Providing packet encoding functions for exchanging data with a remote host.
Definition a01nyub.cpp:7
void IRAM_ATTR HOT delayMicroseconds(uint32_t us)
Definition core.cpp:30
size_t size
Definition helpers.h:929
void HOT delay(uint32_t ms)
Definition core.cpp:28
uint32_t IRAM_ATTR HOT millis()
Definition core.cpp:26
char * format_hex_to(char *buffer, size_t buffer_size, const uint8_t *data, size_t length)
Format byte array as lowercase hex to buffer (base implementation).
Definition helpers.cpp:341
static void uint32_t