rf24/radio/
config.rs

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