Skip to main content

pamoja_radios/sx1302/
spi.rs

1//! The SPI transactions the SX1302 answers.
2//!
3//! Every transfer starts with a target byte, because the concentrator sits in front of the
4//! radios and passes a transfer through to whichever the first byte names. After that comes a
5//! command byte carrying the access bit and the top seven bits of the address, then the low
6//! eight bits, then the data.
7//!
8//! A read costs two bytes more than a write: the chip needs a byte to turn the bus around, so
9//! the host clocks out a dummy and reads the answer in the last byte it receives.
10//!
11//! These functions build the bytes and read them back. They touch no bus, so a caller can
12//! check a frame against the datasheet, or replay one captured from a working gateway,
13//! without any hardware present.
14
15/// The concentrator itself, rather than a radio behind it.
16pub const TARGET_CONCENTRATOR: u8 = 0x00;
17
18/// The radio on the first chain, reached through the concentrator.
19pub const TARGET_RADIO_A: u8 = 0x01;
20
21/// The radio on the second chain.
22pub const TARGET_RADIO_B: u8 = 0x02;
23
24/// The access bit for a read, which is the absence of the write bit.
25pub const READ_ACCESS: u8 = 0x00;
26
27/// The access bit for a write, the top bit of the command byte.
28pub const WRITE_ACCESS: u8 = 0x80;
29
30/// The most bytes one burst carries before the host starts another.
31///
32/// A longer transfer is split into chunks of this size, each with a header of its own and an
33/// address advanced by however much has already gone out.
34pub const BURST_CHUNK: usize = 1024;
35
36/// The clock the concentrator is driven at, in hertz.
37pub const CLOCK_HZ: u32 = 2_000_000;
38
39/// How many bytes a single-byte write transfer takes.
40pub const WRITE_LEN: usize = 4;
41
42/// How many bytes a single-byte read transfer takes.
43pub const READ_LEN: usize = 5;
44
45/// How many bytes the header of a burst write takes, before the data.
46pub const BURST_WRITE_HEADER_LEN: usize = 3;
47
48/// How many bytes the header of a burst read takes, before the data.
49pub const BURST_READ_HEADER_LEN: usize = 4;
50
51/// Builds the transfer that writes one byte to a register.
52///
53/// # Arguments
54///
55/// * `target` - which chip the transfer is for, one of the `TARGET_` constants.
56/// * `address` - the register address.
57/// * `value` - the byte to write.
58///
59/// # Returns
60///
61/// The bytes to clock out, target byte first.
62///
63/// # Examples
64///
65/// ```
66/// use pamoja_radios::sx1302::spi::{write, TARGET_CONCENTRATOR};
67///
68/// // The version register lives at 0x5606.
69/// assert_eq!(write(TARGET_CONCENTRATOR, 0x5606, 0x10), [0x00, 0xd6, 0x06, 0x10]);
70/// ```
71#[must_use]
72pub const fn write(target: u8, address: u16, value: u8) -> [u8; WRITE_LEN] {
73    [
74        target,
75        WRITE_ACCESS | ((address >> 8) as u8 & 0x7f),
76        (address & 0xff) as u8,
77        value,
78    ]
79}
80
81/// Builds the transfer that reads one byte from a register.
82///
83/// # Arguments
84///
85/// * `target` - which chip the transfer is for.
86/// * `address` - the register address.
87///
88/// # Returns
89///
90/// The bytes to clock out. The chip answers in the last byte of what comes back, which
91/// [`read_value`] takes.
92///
93/// # Examples
94///
95/// ```
96/// use pamoja_radios::sx1302::register::COMMON_VERSION;
97/// use pamoja_radios::sx1302::spi::{read, read_value, TARGET_CONCENTRATOR};
98///
99/// // The target, the address most significant byte first, a dummy, and the answer's slot.
100/// let [high, low] = COMMON_VERSION.address.to_be_bytes();
101/// let out = read(TARGET_CONCENTRATOR, COMMON_VERSION.address);
102/// assert_eq!(out, [TARGET_CONCENTRATOR, high, low, 0, 0]);
103///
104/// // What the bus returned while those bytes went out: a version of 0x10, in the last one.
105/// assert_eq!(read_value(&[0, 0, 0, 0, 0x10]), Some(0x10));
106/// ```
107#[must_use]
108pub const fn read(target: u8, address: u16) -> [u8; READ_LEN] {
109    [
110        target,
111        READ_ACCESS | ((address >> 8) as u8 & 0x7f),
112        (address & 0xff) as u8,
113        0x00,
114        0x00,
115    ]
116}
117
118/// Takes the byte a read transfer brought back.
119///
120/// # Arguments
121///
122/// * `received` - everything the bus returned during a [`read`] transfer.
123///
124/// # Returns
125///
126/// The register byte, or `None` when the transfer is too short to hold one.
127#[must_use]
128pub fn read_value(received: &[u8]) -> Option<u8> {
129    received.last().copied()
130}
131
132/// Builds the header that precedes the data of a burst write.
133///
134/// # Arguments
135///
136/// * `target` - which chip the transfer is for.
137/// * `address` - where the first byte lands.
138///
139/// # Returns
140///
141/// The header. The data follows it in the same transfer, unbroken.
142#[must_use]
143pub const fn burst_write_header(target: u8, address: u16) -> [u8; BURST_WRITE_HEADER_LEN] {
144    [
145        target,
146        WRITE_ACCESS | ((address >> 8) as u8 & 0x7f),
147        (address & 0xff) as u8,
148    ]
149}
150
151/// Builds the header that precedes the data of a burst read.
152///
153/// # Arguments
154///
155/// * `target` - which chip the transfer is for.
156/// * `address` - where the first byte is read from.
157///
158/// # Returns
159///
160/// The header, which carries the turnaround byte the chip needs before it answers.
161#[must_use]
162pub const fn burst_read_header(target: u8, address: u16) -> [u8; BURST_READ_HEADER_LEN] {
163    [
164        target,
165        READ_ACCESS | ((address >> 8) as u8 & 0x7f),
166        (address & 0xff) as u8,
167        0x00,
168    ]
169}
170
171/// Walks a long transfer as the chunks it actually goes out in.
172///
173/// A burst longer than [`BURST_CHUNK`] is sent as several, each with a header of its own and
174/// an address advanced by everything already written.
175///
176/// # Arguments
177///
178/// * `address` - where the first byte lands.
179/// * `len` - how many bytes there are in all.
180///
181/// # Returns
182///
183/// The address and length of each chunk, in the order they go out.
184///
185/// # Examples
186///
187/// ```
188/// use pamoja_radios::sx1302::spi::chunks;
189///
190/// // The microcontroller firmware is eight kilobytes, which is eight full chunks.
191/// let plan: Vec<_> = chunks(0x0000, 8192).collect();
192/// assert_eq!(plan.len(), 8);
193/// assert_eq!(plan[0], (0x0000, 1024));
194/// assert_eq!(plan[7], (0x1c00, 1024));
195/// ```
196pub fn chunks(address: u16, len: usize) -> impl Iterator<Item = (u16, usize)> {
197    let mut at = address;
198    let mut left = len;
199    core::iter::from_fn(move || {
200        if left == 0 {
201            return None;
202        }
203        let taken = if left > BURST_CHUNK {
204            BURST_CHUNK
205        } else {
206            left
207        };
208        let chunk = (at, taken);
209        at = at.wrapping_add(taken as u16);
210        left -= taken;
211        Some(chunk)
212    })
213}
214
215/// Reads the bits a register holds out of the byte it lives in.
216///
217/// # Arguments
218///
219/// * `byte` - the byte the register was read from.
220/// * `offset` - which bit the field starts at.
221/// * `width` - how many bits it spans.
222///
223/// # Returns
224///
225/// The field, shifted down to where it can be read as a number.
226///
227/// # Examples
228///
229/// ```
230/// use pamoja_radios::sx1302::spi::field;
231///
232/// // The checksum status sits in the top four bits of the OTP status byte.
233/// assert_eq!(field(0xa0, 4, 4), 0x0a);
234/// ```
235#[must_use]
236pub const fn field(byte: u8, offset: u8, width: u8) -> u8 {
237    if width >= 8 {
238        return byte >> offset;
239    }
240    (byte >> offset) & ((1u8 << width) - 1)
241}
242
243/// Puts a value into the bits a register holds, leaving the rest of the byte alone.
244///
245/// A register narrower than a byte shares that byte with its neighbors, so writing one means
246/// reading the byte, replacing those bits, and writing it back. This is the middle step.
247///
248/// # Arguments
249///
250/// * `byte` - the byte as it reads now.
251/// * `offset` - which bit the field starts at.
252/// * `width` - how many bits it spans.
253/// * `value` - what to put there.
254///
255/// # Returns
256///
257/// The byte to write back.
258///
259/// # Examples
260///
261/// ```
262/// use pamoja_radios::sx1302::spi::with_field;
263///
264/// // Setting the host programming bit leaves the clock enable beside it untouched.
265/// assert_eq!(with_field(0b0001_0000, 1, 1, 1), 0b0001_0010);
266/// ```
267#[must_use]
268pub const fn with_field(byte: u8, offset: u8, width: u8, value: u8) -> u8 {
269    let mask = if width >= 8 {
270        0xffu8
271    } else {
272        ((1u8 << width) - 1) << offset
273    };
274    (byte & !mask) | ((value << offset) & mask)
275}
276
277#[cfg(test)]
278mod tests {
279    use super::*;
280
281    // The transfers the reference implementation clocks out, byte for byte.
282    #[test]
283    fn a_write_is_four_bytes_with_the_access_bit_set() {
284        // The address splits across two bytes, and the top bit of the first says write.
285        assert_eq!(
286            write(TARGET_CONCENTRATOR, 0x5606, 0x10),
287            [0x00, 0x80 | 0x56, 0x06, 0x10]
288        );
289        assert_eq!(
290            write(TARGET_RADIO_A, 0x0000, 0xff),
291            [0x01, 0x80, 0x00, 0xff]
292        );
293    }
294
295    #[test]
296    fn a_read_is_five_bytes_and_answers_in_the_last() {
297        assert_eq!(read(TARGET_CONCENTRATOR, 0x6180), [0x00, 0x61, 0x80, 0, 0]);
298        assert_eq!(read(TARGET_RADIO_B, 0x1234), [0x02, 0x12, 0x34, 0, 0]);
299
300        // The bus returns as many bytes as went out; the register is the last of them.
301        assert_eq!(read_value(&[0, 0, 0, 0, 0x42]), Some(0x42));
302        assert_eq!(read_value(&[]), None);
303    }
304
305    #[test]
306    fn the_address_keeps_only_fifteen_bits() {
307        // The top bit of the command byte is the access bit, so an address cannot reach it.
308        let framed = write(TARGET_CONCENTRATOR, 0xffff, 0x00);
309        assert_eq!(framed[1], 0xff);
310        assert_eq!(framed[1] & 0x80, WRITE_ACCESS);
311
312        let framed = read(TARGET_CONCENTRATOR, 0xffff);
313        assert_eq!(framed[1], 0x7f);
314    }
315
316    #[test]
317    fn a_burst_header_carries_no_data_of_its_own() {
318        assert_eq!(
319            burst_write_header(TARGET_CONCENTRATOR, 0x2000),
320            [0x00, 0x80 | 0x20, 0x00]
321        );
322        // A read needs one more byte, for the chip to turn the bus around.
323        assert_eq!(
324            burst_read_header(TARGET_CONCENTRATOR, 0x2000),
325            [0x00, 0x20, 0x00, 0x00]
326        );
327    }
328
329    #[test]
330    fn a_long_burst_is_split_and_the_address_follows_it() {
331        let plan: Vec<_> = chunks(0x0000, 8192).collect();
332        assert_eq!(plan.len(), 8);
333        for (index, (at, len)) in plan.iter().enumerate() {
334            assert_eq!(*len, BURST_CHUNK);
335            assert_eq!(*at as usize, index * BURST_CHUNK);
336        }
337
338        // A short burst is one chunk, and a partial tail keeps its real length.
339        assert_eq!(chunks(0x4000, 10).collect::<Vec<_>>(), [(0x4000, 10)]);
340        assert_eq!(
341            chunks(0x0000, 1025).collect::<Vec<_>>(),
342            [(0x0000, 1024), (0x0400, 1)]
343        );
344        assert_eq!(chunks(0x0000, 0).count(), 0);
345    }
346
347    #[test]
348    fn a_field_reads_and_writes_without_touching_its_neighbors() {
349        // The AGC control byte holds five registers, so each one has to be picked out.
350        let byte = 0b0001_0101;
351        assert_eq!(field(byte, 4, 1), 1); // clock enable
352        assert_eq!(field(byte, 2, 1), 1); // clear
353        assert_eq!(field(byte, 1, 1), 0); // host programming
354        assert_eq!(field(byte, 0, 1), 1); // parity error
355
356        let set = with_field(byte, 1, 1, 1);
357        assert_eq!(set, 0b0001_0111);
358        assert_eq!(field(set, 4, 1), 1);
359        assert_eq!(field(set, 0, 1), 1);
360
361        let cleared = with_field(set, 2, 1, 0);
362        assert_eq!(cleared, 0b0001_0011);
363
364        // A whole byte reads and writes as itself.
365        assert_eq!(field(0xab, 0, 8), 0xab);
366        assert_eq!(with_field(0x00, 0, 8, 0xab), 0xab);
367    }
368}