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}