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}