STAR
Cal STAR — firmware, DAQ server, and engine design
Loading...
Searching...
No Matches
sx1262_setup.cpp
Go to the documentation of this file.
1#include "sx1262_setup.h"
2
3#include <string.h>
4
5namespace radio_link {
6namespace {
7
8// Opcodes (Rev 1.2 command table, p.62-64).
9constexpr uint8_t kOpSetStandby = 0x80;
10constexpr uint8_t kOpSetRegulatorMode = 0x96;
11constexpr uint8_t kOpSetDio3AsTcxoCtrl = 0x97;
12constexpr uint8_t kOpCalibrate = 0x89;
13constexpr uint8_t kOpCalibrateImage = 0x98;
14constexpr uint8_t kOpClearDeviceErrors = 0x07;
15constexpr uint8_t kOpGetDeviceErrors = 0x17;
16constexpr uint8_t kOpSetDio2AsRfSwitch = 0x9D;
17constexpr uint8_t kOpSetPacketType = 0x8A;
18constexpr uint8_t kOpSetRfFrequency = 0x86;
19constexpr uint8_t kOpSetPaConfig = 0x95;
20constexpr uint8_t kOpSetTxParams = 0x8E;
21constexpr uint8_t kOpSetBufferBase = 0x8F;
22constexpr uint8_t kOpSetModulation = 0x8B;
23constexpr uint8_t kOpSetPacketParams = 0x8C;
24constexpr uint8_t kOpSetDioIrqParams = 0x08;
25constexpr uint8_t kOpWriteRegister = 0x0D;
26constexpr uint8_t kOpReadRegister = 0x1D;
27constexpr uint8_t kOpWriteBuffer = 0x0E;
28constexpr uint8_t kOpSetTx = 0x83;
29constexpr uint8_t kOpSetRxDutyCycle = 0x94; // DS 13.1.7, p.71
30
31constexpr uint16_t kIrqTxDone = 1u << 0; // Table 13-29, p.80
32constexpr uint16_t kIrqTimeout = 1u << 9;
33// DIO1 carries everything the board waits on: TX done/timeout, RX done and
34// the two receive errors Ryan's receive() counts.
35constexpr uint16_t kIrqMask =
37
38constexpr uint16_t kRegTxModulation = 0x0889; // p.66, errata 15.1 (p.102)
39constexpr uint16_t kRegTxClamp = 0x08D8; // p.66, errata 15.2 (p.103)
40constexpr uint16_t kRegSyncWord = 0x0740; // p.65
41
42// SetDIO3AsTCXOCtrl: 1.8 V (Table 13-35, p.82; the Seeed XIAO kits' value,
43// and >= 200 mV under the module's 3.3 V), 5 ms to settle in 15.625 us steps.
44constexpr uint8_t kTcxo1V8 = 0x02;
45constexpr uint32_t kTcxoDelaySteps = 320;
46
47constexpr uint8_t kRamp200us = 0x04; // Table 13-41, p.84
48
49// LoRa symbol time 2^SF / BW in us, for the BW codes this driver allows
50// (Table 13-48, p.87): 125, 250, 500 kHz.
51uint32_t symbol_us(const LoraConfig& cfg) {
52 uint32_t bw_hz = 125000;
53 if (cfg.bw == 0x05) {
54 bw_hz = 250000;
55 } else if (cfg.bw == 0x06) {
56 bw_hz = 500000;
57 }
58 return (1000000u << cfg.sf) / bw_hz;
59}
60
61// Low Data Rate Optimization when a symbol lasts >= 16.38 ms (6.1.1.4, p.39).
62bool use_ldro(const LoraConfig& cfg) {
63 return symbol_us(cfg) >= 16380;
64}
65
66} // namespace
67
68uint32_t sx1262_frequency_word(uint32_t freq_hz) {
69 return static_cast<uint32_t>((static_cast<uint64_t>(freq_hz) << 25) /
70 32000000u);
71}
72
73uint32_t lora_time_on_air_us(const LoraConfig& cfg, uint8_t payload_len) {
74 const int sf = cfg.sf;
75 const uint32_t tsym_us = symbol_us(cfg);
76 const bool ldro = use_ldro(cfg);
77 // Nsymbol, quarter-symbol units to keep the 4.25 exact (p.41).
78 int num = 8 * payload_len + 16 - 4 * sf + 8 + 20; // CRC on, explicit
79 if (num < 0) {
80 num = 0;
81 }
82 const int den = 4 * (ldro ? sf - 2 : sf);
83 const int blocks = (num + den - 1) / den;
84 const uint32_t quarters = static_cast<uint32_t>(4 * cfg.preamble + 17 + 32 +
85 4 * blocks * (cfg.cr + 4));
86 return quarters * tsym_us / 4;
87}
88
89bool Sx1262Setup::cmd(const uint8_t* tx, uint16_t n) {
90 if (!bus_.wait_busy()) {
91 return false;
92 }
93 bus_.transfer(tx, rx_, n);
94 return true;
95}
96
97bool Sx1262Setup::read_register(uint16_t addr, uint8_t& value) {
98 // ReadRegister: opcode, addr hi, addr lo, NOP, then data (p.78).
99 const uint8_t tx[5] = {kOpReadRegister, static_cast<uint8_t>(addr >> 8),
100 static_cast<uint8_t>(addr), 0, 0};
101 if (!cmd(tx, sizeof tx) || !sx1262_status_ok(rx_[1])) {
102 return false;
103 }
104 value = rx_[4];
105 return true;
106}
107
108bool Sx1262Setup::write_register(uint16_t addr, const uint8_t* data,
109 uint8_t n) {
110 uint8_t tx[3 + 4];
111 if (n > 4) {
112 return false;
113 }
114 tx[0] = kOpWriteRegister;
115 tx[1] = static_cast<uint8_t>(addr >> 8);
116 tx[2] = static_cast<uint8_t>(addr);
117 memcpy(tx + 3, data, n);
118 return cmd(tx, static_cast<uint16_t>(3 + n));
119}
120
121bool Sx1262Setup::set_packet_params(uint8_t payload_len) {
122 // LoRa: preamble, explicit header, payload length (max in RX), CRC on,
123 // standard IQ (Tables 13-66..13-70, p.91-92).
124 const uint8_t tx[7] = {kOpSetPacketParams,
125 static_cast<uint8_t>(cfg_.preamble >> 8),
126 static_cast<uint8_t>(cfg_.preamble),
127 0x00,
128 payload_len,
129 0x01,
130 0x00};
131 return cmd(tx, sizeof tx);
132}
133
134bool Sx1262Setup::irq_status(uint16_t& irq) {
135 const uint8_t tx[4] = {kOpGetIrqStatus, 0, 0, 0};
136 if (!cmd(tx, sizeof tx) || !sx1262_status_ok(rx_[1])) {
137 return false;
138 }
139 irq = static_cast<uint16_t>((rx_[2] << 8) | rx_[3]);
140 return true;
141}
142
144 const uint8_t dc[7] = {
145 kOpSetRxDutyCycle,
146 static_cast<uint8_t>(duty_rx_steps_ >> 16),
147 static_cast<uint8_t>(duty_rx_steps_ >> 8),
148 static_cast<uint8_t>(duty_rx_steps_),
149 static_cast<uint8_t>(duty_sleep_steps_ >> 16),
150 static_cast<uint8_t>(duty_sleep_steps_ >> 8),
151 static_cast<uint8_t>(duty_sleep_steps_),
152 };
153 return cmd(dc, sizeof dc);
154}
155
157 control_.set_rx_switch(true);
158 if (!set_packet_params(0xFF))
159 return false;
161 return send_duty_cycle();
162 }
163 const uint8_t rx_cont[4] = {kOpSetRx, 0xFF, 0xFF, 0xFF};
164 return cmd(rx_cont, sizeof rx_cont);
165}
166
167bool Sx1262Setup::start_rx_duty_cycle(uint32_t rx_ms, uint32_t sleep_ms) {
168 // 1 ms = 64 steps (1000 µs / 15.625 µs, DS p.71)
169 const uint8_t standby[2] = {kOpSetStandby, 0x00};
170 if (!cmd(standby, sizeof standby))
171 return false;
172 duty_rx_steps_ = rx_ms * 64u;
173 duty_sleep_steps_ = sleep_ms * 64u;
175 return back_to_rx();
176}
177
179 const uint8_t standby[2] = {kOpSetStandby, 0x00};
180 if (!cmd(standby, sizeof standby))
181 return false;
183 return back_to_rx();
184}
185
188 return true;
189 return back_to_rx();
190}
191
193 control_.set_rx_switch(false);
194 const uint8_t standby[2] = {kOpSetStandby, 0x00}; // STDBY_RC
195 if (!cmd(standby, sizeof standby)) {
196 return SetupStep::Standby;
197 }
198 const uint8_t regulator[2] = {kOpSetRegulatorMode, 0x01}; // DC-DC, p.74
199 if (!cmd(regulator, sizeof regulator)) {
201 }
202 const uint8_t tcxo[5] = {kOpSetDio3AsTcxoCtrl, kTcxo1V8,
203 static_cast<uint8_t>(kTcxoDelaySteps >> 16),
204 static_cast<uint8_t>(kTcxoDelaySteps >> 8),
205 static_cast<uint8_t>(kTcxoDelaySteps)};
206 if (!cmd(tcxo, sizeof tcxo)) {
207 return SetupStep::Tcxo;
208 }
209 // The power-on calibration ran before the chip knew about the TCXO and
210 // fails; recalibrate everything (p.57), then clear the expected
211 // XOSC_START_ERR (p.82). BUSY stays high up to 3.5 ms (p.75).
212 const uint8_t calibrate[2] = {kOpCalibrate, 0x7F};
213 const uint8_t clear_errors[3] = {kOpClearDeviceErrors, 0, 0};
214 if (!cmd(calibrate, sizeof calibrate) ||
215 !cmd(clear_errors, sizeof clear_errors)) {
217 }
218 const uint8_t rf_switch[2] = {kOpSetDio2AsRfSwitch, 0x01}; // p.81
219 if (!cmd(rf_switch, sizeof rf_switch)) {
220 return SetupStep::RfSwitch;
221 }
222 const uint8_t packet_type[2] = {kOpSetPacketType, 0x01}; // LoRa, p.83
223 if (!cmd(packet_type, sizeof packet_type)) {
225 }
226 const uint32_t f = sx1262_frequency_word(cfg_.freq_hz);
227 const uint8_t freq[5] = {kOpSetRfFrequency, static_cast<uint8_t>(f >> 24),
228 static_cast<uint8_t>(f >> 16),
229 static_cast<uint8_t>(f >> 8),
230 static_cast<uint8_t>(f)};
231 if (!cmd(freq, sizeof freq)) {
233 }
234 const uint8_t image[3] = {kOpCalibrateImage, 0xE1, 0xE9}; // 902-928, p.57
235 if (!cmd(image, sizeof image)) {
237 }
238 // +22 dBm optimal setting (Table 13-21, p.77); paDutyCycle <= 0x04 and
239 // hpMax <= 0x07 are the SX1262's limits (p.76-77).
240 const uint8_t pa[5] = {kOpSetPaConfig, 0x04, 0x07, 0x00, 0x01};
241 if (!cmd(pa, sizeof pa)) {
242 return SetupStep::PaConfig;
243 }
244 const uint8_t tx_params[3] = {
245 kOpSetTxParams, static_cast<uint8_t>(cfg_.power_dbm), kRamp200us};
246 if (!cmd(tx_params, sizeof tx_params)) {
247 return SetupStep::TxParams;
248 }
249 const uint8_t base[3] = {kOpSetBufferBase, 0x00, 0x00}; // p.93
250 if (!cmd(base, sizeof base)) {
252 }
253 const uint8_t modulation[5] = {
254 kOpSetModulation, cfg_.sf, cfg_.bw, cfg_.cr,
255 static_cast<uint8_t>(use_ldro(cfg_) ? 1 : 0)};
256 if (!cmd(modulation, sizeof modulation)) {
258 }
259 if (!set_packet_params(0xFF)) {
261 }
262 const uint8_t irq[9] = {kOpSetDioIrqParams,
263 static_cast<uint8_t>(kIrqMask >> 8),
264 static_cast<uint8_t>(kIrqMask),
265 static_cast<uint8_t>(kIrqMask >> 8),
266 static_cast<uint8_t>(kIrqMask),
267 0,
268 0,
269 0,
270 0}; // all on DIO1 (p.79)
271 if (!cmd(irq, sizeof irq)) {
272 return SetupStep::IrqMap;
273 }
274 // Errata 15.2: TxClampConfig bits 4:1 = 1111 after power-on (p.103).
275 uint8_t clamp = 0;
276 if (!read_register(kRegTxClamp, clamp)) {
277 return SetupStep::TxClamp;
278 }
279 clamp = static_cast<uint8_t>(clamp | 0x1E);
280 if (!write_register(kRegTxClamp, &clamp, 1)) {
281 return SetupStep::TxClamp;
282 }
283 const uint8_t sync[2] = {static_cast<uint8_t>(cfg_.sync_word >> 8),
284 static_cast<uint8_t>(cfg_.sync_word)};
285 if (!write_register(kRegSyncWord, sync, 2)) {
286 return SetupStep::SyncWord;
287 }
288 const uint8_t errors[4] = {kOpGetDeviceErrors, 0, 0, 0};
289 if (!cmd(errors, sizeof errors) || !sx1262_status_ok(rx_[1])) {
291 }
292 device_errors_ = static_cast<uint16_t>((rx_[2] << 8) | rx_[3]);
293 if (device_errors_ != 0) {
295 }
297}
298
299RadioStatus Sx1262Setup::send(const uint8_t* bytes, uint8_t len) {
300 if (bytes == nullptr || len == 0) {
301 return RadioStatus::ErrTooLong; // radio.h: 1..255 bytes
302 }
303 const uint8_t standby[2] = {kOpSetStandby, 0x00};
304 if (!cmd(standby, sizeof standby)) {
306 }
307 control_.set_rx_switch(false);
308
309 // Errata 15.1 (p.102): TxModulation bit 2 = 1 unless BW is 500 kHz.
310 uint8_t mod = 0;
311 if (!read_register(kRegTxModulation, mod)) {
312 back_to_rx();
314 }
315 mod = cfg_.bw == 0x06 ? static_cast<uint8_t>(mod & ~0x04)
316 : static_cast<uint8_t>(mod | 0x04);
317 tx_[0] = kOpWriteBuffer;
318 tx_[1] = 0x00; // TX base address
319 memcpy(tx_ + 2, bytes, len);
320 // Clear only the TX flags: a packet that arrived just now (RxDone) must
321 // survive the send so the next receive() still sees it.
322 const uint8_t clear[3] = {
324 static_cast<uint8_t>((kIrqTxDone | kIrqTimeout) >> 8),
325 static_cast<uint8_t>(kIrqTxDone | kIrqTimeout)};
326 // Chip timeout: twice the time on air, in 15.625 us steps (p.69).
327 const uint32_t toa_us = lora_time_on_air_us(cfg_, len);
328 const uint32_t steps = (2u * toa_us * 64u + 999u) / 1000u;
329 const uint8_t set_tx[4] = {kOpSetTx, static_cast<uint8_t>(steps >> 16),
330 static_cast<uint8_t>(steps >> 8),
331 static_cast<uint8_t>(steps)};
332 if (!write_register(kRegTxModulation, &mod, 1) || !set_packet_params(len) ||
333 !cmd(tx_, static_cast<uint16_t>(2 + len)) ||
334 !cmd(clear, sizeof clear) || !cmd(set_tx, sizeof set_tx)) {
335 back_to_rx();
337 }
338
339 // Wait for TxDone or the chip's Timeout; the host gives up 50 ms after
340 // the chip should have, in case the chip itself is stuck.
341 const uint32_t limit_ms = 2u * toa_us / 1000u + 50u;
343 for (uint32_t waited = 0;; ++waited) {
344 uint16_t irq = 0;
345 if (!irq_status(irq)) {
347 break;
348 }
349 if (irq & kIrqTxDone) {
351 break;
352 }
353 if (irq & kIrqTimeout) {
354 break;
355 }
356 if (waited == limit_ms) {
357 break; // waited the limit and checked once more
358 }
359 control_.delay_ms(1);
360 }
361 cmd(clear, sizeof clear);
362 if (result == RadioStatus::Ok) {
363 tx_ok_++;
364 } else if (result == RadioStatus::ErrTimeout) {
365 tx_timeouts_++;
366 }
367 if (!back_to_rx() && result == RadioStatus::Ok) {
369 }
370 return result;
371}
372
373} // namespace radio_link
static volatile uint32_t errors
Definition board_uart9.c:13
json_pointer result
Definition json.hpp:14064
*brief user defined string literal for JSON values *sa std::size_t n
Definition json.hpp:24474