Skip to main content

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}