pamoja_radios/sx1302/timestamp.rs
1//! The counter a concentrator stamps packets against.
2//!
3//! Every packet the chip hears carries the value of a counter running at 32 MHz, and every
4//! packet the chip sends at a chosen moment is given a value of that same counter. It is what
5//! a receive window is measured against, so a gateway that answers an uplink is really doing
6//! arithmetic on this counter and nothing else.
7//!
8//! Two things make it harder than reading a number. The counter is 27 bits wide, so it rolls
9//! over about every two minutes, and the chip does not say how many times it has. A reader
10//! therefore keeps a little state: the last value it saw and how many times it has seen the
11//! count go backward. A packet is widened against that same state, and the rule is not the
12//! same one, because a packet whose count is above the last reading was heard before the most
13//! recent rollover rather than after it.
14//!
15//! Nothing here reads a register. The bytes come from one burst and this turns them into a
16//! counter, so the arithmetic can be checked without hardware.
17
18use super::register::TIMESTAMP_BASE;
19
20/// Where both counters are read from, in one burst.
21pub const COUNTERS: u16 = TIMESTAMP_BASE + 1;
22
23/// How many bytes that burst carries: four for each counter.
24pub const COUNTERS_LEN: usize = 8;
25
26/// How many ticks of the counter make a microsecond.
27pub const TICKS_PER_US: u32 = 32;
28
29/// How many bits the counter actually holds.
30pub const COUNTER_BITS: u32 = 27;
31
32/// How many rollovers are counted before the count itself repeats.
33pub const WRAPS: u8 = 32;
34
35/// One counter, and what is known about how often it has rolled over.
36#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
37pub struct Tracked {
38 /// The last value read, in microseconds.
39 pub reference: u32,
40 /// How many times it has been seen to roll over, kept to five bits.
41 pub wraps: u8,
42}
43
44impl Tracked {
45 /// Takes a new reading, noticing a rollover.
46 ///
47 /// # Arguments
48 ///
49 /// * `now` - the value just read, in microseconds.
50 ///
51 /// # Examples
52 ///
53 /// ```
54 /// use pamoja_radios::sx1302::timestamp::Tracked;
55 ///
56 /// let mut counter = Tracked::default();
57 /// counter.advance(1_000);
58 /// assert_eq!(counter.wraps, 0);
59 ///
60 /// // A reading below the last one can only mean it went round.
61 /// counter.advance(5);
62 /// assert_eq!(counter.wraps, 1);
63 /// ```
64 pub const fn advance(&mut self, now: u32) {
65 if now < self.reference {
66 self.wraps = (self.wraps + 1) % WRAPS;
67 }
68 self.reference = now;
69 }
70
71 /// The last reading, widened past the rollover.
72 ///
73 /// # Returns
74 ///
75 /// The value in microseconds, counting every rollover so far.
76 ///
77 /// # Examples
78 ///
79 /// ```
80 /// use pamoja_radios::sx1302::timestamp::Tracked;
81 ///
82 /// let mut counter = Tracked::default();
83 /// counter.advance(1_000);
84 /// counter.advance(5);
85 ///
86 /// // One rollover, so the reading sits a whole counter above where it reads.
87 /// assert_eq!(counter.widened(), (1 << 27) | 5);
88 /// ```
89 #[must_use]
90 pub const fn widened(&self) -> u32 {
91 ((self.wraps as u32) << COUNTER_BITS) | self.reference
92 }
93}
94
95/// Both counters a concentrator keeps.
96#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
97pub struct Counter {
98 /// The one that free runs, which packets are stamped against.
99 pub free: Tracked,
100 /// The one a pulse per second line latches, which disciplines the other.
101 pub pulse: Tracked,
102}
103
104impl Counter {
105 /// A counter that has read nothing yet.
106 ///
107 /// # Returns
108 ///
109 /// The counter, at zero and having seen no rollover.
110 #[must_use]
111 pub const fn new() -> Counter {
112 Counter {
113 free: Tracked {
114 reference: 0,
115 wraps: 0,
116 },
117 pulse: Tracked {
118 reference: 0,
119 wraps: 0,
120 },
121 }
122 }
123
124 /// Takes a reading from the eight bytes the chip answers with.
125 ///
126 /// The pulse counter comes first and the free running one after it, each most significant
127 /// byte first, and both are scaled from the 32 MHz they count at.
128 ///
129 /// # Arguments
130 ///
131 /// * `bytes` - what the burst read answered with.
132 ///
133 /// # Returns
134 ///
135 /// The free running counter and then the pulse one, both in microseconds and both
136 /// widened past any rollover seen so far.
137 ///
138 /// # Examples
139 ///
140 /// ```
141 /// use pamoja_radios::sx1302::timestamp::Counter;
142 ///
143 /// let mut counter = Counter::new();
144 ///
145 /// // The free running counter reads 64 ticks, which is two microseconds.
146 /// let (free, pulse) = counter.read(&[0, 0, 0, 32, 0, 0, 0, 64]);
147 /// assert_eq!((free, pulse), (2, 1));
148 /// ```
149 pub const fn read(&mut self, bytes: &[u8; COUNTERS_LEN]) -> (u32, u32) {
150 let pulse = microseconds([bytes[0], bytes[1], bytes[2], bytes[3]]);
151 let free = microseconds([bytes[4], bytes[5], bytes[6], bytes[7]]);
152 self.pulse.advance(pulse);
153 self.free.advance(free);
154 (self.free.widened(), self.pulse.widened())
155 }
156
157 /// Widens the counter value a packet was stamped with.
158 ///
159 /// A packet reading above the last counter reading was heard before the most recent
160 /// rollover, not after it, so it belongs to the wrap count before this one.
161 ///
162 /// # Arguments
163 ///
164 /// * `packet` - what the packet carried, in microseconds.
165 ///
166 /// # Returns
167 ///
168 /// The value widened past the rollovers seen so far.
169 ///
170 /// # Examples
171 ///
172 /// ```
173 /// use pamoja_radios::sx1302::timestamp::Counter;
174 ///
175 /// let mut counter = Counter::new();
176 /// counter.read(&[0, 0, 0, 0, 0, 0, 0, 32]);
177 ///
178 /// // A packet heard just before the counter reading sits in the same stretch.
179 /// assert_eq!(counter.widened_packet(1), 1);
180 /// ```
181 #[must_use]
182 pub const fn widened_packet(&self, packet: u32) -> u32 {
183 let wraps = if self.free.reference >= packet {
184 self.free.wraps
185 } else {
186 self.free.wraps.wrapping_sub(1)
187 } % WRAPS;
188 ((wraps as u32) << COUNTER_BITS) | packet
189 }
190}
191
192/// Whether a second reading of the counters disagrees with the first.
193///
194/// A read of eight bytes can catch the counter mid-step, so the reference reads them twice
195/// and takes a third reading when either high byte has moved. It is a different guard from
196/// the one on the receive byte count, where the larger of two readings is kept instead.
197///
198/// # Arguments
199///
200/// * `first` - the first reading.
201/// * `second` - the second.
202///
203/// # Returns
204///
205/// Whether either counter stepped over a high byte between them.
206///
207/// # Examples
208///
209/// ```
210/// use pamoja_radios::sx1302::timestamp::disagrees;
211///
212/// // The low bytes moving is ordinary; the counter is running.
213/// assert!(!disagrees(&[1, 0, 0, 0, 2, 0, 0, 0], &[1, 0, 0, 9, 2, 0, 0, 9]));
214///
215/// // A high byte moving means the reading straddles a step.
216/// assert!(disagrees(&[1, 0, 0, 0, 2, 0, 0, 0], &[1, 0, 0, 0, 3, 0, 0, 0]));
217/// ```
218#[must_use]
219pub const fn disagrees(first: &[u8; COUNTERS_LEN], second: &[u8; COUNTERS_LEN]) -> bool {
220 first[0] != second[0] || first[4] != second[4]
221}
222
223/// Turns the four bytes of a counter into microseconds.
224const fn microseconds(bytes: [u8; 4]) -> u32 {
225 u32::from_be_bytes(bytes) / TICKS_PER_US
226}
227
228#[cfg(test)]
229mod tests {
230 use super::*;
231
232 #[test]
233 fn the_counter_is_scaled_from_the_rate_it_runs_at() {
234 // 32 MHz, so a microsecond is 32 ticks and nothing else.
235 let mut counter = Counter::new();
236 let (free, pulse) = counter.read(&[0, 0, 0x01, 0x00, 0, 0, 0x02, 0x00]);
237 assert_eq!((free, pulse), (16, 8));
238 }
239
240 #[test]
241 fn a_reading_that_goes_backward_is_a_rollover() {
242 let mut counter = Counter::new();
243 counter.read(&[0, 0, 0, 0, 0x07, 0xff, 0xff, 0xe0]);
244 let before = counter.free.wraps;
245
246 // The next reading is lower, which a counter running forward cannot do.
247 counter.read(&[0, 0, 0, 0, 0, 0, 0, 0x20]);
248 assert_eq!(counter.free.wraps, before + 1);
249 assert_eq!(counter.free.widened(), (1 << COUNTER_BITS) | 1);
250 }
251
252 #[test]
253 fn a_packet_heard_before_a_rollover_keeps_the_earlier_count() {
254 let mut counter = Counter::new();
255
256 // The counter has gone round once and now reads low.
257 counter.read(&[0, 0, 0, 0, 0x07, 0xff, 0xff, 0xe0]);
258 counter.read(&[0, 0, 0, 0, 0, 0, 0, 0x20]);
259 assert_eq!(counter.free.wraps, 1);
260
261 // A packet stamped below the reading belongs to this stretch.
262 assert_eq!(counter.widened_packet(1), (1 << COUNTER_BITS) | 1);
263
264 // One stamped above it was heard before the rollover, so it belongs to the last.
265 assert_eq!(counter.widened_packet(0x07ff_fff0), 0x07ff_fff0);
266 }
267
268 #[test]
269 fn the_wrap_count_comes_back_round_rather_than_running_off() {
270 let mut counter = Counter::new();
271 for _ in 0..WRAPS {
272 counter.read(&[0, 0, 0, 0, 0, 0, 0x20, 0x00]);
273 counter.read(&[0, 0, 0, 0, 0, 0, 0, 0x20]);
274 }
275 assert!(counter.free.wraps < WRAPS, "{}", counter.free.wraps);
276
277 // And a packet read against a fresh counter takes the count below zero, which has to
278 // come back round rather than underflow.
279 let fresh = Counter::new();
280 assert_eq!(fresh.widened_packet(5), (31 << COUNTER_BITS) | 5);
281 }
282
283 #[test]
284 fn only_a_high_byte_moving_forces_a_third_reading() {
285 let first = [0x00, 0x11, 0x22, 0x33, 0x00, 0x44, 0x55, 0x66];
286 assert!(!disagrees(
287 &first,
288 &[0x00, 0x11, 0x22, 0x99, 0x00, 0x44, 0x55, 0x99]
289 ));
290 assert!(disagrees(
291 &first,
292 &[0x01, 0x11, 0x22, 0x33, 0x00, 0x44, 0x55, 0x66]
293 ));
294 assert!(disagrees(
295 &first,
296 &[0x00, 0x11, 0x22, 0x33, 0x01, 0x44, 0x55, 0x66]
297 ));
298 }
299}