nrf24l01_commands/
commands.rs

1//! Generate SPI byte sequences for nRF24L01+ commands.
2//!
3//! ## Example with writing the [`CONFIG`][registers::Config] register
4//! ```rust
5//! use nrf24l01_commands::{commands, fields, registers, registers::AddressRegister};
6//!
7//! let config = registers::Config::new()
8//!     .with_mask_rx_dr(true)
9//!     .with_mask_tx_ds(false)
10//!     .with_mask_max_rt(false)
11//!     .with_en_crc(false)
12//!     .with_crco(fields::Crco::TwoByte)
13//!     .with_pwr_up(true)
14//!     .with_prim_rx(false);
15//! let write_command = commands::WRegister(config);
16//! let spi_bytes = write_command.bytes();
17//! assert_eq!(spi_bytes, [0b0010_0000, 0b0100_0110]);
18//! ```
19use crate::registers::{self, AddressRegister, Register};
20use core::marker::PhantomData;
21
22/// A trait for nRF24L01+ commands. Defines the command's _command word_.
23#[const_trait]
24pub trait Command {
25    /// Command word.
26    const WORD: u8;
27}
28
29/// # R_REGISTER command
30/// Read a register.
31///
32/// #### Type Parameter `R`
33/// Register type.
34///
35/// ## Example
36/// ```rust
37/// use nrf24l01_commands::{registers, registers::AddressRegister, commands};
38///
39/// // Generate SPI byte sequence for R_REGISTER on FIFO_STATUS register.
40/// let bytes = commands::RRegister::<registers::FifoStatus>::bytes();
41/// assert_eq!(bytes, [0 | 0x17, 0]);
42/// ```
43pub struct RRegister<R>(PhantomData<R>);
44
45/// # W_REGISTER command
46/// Write a register.
47///
48/// ## Example
49/// ```rust
50/// use nrf24l01_commands::{registers, registers::AddressRegister, commands};
51///
52/// // Generate SPI byte sequence for W_REGISTER on RF_CH register.
53/// let rf_ch = registers::RfCh::new().with_rf_ch(85);
54/// let bytes = commands::WRegister(rf_ch).bytes();
55/// assert_eq!(bytes, [0b0010_0000 | 0x05, 85]);
56///
57/// // Generate SPI byte sequence for W_REGISTER on TX_ADDR register.
58/// let tx_addr = registers::TxAddr::<5>::new().with_tx_addr(0x61DE7C320B);
59/// let bytes = commands::WRegister(tx_addr).bytes();
60/// assert_eq!(bytes, [0b0010_0000 | 0x10, 0x0B, 0x32, 0x7C, 0xDE, 0x61]);
61/// ```
62pub struct WRegister<R>(
63    /// Register to write.
64    pub R,
65);
66
67/// # R_RX_PAYLOAD command
68/// Read RX payload.
69///
70/// #### Const Parameter `N`
71/// Width of RX payload.
72///
73/// <div class="warning">
74/// Must be 1 to 32 bytes.
75/// </div>
76///
77/// ## Example
78/// ```rust
79/// #![feature(generic_const_exprs)] // TODO: https://github.com/rust-lang/rust/issues/133199#issuecomment-2630615573
80/// use nrf24l01_commands::commands;
81///
82/// // Generate SPI byte sequence for R_RX_PAYLOAD with 17 byte payload.
83/// let bytes = commands::RRxPayload::<17>::bytes();
84/// let mut expected_bytes = [0; 18];
85/// expected_bytes[0] = 0b0110_0001;
86/// assert_eq!(bytes, expected_bytes);
87/// ```
88pub struct RRxPayload<const N: usize>();
89
90/// # W_TX_PAYLOAD command
91/// Write TX payload. Payload byte-order is kept as MSByte first contrary to documentation.
92///
93/// ## Example
94/// ```rust
95/// use nrf24l01_commands::commands;
96///
97/// let payload = [1, 2, 3, 4, 5, 6, 7, 8, 9];
98/// let bytes = commands::WTxPayload(payload).bytes();
99/// assert_eq!(bytes, [0b1010_0000, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
100/// ```
101pub struct WTxPayload<const N: usize>(
102    /// Payload to write.
103    /// <div class="warning">
104    /// Payload must be 1 to 32 bytes.
105    /// </div>
106    pub [u8; N],
107);
108
109/// # FLUSH_TX command
110/// Flush TX FIFO. Used in TX mode.
111///
112/// ## Example
113/// ```rust
114/// use nrf24l01_commands::commands::{self, Command};
115///
116/// assert_eq!(commands::FlushTx::WORD, 0b1110_0001);
117/// assert_eq!(commands::FlushTx::bytes(), [0b1110_0001]);
118/// ```
119pub struct FlushTx();
120
121/// # FLUSH_RX command
122/// Flush RX FIFO. Used in RX mode.
123///
124/// ## Example
125/// ```rust
126/// use nrf24l01_commands::commands::{self, Command};
127///
128/// assert_eq!(commands::FlushRx::WORD, 0b1110_0010);
129/// assert_eq!(commands::FlushRx::bytes(), [0b1110_0010]);
130/// ```
131pub struct FlushRx();
132
133/// # REUSE_TX_PL command
134/// Reuse last transmitted payload. Packets are repeatedly transmitted as long
135/// as CE is high. TX payload reuse is active until [`W_TX_PAYLOAD`][WTxPayload] or [`FLUSH_TX`][FlushTx]
136/// is executed.
137///
138/// ## Example
139/// ```rust
140/// use nrf24l01_commands::commands::{self, Command};
141///
142/// assert_eq!(commands::ReuseTxPl::WORD, 0b1110_0011);
143/// assert_eq!(commands::ReuseTxPl::bytes(), [0b1110_0011]);
144/// ```
145pub struct ReuseTxPl();
146
147/// # R_RX_PL_WID command
148/// Read RX payload width for the top payload in RX FIFO.
149///
150/// ## Example
151/// ```rust
152/// use nrf24l01_commands::commands;
153///
154/// let bytes = commands::RRxPlWid::bytes();
155/// assert_eq!(bytes, [0b0110_0000, 0]);
156/// ```
157pub struct RRxPlWid();
158
159/// # W_ACK_PAYLOAD command
160/// Write payload to be transmitted with ACK packet on a data [`pipe`][WAckPayload::pipe]. Used in RX mode.
161/// Maximum three ACK packet payloads can be pending. Payloads with the same [`pipe`][WAckPayload::pipe]
162/// are handled first-in-first-out. Payload byte-order is kept as MSByte first contrary to documentation.
163///
164/// ## Example
165/// ```rust
166/// use nrf24l01_commands::commands;
167///
168/// let pipe = 4;
169/// let payload = [1, 2, 3, 4, 5, 6, 7, 8, 9];
170/// let bytes = commands::WAckPayload { pipe, payload }.bytes();
171/// assert_eq!(bytes, [0b1010_1000 | pipe, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
172/// ```
173pub struct WAckPayload<const N: usize> {
174    /// Data pipe this ACK payload is designated to.
175    pub pipe: u8,
176    /// Payload to send with ACK.
177    /// <div class="warning">
178    /// Payload must be 1 to 32 bytes.
179    /// </div>
180    pub payload: [u8; N],
181}
182
183/// # W_TX_PAYLOAD_NOACK command
184/// Write TX payload with AUTOACK disabled. Payload byte-order is kept as MSByte first contrary to documentation.
185///
186/// ## Example
187/// ```rust
188/// use nrf24l01_commands::commands;
189///
190/// let payload = [1, 2, 3, 4, 5, 6, 7, 8, 9];
191/// let bytes = commands::WTxPayloadNoack(payload).bytes();
192/// assert_eq!(bytes, [0b1011_0000, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
193/// ```
194pub struct WTxPayloadNoack<const N: usize>(
195    /// Payload to write.
196    /// <div class="warning">
197    /// Payload must be 1 to 32 bytes.
198    /// </div>
199    pub [u8; N],
200);
201
202/// # NOP command
203/// No operation. Used to read the status register.
204///
205/// ## Example
206/// ```rust
207/// use nrf24l01_commands::commands::{self, Command};
208///
209/// assert_eq!(commands::Nop::WORD, 0xFF);
210/// assert_eq!(commands::Nop::bytes(), [0xFF]);
211/// ```
212pub struct Nop();
213
214impl<R> const Command for RRegister<R> {
215    const WORD: u8 = 0;
216}
217impl<R> const Command for WRegister<R> {
218    const WORD: u8 = 0b0010_0000;
219}
220impl<const N: usize> const Command for RRxPayload<N> {
221    const WORD: u8 = 0b0110_0001;
222}
223impl<const N: usize> const Command for WTxPayload<N> {
224    const WORD: u8 = 0b1010_0000;
225}
226impl const Command for FlushTx {
227    const WORD: u8 = 0b1110_0001;
228}
229impl const Command for FlushRx {
230    const WORD: u8 = 0b1110_0010;
231}
232impl const Command for ReuseTxPl {
233    const WORD: u8 = 0b1110_0011;
234}
235impl const Command for RRxPlWid {
236    const WORD: u8 = 0b0110_0000;
237}
238impl<const N: usize> const Command for WAckPayload<N> {
239    const WORD: u8 = 0b1010_1000;
240}
241impl<const N: usize> const Command for WTxPayloadNoack<N> {
242    const WORD: u8 = 0b1011_0000;
243}
244impl const Command for Nop {
245    const WORD: u8 = 0b1111_1111;
246}
247
248impl<R: const Register> RRegister<R> {
249    /// Get the command's _command word_.
250    pub const fn word() -> u8 {
251        Self::WORD | R::ADDRESS
252    }
253
254    /// Generate the command's SPI byte sequence.
255    pub const fn bytes() -> [u8; 2] {
256        [Self::word(), 0]
257    }
258}
259
260impl<const N: usize> RRegister<registers::RxAddrP0<N>> {
261    /// Get the command's _command word_.
262    pub const fn word() -> u8 {
263        Self::WORD | registers::RxAddrP0::<N>::ADDRESS
264    }
265
266    /// Generate the command's SPI byte sequence.
267    pub const fn bytes() -> [u8; N + 1] {
268        let mut bytes = [0; N + 1];
269        bytes[0] = Self::word();
270        bytes
271    }
272}
273
274impl<const N: usize> RRegister<registers::RxAddrP1<N>> {
275    /// Get the command's _command word_.
276    pub const fn word() -> u8 {
277        Self::WORD | registers::RxAddrP1::<N>::ADDRESS
278    }
279
280    /// Generate the command's SPI byte sequence.
281    pub const fn bytes() -> [u8; N + 1] {
282        let mut bytes = [0; N + 1];
283        bytes[0] = Self::word();
284        bytes
285    }
286}
287
288impl<const N: usize> RRegister<registers::TxAddr<N>> {
289    /// Get the command's _command word_.
290    pub const fn word() -> u8 {
291        Self::WORD | registers::TxAddr::<N>::ADDRESS
292    }
293
294    /// Generate the command's SPI byte sequence.
295    pub const fn bytes() -> [u8; N + 1] {
296        let mut bytes = [0; N + 1];
297        bytes[0] = Self::word();
298        bytes
299    }
300}
301
302impl<R: const Register> WRegister<R> {
303    /// Get the command's _command word_.
304    pub const fn word() -> u8 {
305        Self::WORD | R::ADDRESS
306    }
307
308    /// Generate the command's SPI byte sequence.
309    pub const fn bytes(&self) -> [u8; 2] {
310        [Self::word(), self.0.into_bits()]
311    }
312}
313
314/// Concatenate the command word and address bytes into an array.
315#[inline(always)]
316const fn concat_word_addr<const N: usize>(word: u8, addr: [u8; N]) -> [u8; N + 1] {
317    let mut bytes: [u8; N + 1] = [0; N + 1];
318    bytes[0] = word;
319    // Addr is already in little-endian byte-order
320    let mut i = 1;
321    while i < N + 1 {
322        bytes[i] = addr[i - 1];
323        i += 1;
324    }
325    bytes
326}
327
328impl<const N: usize> WRegister<registers::RxAddrP0<N>> {
329    /// Get the command's _command word_.
330    pub const fn word() -> u8 {
331        Self::WORD | registers::RxAddrP0::<N>::ADDRESS
332    }
333
334    /// Generate the command's SPI byte sequence.
335    pub const fn bytes(&self) -> [u8; N + 1] {
336        concat_word_addr(Self::word(), self.0.into_bytes())
337    }
338}
339
340impl<const N: usize> WRegister<registers::RxAddrP1<N>> {
341    /// Get the command's _command word_.
342    pub const fn word() -> u8 {
343        Self::WORD | registers::RxAddrP1::<N>::ADDRESS
344    }
345
346    /// Generate the command's SPI byte sequence.
347    pub const fn bytes(&self) -> [u8; N + 1] {
348        concat_word_addr(Self::word(), self.0.into_bytes())
349    }
350}
351
352impl<const N: usize> WRegister<registers::TxAddr<N>> {
353    /// Get the command's _command word_.
354    pub const fn word() -> u8 {
355        Self::WORD | registers::TxAddr::<N>::ADDRESS
356    }
357
358    /// Generate the command's SPI byte sequence.
359    pub const fn bytes(&self) -> [u8; N + 1] {
360        concat_word_addr(Self::word(), self.0.into_bytes())
361    }
362}
363
364impl<const N: usize> RRxPayload<N> {
365    /// Generate the command's SPI byte sequence.
366    pub const fn bytes() -> [u8; N + 1] {
367        let mut bytes: [u8; N + 1] = [0; N + 1];
368        bytes[0] = Self::WORD;
369        bytes
370    }
371}
372
373/// Concatenate the command word and payload bytes into an array.
374#[inline(always)]
375const fn concat_word_payload<const N: usize>(word: u8, payload: [u8; N]) -> [u8; N + 1] {
376    let mut bytes: [u8; N + 1] = [0; N + 1];
377    bytes[0] = word;
378
379    let mut bytes_idx = 1;
380    while bytes_idx < N + 1 {
381        bytes[bytes_idx] = payload[bytes_idx - 1];
382        bytes_idx += 1;
383    }
384    bytes
385}
386
387impl<const N: usize> WTxPayload<N> {
388    /// Generate the command's SPI byte sequence.
389    pub const fn bytes(&self) -> [u8; N + 1] {
390        concat_word_payload(Self::WORD, self.0)
391    }
392}
393
394impl FlushTx {
395    /// Generate the command's SPI byte sequence.
396    pub const fn bytes() -> [u8; 1] {
397        [Self::WORD]
398    }
399}
400
401impl FlushRx {
402    /// Generate the command's SPI byte sequence.
403    pub const fn bytes() -> [u8; 1] {
404        [Self::WORD]
405    }
406}
407
408impl ReuseTxPl {
409    /// Generate the command's SPI byte sequence.
410    pub const fn bytes() -> [u8; 1] {
411        [Self::WORD]
412    }
413}
414
415impl RRxPlWid {
416    /// Generate the command's SPI byte sequence.
417    pub const fn bytes() -> [u8; 2] {
418        [Self::WORD, 0]
419    }
420}
421
422impl<const N: usize> WAckPayload<N> {
423    /// Generate the command's SPI byte sequence.
424    pub const fn bytes(&self) -> [u8; N + 1] {
425        concat_word_payload(Self::WORD | self.pipe, self.payload)
426    }
427}
428
429impl<const N: usize> WTxPayloadNoack<N> {
430    /// Generate the command's SPI byte sequence.
431    pub const fn bytes(&self) -> [u8; N + 1] {
432        concat_word_payload(Self::WORD, self.0)
433    }
434}
435
436impl Nop {
437    /// Generate the command's SPI byte sequence.
438    pub const fn bytes() -> [u8; 1] {
439        [Self::WORD]
440    }
441}
442
443#[cfg(test)]
444mod tests {
445    use super::*;
446    use crate::registers;
447
448    #[test]
449    fn test_read_address_registers() {
450        const READ_RX_ADDR_P0_BYTES: [u8; 5] = RRegister::<registers::RxAddrP0<4>>::bytes();
451        assert_eq!(READ_RX_ADDR_P0_BYTES, [0x0A, 0, 0, 0, 0]);
452
453        const READ_RX_ADDR_P1_BYTES: [u8; 4] = RRegister::<registers::RxAddrP1<3>>::bytes();
454        assert_eq!(READ_RX_ADDR_P1_BYTES, [0x0B, 0, 0, 0]);
455
456        const READ_TX_ADDR_BYTES: [u8; 6] = RRegister::<registers::TxAddr<5>>::bytes();
457        assert_eq!(READ_TX_ADDR_BYTES, [0x10, 0, 0, 0, 0, 0]);
458    }
459
460    #[test]
461    fn test_write_address_registers() {
462        const RX_ADDR_P0: registers::RxAddrP0<5> =
463            registers::RxAddrP0::<5>::new().with_rx_addr_p0(0x8106310AC0);
464        const RX_ADDR_P0_BYTES: [u8; 6] = WRegister(RX_ADDR_P0).bytes();
465        assert_eq!(
466            RX_ADDR_P0_BYTES,
467            [0b0010_0000 | 0x0A, 0xC0, 0x0A, 0x31, 0x06, 0x81]
468        );
469
470        const RX_ADDR_P1: registers::RxAddrP1<4> =
471            registers::RxAddrP1::<4>::new().with_rx_addr_p1(0x605F4459BF);
472        const RX_ADDR_P1_BYTES: [u8; 5] = WRegister(RX_ADDR_P1).bytes();
473        assert_eq!(
474            RX_ADDR_P1_BYTES,
475            [0b0010_0000 | 0x0B, 0xBF, 0x59, 0x44, 0x5F]
476        );
477
478        const TX_ADDR: registers::TxAddr<3> =
479            registers::TxAddr::<3>::new().with_tx_addr(0xFF32C8ED07);
480        const TX_ADDR_BYTES: [u8; 4] = WRegister(TX_ADDR).bytes();
481        assert_eq!(TX_ADDR_BYTES, [0b0010_0000 | 0x10, 0x07, 0xED, 0xC8]);
482    }
483}