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}