Skip to main content

pamoja_radios/sx1302/
chip.rs

1//! Telling the concentrator apart from its relatives, and from nothing at all.
2//!
3//! Two questions get asked of a board before anything is loaded onto it. The version register
4//! answers the first: a concentrator that is powered, wired the right way round, and clocked
5//! reads back [`EXPECTED_VERSION`], and a board that reads anything else is not talking.
6//!
7//! The part number answers the second, and it lives in one-time programmable memory rather
8//! than a register, so it takes two transfers: select the byte, then read it. The SX1302 and
9//! the SX1303 are the same silicon to the SPI bus, and only this byte separates them.
10
11use super::register::{self, Register};
12
13/// What the version register reads on a concentrator that is answering.
14pub const EXPECTED_VERSION: u8 = 0x10;
15
16/// Where the part number sits in the one-time programmable memory.
17pub const MODEL_BYTE_ADDRESS: u8 = 0xd0;
18
19/// Which concentrator this is.
20#[derive(Clone, Copy, Debug, PartialEq, Eq)]
21pub enum Model {
22    /// An SX1302, which reports itself as either zero or two.
23    Sx1302,
24    /// An SX1303, which adds the fine timestamping the SX1302 has not.
25    Sx1303,
26    /// A part number this build does not know, carried so the caller can report it.
27    Unknown(u8),
28}
29
30impl Model {
31    /// Reads the part number byte.
32    ///
33    /// # Arguments
34    ///
35    /// * `byte` - what the one-time programmable memory answered with.
36    ///
37    /// # Returns
38    ///
39    /// Which concentrator it is.
40    ///
41    /// # Examples
42    ///
43    /// ```
44    /// use pamoja_radios::sx1302::chip::Model;
45    ///
46    /// // The SX1302 reports itself two ways, and both mean the same part.
47    /// assert_eq!(Model::of(0x00), Model::Sx1302);
48    /// assert_eq!(Model::of(0x02), Model::Sx1302);
49    /// assert_eq!(Model::of(0x03), Model::Sx1303);
50    /// assert_eq!(Model::of(0x7f), Model::Unknown(0x7f));
51    /// ```
52    #[must_use]
53    pub const fn of(byte: u8) -> Model {
54        match byte {
55            0x00 | 0x02 => Model::Sx1302,
56            0x03 => Model::Sx1303,
57            other => Model::Unknown(other),
58        }
59    }
60
61    /// The part number as a name.
62    ///
63    /// # Returns
64    ///
65    /// The name, or `unknown` for a part number this build does not know.
66    #[must_use]
67    pub const fn name(&self) -> &'static str {
68        match self {
69            Model::Sx1302 => "SX1302",
70            Model::Sx1303 => "SX1303",
71            Model::Unknown(_) => "unknown",
72        }
73    }
74
75    /// Whether this is a concentrator this crate drives.
76    ///
77    /// # Returns
78    ///
79    /// Whether the part number is one of the two known ones.
80    #[must_use]
81    pub const fn is_known(&self) -> bool {
82        matches!(self, Model::Sx1302 | Model::Sx1303)
83    }
84}
85
86/// A step in reading the part number.
87///
88/// Reading it takes two transfers in order, so the sequence is carried as data: a driver walks
89/// it, and a test walks it without a bus.
90#[derive(Clone, Copy, Debug, PartialEq, Eq)]
91pub enum Step {
92    /// Write a value to a register.
93    Write(Register, u8),
94    /// Read a register, which is the answer when it is the last step.
95    Read(Register),
96}
97
98/// The transfers that read the part number, in order.
99///
100/// # Returns
101///
102/// Select the model byte in the one-time programmable memory, then read it. What the last
103/// step reads goes to [`Model::of`].
104///
105/// # Examples
106///
107/// ```
108/// use pamoja_radios::sx1302::chip::{identify, Model, Step, MODEL_BYTE_ADDRESS};
109/// use pamoja_radios::sx1302::register;
110///
111/// let steps = identify();
112/// assert_eq!(steps[0], Step::Write(register::OTP_BYTE_ADDR, MODEL_BYTE_ADDRESS));
113/// assert_eq!(steps[1], Step::Read(register::OTP_RD_DATA));
114///
115/// // A board that answered with three is an SX1303.
116/// assert_eq!(Model::of(0x03), Model::Sx1303);
117/// ```
118#[must_use]
119pub const fn identify() -> [Step; 2] {
120    [
121        Step::Write(register::OTP_BYTE_ADDR, MODEL_BYTE_ADDRESS),
122        Step::Read(register::OTP_RD_DATA),
123    ]
124}
125
126/// Whether the version register says a concentrator is answering.
127///
128/// # Arguments
129///
130/// * `version` - what [`COMMON_VERSION`](register::COMMON_VERSION) read.
131///
132/// # Returns
133///
134/// Whether it matches [`EXPECTED_VERSION`]. A board that is unpowered, miswired, or held in
135/// reset reads zero or all ones instead.
136///
137/// # Examples
138///
139/// ```
140/// use pamoja_radios::sx1302::chip::answers;
141///
142/// assert!(answers(0x10));
143/// assert!(!answers(0x00), "an unpowered board reads zero");
144/// assert!(!answers(0xff), "a floating bus reads all ones");
145/// ```
146#[must_use]
147pub const fn answers(version: u8) -> bool {
148    version == EXPECTED_VERSION
149}
150
151#[cfg(test)]
152mod tests {
153    use super::*;
154
155    #[test]
156    fn the_part_numbers_are_the_ones_the_chip_reports() {
157        // The SX1302 leaves this byte unprogrammed on some parts, so zero means the same.
158        assert_eq!(Model::of(0x00), Model::Sx1302);
159        assert_eq!(Model::of(0x02), Model::Sx1302);
160        assert_eq!(Model::of(0x03), Model::Sx1303);
161
162        assert_eq!(Model::of(0x00).name(), "SX1302");
163        assert_eq!(Model::of(0x03).name(), "SX1303");
164        assert!(Model::of(0x02).is_known());
165        assert!(Model::of(0x03).is_known());
166    }
167
168    #[test]
169    fn a_part_number_we_do_not_know_is_carried_not_discarded() {
170        // A caller reporting a board that will not run should be able to say what it read.
171        let read = Model::of(0x2a);
172        assert_eq!(read, Model::Unknown(0x2a));
173        assert!(!read.is_known());
174        assert_eq!(read.name(), "unknown");
175        assert!(format!("{read:?}").contains("42"));
176    }
177
178    #[test]
179    fn identifying_selects_the_byte_before_reading_it() {
180        let steps = identify();
181        assert_eq!(
182            steps,
183            [
184                Step::Write(register::OTP_BYTE_ADDR, 0xd0),
185                Step::Read(register::OTP_RD_DATA),
186            ]
187        );
188
189        // The read is of a register the chip will not let anyone write.
190        let Step::Read(answer) = steps[1] else {
191            panic!("the last step reads the answer");
192        };
193        assert!(answer.read_only);
194        assert_eq!(answer.address, register::OTP_BASE + 1);
195    }
196
197    #[test]
198    fn only_the_documented_version_counts_as_answering() {
199        assert!(answers(EXPECTED_VERSION));
200        assert_eq!(EXPECTED_VERSION, 0x10);
201
202        // The two ways a bus reads when nothing is driving it.
203        assert!(!answers(0x00));
204        assert!(!answers(0xff));
205        // And a neighboring value is still not a match.
206        assert!(!answers(0x11));
207    }
208}