Skip to main content

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}