Skip to main content

pamoja_radios/sx1302/
mcu.rs

1//! Starting the two microcontrollers inside a concentrator.
2//!
3//! Giving a microcontroller its firmware is only half of bringing one up. The gain control
4//! comes out of reset waiting to be configured, and it is configured through four mailbox
5//! registers and a status register, one small group of settings at a time: the host writes
6//! the values, writes a code saying which group it just wrote, waits for the status to reach
7//! the next number, and reads every value back to see that it took. The arbiter is simpler
8//! but works the same way, through its own debug registers.
9//!
10//! A concentrator whose microcontrollers have been loaded but not started reports a healthy
11//! version register, passes every firmware check, and hears nothing useful, because its gain
12//! control has never been told what gains to use.
13//!
14//! Nothing here reads a register. The order and the values are data, so the whole handshake
15//! can be checked against the reference with no hardware present.
16
17use super::tx::FrontEnd;
18
19/// The gain control firmware a concentrator with SX1250 front ends expects.
20pub const AGC_VERSION_SX1250: u8 = 10;
21
22/// The one a concentrator with the earlier front ends expects.
23pub const AGC_VERSION_SX125X: u8 = 6;
24
25/// The arbiter firmware a concentrator expects.
26pub const ARB_VERSION: u8 = 2;
27
28/// The gain value that leaves the gain control to decide for itself.
29pub const GAIN_AUTOMATIC: u8 = 0xff;
30
31/// How long the amplifier is started before a packet, in hundreds of microseconds.
32pub const PA_START_DELAY: u8 = 8;
33
34/// The status a microcontroller reports once its firmware is running.
35pub const STATUS_RUNNING: u8 = 0x01;
36
37/// The status the arbiter returns to once it has been told to resume.
38pub const ARB_STATUS_RESUMED: u8 = 0x00;
39
40/// How many peaks a second detection has to clear to be believed.
41pub const ARB_DETECT_THRESHOLD: u8 = 3;
42
43/// The gains a gain control is configured with.
44///
45/// Every value comes from the reference, which keeps one set for each kind of front end.
46#[derive(Clone, Copy, Debug, PartialEq, Eq)]
47pub struct Gains {
48    /// The least analog gain it may use.
49    pub analog_min: u8,
50    /// The most.
51    pub analog_max: u8,
52    /// The level it stops reducing analog gain below.
53    pub analog_low: u8,
54    /// The level it starts reducing above.
55    pub analog_high: u8,
56    /// The least the decimator may attenuate.
57    pub decimator_min: u8,
58    /// The most.
59    pub decimator_max: u8,
60    /// The level the decimator stops attenuating below.
61    pub decimator_low: u8,
62    /// The first level it attenuates above.
63    pub decimator_high: u8,
64    /// The second, which it attenuates harder above.
65    pub decimator_higher: u8,
66    /// The least a channel may be attenuated.
67    pub channel_min: u8,
68    /// The most.
69    pub channel_max: u8,
70    /// The level a channel stops being attenuated below.
71    pub channel_low: u8,
72    /// The level it starts being attenuated above.
73    pub channel_high: u8,
74    /// Which amplifier an SX1250 uses.
75    pub device: u8,
76    /// How far its high power amplifier is driven.
77    pub high_power_max: u8,
78    /// The duty cycle it drives at.
79    pub duty_cycle: u8,
80}
81
82/// The gains an SX1250 front end is configured with.
83pub const SX1250_GAINS: Gains = Gains {
84    analog_min: 1,
85    analog_max: 13,
86    analog_low: 3,
87    analog_high: 12,
88    decimator_min: 4,
89    decimator_max: 15,
90    decimator_low: 40,
91    decimator_high: 80,
92    decimator_higher: 90,
93    channel_min: 4,
94    channel_max: 14,
95    channel_low: 52,
96    channel_high: 132,
97    device: 0,
98    high_power_max: 7,
99    duty_cycle: 4,
100};
101
102/// The gains an SX1255 or SX1257 front end is configured with.
103pub const SX125X_GAINS: Gains = Gains {
104    analog_min: 0,
105    analog_max: 9,
106    analog_low: 16,
107    analog_high: 35,
108    decimator_min: 7,
109    decimator_max: 11,
110    decimator_low: 45,
111    decimator_high: 100,
112    decimator_higher: 115,
113    channel_min: 4,
114    channel_max: 14,
115    channel_low: 52,
116    channel_high: 132,
117    device: 0,
118    high_power_max: 0,
119    duty_cycle: 0,
120};
121
122/// A step in the exchange that starts a microcontroller.
123#[derive(Clone, Copy, Debug, PartialEq, Eq)]
124pub enum Step {
125    /// Wait until the status reads this.
126    Await(u8),
127    /// Read the firmware version and refuse anything but this.
128    Version(u8),
129    /// Put a value in a mailbox.
130    Write(u8, u8),
131    /// Tell the microcontroller which group of settings was just written.
132    Notify(u8),
133    /// Read a mailbox back and refuse anything but this.
134    Verify(u8, u8),
135}
136
137/// What a gain control is configured with and how it is told.
138///
139/// The exchange is strictly ordered: every group is written, announced, acknowledged and read
140/// back before the next begins, and the status the microcontroller reports rises by one at
141/// each acknowledgment. Skipping a read-back would let a setting that never took pass for
142/// one that did.
143///
144/// # Arguments
145///
146/// * `front_end` - which front end the board carries, which decides both the gain table and
147///   the firmware version expected.
148/// * `listen_before_talk` - whether the SX1261 beside the concentrator is checking channels
149///   before the gateway talks on them.
150///
151/// # Returns
152///
153/// The steps, in the order the microcontroller takes them.
154///
155/// # Examples
156///
157/// ```
158/// use pamoja_radios::sx1302::mcu::{start_gain_control, Step};
159/// use pamoja_radios::sx1302::tx::FrontEnd;
160///
161/// let mut order = start_gain_control(FrontEnd::Sx1250, false);
162///
163/// // Nothing is written until the firmware says it is running and names its version.
164/// assert_eq!(order.next(), Some(Step::Await(0x01)));
165/// assert_eq!(order.next(), Some(Step::Version(10)));
166///
167/// // The exchange ends by telling the microcontroller there is nothing more to come.
168/// assert_eq!(
169///     start_gain_control(FrontEnd::Sx1250, false).last(),
170///     Some(Step::Notify(0x0f))
171/// );
172/// ```
173pub fn start_gain_control(
174    front_end: FrontEnd,
175    listen_before_talk: bool,
176) -> impl Iterator<Item = Step> {
177    let gains = match front_end {
178        FrontEnd::Sx1250 => SX1250_GAINS,
179        FrontEnd::Sx125x => SX125X_GAINS,
180    };
181    let version = match front_end {
182        FrontEnd::Sx1250 => AGC_VERSION_SX1250,
183        FrontEnd::Sx125x => AGC_VERSION_SX125X,
184    };
185
186    // The earlier front ends take a duplex setting in the third mailbox that the SX1250 has
187    // no use for, and a radio is configured twice, once for each chain.
188    let duplex = matches!(front_end, FrontEnd::Sx125x).then_some(Step::Write(2, 0));
189    let radio = move |code: u8, acknowledged: u8| {
190        [
191            Some(Step::Write(0, GAIN_AUTOMATIC)),
192            Some(Step::Write(1, GAIN_AUTOMATIC)),
193            duplex,
194            Some(Step::Notify(code)),
195            Some(Step::Await(acknowledged)),
196            Some(Step::Verify(0, GAIN_AUTOMATIC)),
197            Some(Step::Verify(1, GAIN_AUTOMATIC)),
198        ]
199        .into_iter()
200        .flatten()
201    };
202
203    let amplifier = matches!(front_end, FrontEnd::Sx1250).then(|| {
204        [
205            Step::Write(0, gains.device),
206            Step::Write(1, gains.high_power_max),
207            Step::Write(2, gains.duty_cycle),
208            Step::Notify(0x09),
209            Step::Await(0x0a),
210            Step::Verify(0, gains.device),
211            Step::Verify(1, gains.high_power_max),
212            Step::Verify(2, gains.duty_cycle),
213        ]
214    });
215
216    [Step::Await(STATUS_RUNNING), Step::Version(version)]
217        .into_iter()
218        .chain(radio(0x80, 0x02))
219        .chain(radio(0x20, 0x03))
220        .chain(pair(0x03, 0x04, gains.analog_min, gains.analog_max))
221        .chain(pair(0x04, 0x05, gains.analog_low, gains.analog_high))
222        .chain(pair(0x05, 0x06, gains.decimator_min, gains.decimator_max))
223        .chain([
224            Step::Write(0, gains.decimator_low),
225            Step::Write(1, gains.decimator_high),
226            Step::Write(2, gains.decimator_higher),
227            Step::Notify(0x06),
228            Step::Await(0x07),
229            Step::Verify(0, gains.decimator_low),
230            Step::Verify(1, gains.decimator_high),
231            Step::Verify(2, gains.decimator_higher),
232        ])
233        .chain(pair(0x07, 0x08, gains.channel_min, gains.channel_max))
234        .chain(pair(0x08, 0x09, gains.channel_low, gains.channel_high))
235        .chain(amplifier.into_iter().flatten())
236        .chain([
237            Step::Write(0, PA_START_DELAY),
238            Step::Notify(0x0a),
239            Step::Await(0x0b),
240            Step::Verify(0, PA_START_DELAY),
241            Step::Write(0, u8::from(listen_before_talk)),
242            Step::Notify(0x0b),
243            Step::Await(0x0f),
244            Step::Verify(0, u8::from(listen_before_talk)),
245            Step::Notify(0x0f),
246        ])
247}
248
249/// One group of two settings, written, announced, acknowledged and read back.
250fn pair(code: u8, acknowledged: u8, first: u8, second: u8) -> impl Iterator<Item = Step> {
251    [
252        Step::Write(0, first),
253        Step::Write(1, second),
254        Step::Notify(code),
255        Step::Await(acknowledged),
256        Step::Verify(0, first),
257        Step::Verify(1, second),
258    ]
259    .into_iter()
260}
261
262/// What the arbiter is told before it is let go.
263///
264/// The arbiter shares the receivers between the channels. It comes up halted, reports its
265/// version, takes a few settings in its own registers, and is then told to resume.
266///
267/// # Arguments
268///
269/// * `dual_demodulation` - which spreading factors are demodulated twice over, one bit each
270///   counting from SF5. Demodulating twice buys a finer timestamp at the cost of capacity,
271///   so a gateway that does not need fine timestamps passes zero.
272///
273/// # Returns
274///
275/// The steps, in the order the arbiter takes them.
276///
277/// # Examples
278///
279/// ```
280/// use pamoja_radios::sx1302::mcu::{start_arbiter, Step};
281///
282/// let mut order = start_arbiter(0x00);
283/// assert_eq!(order.next(), Some(Step::Await(0x01)));
284/// assert_eq!(order.next(), Some(Step::Version(2)));
285///
286/// // It ends waiting for the arbiter to report that it is running again.
287/// assert_eq!(start_arbiter(0x00).last(), Some(Step::Await(0x00)));
288/// ```
289pub fn start_arbiter(dual_demodulation: u8) -> impl Iterator<Item = Step> {
290    [
291        Step::Await(STATUS_RUNNING),
292        Step::Version(ARB_VERSION),
293        // Which spreading factor the arbiter keeps detection statistics for.
294        Step::Write(0, 7),
295        Step::Write(3, dual_demodulation),
296        Step::Write(2, ARB_DETECT_THRESHOLD),
297        Step::Write(1, 1),
298        Step::Await(ARB_STATUS_RESUMED),
299    ]
300    .into_iter()
301}
302
303#[cfg(test)]
304mod tests {
305    use super::*;
306
307    #[test]
308    fn every_group_is_acknowledged_and_read_back_before_the_next() {
309        // A write that is never verified is a setting that can quietly not take, so each
310        // group has to be announced, acknowledged, and then read back, in that order.
311        let steps: Vec<Step> = start_gain_control(FrontEnd::Sx1250, false).collect();
312
313        for (at, step) in steps.iter().enumerate() {
314            let Step::Notify(code) = step else {
315                continue;
316            };
317
318            // The last one only says there is nothing more to come.
319            if *code == 0x0f {
320                assert_eq!(at, steps.len() - 1, "the finish code is not the last step");
321                continue;
322            }
323
324            let rest = &steps[at + 1..];
325            let awaited = rest
326                .iter()
327                .position(|step| matches!(step, Step::Await(_)))
328                .expect("a group that is announced is acknowledged");
329            let verified = rest
330                .iter()
331                .position(|step| matches!(step, Step::Verify(_, _)))
332                .expect("a group that is acknowledged is read back");
333            assert!(
334                awaited < verified,
335                "group {code:#04x} is read back before it is acknowledged"
336            );
337
338            // And nothing else is written until this group has been read back.
339            assert!(
340                !rest[..verified]
341                    .iter()
342                    .any(|step| matches!(step, Step::Write(_, _))),
343                "group {code:#04x} is followed by another write before its read back"
344            );
345        }
346    }
347
348    #[test]
349    fn the_status_the_exchange_waits_for_only_ever_rises() {
350        let steps: Vec<Step> = start_gain_control(FrontEnd::Sx1250, true).collect();
351        let mut last = 0;
352        for step in steps {
353            if let Step::Await(status) = step {
354                assert!(status > last, "{status} came after {last}");
355                last = status;
356            }
357        }
358        assert_eq!(last, 0x0f);
359    }
360
361    #[test]
362    fn an_earlier_front_end_is_told_its_duplex_mode_and_takes_no_amplifier_settings() {
363        let older: Vec<Step> = start_gain_control(FrontEnd::Sx125x, false).collect();
364        let newer: Vec<Step> = start_gain_control(FrontEnd::Sx1250, false).collect();
365
366        // The older front end takes a third mailbox for each radio, twice over.
367        assert_eq!(
368            older
369                .iter()
370                .filter(|step| **step == Step::Write(2, 0))
371                .count(),
372            2
373        );
374
375        // And the newer one takes a group of amplifier settings the older has no use for.
376        assert!(newer.contains(&Step::Notify(0x09)));
377        assert!(!older.contains(&Step::Notify(0x09)));
378    }
379
380    #[test]
381    fn each_front_end_expects_its_own_firmware() {
382        assert!(start_gain_control(FrontEnd::Sx1250, false).any(|step| step == Step::Version(10)));
383        assert!(start_gain_control(FrontEnd::Sx125x, false).any(|step| step == Step::Version(6)));
384    }
385
386    #[test]
387    fn the_gains_are_the_values_the_reference_keeps() {
388        // Spot-checked against the reference table rather than restated from the struct.
389        assert_eq!(SX1250_GAINS.analog_max, 13);
390        assert_eq!(SX1250_GAINS.decimator_higher, 90);
391        assert_eq!(SX1250_GAINS.channel_high, 132);
392        assert_eq!(SX125X_GAINS.analog_max, 9);
393        assert_eq!(SX125X_GAINS.decimator_higher, 115);
394
395        // The channel settings are the only group both front ends share.
396        assert_eq!(SX1250_GAINS.channel_min, SX125X_GAINS.channel_min);
397        assert_eq!(SX1250_GAINS.channel_high, SX125X_GAINS.channel_high);
398    }
399
400    #[test]
401    fn listen_before_talk_is_carried_to_the_gain_control() {
402        // The flag has a group of its own, so it is found by that group rather than by the
403        // value it carries: a one in the first mailbox is also the analog gain minimum.
404        for enabled in [false, true] {
405            let steps: Vec<Step> = start_gain_control(FrontEnd::Sx1250, enabled).collect();
406            let announced = steps
407                .iter()
408                .position(|step| *step == Step::Notify(0x0b))
409                .expect("the flag is announced as its own group");
410            let read_back = steps[announced..]
411                .iter()
412                .find_map(|step| match step {
413                    Step::Verify(0, value) => Some(*value),
414                    _ => None,
415                })
416                .expect("and it is read back");
417
418            assert_eq!(read_back, u8::from(enabled));
419        }
420    }
421
422    #[test]
423    fn the_arbiter_is_resumed_last() {
424        let steps: Vec<Step> = start_arbiter(0xff).collect();
425        assert_eq!(steps.last(), Some(&Step::Await(ARB_STATUS_RESUMED)));
426
427        // The resume is the write before that wait, and the mask reaches it beforehand.
428        assert!(steps.contains(&Step::Write(3, 0xff)));
429        assert!(steps.contains(&Step::Write(1, 1)));
430    }
431}