pamoja_radios/sx1302/sx1250.rs
1//! The SX1250 front ends a concentrator listens through.
2//!
3//! A concentrator has no radio of its own. An SX1250 sits on each chain, and that is what a
4//! carrier is tuned to: the concentrator only ever sees an intermediate frequency. Both front
5//! ends are reached through the concentrator, on the same SPI device, with the first byte of
6//! every frame naming the front end rather than the concentrator.
7//!
8//! A front end gives the host no BUSY line to watch, so the wait before a frame is a flat
9//! millisecond rather than a pin. The reference does the same.
10//!
11//! Nothing here opens a bus. The framing, the register value a carrier becomes, the bands
12//! image calibration is run over, and the order a front end is brought up in are all data, so
13//! each one can be checked against the reference with no hardware present.
14
15use super::spi;
16use super::tx::Chain;
17
18/// How long to wait before a frame, in microseconds.
19pub const WAIT_BUSY_US: u32 = 1_000;
20
21/// How long a mode change is given to settle, in microseconds.
22pub const SETTLE_US: u32 = 10_000;
23
24/// How long image calibration is given to finish, in microseconds.
25pub const IMAGE_CALIBRATE_US: u32 = 10_000;
26
27/// How many bytes of framing come before a command payload.
28pub const HEADER_LEN: usize = 2;
29
30/// The crystal the front end counts its carrier in, in hertz.
31pub const XTAL_HZ: u64 = 32_000_000;
32
33/// Selects a standby mode.
34pub const OP_SET_STANDBY: u8 = 0x80;
35
36/// Reads the chip status, which carries the mode it is in.
37pub const OP_GET_STATUS: u8 = 0xc0;
38
39/// Runs the calibrations named by a mask.
40pub const OP_CALIBRATE: u8 = 0x89;
41
42/// Runs image calibration over a band.
43pub const OP_CALIBRATE_IMAGE: u8 = 0x98;
44
45/// Writes one of the registers behind the command set.
46pub const OP_WRITE_REGISTER: u8 = 0x0d;
47
48/// Sets the carrier.
49pub const OP_SET_RF_FREQUENCY: u8 = 0x86;
50
51/// Puts the front end in receive.
52pub const OP_SET_RX: u8 = 0x82;
53
54/// Reads what the chip believes went wrong.
55pub const OP_GET_DEVICE_ERRORS: u8 = 0x17;
56
57/// The mask that runs every calibration the chip has.
58pub const CALIBRATE_ALL: u8 = 0x7f;
59
60/// The timeout that means receive forever.
61pub const RX_CONTINUOUS: [u8; 3] = [0xff, 0xff, 0xff];
62
63/// The mode a front end reports once it is in standby on its own oscillator.
64pub const MODE_STANDBY_RC: u8 = 0x02;
65
66/// The mode it reports once it is in standby on the crystal.
67pub const MODE_STANDBY_XOSC: u8 = 0x03;
68
69/// Which clock a standby runs on.
70#[derive(Clone, Copy, Debug, PartialEq, Eq)]
71pub enum Standby {
72 /// The internal oscillator, which is where calibration is run.
73 Rc,
74 /// The crystal, which is where the carrier is set.
75 Xosc,
76}
77
78impl Standby {
79 /// The byte the standby command takes.
80 ///
81 /// # Returns
82 ///
83 /// The value for this mode.
84 #[must_use]
85 pub const fn value(&self) -> u8 {
86 match self {
87 Standby::Rc => 0x00,
88 Standby::Xosc => 0x01,
89 }
90 }
91
92 /// What the status register reads once the chip is in this mode.
93 ///
94 /// # Returns
95 ///
96 /// The mode to expect from [`mode`].
97 #[must_use]
98 pub const fn expected_mode(&self) -> u8 {
99 match self {
100 Standby::Rc => MODE_STANDBY_RC,
101 Standby::Xosc => MODE_STANDBY_XOSC,
102 }
103 }
104}
105
106/// A step in bringing a front end up.
107///
108/// The order is carried as data, so a driver walks it against a bus and a test walks it
109/// against nothing.
110#[derive(Clone, Copy, Debug, PartialEq, Eq)]
111pub enum Step {
112 /// Put the front end in a standby mode.
113 Standby(Standby),
114 /// Wait, in microseconds.
115 Wait(u32),
116 /// Read the status and refuse anything but this mode.
117 ExpectMode(u8),
118 /// Run the calibrations named by this mask.
119 Calibrate(u8),
120 /// Write one byte to a register behind the command set.
121 Register(u16, u8),
122 /// Clear the three bytes of the frequency offset at this register.
123 ClearOffset(u16),
124 /// Tune the carrier, in hertz.
125 Tune(u32),
126 /// Listen until told otherwise, which is also what clocks the concentrator.
127 ListenContinuously,
128}
129
130/// Which front end a chain talks to.
131///
132/// # Arguments
133///
134/// * `chain` - the chain the front end is on.
135///
136/// # Returns
137///
138/// The target byte every frame to that front end starts with.
139///
140/// # Examples
141///
142/// ```
143/// use pamoja_radios::sx1302::sx1250::target;
144/// use pamoja_radios::sx1302::tx::Chain;
145///
146/// assert_eq!(target(Chain::A), 0x01);
147/// assert_eq!(target(Chain::B), 0x02);
148/// ```
149#[must_use]
150pub const fn target(chain: Chain) -> u8 {
151 match chain {
152 Chain::A => spi::TARGET_RADIO_A,
153 Chain::B => spi::TARGET_RADIO_B,
154 }
155}
156
157/// The two bytes every frame to a front end starts with.
158///
159/// A command is this header and then its payload. A read sends the same frame and takes the
160/// answer from where the payload would have been, which is why [`HEADER_LEN`] is what a
161/// reader skips.
162///
163/// # Arguments
164///
165/// * `chain` - the chain the front end is on.
166/// * `opcode` - the command.
167///
168/// # Returns
169///
170/// The header.
171///
172/// # Examples
173///
174/// ```
175/// use pamoja_radios::sx1302::sx1250::{header, OP_SET_RF_FREQUENCY};
176/// use pamoja_radios::sx1302::tx::Chain;
177///
178/// assert_eq!(header(Chain::A, OP_SET_RF_FREQUENCY), [0x01, 0x86]);
179/// ```
180#[must_use]
181pub const fn header(chain: Chain, opcode: u8) -> [u8; HEADER_LEN] {
182 [target(chain), opcode]
183}
184
185/// Turns a carrier into the value the frequency command takes.
186///
187/// The chip counts the carrier in steps of its crystal, so the value is the carrier times two
188/// to the twenty-fifth over the crystal. That divides exactly, which keeps this integer.
189///
190/// # Arguments
191///
192/// * `hertz` - the carrier.
193///
194/// # Returns
195///
196/// The value, most significant byte first when it is written.
197///
198/// # Examples
199///
200/// ```
201/// use pamoja_radios::sx1302::sx1250::frequency_value;
202///
203/// assert_eq!(frequency_value(868_100_000), 0x3641_9999);
204/// assert_eq!(frequency_value(915_000_000), 0x3930_0000);
205/// ```
206#[must_use]
207pub const fn frequency_value(hertz: u32) -> u32 {
208 ((hertz as u64) * (1 << 25) / XTAL_HZ) as u32
209}
210
211/// Splits a carrier into the four bytes the frequency command takes.
212///
213/// # Arguments
214///
215/// * `hertz` - the carrier.
216///
217/// # Returns
218///
219/// The bytes, most significant first.
220///
221/// # Examples
222///
223/// ```
224/// use pamoja_radios::sx1302::sx1250::frequency_bytes;
225///
226/// assert_eq!(frequency_bytes(868_100_000), [0x36, 0x41, 0x99, 0x99]);
227/// ```
228#[must_use]
229pub const fn frequency_bytes(hertz: u32) -> [u8; 4] {
230 frequency_value(hertz).to_be_bytes()
231}
232
233/// The mode a status byte reports.
234///
235/// # Arguments
236///
237/// * `status` - what the status command answered with.
238///
239/// # Returns
240///
241/// The three bits that name the mode.
242///
243/// # Examples
244///
245/// ```
246/// use pamoja_radios::sx1302::sx1250::{mode, MODE_STANDBY_XOSC};
247///
248/// assert_eq!(mode(0b0011_0000), MODE_STANDBY_XOSC);
249/// ```
250#[must_use]
251pub const fn mode(status: u8) -> u8 {
252 (status >> 4) & 0b111
253}
254
255/// The pair of bytes image calibration takes for the band a carrier is in.
256///
257/// The chip calibrates a band rather than a frequency, and it knows five of them. A carrier
258/// outside all five has no answer, which is a configuration mistake rather than a bus one.
259///
260/// # Arguments
261///
262/// * `hertz` - the carrier.
263///
264/// # Returns
265///
266/// The band, or `None` for a carrier outside every band the chip calibrates.
267///
268/// # Examples
269///
270/// ```
271/// use pamoja_radios::sx1302::sx1250::image_band;
272///
273/// // The two bands a LoRaWAN gateway runs in.
274/// assert_eq!(image_band(868_100_000), Some([0xd7, 0xdb]));
275/// assert_eq!(image_band(915_000_000), Some([0xe1, 0xe9]));
276///
277/// // A carrier between the bands has none.
278/// assert_eq!(image_band(880_000_000), None);
279/// ```
280#[must_use]
281pub const fn image_band(hertz: u32) -> Option<[u8; 2]> {
282 match hertz {
283 430_000_001..=439_999_999 => Some([0x6b, 0x6f]),
284 470_000_001..=509_999_999 => Some([0x75, 0x81]),
285 779_000_001..=786_999_999 => Some([0xc1, 0xc5]),
286 863_000_001..=869_999_999 => Some([0xd7, 0xdb]),
287 902_000_001..=927_999_999 => Some([0xe1, 0xe9]),
288 _ => None,
289 }
290}
291
292/// Whether the device errors say image calibration failed.
293///
294/// # Arguments
295///
296/// * `third` - the third byte the device errors command answered with.
297///
298/// # Returns
299///
300/// Whether the image calibration bit is set.
301///
302/// # Examples
303///
304/// ```
305/// use pamoja_radios::sx1302::sx1250::image_failed;
306///
307/// assert!(image_failed(0b0001_0000));
308/// assert!(!image_failed(0b0000_0000));
309/// ```
310#[must_use]
311pub const fn image_failed(third: u8) -> bool {
312 (third >> 4) & 1 == 1
313}
314
315/// The order a front end is brought up in.
316///
317/// It is calibrated on its own oscillator, moved to the crystal, given the register settings
318/// the reference sets, tuned, and left listening. The last part matters for more than
319/// receiving: the concentrator takes its clock from a front end that is in receive, so a
320/// front end left idle stops the chip beside it.
321///
322/// # Arguments
323///
324/// * `hertz` - the carrier to tune to.
325/// * `single_input` - whether this board wires the front end single ended rather than
326/// differential.
327///
328/// # Returns
329///
330/// The steps, in the order the chip takes them.
331///
332/// # Examples
333///
334/// ```
335/// use pamoja_radios::sx1302::sx1250::{setup, Standby, Step};
336///
337/// // A differential board takes one step fewer than a single ended one.
338/// assert_eq!(setup(868_100_000, false).count(), 21);
339/// assert_eq!(setup(868_100_000, true).count(), 22);
340///
341/// // Calibration runs on the internal oscillator, before the crystal is started.
342/// let mut order = setup(868_100_000, false);
343/// assert_eq!(order.next(), Some(Step::Standby(Standby::Rc)));
344/// ```
345pub fn setup(hertz: u32, single_input: bool) -> impl Iterator<Item = Step> {
346 let single = single_input.then_some(Step::Register(0x08e2, 0x0d));
347 [
348 Some(Step::Standby(Standby::Rc)),
349 Some(Step::Wait(SETTLE_US)),
350 Some(Step::ExpectMode(MODE_STANDBY_RC)),
351 Some(Step::Calibrate(CALIBRATE_ALL)),
352 Some(Step::Wait(SETTLE_US)),
353 Some(Step::Standby(Standby::Xosc)),
354 Some(Step::Wait(SETTLE_US)),
355 Some(Step::ExpectMode(MODE_STANDBY_XOSC)),
356 // The bitrate is set to its maximum, which shortens the switch into transmit.
357 Some(Step::Register(0x06a1, 0x01)),
358 Some(Step::Register(0x06a2, 0x00)),
359 Some(Step::Register(0x06a3, 0x00)),
360 // The pins are driven weakly, with nothing enabled as an input and no pulls.
361 Some(Step::Register(0x0582, 0x00)),
362 Some(Step::Register(0x0583, 0x00)),
363 Some(Step::Register(0x0584, 0x00)),
364 Some(Step::Register(0x0585, 0x00)),
365 Some(Step::Register(0x0580, 0x00)),
366 Some(Step::Register(0x08b6, 0x2a)),
367 Some(Step::Tune(hertz)),
368 Some(Step::ClearOffset(0x088f)),
369 Some(Step::ListenContinuously),
370 single,
371 Some(Step::Register(0x0587, 0x0b)),
372 ]
373 .into_iter()
374 .flatten()
375}
376
377#[cfg(test)]
378mod tests {
379 use super::*;
380
381 #[test]
382 fn a_carrier_is_counted_in_crystal_steps() {
383 // Worked out from the crystal rather than from the function: 868.1 MHz is
384 // 868100000 * 2^25 / 32000000, which is 910268825.
385 assert_eq!(frequency_value(868_100_000), 910_268_825);
386 assert_eq!(frequency_bytes(868_100_000), [0x36, 0x41, 0x99, 0x99]);
387 }
388
389 #[test]
390 fn the_bands_stop_where_the_chip_stops() {
391 // The edges are exclusive in the reference, so a carrier exactly on one is outside.
392 assert_eq!(image_band(863_000_000), None);
393 assert_eq!(image_band(870_000_000), None);
394 assert_eq!(image_band(863_000_001), Some([0xd7, 0xdb]));
395 assert_eq!(image_band(869_999_999), Some([0xd7, 0xdb]));
396 }
397
398 #[test]
399 fn every_band_the_chip_knows_has_a_pair() {
400 for (hertz, expected) in [
401 (433_000_000, [0x6b, 0x6f]),
402 (490_000_000, [0x75, 0x81]),
403 (783_000_000, [0xc1, 0xc5]),
404 (868_100_000, [0xd7, 0xdb]),
405 (915_000_000, [0xe1, 0xe9]),
406 ] {
407 assert_eq!(image_band(hertz), Some(expected), "{hertz}");
408 }
409 }
410
411 #[test]
412 fn a_status_byte_carries_the_mode_in_three_bits() {
413 assert_eq!(mode(0b0010_0000), MODE_STANDBY_RC);
414 assert_eq!(mode(0b0011_1010), MODE_STANDBY_XOSC);
415 }
416
417 #[test]
418 fn the_front_end_is_left_listening() {
419 // The concentrator is clocked by a front end in receive, so this is the step that
420 // keeps the chip beside it running, not just the one that hears packets.
421 let steps: Vec<Step> = setup(868_100_000, false).collect();
422 assert!(steps.contains(&Step::ListenContinuously));
423 assert!(steps.contains(&Step::Tune(868_100_000)));
424 }
425
426 #[test]
427 fn calibration_comes_before_the_crystal() {
428 let steps: Vec<Step> = setup(868_100_000, false).collect();
429 let calibrate = steps
430 .iter()
431 .position(|step| matches!(step, Step::Calibrate(_)))
432 .expect("the front end is calibrated");
433 let crystal = steps
434 .iter()
435 .position(|step| matches!(step, Step::Standby(Standby::Xosc)))
436 .expect("the crystal is started");
437 assert!(calibrate < crystal, "{steps:?}");
438 }
439
440 #[test]
441 fn a_frame_names_the_front_end_first() {
442 assert_eq!(header(Chain::A, OP_GET_STATUS), [0x01, 0xc0]);
443 assert_eq!(header(Chain::B, OP_SET_RX), [0x02, 0x82]);
444 }
445}