rf24/radio/
config.rs

1use crate::radio::rf24::bit_fields::{ConfigReg, Feature, SetupRetry, SetupRfAw};
2use crate::{CrcLength, DataRate, PaLevel};
3
4/// A struct to contain configuration about pipe addresses.
5#[derive(Debug, Clone, Copy)]
6pub struct EsbPipeConfig {
7    pub(super) tx_address: [u8; 5],
8    pub(super) pipe0: [u8; 5],
9    pub(super) pipe1: [u8; 5],
10    pub(super) subsequent_pipe_prefixes: [u8; 6],
11    pub(super) rx_pipes_enabled: u8,
12}
13
14impl Default for EsbPipeConfig {
15    fn default() -> Self {
16        Self {
17            tx_address: [0xE7; 5],
18            pipe0: [0xE7; 5],
19            pipe1: [0xC2; 5],
20            subsequent_pipe_prefixes: [0xC3, 0xC4, 0xC5, 0xC6, 0xC7, 0xC8],
21            rx_pipes_enabled: 2,
22        }
23    }
24}
25
26impl EsbPipeConfig {
27    pub fn set_tx_address(&mut self, address: &[u8]) {
28        let len = address.len().min(5);
29        self.tx_address[..len].copy_from_slice(&address[..len]);
30    }
31
32    pub fn set_rx_address(&mut self, pipe: u8, address: &[u8]) {
33        let len = address.len().min(5);
34        if len == 0 {
35            return;
36        }
37        if pipe < 8 {
38            self.rx_pipes_enabled |= 1 << pipe;
39        }
40        if pipe == 0 {
41            self.pipe0[..len].copy_from_slice(&address[..len]);
42        } else if pipe == 1 {
43            self.pipe1[..len].copy_from_slice(&address[..len]);
44        } else if pipe < 8 {
45            self.subsequent_pipe_prefixes[pipe as usize - 2] = address[0];
46        }
47    }
48
49    pub fn close_rx_pipe(&mut self, pipe: u8) {
50        if pipe < 8 {
51            self.rx_pipes_enabled &= !(1 << pipe);
52        }
53    }
54
55    pub(super) fn get_rx_address(&self, pipe: u8, address: &mut [u8]) {
56        let len = address.len().min(5);
57        if pipe == 0 {
58            address[..len].copy_from_slice(&self.pipe0[..len]);
59        } else if pipe == 1 {
60            address[..len].copy_from_slice(&self.pipe1[..len]);
61        } else if pipe < 8 {
62            address[0] = self.subsequent_pipe_prefixes[pipe as usize - 2];
63        }
64        if pipe > 1 && len > 1 {
65            address[1..(len - 1)].copy_from_slice(&self.pipe1[1..(len - 1)]);
66        }
67    }
68}
69
70/// An object to configure the radio.
71///
72/// This struct follows a builder pattern. Since all fields are private, users should
73/// start with the [`RadioConfig::default`] constructor, then mutate the object accordingly.
74/// ```
75/// use rf24::radio::RadioConfig;
76///
77/// let mut config = RadioConfig::default();
78/// config = config.with_channel(42);
79/// ```
80#[derive(Debug, Clone, Copy)]
81pub struct RadioConfig {
82    pub(crate) config_reg: ConfigReg,
83    pub(crate) auto_retries: SetupRetry,
84    pub(crate) setup_rf_aw: SetupRfAw,
85    pub(crate) feature: Feature,
86    channel: u8,
87    payload_length: u8,
88    auto_ack: u8,
89    pub(crate) pipes: EsbPipeConfig,
90}
91
92impl Default for RadioConfig {
93    /// Instantiate a [`RadioConfig`] object with library defaults.
94    ///
95    /// | feature | default value |
96    /// |--------:|:--------------|
97    /// | [`RadioConfig::channel()`] | `76` |
98    /// | [`RadioConfig::address_length()`] | `5` |
99    /// | [`RadioConfig::pa_level()`] | [`PaLevel::Max`] |
100    /// | [`RadioConfig::lna_enable()`] | `true` |
101    /// | [`RadioConfig::crc_length()`] | [`CrcLength::Bit16`] |
102    /// | [`RadioConfig::data_rate()`] | [`DataRate::Mbps1`] |
103    /// | [`RadioConfig::payload_length()`] | `32` |
104    /// | [`RadioConfig::dynamic_payloads()`] | `false` |
105    /// | [`RadioConfig::auto_ack()`] | `0x3F` (enabled for pipes 0 - 5) |
106    /// | [`RadioConfig::ack_payloads()`] | `false` |
107    /// | [`RadioConfig::ask_no_ack()`] | `false` |
108    /// | [`RadioConfig::auto_retry_delay()`] | `5` |
109    /// | [`RadioConfig::auto_retry_count()`] | `15` |
110    /// | [`RadioConfig::tx_address()`] | `[0xE7; 5]` |
111    /// | [`RadioConfig::rx_address()`] | See below table about [Default RX addresses](#default-rx-pipes-configuration) |
112    /// | [`RadioConfig::rx_dr()`] | `true` |
113    /// | [`RadioConfig::tx_ds()`] | `true` |
114    /// | [`RadioConfig::tx_df()`] | `true` |
115    ///
116    /// ## Default RX pipes' configuration
117    ///
118    /// | pipe number | state  | address     |
119    /// |-------------|--------|-------------|
120    /// |      0[^2]  | closed | `[0xE7; 5]` |
121    /// |      1      | open   | `[0xC2; 5]` |
122    /// |      2[^1]  | closed | `0xC3`      |
123    /// |      3[^1]  | closed | `0xC4`      |
124    /// |      4[^1]  | closed | `0xC5`      |
125    /// |      5[^1]  | closed | `0xC6`      |
126    ///
127    /// [^1]: Remember, pipes 2 - 5 share the same 4 LSBytes as the address on pipe 1.
128    /// [^2]: The RX address default value is the same as pipe 0 default TX address.
129    fn default() -> Self {
130        Self {
131            /*
132               - all events enabled for IRQ pin
133               - 8 bit CRC
134               - powered down
135               - inactive TX (StandBy-I) mode
136            */
137            config_reg: ConfigReg::default(),
138            /*
139               - 5 * 250 + 250 = 1500 us delay between attempts
140               - 15 max attempts
141            */
142            auto_retries: SetupRetry::default(),
143            /*
144                - 5 byte address length
145                - 1 Mbps data rate
146                - Max PA level
147                - LNA enabled
148            */
149            setup_rf_aw: SetupRfAw::default(),
150            /*
151               - disabled dynamic payloads
152               - disabled ACK payloads
153               - disabled ask_no_ack param
154            */
155            feature: Feature::default(),
156            channel: 76,
157            payload_length: 32,
158            // enable auto-ACK for pipes 0 - 5
159            auto_ack: 0x3F,
160            pipes: EsbPipeConfig::default(),
161        }
162    }
163}
164
165impl RadioConfig {
166    /// Returns the value set by [`RadioConfig::with_crc_length()`].
167    pub const fn crc_length(&self) -> CrcLength {
168        self.config_reg.crc_length()
169    }
170
171    /// The Cyclical Redundancy Checksum (CRC) length.
172    ///
173    /// See [`EsbCrcLength::set_crc_length()`](fn@crate::radio::prelude::EsbCrcLength::set_crc_length).
174    pub fn with_crc_length(self, length: CrcLength) -> Self {
175        let new_config = self.config_reg.with_crc_length(length);
176        Self {
177            config_reg: new_config,
178            ..self
179        }
180    }
181
182    /// Returns the value set by [`RadioConfig::with_data_rate()`].
183    pub const fn data_rate(&self) -> DataRate {
184        self.setup_rf_aw.data_rate()
185    }
186
187    /// The Data Rate (over the air).
188    ///
189    /// See [`EsbDataRate::set_data_rate()`](fn@crate::radio::prelude::EsbDataRate::set_data_rate).
190    pub fn with_data_rate(self, data_rate: DataRate) -> Self {
191        let new_config = self.setup_rf_aw.with_data_rate(data_rate);
192        Self {
193            setup_rf_aw: new_config,
194            ..self
195        }
196    }
197
198    /// Returns the value set by [`RadioConfig::with_pa_level()`].
199    pub const fn pa_level(&self) -> PaLevel {
200        self.setup_rf_aw.pa_level()
201    }
202
203    /// The Power Amplitude (PA) level.
204    ///
205    /// See [`EsbPaLevel::set_pa_level()`](fn@crate::radio::prelude::EsbPaLevel::set_pa_level).
206    pub fn with_pa_level(self, level: PaLevel) -> Self {
207        let new_config = self.setup_rf_aw.with_pa_level(level);
208        Self {
209            setup_rf_aw: new_config,
210            ..self
211        }
212    }
213
214    /// Returns the value set by [`RadioConfig::with_lna_enable()`].
215    pub const fn lna_enable(&self) -> bool {
216        self.setup_rf_aw.lna_enable()
217    }
218
219    /// Enable or disable the chip's Low Noise Amplifier (LNA) feature.
220    ///
221    /// This value may not be respected depending on the radio module used.
222    /// Consult the radio's manufacturer for accurate details.
223    pub fn with_lna_enable(self, enable: bool) -> Self {
224        let new_config = self.setup_rf_aw.with_lna_enable(enable);
225        Self {
226            setup_rf_aw: new_config,
227            ..self
228        }
229    }
230
231    /// Returns the value set by [`RadioConfig::with_address_length()`].
232    pub const fn address_length(&self) -> u8 {
233        self.setup_rf_aw.address_length()
234    }
235
236    /// The address length.
237    ///
238    /// This value is clamped to range [2, 5].
239    pub fn with_address_length(self, value: u8) -> Self {
240        let new_config = self.setup_rf_aw.with_address_length(value);
241        Self {
242            setup_rf_aw: new_config,
243            ..self
244        }
245    }
246
247    /// Returns the value set by [`RadioConfig::with_channel()`].
248    pub const fn channel(&self) -> u8 {
249        self.channel
250    }
251
252    /// Set the channel (over the air frequency).
253    ///
254    /// This value is clamped to range [0, 125].
255    /// The radio's frequency can be determined by the following equation:
256    /// ```text
257    /// frequency (in Hz) = channel + 2400
258    /// ```
259    pub fn with_channel(self, value: u8) -> Self {
260        Self {
261            channel: value.min(125),
262            ..self
263        }
264    }
265
266    /// The auto-retry feature's `delay` (set via [`RadioConfig::with_auto_retries()`])
267    pub const fn auto_retry_delay(&self) -> u8 {
268        self.auto_retries.ard()
269    }
270
271    /// The auto-retry feature's `count` (set via [`RadioConfig::with_auto_retries()`])
272    pub const fn auto_retry_count(&self) -> u8 {
273        self.auto_retries.arc()
274    }
275
276    /// Set the auto-retry feature's `delay` and `count` parameters.
277    ///
278    /// See [`EsbAutoAck::set_auto_retries()`](fn@crate::radio::prelude::EsbAutoAck::set_auto_retries).
279    pub fn with_auto_retries(self, delay: u8, count: u8) -> Self {
280        let new_config = self
281            .auto_retries
282            .with_ard(delay.min(15))
283            .with_arc(count.min(15));
284        Self {
285            auto_retries: new_config,
286            ..self
287        }
288    }
289
290    /// Get the value set by [`RadioConfig::rx_dr()`].
291    pub const fn rx_dr(&self) -> bool {
292        self.config_reg.rx_dr()
293    }
294
295    /// Enable or disable the "RX Data Ready" event triggering the radio's IRQ.
296    ///
297    /// See [`StatusFlags::rx_dr()`](fn@crate::StatusFlags::rx_dr).
298    pub fn with_rx_dr(self, enable: bool) -> Self {
299        let new_config = self.config_reg.with_rx_dr(enable);
300        Self {
301            config_reg: new_config,
302            ..self
303        }
304    }
305
306    /// Get the value set by [`RadioConfig::tx_ds()`].
307    pub const fn tx_ds(&self) -> bool {
308        self.config_reg.tx_ds()
309    }
310
311    /// Enable or disable the "TX Data Sent" event triggering the radio's IRQ.
312    ///
313    /// See [`StatusFlags::tx_ds()`](fn@crate::StatusFlags::tx_ds).
314    pub fn with_tx_ds(self, enable: bool) -> Self {
315        let new_config = self.config_reg.with_tx_ds(enable);
316        Self {
317            config_reg: new_config,
318            ..self
319        }
320    }
321
322    /// Get the value set by [`RadioConfig::tx_df()`].
323    pub const fn tx_df(&self) -> bool {
324        self.config_reg.tx_df()
325    }
326
327    /// Enable or disable the "TX Data Failed" event triggering the radio's IRQ.
328    ///
329    /// See [`StatusFlags::tx_df()`](fn@crate::StatusFlags::tx_df).
330    pub fn with_tx_df(self, enable: bool) -> Self {
331        let new_config = self.config_reg.with_tx_df(enable);
332        Self {
333            config_reg: new_config,
334            ..self
335        }
336    }
337
338    /// Return the value set by [`RadioConfig::with_ask_no_ack()`].
339    pub const fn ask_no_ack(&self) -> bool {
340        self.feature.ask_no_ack()
341    }
342
343    /// Allow disabling auto-ack per payload.
344    ///
345    /// See `ask_no_ack` parameter for
346    /// [`EsbRadio::send()`](fn@crate::radio::prelude::EsbRadio::send) and
347    /// [`EsbRadio::write()`](fn@crate::radio::prelude::EsbRadio::write).
348    pub fn with_ask_no_ack(self, enable: bool) -> Self {
349        let new_config = self.feature.with_ask_no_ack(enable);
350        Self {
351            feature: new_config,
352            ..self
353        }
354    }
355
356    /// Return the value set by [`RadioConfig::with_dynamic_payloads()`].
357    ///
358    /// This feature is enabled automatically when enabling ACK payloads
359    /// via [`RadioConfig::with_ack_payloads()`].
360    pub const fn dynamic_payloads(&self) -> bool {
361        self.feature.dynamic_payloads()
362    }
363
364    /// Enable or disable dynamically sized payloads.
365    ///
366    /// Enabling this feature nullifies the utility of [`RadioConfig::payload_length()`].
367    pub fn with_dynamic_payloads(self, enable: bool) -> Self {
368        let new_config = self.feature.with_dynamic_payloads(enable);
369        Self {
370            feature: new_config,
371            ..self
372        }
373    }
374
375    /// Return the value set by [`RadioConfig::with_auto_ack()`].
376    pub const fn auto_ack(&self) -> u8 {
377        self.auto_ack
378    }
379
380    /// Enable or disable auto-ACK feature.
381    ///
382    /// The given value (in binary form) is used to control the auto-ack feature for each pipe.
383    /// Bit 0 controls the feature for pipe 0. Bit 1 controls the feature for pipe 1. And so on.
384    ///
385    /// To enable the feature for pipes 0, 1 and 4:
386    /// ```
387    /// use rf24::radio::RadioConfig;
388    ///
389    /// let config = RadioConfig::default().with_auto_ack(0b010011);
390    /// ```
391    /// If enabling the feature for any pipe other than 0, then the pipe 0 should also have the
392    /// feature enabled because pipe 0 is used to transmit automatic ACK packets in RX mode.
393    pub fn with_auto_ack(self, enable: u8) -> Self {
394        Self {
395            auto_ack: enable,
396            ..self
397        }
398    }
399
400    /// Return the value set by [`RadioConfig::with_ack_payloads()`].
401    pub const fn ack_payloads(&self) -> bool {
402        self.feature.ack_payloads()
403    }
404
405    /// Enable or disable custom ACK payloads for auto-ACK packets.
406    ///
407    /// ACK payloads require the [`RadioConfig::auto_ack`] and [`RadioConfig::dynamic_payloads`]
408    /// to be enabled. If ACK payloads are enabled, then this function also enables those
409    /// features (for all pipes).
410    pub fn with_ack_payloads(self, enable: bool) -> Self {
411        let auto_ack = if enable { 0xFF } else { self.auto_ack };
412        let new_config = self.feature.with_ack_payloads(enable);
413        Self {
414            auto_ack,
415            feature: new_config,
416            ..self
417        }
418    }
419
420    /// Return the value set by [`RadioConfig::with_payload_length()`].
421    ///
422    /// The hardware's maximum payload length is enforced by the hardware specific
423    /// implementations of [`EsbPayloadLength::set_payload_length()`](fn@crate::radio::prelude::EsbPayloadLength::set_payload_length).
424    pub const fn payload_length(&self) -> u8 {
425        self.payload_length
426    }
427
428    /// The payload length for statically sized payloads.
429    ///
430    /// See [`EsbPayloadLength::set_payload_length()`](fn@crate::radio::prelude::EsbPayloadLength::set_payload_length).
431    pub fn with_payload_length(self, value: u8) -> Self {
432        // NOTE: max payload length is enforced in hardware-specific implementations
433        Self {
434            payload_length: value,
435            ..self
436        }
437    }
438
439    // Close a RX pipe from receiving data.
440    //
441    // This is only useful if pipe 1 should be closed instead of open (after [`RadioConfig::default()`]).
442    pub fn close_rx_pipe(self, pipe: u8) -> Self {
443        let mut pipes = self.pipes;
444        pipes.close_rx_pipe(pipe);
445        Self { pipes, ..self }
446    }
447
448    /// Is a specified RX pipe open (`true`) or closed (`false`)?
449    ///
450    /// The value returned here is controlled by
451    /// [`RadioConfig::with_rx_address()`] (to open a pipe) and [`RadioConfig::close_rx_pipe()`].
452    pub fn is_rx_pipe_enabled(&self, pipe: u8) -> bool {
453        self.pipes.rx_pipes_enabled & (1u8 << pipe.min(8)) > 0
454    }
455
456    /// Get the address for a specified `pipe` set by [`RadioConfig::with_rx_address()`]
457    pub fn rx_address(&self, pipe: u8, address: &mut [u8]) {
458        self.pipes.get_rx_address(pipe, address);
459    }
460
461    /// Set the address of a specified RX `pipe` for receiving data.
462    ///
463    /// This does nothing if the given `pipe` is greater than `8`.
464    /// For pipes 2 - 5, the 4 LSBytes are used from address set to pipe 1 with the
465    /// MSByte from the given `address`.
466    ///
467    /// See also [`RadioConfig::with_tx_address()`].
468    pub fn with_rx_address(self, pipe: u8, address: &[u8]) -> Self {
469        let mut pipes = self.pipes;
470        pipes.set_rx_address(pipe, address);
471        Self { pipes, ..self }
472    }
473
474    /// Get the address set by [`RadioConfig::with_tx_address()`]
475    pub fn tx_address(&self, address: &mut [u8]) {
476        let len = address.len().min(5);
477        address[..len].copy_from_slice(&self.pipes.tx_address[..len]);
478    }
479
480    /// Set the TX address.
481    ///
482    /// Only pipe 0 can be used for TX operations (including auto-ACK packets during RX operations).
483    pub fn with_tx_address(self, address: &[u8]) -> Self {
484        let mut pipes = self.pipes;
485        pipes.set_tx_address(address);
486        Self { pipes, ..self }
487    }
488}
489
490#[cfg(test)]
491mod test {
492    use super::RadioConfig;
493    use crate::{CrcLength, DataRate, PaLevel};
494
495    #[test]
496    fn crc_length() {
497        let mut config = RadioConfig::default();
498        for len in [CrcLength::Disabled, CrcLength::Bit16, CrcLength::Bit8] {
499            config = config.with_crc_length(len);
500            assert_eq!(len, config.crc_length());
501        }
502    }
503
504    #[test]
505    fn config_irq_flags() {
506        let mut config = RadioConfig::default();
507        assert!(config.rx_dr());
508        assert!(config.tx_ds());
509        assert!(config.tx_df());
510        config = config.with_rx_dr(false).with_tx_ds(false).with_tx_df(false);
511        assert!(!config.rx_dr());
512        assert!(!config.tx_ds());
513        assert!(!config.tx_df());
514    }
515
516    #[test]
517    fn address_length() {
518        let mut config = RadioConfig::default();
519        for len in 0..10 {
520            config = config.with_address_length(len);
521            assert_eq!(config.address_length(), len.clamp(2, 5));
522        }
523    }
524
525    #[test]
526    fn pa_level() {
527        let mut config = RadioConfig::default();
528        for level in [PaLevel::Max, PaLevel::High, PaLevel::Low, PaLevel::Min] {
529            config = config.with_pa_level(level);
530            assert_eq!(config.pa_level(), level);
531        }
532        assert!(config.lna_enable());
533        config = config.with_lna_enable(false);
534        assert!(!config.lna_enable());
535    }
536
537    #[test]
538    fn data_rate() {
539        let mut config = RadioConfig::default();
540        for rate in [DataRate::Kbps250, DataRate::Mbps1, DataRate::Mbps2] {
541            config = config.with_data_rate(rate);
542            assert_eq!(config.data_rate(), rate);
543        }
544    }
545
546    #[test]
547    fn feature_register() {
548        let mut config = RadioConfig::default();
549        assert_eq!(config.auto_ack(), 0x3F);
550        assert!(!config.ack_payloads());
551        assert!(!config.dynamic_payloads());
552        assert!(!config.ask_no_ack());
553
554        config = config.with_ack_payloads(true);
555        assert_eq!(config.auto_ack(), 0xFF);
556        assert!(config.ack_payloads());
557        assert!(config.dynamic_payloads());
558        assert!(!config.ask_no_ack());
559
560        config = config.with_ask_no_ack(true).with_ack_payloads(false);
561        assert!(!config.ack_payloads());
562        assert!(config.dynamic_payloads());
563        assert!(config.ask_no_ack());
564
565        config = config.with_dynamic_payloads(false);
566        assert!(!config.dynamic_payloads());
567        assert!(!config.ack_payloads());
568        assert!(config.ask_no_ack());
569
570        config = config.with_auto_ack(3);
571        assert_eq!(config.auto_ack(), 3);
572        assert!(!config.dynamic_payloads());
573    }
574
575    #[test]
576    fn payload_length() {
577        let config = RadioConfig::default().with_payload_length(255);
578        assert_eq!(config.payload_length(), 255);
579    }
580
581    #[test]
582    fn channel() {
583        let config = RadioConfig::default().with_channel(255);
584        assert_eq!(config.channel(), 125);
585    }
586    #[test]
587    fn auto_retries() {
588        let mut config = RadioConfig::default();
589        assert_eq!(config.auto_retry_count(), 15);
590        assert_eq!(config.auto_retry_delay(), 5);
591        config = config.with_auto_retries(20, 3);
592        assert_eq!(config.auto_retry_count(), 3);
593        assert_eq!(config.auto_retry_delay(), 15);
594    }
595
596    #[test]
597    fn pipe_addresses() {
598        let mut config = RadioConfig::default();
599        let mut address = [0xB0; 5];
600        config = config.with_tx_address(&address);
601        let mut result = [0; 3];
602        config.tx_address(&mut result);
603        assert!(address.starts_with(&result));
604        config = config.close_rx_pipe(1).close_rx_pipe(10);
605        // just for coverage, pass an empty byte array as RX address
606        config = config.with_rx_address(0, &[]);
607        assert!(!config.is_rx_pipe_enabled(1));
608        for pipe in 0..=8 {
609            address.copy_from_slice(&[0xB0 + pipe; 5]);
610            config = config.with_rx_address(pipe, &address);
611            config.rx_address(pipe, &mut result);
612            if pipe < 2 {
613                assert!(address.starts_with(&result));
614            } else if pipe < 8 {
615                assert_eq!(address[0], result[0]);
616                // check base from pipe 1 is used for LSBs
617                assert!(result[1..].starts_with(&[0xB1, 0xB1]));
618            } else {
619                // pipe > 8 result in non-op mutations
620                assert_ne!(address[0], result[0]);
621                // check base from pipe 1 is still used for LSBs
622                assert!(result[1..].starts_with(&[0xB1, 0xB1]));
623            }
624
625            if pipe < 8 {
626                assert!(config.is_rx_pipe_enabled(pipe));
627            }
628        }
629    }
630}