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}