rf24/radio/rf24/
mod.rs

1use embedded_hal::{delay::DelayNs, digital::OutputPin, spi::SpiDevice};
2mod auto_ack;
3pub(crate) mod bit_fields;
4mod channel;
5mod init;
6use bit_fields::{Config, Feature};
7mod constants;
8mod crc_length;
9mod data_rate;
10mod fifo;
11mod pa_level;
12mod payload_length;
13mod pipe;
14mod power;
15mod radio;
16pub use constants::{commands, mnemonics, registers};
17mod details;
18mod status;
19use super::prelude::{
20    EsbAutoAck, EsbChannel, EsbCrcLength, EsbFifo, EsbPaLevel, EsbPower, EsbRadio,
21};
22use crate::{
23    types::{CrcLength, PaLevel},
24    StatusFlags,
25};
26
27/// An collection of error types to describe hardware malfunctions.
28#[derive(Clone, Copy, Debug, PartialEq)]
29pub enum Nrf24Error<SPI, DO> {
30    /// Represents a SPI transaction error.
31    Spi(SPI),
32    /// Represents a DigitalOutput error.
33    Gpo(DO),
34    /// Represents a corruption of binary data (as it was transferred over the SPI bus' MISO)
35    BinaryCorruption,
36    /// An Error used to prevent an infinite loop in [`RF24::send()`].
37    ///
38    /// This only occurs when user code neglected to call [`RF24::as_tx()`] at least once
39    /// before calling [`RF24::send()`].
40    NotAsTxError,
41}
42
43/// This struct implements the [`Esb*` traits](mod@crate::radio::prelude)
44/// for the nRF24L01 transceiver.
45///
46/// Additionally, there are some functions implemented that are specific to the nRF24L01.
47pub struct RF24<SPI, DO, DELAY> {
48    /// The delay (in microseconds) in which [`RF24::as_rx()`] will wait for
49    /// ACK packets to complete.
50    ///
51    /// If the auto-ack feature is disabled, then this can be set as low as 0.
52    /// If the auto-ack feature is enabled, then set to 100 microseconds minimum on
53    /// generally faster devices (like RPi).
54    ///
55    /// Since this value can be optimized per the radio's data rate, this value is
56    /// automatically adjusted when calling
57    /// [`EsbDataRate::set_data_rate()`](fn@crate::radio::prelude::EsbDataRate::set_data_rate).
58    /// If setting this to a custom value be sure, to set it *after*
59    /// changing the radio's data rate.
60    ///
61    /// <div class="warning">
62    ///
63    /// If set to 0, ensure 130 microsecond delay
64    /// after calling [`RF24::as_rx()`]
65    /// and before transmitting.
66    ///
67    /// </div>
68    ///
69    pub tx_delay: u32,
70    _spi: SPI,
71    /// The CE pin for the radio.
72    ///
73    /// This really only exposed for advanced manipulation of active TX mode.
74    /// It is strongly recommended to enter RX or TX mode using [`RF24::as_rx()`] and
75    /// [`RF24::as_tx()`] because those methods guarantee proper radio usage.
76    pub ce_pin: DO,
77    _delay_impl: DELAY,
78    _buf: [u8; 33],
79    _status: StatusFlags,
80    _config_reg: Config,
81    _feature: Feature,
82    _pipe0_rx_addr: Option<[u8; 5]>,
83    _payload_length: u8,
84}
85
86impl<SPI, DO, DELAY> RF24<SPI, DO, DELAY>
87where
88    SPI: SpiDevice,
89    DO: OutputPin,
90    DELAY: DelayNs,
91{
92    /// Instantiate an [`RF24`] object for use on the specified
93    /// `spi` bus with the given `ce_pin`.
94    ///
95    /// The radio's CSN pin (aka Chip Select pin) shall be defined
96    /// when instantiating the [`SpiDevice`](trait@embedded-hal::spi::SpiDevice)
97    /// object (passed to the `spi` parameter).
98    pub fn new(ce_pin: DO, spi: SPI, delay_impl: DELAY) -> RF24<SPI, DO, DELAY> {
99        RF24 {
100            tx_delay: 250,
101            ce_pin,
102            _spi: spi,
103            _delay_impl: delay_impl,
104            _status: StatusFlags::from_bits(0),
105            _buf: [0u8; 33],
106            _pipe0_rx_addr: None,
107            _feature: Feature::from_bits(0)
108                .with_address_length(5)
109                .with_is_plus_variant(true),
110            // 16 bit CRC, enable all IRQ, and power down as TX
111            _config_reg: Config::from_bits(0xC),
112            _payload_length: 32,
113        }
114    }
115
116    fn spi_transfer(&mut self, len: u8) -> Result<(), Nrf24Error<SPI::Error, DO::Error>> {
117        self._spi
118            .transfer_in_place(&mut self._buf[..len as usize])
119            .map_err(Nrf24Error::Spi)?;
120        self._status = StatusFlags::from_bits(self._buf[0]);
121        Ok(())
122    }
123
124    /// This is also used to write SPI commands that consist of 1 byte:
125    /// ```ignore
126    /// self.spi_read(0, commands::NOP)?;
127    /// // STATUS register is now stored in self._status
128    /// ```
129    fn spi_read(&mut self, len: u8, command: u8) -> Result<(), Nrf24Error<SPI::Error, DO::Error>> {
130        self._buf[0] = command;
131        self.spi_transfer(len + 1)
132    }
133
134    fn spi_write_byte(
135        &mut self,
136        command: u8,
137        byte: u8,
138    ) -> Result<(), Nrf24Error<SPI::Error, DO::Error>> {
139        self._buf[0] = command | commands::W_REGISTER;
140        self._buf[1] = byte;
141        self.spi_transfer(2)
142    }
143
144    fn spi_write_buf(
145        &mut self,
146        command: u8,
147        buf: &[u8],
148    ) -> Result<(), Nrf24Error<SPI::Error, DO::Error>> {
149        self._buf[0] = command | commands::W_REGISTER;
150        let buf_len = buf.len();
151        self._buf[1..(buf_len + 1)].copy_from_slice(&buf[..buf_len]);
152        self.spi_transfer(buf_len as u8 + 1)
153    }
154
155    /// A private function to write a special SPI command specific to older
156    /// non-plus variants of the nRF24L01 radio module. It has no effect on plus variants.
157    fn toggle_features(&mut self) -> Result<(), Nrf24Error<SPI::Error, DO::Error>> {
158        self._buf[0] = commands::ACTIVATE;
159        self._buf[1] = 0x73;
160        self.spi_transfer(2)
161    }
162
163    /// Is this radio a nRF24L01+ variant?
164    ///
165    /// The bool that this function returns is only valid _after_ calling
166    /// [`init()`](fn@crate::radio::prelude::EsbInit::init).
167    pub fn is_plus_variant(&self) -> bool {
168        self._feature.is_plus_variant()
169    }
170
171    pub fn rpd(&mut self) -> Result<bool, Nrf24Error<SPI::Error, DO::Error>> {
172        self.spi_read(1, registers::RPD)?;
173        Ok(self._buf[1] & 1 == 1)
174    }
175
176    pub fn start_carrier_wave(
177        &mut self,
178        level: PaLevel,
179        channel: u8,
180    ) -> Result<(), Nrf24Error<SPI::Error, DO::Error>> {
181        self.as_tx()?;
182        self.spi_read(1, registers::RF_SETUP)?;
183        self.spi_write_byte(registers::RF_SETUP, self._buf[1] | 0x90)?;
184        if self._feature.is_plus_variant() {
185            self.set_auto_ack(false)?;
186            self.set_auto_retries(0, 0)?;
187            let buf = [0xFF; 32];
188
189            // use spi_write_buf() instead of open_tx_pipe() to bypass
190            // truncation of the address with the current RF24::addr_width value
191            self.spi_write_buf(registers::TX_ADDR, &buf[..5])?;
192            self.flush_tx()?; // so we can write to top level
193
194            self.spi_write_buf(commands::W_TX_PAYLOAD, &buf)?;
195
196            self.set_crc_length(CrcLength::Disabled)?;
197        }
198        self.set_pa_level(level)?;
199        self.set_channel(channel)?;
200        self.ce_pin.set_high().map_err(Nrf24Error::Gpo)?;
201        if self._feature.is_plus_variant() {
202            self._delay_impl.delay_ns(1000000); // datasheet says 1 ms is ok in this instance
203            self.rewrite()?;
204        }
205        Ok(())
206    }
207
208    pub fn stop_carrier_wave(&mut self) -> Result<(), Nrf24Error<SPI::Error, DO::Error>> {
209        /*
210         * A note from the datasheet:
211         * Do not use REUSE_TX_PL together with CONT_WAVE=1. When both these
212         * registers are set the chip does not react when setting CE low. If
213         * however, both registers are set PWR_UP = 0 will turn TX mode off.
214         */
215        self.power_down()?; // per datasheet recommendation (just to be safe)
216        self.spi_read(1, registers::RF_SETUP)?;
217        self.spi_write_byte(registers::RF_SETUP, self._buf[1] & !0x90)?;
218        self.ce_pin.set_low().map_err(Nrf24Error::Gpo)
219    }
220
221    /// Control the builtin LNA feature on nRF24L01 (older non-plus variants) and Si24R1
222    /// (cheap chinese clones of the nRF24L01).
223    ///
224    /// This is enabled by default (regardless of chip variant).
225    /// See [`PaLevel`] for effective behavior.
226    ///
227    /// This function has no effect on nRF24L01+ modules and PA/LNA variants because
228    /// the LNA feature is always enabled.
229    pub fn set_lna(&mut self, enable: bool) -> Result<(), Nrf24Error<SPI::Error, DO::Error>> {
230        self.spi_read(1, registers::RF_SETUP)?;
231        let out = self._buf[1] & !1 | enable as u8;
232        self.spi_write_byte(registers::RF_SETUP, out)
233    }
234}
235
236/////////////////////////////////////////////////////////////////////////////////
237/// unit tests
238#[cfg(test)]
239mod test {
240    extern crate std;
241    use super::{commands, registers, RF24};
242    use crate::radio::rf24::mnemonics;
243    use crate::spi_test_expects;
244    use embedded_hal_mock::eh1::delay::NoopDelay;
245    use embedded_hal_mock::eh1::digital::{
246        Mock as PinMock, State as PinState, Transaction as PinTransaction,
247    };
248    use embedded_hal_mock::eh1::spi::{Mock as SpiMock, Transaction as SpiTransaction};
249    use std::vec;
250
251    #[test]
252    pub fn test_rpd() {
253        // Create pin
254        let pin_expectations = [];
255        let mut pin_mock = PinMock::new(&pin_expectations);
256
257        // create delay fn
258        let delay_mock = NoopDelay::new();
259
260        let spi_expectations = spi_test_expects![
261            // get the RPD register value
262            (vec![registers::RPD, 0u8], vec![0xEu8, 0xFFu8]),
263        ];
264        let mut spi_mock = SpiMock::new(&spi_expectations);
265        let mut radio = RF24::new(pin_mock.clone(), spi_mock.clone(), delay_mock);
266        assert!(radio.rpd().unwrap());
267        spi_mock.done();
268        pin_mock.done();
269    }
270
271    pub fn start_carrier_wave_parametrized(is_plus_variant: bool) {
272        // Create pin
273        let mut pin_expectations = [
274            PinTransaction::set(PinState::Low),
275            PinTransaction::set(PinState::High),
276        ]
277        .to_vec();
278        if is_plus_variant {
279            pin_expectations.extend([
280                PinTransaction::set(PinState::Low),
281                PinTransaction::set(PinState::High),
282            ]);
283        }
284
285        let mut pin_mock = PinMock::new(&pin_expectations);
286
287        // create delay fn
288        let delay_mock = NoopDelay::new();
289
290        let mut buf = [0xFFu8; 33];
291        buf[0] = commands::W_TX_PAYLOAD;
292        let mut address = [0xFFu8; 6];
293        address[0] = registers::TX_ADDR | commands::W_REGISTER;
294
295        let mut spi_expectations = spi_test_expects![
296            // as_tx()
297            // clear PRIM_RX flag
298            (
299                vec![registers::CONFIG | commands::W_REGISTER, 0xCu8],
300                vec![0xEu8, 0u8],
301            ),
302            // open pipe 0 for TX (regardless of auto-ack)
303            (vec![registers::EN_RXADDR, 0u8], vec![0xEu8, 0u8]),
304            (
305                vec![registers::EN_RXADDR | commands::W_REGISTER, 1u8],
306                vec![0xEu8, 0u8],
307            ),
308            // set special flags in RF_SETUP register value
309            (vec![registers::RF_SETUP, 0u8], vec![0xEu8, 0u8]),
310            (
311                vec![registers::RF_SETUP | commands::W_REGISTER, 0x90u8],
312                vec![0xEu8, 0u8],
313            ),
314        ]
315        .to_vec();
316        if is_plus_variant {
317            spi_expectations.extend(spi_test_expects![
318                // disable auto-ack
319                (
320                    vec![registers::EN_AA | commands::W_REGISTER, 0u8],
321                    vec![0xEu8, 0u8],
322                ),
323                // disable auto-retries
324                (
325                    vec![registers::SETUP_RETR | commands::W_REGISTER, 0u8],
326                    vec![0xEu8, 0u8],
327                ),
328                // set TX address
329                (address.to_vec(), vec![0u8; 6]),
330                // flush_tx()
331                (vec![commands::FLUSH_TX], vec![0xEu8]),
332                // set TX payload
333                (buf.to_vec(), vec![0u8; 33]),
334                // set_crc_length(disabled)
335                (vec![registers::CONFIG, 0u8], vec![0xEu8, 0xCu8]),
336                (
337                    vec![registers::CONFIG | commands::W_REGISTER, 0u8],
338                    vec![0xEu8, 0u8],
339                ),
340            ]);
341        }
342        spi_expectations.extend(spi_test_expects![
343            // set_pa_level()
344            // set special flags in RF_SETUP register value
345            (vec![registers::RF_SETUP, 0u8], vec![0xEu8, 0x91u8]),
346            (
347                vec![registers::RF_SETUP | commands::W_REGISTER, 0x97u8],
348                vec![0xEu8, 0u8],
349            ),
350            // set_channel(125)
351            (
352                vec![registers::RF_CH | commands::W_REGISTER, 125u8],
353                vec![0xEu8, 0u8],
354            ),
355        ]);
356        if is_plus_variant {
357            spi_expectations.extend(spi_test_expects![
358                // clear the tx_df and tx_ds events
359                (
360                    vec![
361                        registers::STATUS | commands::W_REGISTER,
362                        mnemonics::MASK_MAX_RT | mnemonics::MASK_TX_DS,
363                    ],
364                    vec![0xEu8, 0u8],
365                ),
366                // assert the REUSE_TX_PL flag
367                (vec![commands::REUSE_TX_PL], vec![0xEu8]),
368            ]);
369        }
370
371        let mut spi_mock = SpiMock::new(&spi_expectations);
372        let mut radio = RF24::new(pin_mock.clone(), spi_mock.clone(), delay_mock);
373        radio._feature = radio._feature.with_is_plus_variant(is_plus_variant);
374        radio.start_carrier_wave(crate::PaLevel::Max, 0xFF).unwrap();
375        spi_mock.done();
376        pin_mock.done();
377    }
378
379    #[test]
380    fn start_carrier_wave_plus_variant() {
381        start_carrier_wave_parametrized(true);
382    }
383
384    #[test]
385    fn start_carrier_wave_non_plus_variant() {
386        start_carrier_wave_parametrized(false);
387    }
388
389    #[test]
390    pub fn stop_carrier_wave() {
391        // Create pin
392        let pin_expectations = [
393            PinTransaction::set(PinState::Low),
394            // CE is set LOW twice due to how it behaves during transmission of
395            // constant carrier wave. See comment in start_carrier_wave()
396            PinTransaction::set(PinState::Low),
397        ];
398        let mut pin_mock = PinMock::new(&pin_expectations);
399
400        // create delay fn
401        let delay_mock = NoopDelay::new();
402
403        let spi_expectations = spi_test_expects![
404            // power_down()
405            (
406                vec![registers::CONFIG | commands::W_REGISTER, 0xCu8],
407                vec![0xEu8, 0u8],
408            ),
409            // clear special flags in RF_SETUP register
410            (vec![registers::RF_SETUP, 0u8], vec![0xEu8, 0x90u8]),
411            (
412                vec![registers::RF_SETUP | commands::W_REGISTER, 0u8],
413                vec![0xEu8, 0u8],
414            ),
415        ];
416        let mut spi_mock = SpiMock::new(&spi_expectations);
417        let mut radio = RF24::new(pin_mock.clone(), spi_mock.clone(), delay_mock);
418        radio.stop_carrier_wave().unwrap();
419        spi_mock.done();
420        pin_mock.done();
421    }
422
423    #[test]
424    pub fn set_lna() {
425        // Create pin
426        let pin_expectations = [];
427        let mut pin_mock = PinMock::new(&pin_expectations);
428
429        // create delay fn
430        let delay_mock = NoopDelay::new();
431
432        let spi_expectations = spi_test_expects![
433            // clear the LNA_CUR flag in RF-SETUP
434            (vec![registers::RF_SETUP, 0u8], vec![0xEu8, 1u8]),
435            (
436                vec![registers::RF_SETUP | commands::W_REGISTER, 0u8],
437                vec![0xEu8, 0u8],
438            ),
439        ];
440        let mut spi_mock = SpiMock::new(&spi_expectations);
441        let mut radio = RF24::new(pin_mock.clone(), spi_mock.clone(), delay_mock);
442        radio.set_lna(false).unwrap();
443        spi_mock.done();
444        pin_mock.done();
445    }
446}