pamoja_radios/sx1302/tx.rs
1//! Putting a packet on the air.
2//!
3//! Transmitting is three things in order: the payload goes into a buffer belonging to one of
4//! the two chains, the chip is told how long to wait before it starts, and then a trigger is
5//! armed. Which trigger decides when: now, at a counter value, or on the pulse from a GPS.
6//!
7//! The waiting is the part worth care. A gateway answers a device inside a receive window a
8//! second wide, and the chip does not begin radiating the instant it is triggered: it has a
9//! front end to settle, a filter to fill, and a modulator to start. So a timed send is
10//! programmed early by exactly those delays, and [`start_delay`] works out by how much. Get it
11//! wrong and nothing fails anywhere: the packet goes out, the device is no longer listening,
12//! and the join silently never completes.
13//!
14//! Nothing here opens a bus. The buffer address, the delay, and the trigger word are values,
15//! so they can be checked against the reference implementation with no hardware present.
16
17use pamoja_lora::LinkSettings;
18
19use super::register::{Register, TX_TOP_A_BASE, TX_TOP_B_BASE};
20
21/// Where the payload for the first chain is written.
22pub const TX_BUFFER_A: u16 = 0x5300;
23
24/// Where the payload for the second chain is written.
25pub const TX_BUFFER_B: u16 = 0x5500;
26
27/// The delay the chip starts from, in its own ticks, before the parts below are taken off.
28///
29/// The reference keeps this as microseconds and multiplies by the thirty-two ticks each one
30/// takes, which is why it is not a round number here.
31pub const START_DELAY_BASE: u16 = 1500 * 32;
32
33/// How many ticks of the concentrator counter go by in a microsecond.
34pub const TICKS_PER_US: u32 = 32;
35
36/// The value the status register reads when a chain is idle.
37pub const STATUS_FREE: u8 = 0x80;
38
39/// Which of the two transmit chains a packet goes out on.
40#[derive(Clone, Copy, Debug, PartialEq, Eq)]
41pub enum Chain {
42 /// The first chain.
43 A,
44 /// The second chain.
45 B,
46}
47
48impl Chain {
49 /// Where this chain keeps the payload it is about to send.
50 ///
51 /// # Returns
52 ///
53 /// The address the first payload byte is written to.
54 ///
55 /// # Examples
56 ///
57 /// ```
58 /// use pamoja_radios::sx1302::tx::Chain;
59 ///
60 /// assert_eq!(Chain::A.buffer(), 0x5300);
61 /// assert_eq!(Chain::B.buffer(), 0x5500);
62 /// ```
63 #[must_use]
64 pub const fn buffer(&self) -> u16 {
65 match self {
66 Chain::A => TX_BUFFER_A,
67 Chain::B => TX_BUFFER_B,
68 }
69 }
70
71 /// The block of registers that belong to this chain.
72 ///
73 /// # Returns
74 ///
75 /// The base address the chain's registers are offsets from.
76 #[must_use]
77 pub const fn base(&self) -> u16 {
78 match self {
79 Chain::A => TX_TOP_A_BASE,
80 Chain::B => TX_TOP_B_BASE,
81 }
82 }
83
84 /// The control that hands this chain's buffer to the host to write.
85 ///
86 /// # Returns
87 ///
88 /// The register, set before the payload is written and cleared after it.
89 #[must_use]
90 pub const fn write_buffer(&self) -> Register {
91 Register::new(self.base() + 7, 0, 1, false)
92 }
93
94 /// Where the chain reports what it is doing.
95 ///
96 /// # Returns
97 ///
98 /// The register [`TxStatus::of`] reads.
99 #[must_use]
100 pub const fn status(&self) -> Register {
101 Register::new(self.base() + 17, 0, 8, true)
102 }
103
104 /// The bit that starts a send of this kind.
105 ///
106 /// # Arguments
107 ///
108 /// * `trigger` - what the send waits for.
109 ///
110 /// # Returns
111 ///
112 /// The register, which is cleared and then set to arm it.
113 ///
114 /// # Examples
115 ///
116 /// ```
117 /// use pamoja_radios::sx1302::tx::{Chain, Trigger};
118 ///
119 /// // All three share a byte and differ only in which bit they are.
120 /// let immediate = Chain::A.trigger(Trigger::Immediate);
121 /// let gps = Chain::A.trigger(Trigger::OnGps);
122 /// assert_eq!(immediate.address, gps.address);
123 /// assert_eq!((immediate.offset, gps.offset), (0, 2));
124 /// ```
125 #[must_use]
126 pub const fn trigger(&self, trigger: Trigger) -> Register {
127 let offset = match trigger {
128 Trigger::Immediate => 0,
129 Trigger::At(_) => 1,
130 Trigger::OnGps => 2,
131 };
132 Register::new(self.base(), offset, 1, false)
133 }
134
135 /// One of the four bytes a timed send is programmed across.
136 ///
137 /// The bytes run backward: the least significant sits at the highest address. So index
138 /// zero is the first byte [`trigger_bytes`] hands back, and each one after it is written
139 /// an address lower.
140 ///
141 /// # Arguments
142 ///
143 /// * `index` - which byte, from zero to three. Anything higher is clamped to three.
144 ///
145 /// # Returns
146 ///
147 /// The register that byte is written to.
148 ///
149 /// # Examples
150 ///
151 /// ```
152 /// use pamoja_radios::sx1302::tx::{trigger_bytes, Chain};
153 ///
154 /// let bytes = trigger_bytes(0x1234_5678);
155 /// // The least significant byte goes to the highest of the four addresses.
156 /// assert_eq!(bytes[0], 0x78);
157 /// assert!(Chain::A.timer_byte(0).address > Chain::A.timer_byte(3).address);
158 /// ```
159 #[must_use]
160 pub const fn timer_byte(&self, index: u8) -> Register {
161 let index = if index > 3 { 3 } else { index };
162 Register::new(self.base() + 4 - index as u16, 0, 8, false)
163 }
164
165 /// The high byte of the delay the chain starts early by.
166 ///
167 /// # Returns
168 ///
169 /// The register the first of [`start_delay_bytes`] is written to.
170 #[must_use]
171 pub const fn start_delay_msb(&self) -> Register {
172 Register::new(self.base() + 5, 0, 8, false)
173 }
174
175 /// The low byte of that delay.
176 ///
177 /// # Returns
178 ///
179 /// The register the second of [`start_delay_bytes`] is written to.
180 #[must_use]
181 pub const fn start_delay_lsb(&self) -> Register {
182 Register::new(self.base() + 6, 0, 8, false)
183 }
184
185 /// Which modulator the chain runs.
186 ///
187 /// # Returns
188 ///
189 /// The register that takes [`MODULATION_LORA`] or [`MODULATION_FSK`].
190 #[must_use]
191 pub const fn modulation_type(&self) -> Register {
192 Register::new(self.base() + 9, 0, 1, false)
193 }
194
195 /// Where the modulator takes its samples from.
196 ///
197 /// # Returns
198 ///
199 /// The register that takes [`IF_SOURCE_LORA`] or [`IF_SOURCE_FSK`]. This is what parts
200 /// LoRa from an unmodulated carrier, since both name the same modulator.
201 #[must_use]
202 pub const fn if_source(&self) -> Register {
203 Register::new(self.base() + 32, 0, 2, false)
204 }
205
206 /// The bandwidth the gain control works the transmission out against.
207 ///
208 /// # Returns
209 ///
210 /// The register that takes what [`bandwidth_value`] gives.
211 #[must_use]
212 pub const fn agc_bandwidth(&self) -> Register {
213 Register::new(self.base() + 12, 0, 8, false)
214 }
215
216 /// The power the chain transmits at.
217 ///
218 /// # Returns
219 ///
220 /// The register that takes what [`Gain::power_value`] gives, which is the amplifier and
221 /// the power index together rather than a number of decibels.
222 #[must_use]
223 pub const fn power(&self) -> Register {
224 Register::new(self.base() + 13, 0, 8, false)
225 }
226
227 /// The digital gain applied before the front end.
228 ///
229 /// # Returns
230 ///
231 /// The register.
232 #[must_use]
233 pub const fn digital_gain(&self) -> Register {
234 Register::new(self.base() + 34, 0, 2, false)
235 }
236
237 /// The calibrated offset of the in-phase channel.
238 ///
239 /// # Returns
240 ///
241 /// The register.
242 #[must_use]
243 pub const fn offset_i(&self) -> Register {
244 Register::new(self.base() + 35, 0, 8, false)
245 }
246
247 /// The calibrated offset of the quadrature channel.
248 ///
249 /// # Returns
250 ///
251 /// The register.
252 #[must_use]
253 pub const fn offset_q(&self) -> Register {
254 Register::new(self.base() + 36, 0, 8, false)
255 }
256
257 /// One byte of the carrier the chain transmits on, most significant first.
258 ///
259 /// # Arguments
260 ///
261 /// * `index` - which byte, 0 to 2. Anything higher is taken as 2.
262 ///
263 /// # Returns
264 ///
265 /// The register that byte of [`frequency_value`] is written to.
266 #[must_use]
267 pub const fn frequency_byte(&self, index: u8) -> Register {
268 let index = if index > 2 { 2 } else { index };
269 Register::new(self.base() + 37 + index as u16, 0, 8, false)
270 }
271
272 /// The high bits of the frequency deviation, which is four bits rather than eight.
273 ///
274 /// # Returns
275 ///
276 /// The register.
277 #[must_use]
278 pub const fn deviation_msb(&self) -> Register {
279 Register::new(self.base() + 40, 0, 4, false)
280 }
281
282 /// The low bits of it.
283 ///
284 /// # Returns
285 ///
286 /// The register.
287 #[must_use]
288 pub const fn deviation_lsb(&self) -> Register {
289 Register::new(self.base() + 41, 0, 8, false)
290 }
291
292 /// The bandwidth the modulator runs at.
293 ///
294 /// # Returns
295 ///
296 /// The register, which shares a byte with the spreading factor.
297 #[must_use]
298 pub const fn bandwidth(&self) -> Register {
299 Register::new(self.base() + 96, 4, 4, false)
300 }
301
302 /// The spreading factor it runs at.
303 ///
304 /// # Returns
305 ///
306 /// The register, which is the factor itself rather than an index.
307 #[must_use]
308 pub const fn spreading_factor(&self) -> Register {
309 Register::new(self.base() + 96, 0, 4, false)
310 }
311
312 /// Whether the header carries the low data rate setting.
313 ///
314 /// # Returns
315 ///
316 /// The register, which the reference leaves at zero.
317 #[must_use]
318 pub const fn header_control(&self) -> Register {
319 Register::new(self.base() + 97, 6, 2, false)
320 }
321
322 /// Whether low data rate optimization is on.
323 ///
324 /// # Returns
325 ///
326 /// The register. A symbol longer than sixteen milliseconds needs it, and the airtime
327 /// assumes it, so the two have to agree.
328 #[must_use]
329 pub const fn ppm_offset(&self) -> Register {
330 Register::new(self.base() + 97, 4, 2, false)
331 }
332
333 /// The coding rate it runs at.
334 ///
335 /// # Returns
336 ///
337 /// The register, which takes the denominator less four.
338 #[must_use]
339 pub const fn coding_rate(&self) -> Register {
340 Register::new(self.base() + 97, 0, 3, false)
341 }
342
343 /// Whether the fine synchronization the two lowest spreading factors need is on.
344 ///
345 /// # Returns
346 ///
347 /// The register.
348 #[must_use]
349 pub const fn fine_sync(&self) -> Register {
350 Register::new(self.base() + 98, 7, 1, false)
351 }
352
353 /// Whether the modulator runs at all.
354 ///
355 /// # Returns
356 ///
357 /// The register.
358 #[must_use]
359 pub const fn modem_enable(&self) -> Register {
360 Register::new(self.base() + 98, 6, 1, false)
361 }
362
363 /// Which of listening, transmitting and channel activity detection the modem does.
364 ///
365 /// # Returns
366 ///
367 /// The register, which takes [`CAD_RX_TX_TRANSMIT`] for a send.
368 #[must_use]
369 pub const fn cad_rx_tx(&self) -> Register {
370 Register::new(self.base() + 98, 4, 2, false)
371 }
372
373 /// Whether the packet goes out without a header.
374 ///
375 /// # Returns
376 ///
377 /// The register.
378 #[must_use]
379 pub const fn implicit_header(&self) -> Register {
380 Register::new(self.base() + 98, 1, 1, false)
381 }
382
383 /// Whether the packet carries a physical layer checksum.
384 ///
385 /// # Returns
386 ///
387 /// The register. A LoRaWAN downlink goes out without one.
388 #[must_use]
389 pub const fn crc_enable(&self) -> Register {
390 Register::new(self.base() + 98, 0, 1, false)
391 }
392
393 /// How many bytes the packet carries.
394 ///
395 /// # Returns
396 ///
397 /// The register.
398 #[must_use]
399 pub const fn payload_length(&self) -> Register {
400 Register::new(self.base() + 99, 0, 8, false)
401 }
402
403 /// The bit that starts the modulator once it is configured.
404 ///
405 /// # Returns
406 ///
407 /// The register.
408 #[must_use]
409 pub const fn modem_start(&self) -> Register {
410 Register::new(self.base() + 101, 7, 1, false)
411 }
412
413 /// The low bits of the preamble length, in symbols.
414 ///
415 /// # Returns
416 ///
417 /// The register.
418 #[must_use]
419 pub const fn preamble_lsb(&self) -> Register {
420 Register::new(self.base() + 102, 0, 8, false)
421 }
422
423 /// The high bits of it.
424 ///
425 /// # Returns
426 ///
427 /// The register.
428 #[must_use]
429 pub const fn preamble_msb(&self) -> Register {
430 Register::new(self.base() + 103, 0, 8, false)
431 }
432
433 /// How hard the chirp is filtered, which the start delay is worked from.
434 ///
435 /// # Returns
436 ///
437 /// The register.
438 #[must_use]
439 pub const fn chirp_lowpass(&self) -> Register {
440 Register::new(self.base() + 105, 4, 3, false)
441 }
442
443 /// Whether the chirp runs continuously.
444 ///
445 /// # Returns
446 ///
447 /// The register.
448 #[must_use]
449 pub const fn continuous_chirp(&self) -> Register {
450 Register::new(self.base() + 105, 2, 1, false)
451 }
452
453 /// Whether the chirp sweeps downward, which a LoRaWAN downlink does.
454 ///
455 /// # Returns
456 ///
457 /// The register.
458 #[must_use]
459 pub const fn chirp_invert(&self) -> Register {
460 Register::new(self.base() + 105, 1, 1, false)
461 }
462
463 /// Whether the chain transmits continuously rather than one packet.
464 ///
465 /// # Returns
466 ///
467 /// The register.
468 #[must_use]
469 pub const fn continuous(&self) -> Register {
470 Register::new(self.base() + 105, 0, 1, false)
471 }
472
473 /// Where the first sync symbol goes, which says which network the packet belongs to.
474 ///
475 /// # Returns
476 ///
477 /// The register. It powers up holding the private position, as the receive side does.
478 #[must_use]
479 pub const fn sync_peak1(&self) -> Register {
480 Register::new(self.base() + 109, 0, 5, false)
481 }
482
483 /// Where the second one goes.
484 ///
485 /// # Returns
486 ///
487 /// The register.
488 #[must_use]
489 pub const fn sync_peak2(&self) -> Register {
490 Register::new(self.base() + 110, 0, 5, false)
491 }
492}
493
494/// Which front end is wired to a chain, since each settles at its own pace.
495#[derive(Clone, Copy, Debug, PartialEq, Eq)]
496pub enum FrontEnd {
497 /// The SX1250, the front end a current gateway pairs with this chip.
498 Sx1250,
499 /// The SX1255 or SX1257, which the earlier boards used.
500 Sx125x,
501}
502
503/// What the transmit chain is doing.
504#[derive(Clone, Copy, Debug, PartialEq, Eq)]
505pub enum TxStatus {
506 /// Idle, and ready to be given a packet.
507 Free,
508 /// Holding a packet, waiting for its trigger.
509 Scheduled,
510 /// On the air now.
511 Emitting,
512 /// A value the chip does not document, carried so a caller can report it.
513 Unknown(u8),
514}
515
516impl TxStatus {
517 /// Reads the transmit status register.
518 ///
519 /// # Arguments
520 ///
521 /// * `value` - what the status register answered with.
522 ///
523 /// # Returns
524 ///
525 /// What the chain is doing.
526 ///
527 /// # Examples
528 ///
529 /// ```
530 /// use pamoja_radios::sx1302::tx::TxStatus;
531 ///
532 /// assert_eq!(TxStatus::of(0x80), TxStatus::Free);
533 /// assert_eq!(TxStatus::of(0x91), TxStatus::Scheduled);
534 /// assert_eq!(TxStatus::of(0x60), TxStatus::Emitting);
535 /// ```
536 #[must_use]
537 pub const fn of(value: u8) -> TxStatus {
538 match value {
539 STATUS_FREE => TxStatus::Free,
540 0x91 | 0x92 => TxStatus::Scheduled,
541 0x30 | 0x50 | 0x60 | 0x70 => TxStatus::Emitting,
542 other => TxStatus::Unknown(other),
543 }
544 }
545
546 /// Whether a chain in this state will take another packet.
547 ///
548 /// # Returns
549 ///
550 /// Whether it is idle. A chain that is scheduled or emitting keeps what it holds, so
551 /// writing to it now would lose one of the two packets.
552 #[must_use]
553 pub const fn is_free(&self) -> bool {
554 matches!(self, TxStatus::Free)
555 }
556}
557
558/// When a packet goes out.
559#[derive(Clone, Copy, Debug, PartialEq, Eq)]
560pub enum Trigger {
561 /// As soon as the chip is told.
562 Immediate,
563 /// When the concentrator counter reaches a value, in microseconds.
564 At(u32),
565 /// On the next pulse from a GPS receiver.
566 OnGps,
567}
568
569/// A step in sending a packet.
570///
571/// The order is carried as data, so a driver walks it against a bus and a test walks it
572/// against nothing.
573#[derive(Clone, Copy, Debug, PartialEq, Eq)]
574pub enum Send {
575 /// Open the transmit buffer for writing.
576 OpenBuffer(Register),
577 /// Write the payload, starting here. For frequency shift keying the length goes first.
578 WritePayload(u16),
579 /// Close the buffer again.
580 CloseBuffer(Register),
581 /// Set the delay the chip starts early by, as a pair of bytes.
582 SetStartDelay(u16),
583 /// Program the counter value a timed send goes out at.
584 SetTimerTrigger(u32),
585 /// Reset a trigger, which the chip needs before it will take a new one.
586 ResetTrigger(Register),
587 /// Arm it.
588 ArmTrigger(Register),
589}
590
591/// How far ahead of its window a chain has to be started.
592///
593/// The chip does not radiate the moment it is triggered. The front end settles, the filter
594/// fills, and the modulator starts, so a send meant for a given moment is programmed by that
595/// much earlier.
596///
597/// # Arguments
598///
599/// * `front_end` - which front end is wired to the chain.
600/// * `bandwidth_hz` - the bandwidth the packet goes out at.
601/// * `chirp_lowpass` - the filter setting the chain is configured with.
602///
603/// # Returns
604///
605/// The delay in the chip's own ticks, or `None` for a bandwidth no front end supports.
606/// Anything that is not LoRa needs no delay at all, so a caller sends those with zero.
607///
608/// # Examples
609///
610/// ```
611/// use pamoja_radios::sx1302::tx::{start_delay, FrontEnd};
612///
613/// // The usual case: an SX1250 at 125 kHz.
614/// let delay = start_delay(FrontEnd::Sx1250, 125_000, 6).expect("a supported bandwidth");
615/// assert!(delay < 1500 * 32, "the chip starts early, never late");
616///
617/// // A bandwidth no front end covers has no answer.
618/// assert_eq!(start_delay(FrontEnd::Sx1250, 62_500, 6), None);
619/// ```
620#[must_use]
621pub fn start_delay(front_end: FrontEnd, bandwidth_hz: u32, chirp_lowpass: u8) -> Option<u16> {
622 let radio = match (front_end, bandwidth_hz) {
623 (FrontEnd::Sx1250, 125_000) => 19,
624 (FrontEnd::Sx1250, 250_000) => 24,
625 (FrontEnd::Sx1250, 500_000) => 21,
626 // The earlier front ends take a fixed time, and only 250 kHz adds to it.
627 (FrontEnd::Sx125x, 125_000 | 500_000) => 3 * 32 + 4,
628 (FrontEnd::Sx125x, 250_000) => 3 * 32 + 4 + 6,
629 _ => return None,
630 };
631
632 let filter = ((1u32 << chirp_lowpass) - 1) * 1_000_000 / bandwidth_hz;
633 let modem = 8 * (32_000_000 / (32 * bandwidth_hz));
634
635 Some(START_DELAY_BASE.wrapping_sub((radio + filter + modem) as u16))
636}
637
638/// The counter value a timed send is programmed at.
639///
640/// The counter runs in the chip's own ticks rather than microseconds, and the send is moved
641/// earlier by the start delay so the packet is on the air at the moment asked for.
642///
643/// # Arguments
644///
645/// * `at_us` - when the packet should be heard, in microseconds of the counter.
646/// * `start_delay` - what [`start_delay`] worked out.
647///
648/// # Returns
649///
650/// The value to write across the four trigger bytes.
651///
652/// # Examples
653///
654/// ```
655/// use pamoja_radios::sx1302::tx::trigger_value;
656///
657/// // A send a whole second out, brought forward by the delay.
658/// assert_eq!(trigger_value(1_000_000, 48_000), 1_000_000 * 32 - 48_000);
659/// ```
660#[must_use]
661pub const fn trigger_value(at_us: u32, start_delay: u16) -> u32 {
662 at_us
663 .wrapping_mul(TICKS_PER_US)
664 .wrapping_sub(start_delay as u32)
665}
666
667/// Splits a trigger value into the four bytes the chip takes.
668///
669/// # Arguments
670///
671/// * `value` - what [`trigger_value`] worked out.
672///
673/// # Returns
674///
675/// The bytes, least significant first, which is the order the four registers are written in.
676///
677/// # Examples
678///
679/// ```
680/// use pamoja_radios::sx1302::tx::trigger_bytes;
681///
682/// assert_eq!(trigger_bytes(0x1234_5678), [0x78, 0x56, 0x34, 0x12]);
683/// ```
684#[must_use]
685pub const fn trigger_bytes(value: u32) -> [u8; 4] {
686 value.to_le_bytes()
687}
688
689/// Splits a start delay into the two bytes the chip takes.
690///
691/// # Arguments
692///
693/// * `delay` - what [`start_delay`] worked out.
694///
695/// # Returns
696///
697/// The bytes, most significant first, which is the order this one register takes them in.
698/// It is the opposite of the trigger, which is easy to get backwards.
699///
700/// # Examples
701///
702/// ```
703/// use pamoja_radios::sx1302::tx::start_delay_bytes;
704///
705/// assert_eq!(start_delay_bytes(0x1234), [0x12, 0x34]);
706/// ```
707#[must_use]
708pub const fn start_delay_bytes(delay: u16) -> [u8; 2] {
709 delay.to_be_bytes()
710}
711
712/// The order a packet is loaded and sent in.
713///
714/// A timed send carries one step the other two do not, since only it programs the counter
715/// value to go out at.
716///
717/// # Arguments
718///
719/// * `chain` - which transmit chain is sending.
720/// * `trigger` - what the send waits for.
721/// * `start_delay` - what [`start_delay`] worked out for this packet.
722///
723/// # Returns
724///
725/// The steps, in the order the chip takes them.
726///
727/// # Examples
728///
729/// ```
730/// use pamoja_radios::sx1302::tx::{steps, Chain, Send, Trigger};
731///
732/// // A send that goes out now: delay, buffer, trigger.
733/// assert_eq!(steps(Chain::A, Trigger::Immediate, 1_000).count(), 6);
734///
735/// // A timed one programs the counter as well.
736/// assert_eq!(steps(Chain::A, Trigger::At(1_000_000), 1_000).count(), 7);
737///
738/// let mut order = steps(Chain::A, Trigger::Immediate, 1_000);
739/// assert!(matches!(order.next(), Some(Send::SetStartDelay(1_000))));
740/// assert!(matches!(order.next(), Some(Send::OpenBuffer(_))));
741/// assert!(matches!(order.next(), Some(Send::WritePayload(0x5300))));
742/// ```
743pub fn steps(chain: Chain, trigger: Trigger, start_delay: u16) -> impl Iterator<Item = Send> {
744 let arm = chain.trigger(trigger);
745 let timed = match trigger {
746 Trigger::At(at_us) => Some(Send::SetTimerTrigger(trigger_value(at_us, start_delay))),
747 Trigger::Immediate | Trigger::OnGps => None,
748 };
749 [
750 Some(Send::SetStartDelay(start_delay)),
751 Some(Send::OpenBuffer(chain.write_buffer())),
752 Some(Send::WritePayload(chain.buffer())),
753 Some(Send::CloseBuffer(chain.write_buffer())),
754 timed,
755 Some(Send::ResetTrigger(arm)),
756 Some(Send::ArmTrigger(arm)),
757 ]
758 .into_iter()
759 .flatten()
760}
761
762/// What the modulation register takes for LoRa, which is also what it takes for an
763/// unmodulated carrier. [`Chain::if_source`] is what parts the two.
764pub const MODULATION_LORA: u8 = 0x00;
765
766/// What it takes for frequency shift keying.
767pub const MODULATION_FSK: u8 = 0x01;
768
769/// What the sample source takes for LoRa.
770pub const IF_SOURCE_LORA: u8 = 0x01;
771
772/// What it takes for frequency shift keying.
773pub const IF_SOURCE_FSK: u8 = 0x02;
774
775/// What the modem mode takes to transmit.
776pub const CAD_RX_TX_TRANSMIT: u8 = 0x02;
777
778/// The chirp filtering a spreading factor below ten is sent with.
779pub const CHIRP_LOWPASS_FAST: u8 = 6;
780
781/// And what the slower ones are sent with.
782pub const CHIRP_LOWPASS_SLOW: u8 = 7;
783
784/// The sync symbol positions a public network transmits with.
785pub const PUBLIC_PEAKS: (u8, u8) = (6, 8);
786
787/// The ones a private network uses, which is what the chain powers up holding.
788pub const PRIVATE_PEAKS: (u8, u8) = (2, 4);
789
790/// Turns a carrier into the value the transmit chain is tuned with.
791///
792/// This is the concentrator's own scaling and it is not the one a front end takes: the
793/// divisor is the same 32 MHz but the shift is eighteen rather than twenty five, so using
794/// [`sx1250::frequency_value`](super::sx1250::frequency_value) here would be wrong by a
795/// factor of 128.
796///
797/// # Arguments
798///
799/// * `hertz` - the carrier.
800///
801/// # Returns
802///
803/// The value, of which three bytes are written most significant first.
804///
805/// # Examples
806///
807/// ```
808/// use pamoja_radios::sx1302::tx::frequency_value;
809///
810/// // Worked from the ratio rather than from the function: 868100000 * 2^18 / 32000000.
811/// assert_eq!(frequency_value(868_100_000), 7_111_475);
812/// ```
813#[must_use]
814pub const fn frequency_value(hertz: u32) -> u32 {
815 ((hertz as u64 * (1 << 18)) / 32_000_000) as u32
816}
817
818/// Turns a bandwidth into the value the modulator takes.
819///
820/// The values do not start at zero and they are not hertz, so a plausible guess is wrong by
821/// four.
822///
823/// # Arguments
824///
825/// * `bandwidth_hz` - the bandwidth.
826///
827/// # Returns
828///
829/// The value, or `None` for a bandwidth this chip does not transmit at.
830///
831/// # Examples
832///
833/// ```
834/// use pamoja_radios::sx1302::tx::bandwidth_value;
835///
836/// assert_eq!(bandwidth_value(125_000), Some(4));
837/// assert_eq!(bandwidth_value(250_000), Some(5));
838/// assert_eq!(bandwidth_value(500_000), Some(6));
839/// assert_eq!(bandwidth_value(62_500), None);
840/// ```
841#[must_use]
842pub const fn bandwidth_value(bandwidth_hz: u32) -> Option<u8> {
843 match bandwidth_hz {
844 125_000 => Some(4),
845 250_000 => Some(5),
846 500_000 => Some(6),
847 _ => None,
848 }
849}
850
851/// One entry of the table that turns a wanted power into what the chain is set to.
852///
853/// A concentrator is not told a number of decibels. It is told an amplifier setting and an
854/// index into the front end's own power steps, and which pair gives which radiated power is a
855/// property of the board: its amplifier, its filters, and its losses. So the table belongs to
856/// whoever built the gateway, and the one here is the reference design's.
857#[derive(Clone, Copy, Debug, PartialEq, Eq)]
858pub struct Gain {
859 /// What this entry radiates, in dBm.
860 pub radiated_dbm: i8,
861 /// The external amplifier setting. Anything above zero switches it on.
862 pub amplifier: u8,
863 /// The index into the front end's power steps.
864 pub power_index: u8,
865 /// The digital gain applied ahead of the front end.
866 pub digital_gain: u8,
867 /// The calibrated offset of the in-phase channel.
868 pub offset_i: i8,
869 /// The calibrated offset of the quadrature channel.
870 pub offset_q: i8,
871}
872
873impl Gain {
874 /// What the power register takes for this entry.
875 ///
876 /// # Returns
877 ///
878 /// The amplifier bit above the power index, which is how an SX1250 board is set.
879 ///
880 /// # Examples
881 ///
882 /// ```
883 /// use pamoja_radios::sx1302::tx::DEFAULT_GAINS;
884 ///
885 /// // The lowest entry runs the amplifier off, so only the index is carried.
886 /// assert_eq!(DEFAULT_GAINS[0].power_value(), 15);
887 ///
888 /// // The highest runs it on, which is the bit above.
889 /// assert_eq!(DEFAULT_GAINS[15].power_value(), 0x40 | 14);
890 /// ```
891 #[must_use]
892 pub const fn power_value(&self) -> u8 {
893 let amplifier = if self.amplifier > 0 { 1 } else { 0 };
894 (amplifier << 6) | (self.power_index & 0x3f)
895 }
896}
897
898/// The table the reference design carries for an SX1250 board on the European band.
899///
900/// A gateway with different hardware has a different table, which is why a configuration can
901/// name its own. Nothing here is a law of the chip.
902pub const DEFAULT_GAINS: [Gain; 16] = [
903 gain(12, 0, 15),
904 gain(13, 0, 16),
905 gain(14, 0, 17),
906 gain(15, 0, 19),
907 gain(16, 0, 20),
908 gain(17, 0, 22),
909 gain(18, 1, 1),
910 gain(19, 1, 2),
911 gain(20, 1, 3),
912 gain(21, 1, 4),
913 gain(22, 1, 5),
914 gain(23, 1, 6),
915 gain(24, 1, 7),
916 gain(25, 1, 9),
917 gain(26, 1, 11),
918 gain(27, 1, 14),
919];
920
921/// One entry of the reference table, whose digital gain and offsets are all zero.
922const fn gain(radiated_dbm: i8, amplifier: u8, power_index: u8) -> Gain {
923 Gain {
924 radiated_dbm,
925 amplifier,
926 power_index,
927 digital_gain: 0,
928 offset_i: 0,
929 offset_q: 0,
930 }
931}
932
933/// Picks the entry to transmit a wanted power at.
934///
935/// The strongest entry that does not exceed what was asked for, so a gateway told to
936/// transmit at more than its table reaches transmits at the most it has rather than refusing
937/// or exceeding it. A request below the whole table takes the weakest entry.
938///
939/// # Arguments
940///
941/// * `table` - the board's table, strongest last.
942/// * `wanted_dbm` - the power asked for.
943///
944/// # Returns
945///
946/// The entry, or `None` for an empty table.
947///
948/// # Examples
949///
950/// ```
951/// use pamoja_radios::sx1302::tx::{gain_for, DEFAULT_GAINS};
952///
953/// // Exactly on an entry, and between two.
954/// assert_eq!(gain_for(&DEFAULT_GAINS, 20).map(|g| g.radiated_dbm), Some(20));
955/// assert_eq!(gain_for(&DEFAULT_GAINS, 16).map(|g| g.radiated_dbm), Some(16));
956///
957/// // Past the top of the table, and below the bottom of it.
958/// assert_eq!(gain_for(&DEFAULT_GAINS, 30).map(|g| g.radiated_dbm), Some(27));
959/// assert_eq!(gain_for(&DEFAULT_GAINS, 2).map(|g| g.radiated_dbm), Some(12));
960/// ```
961#[must_use]
962pub fn gain_for(table: &[Gain], wanted_dbm: i8) -> Option<Gain> {
963 table
964 .iter()
965 .rev()
966 .find(|entry| entry.radiated_dbm <= wanted_dbm)
967 .or_else(|| table.first())
968 .copied()
969}
970
971/// Why a packet cannot be described to the chain.
972#[derive(Clone, Copy, Debug, PartialEq, Eq)]
973pub enum TransmitError {
974 /// A bandwidth the transmit chain does not run at.
975 Bandwidth {
976 /// The bandwidth asked for, in hertz.
977 hertz: u32,
978 },
979 /// A packet longer than the length register can describe.
980 PayloadTooLong {
981 /// How many bytes were offered.
982 bytes: usize,
983 },
984}
985
986impl core::fmt::Display for TransmitError {
987 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
988 match self {
989 TransmitError::Bandwidth { hertz } => {
990 write!(f, "a chain does not transmit at {hertz} Hz")
991 }
992 TransmitError::PayloadTooLong { bytes } => write!(
993 f,
994 "a packet of {bytes} bytes is longer than the length register describes"
995 ),
996 }
997 }
998}
999
1000impl core::error::Error for TransmitError {}
1001
1002/// A packet to put on the air, and how.
1003///
1004/// Everything the chain has to be told before a payload means anything. Built from the link
1005/// settings the rest of pamoja works in, so the airtime, the duty cycle and the transmission
1006/// itself all come from one description rather than three.
1007#[derive(Clone, Copy, Debug)]
1008pub struct Transmit<'a> {
1009 /// The carrier to transmit on.
1010 pub frequency_hz: u32,
1011 /// The spreading factor, bandwidth, coding rate, preamble, header and checksum.
1012 pub link: LinkSettings,
1013 /// What the chain is set to, from the board's table.
1014 pub gain: Gain,
1015 /// Whether the chirp sweeps the other way, which every LoRaWAN downlink does.
1016 pub invert_polarity: bool,
1017 /// Whether the network is a public one, which picks the sync word.
1018 pub public: bool,
1019 /// The packet.
1020 pub payload: &'a [u8],
1021}
1022
1023/// Everything a chain is told before a packet is loaded.
1024///
1025/// The order is the reference order. A modulator is configured and then started, and the
1026/// payload and the trigger that [`steps`] carries come after all of it.
1027///
1028/// # Arguments
1029///
1030/// * `chain` - the chain that sends it.
1031/// * `transmit` - what to send and how.
1032///
1033/// # Returns
1034///
1035/// The writes, in order.
1036///
1037/// # Errors
1038///
1039/// Returns [`TransmitError::Bandwidth`] for a bandwidth the chain does not run at, and
1040/// [`TransmitError::PayloadTooLong`] for a packet the length register cannot describe.
1041pub fn settings(
1042 chain: Chain,
1043 transmit: &Transmit<'_>,
1044) -> Result<[(Register, u8); 31], TransmitError> {
1045 let link = &transmit.link;
1046 let bandwidth = bandwidth_value(link.bandwidth_hz()).ok_or(TransmitError::Bandwidth {
1047 hertz: link.bandwidth_hz(),
1048 })?;
1049 let length =
1050 u8::try_from(transmit.payload.len()).map_err(|_| TransmitError::PayloadTooLong {
1051 bytes: transmit.payload.len(),
1052 })?;
1053 let carrier = frequency_value(transmit.frequency_hz);
1054
1055 // The deviation is half the bandwidth in the chip's own frequency units, and its high
1056 // half is four bits wide, which every supported bandwidth fits inside.
1057 let deviation = frequency_value(link.bandwidth_hz() / 2);
1058 let preamble = link.preamble_symbols();
1059 let spreading_factor = link.spreading_factor();
1060
1061 let (peak1, peak2) = if transmit.public {
1062 PUBLIC_PEAKS
1063 } else {
1064 PRIVATE_PEAKS
1065 };
1066
1067 // The two lowest spreading factors have no public form and need the fine synchronization
1068 // the others do not.
1069 let low = spreading_factor <= 6;
1070 let (peak1, peak2) = if low { PRIVATE_PEAKS } else { (peak1, peak2) };
1071
1072 let lowpass = if spreading_factor < 10 {
1073 CHIRP_LOWPASS_FAST
1074 } else {
1075 CHIRP_LOWPASS_SLOW
1076 };
1077
1078 Ok([
1079 (chain.modulation_type(), MODULATION_LORA),
1080 (chain.if_source(), IF_SOURCE_LORA),
1081 (chain.offset_i(), transmit.gain.offset_i as u8),
1082 (chain.offset_q(), transmit.gain.offset_q as u8),
1083 (chain.power(), transmit.gain.power_value()),
1084 (chain.digital_gain(), transmit.gain.digital_gain),
1085 (chain.frequency_byte(0), ((carrier >> 16) & 0xff) as u8),
1086 (chain.frequency_byte(1), ((carrier >> 8) & 0xff) as u8),
1087 (chain.frequency_byte(2), (carrier & 0xff) as u8),
1088 (chain.agc_bandwidth(), bandwidth),
1089 (chain.deviation_msb(), ((deviation >> 8) & 0xff) as u8),
1090 (chain.deviation_lsb(), (deviation & 0xff) as u8),
1091 (chain.bandwidth(), bandwidth),
1092 (chain.preamble_msb(), ((preamble >> 8) & 0xff) as u8),
1093 (chain.preamble_lsb(), (preamble & 0xff) as u8),
1094 (chain.spreading_factor(), spreading_factor),
1095 (chain.chirp_lowpass(), lowpass),
1096 (
1097 chain.coding_rate(),
1098 link.coding_rate_denominator().saturating_sub(4),
1099 ),
1100 (chain.modem_enable(), 1),
1101 (chain.cad_rx_tx(), CAD_RX_TX_TRANSMIT),
1102 (chain.modem_start(), 1),
1103 (chain.continuous(), 0),
1104 (chain.chirp_invert(), u8::from(transmit.invert_polarity)),
1105 (chain.implicit_header(), u8::from(!link.explicit_header())),
1106 (chain.crc_enable(), u8::from(link.crc())),
1107 (chain.sync_peak1(), peak1),
1108 (chain.sync_peak2(), peak2),
1109 (chain.fine_sync(), u8::from(low)),
1110 (chain.payload_length(), length),
1111 (chain.header_control(), 0),
1112 (
1113 chain.ppm_offset(),
1114 u8::from(link.low_data_rate_optimization()),
1115 ),
1116 ])
1117}
1118
1119#[cfg(test)]
1120mod tests {
1121 use super::*;
1122
1123 /// What a chain is told for one packet, so a test can look a register up by name.
1124 fn settings_for(link: LinkSettings, public: bool, payload: &[u8]) -> [(Register, u8); 31] {
1125 let request = Transmit {
1126 frequency_hz: 868_100_000,
1127 link,
1128 gain: DEFAULT_GAINS[8],
1129 invert_polarity: true,
1130 public,
1131 payload,
1132 };
1133 settings(Chain::A, &request).expect("a packet the chain can describe")
1134 }
1135
1136 fn value_of(written: &[(Register, u8)], want: Register) -> u8 {
1137 written
1138 .iter()
1139 .find_map(|(named, value)| (*named == want).then_some(*value))
1140 .expect("the register is written")
1141 }
1142
1143 #[test]
1144 fn a_packet_is_described_in_the_units_the_chip_counts_in() {
1145 let link = LinkSettings::new(7, 125_000)
1146 .with_coding_rate(5)
1147 .with_preamble(8);
1148 let written = settings_for(link, true, &[0; 12]);
1149 let chain = Chain::A;
1150
1151 // None of these are the numbers a person would write. The bandwidth is 4 rather than
1152 // 125000 or an index from zero, the coding rate is the denominator less four, and the
1153 // spreading factor is the only one that is itself.
1154 assert_eq!(value_of(&written, chain.bandwidth()), 4);
1155 assert_eq!(value_of(&written, chain.agc_bandwidth()), 4);
1156 assert_eq!(value_of(&written, chain.coding_rate()), 1);
1157 assert_eq!(value_of(&written, chain.spreading_factor()), 7);
1158
1159 // The carrier is three bytes of the chip's own scaling, most significant first.
1160 assert_eq!(value_of(&written, chain.frequency_byte(0)), 0x6c);
1161 assert_eq!(value_of(&written, chain.frequency_byte(1)), 0x83);
1162 assert_eq!(value_of(&written, chain.frequency_byte(2)), 0x33);
1163
1164 // The deviation is half the bandwidth in those same units, and its high half is four
1165 // bits wide, which 512 fits inside.
1166 assert_eq!(value_of(&written, chain.deviation_msb()), 2);
1167 assert_eq!(value_of(&written, chain.deviation_lsb()), 0);
1168
1169 assert_eq!(value_of(&written, chain.payload_length()), 12);
1170 assert_eq!(value_of(&written, chain.preamble_lsb()), 8);
1171 assert_eq!(value_of(&written, chain.preamble_msb()), 0);
1172 assert_eq!(value_of(&written, chain.chirp_invert()), 1);
1173 }
1174
1175 #[test]
1176 fn the_modulator_is_named_by_its_sample_source_rather_than_its_type() {
1177 let link = LinkSettings::new(9, 125_000);
1178 let written = settings_for(link, true, &[0; 4]);
1179
1180 // Writing one into the modulation type would select frequency shift keying. LoRa and
1181 // an unmodulated carrier share it, and the sample source is what parts them.
1182 assert_eq!(
1183 value_of(&written, Chain::A.modulation_type()),
1184 MODULATION_LORA
1185 );
1186 assert_eq!(value_of(&written, Chain::A.if_source()), IF_SOURCE_LORA);
1187 assert_ne!(MODULATION_LORA, MODULATION_FSK);
1188 }
1189
1190 #[test]
1191 fn a_transmission_says_which_network_it_belongs_to() {
1192 let link = LinkSettings::new(7, 125_000);
1193 let public = settings_for(link, true, &[0; 4]);
1194 let private = settings_for(link, false, &[0; 4]);
1195
1196 assert_eq!(value_of(&public, Chain::A.sync_peak1()), 6);
1197 assert_eq!(value_of(&public, Chain::A.sync_peak2()), 8);
1198 assert_eq!(value_of(&private, Chain::A.sync_peak1()), 2);
1199 assert_eq!(value_of(&private, Chain::A.sync_peak2()), 4);
1200
1201 // The two lowest spreading factors have no public form, and take the fine
1202 // synchronization the others do not.
1203 let low = settings_for(LinkSettings::new(5, 125_000), true, &[0; 4]);
1204 assert_eq!(value_of(&low, Chain::A.sync_peak1()), 2);
1205 assert_eq!(value_of(&low, Chain::A.sync_peak2()), 4);
1206 assert_eq!(value_of(&low, Chain::A.fine_sync()), 1);
1207 assert_eq!(value_of(&public, Chain::A.fine_sync()), 0);
1208 }
1209
1210 #[test]
1211 fn the_slow_spreading_factors_are_sent_the_way_their_airtime_assumes() {
1212 let fast = settings_for(LinkSettings::new(7, 125_000), true, &[0; 4]);
1213 let slow = settings_for(LinkSettings::new(11, 125_000), true, &[0; 4]);
1214
1215 // Low data rate optimization is on exactly when a symbol runs past sixteen
1216 // milliseconds, which is what the airtime already assumes.
1217 assert_eq!(value_of(&fast, Chain::A.ppm_offset()), 0);
1218 assert_eq!(value_of(&slow, Chain::A.ppm_offset()), 1);
1219
1220 // And the chirp is filtered harder from SF10 upward.
1221 assert_eq!(
1222 value_of(&fast, Chain::A.chirp_lowpass()),
1223 CHIRP_LOWPASS_FAST
1224 );
1225 assert_eq!(
1226 value_of(&slow, Chain::A.chirp_lowpass()),
1227 CHIRP_LOWPASS_SLOW
1228 );
1229 }
1230
1231 #[test]
1232 fn a_packet_the_chain_cannot_describe_is_refused() {
1233 let narrow = Transmit {
1234 frequency_hz: 868_100_000,
1235 link: LinkSettings::new(7, 62_500),
1236 gain: DEFAULT_GAINS[0],
1237 invert_polarity: false,
1238 public: true,
1239 payload: &[0; 4],
1240 };
1241 assert_eq!(
1242 settings(Chain::A, &narrow),
1243 Err(TransmitError::Bandwidth { hertz: 62_500 })
1244 );
1245
1246 // A packet longer than the length register describes is refused rather than sent
1247 // with a length that is not its own.
1248 let long = Transmit {
1249 frequency_hz: 868_100_000,
1250 link: LinkSettings::new(7, 125_000),
1251 gain: DEFAULT_GAINS[0],
1252 invert_polarity: false,
1253 public: true,
1254 payload: &[0; 300],
1255 };
1256 assert_eq!(
1257 settings(Chain::A, &long),
1258 Err(TransmitError::PayloadTooLong { bytes: 300 })
1259 );
1260 }
1261
1262 #[test]
1263 fn each_chain_has_its_own_buffer_and_registers() {
1264 assert_eq!(Chain::A.buffer(), 0x5300);
1265 assert_eq!(Chain::B.buffer(), 0x5500);
1266 assert_ne!(Chain::A.buffer(), Chain::B.buffer());
1267
1268 // The buffers sit inside the blocks that belong to each chain.
1269 assert!(Chain::A.buffer() > Chain::A.base());
1270 assert!(Chain::B.buffer() > Chain::B.base());
1271 assert!(Chain::A.buffer() < Chain::B.base());
1272 }
1273
1274 #[test]
1275 fn the_status_values_are_the_ones_the_chip_reports() {
1276 assert_eq!(TxStatus::of(0x80), TxStatus::Free);
1277 assert!(TxStatus::of(0x80).is_free());
1278
1279 for scheduled in [0x91, 0x92] {
1280 assert_eq!(TxStatus::of(scheduled), TxStatus::Scheduled);
1281 assert!(!TxStatus::of(scheduled).is_free());
1282 }
1283 for emitting in [0x30, 0x50, 0x60, 0x70] {
1284 assert_eq!(TxStatus::of(emitting), TxStatus::Emitting);
1285 assert!(!TxStatus::of(emitting).is_free());
1286 }
1287
1288 // Anything else is carried rather than mistaken for idle.
1289 assert_eq!(TxStatus::of(0x00), TxStatus::Unknown(0x00));
1290 assert!(!TxStatus::of(0x00).is_free());
1291 }
1292
1293 #[test]
1294 fn the_chip_always_starts_early_and_never_late() {
1295 // Whatever the settings, the delay comes off the base rather than adding to it.
1296 for bandwidth in [125_000, 250_000, 500_000] {
1297 for lowpass in 0..=8 {
1298 for front_end in [FrontEnd::Sx1250, FrontEnd::Sx125x] {
1299 let delay = start_delay(front_end, bandwidth, lowpass)
1300 .expect("these are supported bandwidths");
1301 assert!(
1302 delay < START_DELAY_BASE,
1303 "{front_end:?} {bandwidth} {lowpass}"
1304 );
1305 }
1306 }
1307 }
1308 }
1309
1310 #[test]
1311 fn the_front_ends_settle_at_their_own_pace() {
1312 // The two differ, which is the whole reason the front end is a parameter.
1313 let modern = start_delay(FrontEnd::Sx1250, 125_000, 6).expect("supported");
1314 let earlier = start_delay(FrontEnd::Sx125x, 125_000, 6).expect("supported");
1315 assert_ne!(modern, earlier);
1316
1317 // The older part takes longer to settle, so it starts earlier still.
1318 assert!(earlier < modern);
1319 }
1320
1321 #[test]
1322 fn a_bandwidth_no_front_end_covers_has_no_answer() {
1323 for bandwidth in [7_800, 62_500, 1_000_000] {
1324 assert_eq!(start_delay(FrontEnd::Sx1250, bandwidth, 6), None);
1325 assert_eq!(start_delay(FrontEnd::Sx125x, bandwidth, 6), None);
1326 }
1327 }
1328
1329 #[test]
1330 fn a_wider_filter_takes_longer_to_fill() {
1331 // The filter term grows with the setting, so the chip has to start earlier.
1332 let narrow = start_delay(FrontEnd::Sx1250, 125_000, 2).expect("supported");
1333 let wide = start_delay(FrontEnd::Sx1250, 125_000, 8).expect("supported");
1334 assert!(wide < narrow, "a wider filter means an earlier start");
1335 }
1336
1337 #[test]
1338 fn a_timed_send_is_programmed_earlier_than_the_moment_asked_for() {
1339 // The window is counted in microseconds and the chip in ticks.
1340 let delay = 48_000u16;
1341 let value = trigger_value(1_000_000, delay);
1342 assert_eq!(value, 1_000_000 * TICKS_PER_US - u32::from(delay));
1343
1344 // Which is earlier than the plain conversion would be.
1345 assert!(value < 1_000_000 * TICKS_PER_US);
1346 }
1347
1348 #[test]
1349 fn the_two_words_go_out_in_opposite_orders() {
1350 // The trigger is least significant first and the delay most significant first, which
1351 // is the kind of thing that reads correctly and transmits at the wrong moment.
1352 assert_eq!(trigger_bytes(0x1234_5678), [0x78, 0x56, 0x34, 0x12]);
1353 assert_eq!(start_delay_bytes(0x1234), [0x12, 0x34]);
1354
1355 // Round trips, so neither is quietly reversed.
1356 let value = 0xdead_beefu32;
1357 assert_eq!(u32::from_le_bytes(trigger_bytes(value)), value);
1358 let delay = 0xbeefu16;
1359 assert_eq!(u16::from_be_bytes(start_delay_bytes(delay)), delay);
1360 }
1361
1362 #[test]
1363 fn a_trigger_that_wraps_the_counter_still_reads_back() {
1364 // The counter is thirty-two bits and wraps about every thirty-six minutes, so a
1365 // send near the wrap must not panic or saturate.
1366 let near_wrap = trigger_value(u32::MAX / TICKS_PER_US, 48_000);
1367 assert_eq!(
1368 near_wrap,
1369 (u32::MAX / TICKS_PER_US)
1370 .wrapping_mul(TICKS_PER_US)
1371 .wrapping_sub(48_000)
1372 );
1373
1374 // And a moment so early that the delay takes it below zero wraps rather than panics.
1375 let underflow = trigger_value(0, 48_000);
1376 assert_eq!(underflow, 0u32.wrapping_sub(48_000));
1377 }
1378}