diff --git a/Cargo.toml b/Cargo.toml
index 358f84e..4fc0f89 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -4,3 +4,4 @@ version = "0.1.0"
edition = "2021"
[dependencies]
+embedded-hal = "1.0.0"
diff --git a/src/enums.rs b/src/enums.rs
new file mode 100644
index 0000000..8594dda
--- /dev/null
+++ b/src/enums.rs
@@ -0,0 +1,52 @@
+/// Power Amplifier level. The units dBm (decibel-milliwatts or dBmW)
+/// represents a logarithmic signal loss.
+pub enum PaLevel {
+ /// | nRF24L01 | Si24R1 with
lnaEnabled = 1 | Si24R1 with
lnaEnabled = 0 |
+ /// | :-------:|:-----------------------------:|:-----------------------------:|
+ /// | -18 dBm | -6 dBm | -12 dBm |
+ MIN,
+ /// | nRF24L01 | Si24R1 with
lnaEnabled = 1 | Si24R1 with
lnaEnabled = 0 |
+ /// | :-------:|:-----------------------------:|:-----------------------------:|
+ /// | -12 dBm | 0 dBm | -4 dBm |
+ LOW,
+ /// | nRF24L01 | Si24R1 with
lnaEnabled = 1 | Si24R1 with
lnaEnabled = 0 |
+ /// | :-------:|:-----------------------------:|:-----------------------------:|
+ /// | -6 dBm | 3 dBm | 1 dBm |
+ HIGH,
+ /// | nRF24L01 | Si24R1 with
lnaEnabled = 1 | Si24R1 with
lnaEnabled = 0 |
+ /// | :-------:|:-----------------------------:|:-----------------------------:|
+ /// | 0 dBm | 7 dBm | 4 dBm |
+ MAX,
+}
+
+/// How fast data moves through the air. Units are in bits per second (bps).
+pub enum DataRate {
+ /// represents 1 Mbps
+ Mbps1,
+ /// represents 2 Mbps
+ Mbps2,
+ /// represents 250 Kbps
+ Kbps250,
+}
+
+/// The length of a CRC checksum that is used (if any).
+///
+/// Cyclical Redundancy Checking (CRC) is commonly used to ensure data integrity.
+pub enum CrcLength {
+ /// represents no CRC checksum is used
+ DISABLED,
+ /// represents CRC 8 bit checksum is used
+ BIT8,
+ /// represents CRC 16 bit checksum is used
+ BIT16,
+}
+
+/// The possible states of a FIFO.
+pub enum FifoState {
+ /// Represent the state of a FIFO when it is full.
+ Full,
+ /// Represent the state of a FIFO when it is empty.
+ Empty,
+ /// Represent the state of a FIFO when it is not full but not empty either.
+ Occupied,
+}
diff --git a/src/lib.rs b/src/lib.rs
index 7d12d9a..daa5908 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,14 +1,16 @@
-pub fn add(left: usize, right: usize) -> usize {
- left + right
-}
+#![doc(
+ html_logo_url = "https://raw.githubusercontent.com/nRF24/RF24/master/docs/sphinx/_static/Logo%20large.png"
+)]
+#![doc(
+ html_favicon_url = "https://github.com/nRF24/RF24/raw/master/docs/sphinx/_static/new_favicon.ico"
+)]
-#[cfg(test)]
-mod tests {
- use super::*;
-
- #[test]
- fn it_works() {
- let result = add(2, 2);
- assert_eq!(result, 4);
- }
-}
+mod enums;
+pub use enums::{CrcLength, DataRate, FifoState, PaLevel};
+pub use radio::{
+ EsbAutoAck, EsbChannel, EsbCrcLength, EsbDataRate, EsbFifo, EsbPaLevel, EsbPayloadLength,
+ EsbPipe, EsbPower, EsbRadio, EsbStatus,
+};
+mod radio;
+#[doc(inline)]
+pub use radio::{Nrf24Error, RF24};
diff --git a/src/radio/mod.rs b/src/radio/mod.rs
new file mode 100644
index 0000000..2ceae25
--- /dev/null
+++ b/src/radio/mod.rs
@@ -0,0 +1,439 @@
+use crate::enums::{CrcLength, DataRate, FifoState, PaLevel};
+mod rf24;
+pub use rf24::{Nrf24Error, RF24};
+
+pub trait EsbPipe {
+ type PipeErrorType;
+
+ /// Open a specified `pipe` for receiving data when radio is in RX role.
+ ///
+ /// If the specified `pipe` is not in range [0, 5], then this function does nothing.
+ ///
+ /// Up to 6 pipes can be open for reading at once. Open all the required
+ /// reading pipes, and then call [`RF24::start_listening()`].
+ ///
+ /// ### About pipe addresses
+ /// Pipes 0 and 1 will store a full 5-byte address. Pipes 2-5 will technically
+ /// only store a single byte, borrowing up to 4 additional bytes from pipe 1 per
+ /// [`RF24::set_address_length()`].
+ ///
+ /// Pipes 1-5 should share the same address, except the first byte.
+ /// Only the first byte in the array should be unique, e.g.
+ /// ```rust
+ /// let a = ["Prime", "2Node", "3xxxx", "4xxxx"];
+ /// radio.open_rx_pipe(0, a[0].as_bytes()).unwrap(); // address used is "Prime"
+ /// radio.open_rx_pipe(1, a[1].as_bytes()).unwrap(); // address used is "2Node"
+ /// radio.open_rx_pipe(2, a[2].as_bytes()).unwrap(); // address used is "3Node"
+ /// radio.open_rx_pipe(3, a[3].as_bytes()).unwrap(); // address used is "4Node"
+ /// ```
+ ///
+ ///
+ ///
+ /// If the pipe 0 is opened for receiving by this function, the `address`
+ /// passed to this function (for pipe 0) will be restored at every call to
+ /// [`RF24::start_listening()`].
+ /// This address restoration is implemented because of the underlying necessary
+ /// functionality of [`RF24::open_tx_pipe()`].
+ ///
+ /// It is important that the `address` length for pipe 0
+ /// is equal to the length configured by [`RF24::set_address_length()`].
+ ///
+ ///
+ ///
+ /// Read [maniacBug's blog post](http://maniacalbits.blogspot.com/2013/04/rf24-addressing-nrf24l01-radios-require.html)
+ /// to understand how to avoid using malformed addresses.
+ fn open_rx_pipe(&mut self, pipe: u8, address: &[u8]) -> Result<(), Self::PipeErrorType>;
+
+ /// Set an address to pipe 0 for transmitting when radio is in TX role.
+ ///
+ fn open_tx_pipe(&mut self, address: &[u8]) -> Result<(), Self::PipeErrorType>;
+
+ /// Close a specified pipe from receiving data when radio is in RX role.
+ fn close_rx_pipe(&mut self, pipe: u8) -> Result<(), Self::PipeErrorType>;
+ fn set_address_length(&mut self, length: u8) -> Result<(), Self::PipeErrorType>;
+ fn get_address_length(&mut self) -> Result;
+}
+pub trait EsbChannel {
+ type ChannelErrorType;
+
+ /// Set the radio's currently selected channel.
+ ///
+ /// These channels translate to the RF frequency as an offset of Hz from 2400 MHz.
+ /// The default channel is 76 (2400 + 76 = 2.476 GHz).
+ fn set_channel(&mut self, channel: u8) -> Result<(), Self::ChannelErrorType>;
+
+ /// Get the radio's currently selected channel.
+ fn get_channel(&mut self) -> Result;
+}
+
+pub trait EsbStatus {
+ type StatusErrorType;
+ fn get_status_flags(
+ &mut self,
+ rx_dr: &mut Option,
+ tx_ds: &mut Option,
+ tx_df: &mut Option,
+ ) -> Result<(), Self::StatusErrorType>;
+ fn set_status_flags(
+ &mut self,
+ rx_dr: bool,
+ tx_ds: bool,
+ tx_df: bool,
+ ) -> Result<(), Self::StatusErrorType>;
+ fn clear_status_flags(
+ &mut self,
+ rx_dr: bool,
+ tx_ds: bool,
+ tx_df: bool,
+ ) -> Result<(), Self::StatusErrorType>;
+ fn update(&mut self) -> Result<(), Self::StatusErrorType>;
+}
+
+pub trait EsbFifo {
+ type FifoErrorType;
+
+ /// Flush the radio's RX FIFO.
+ fn flush_rx(&mut self) -> Result<(), Self::FifoErrorType>;
+
+ /// Flush the radio's TX FIFO.
+ ///
+ /// This function is automatically called by [`EsbRadio::stop_listening()`]
+ /// if ACK payloads are enabled.
+ fn flush_tx(&mut self) -> Result<(), Self::FifoErrorType>;
+
+ /// Get the state of the specified FIFO.
+ ///
+ /// - Pass `true` to `about_tx` parameter to get the state of the TX FIFO.
+ /// - Pass `false` to `about_tx` parameter to get the state of the RX FIFO.
+ fn get_fifo_state(&mut self, about_tx: bool) -> Result;
+
+ /// Is there a payload available in the radio's RX FIFO?
+ ///
+ /// This function simply returns true if there is data to [`EsbRadio::read()`] from the RX FIFO.
+ /// Use [`EsbFifo::available_pipe()`] to get information about the pipe that received the data.
+ fn available(&mut self) -> Result;
+
+ /// This is similar to [`EsbFifo::available()`] except that if the `pipe` parameter is given
+ /// a mutable [`Some`] value, then the pipe number that received the data is stored to it.
+ ///
+ /// If there is no data ready to [`EsbRadio::read()`] in the RX FIFO, then the `pipe` parameter's
+ /// value is untouched.
+ ///
+ /// ```rust
+ /// let mut pipe = Some(9 as u8); // using an invalid pipe number
+ /// if radio.available_pipe(&pipe).is_ok_and(|rv| rv) {
+ /// // `pipe` should now be set to a valid pipe number
+ /// print!("A Payload was received on pipe {}", pipe.unwrap());
+ /// }
+ /// ```
+ ///
+ ///
+ ///
+ /// According to the nRF24L01 datasheet, the data saved to `pipe` is
+ /// "unreliable" during a FALLING transition on the IRQ pin.
+ ///
+ /// During an ISR (Interrupt Service Routine), call
+ /// [`EsbStatus::get_status_flags()`] and/or [`EsbStatus::clear_status_flags()`]
+ /// before calling this function.
+ ///
+ ///
+ fn available_pipe(&mut self, pipe: &mut Option) -> Result;
+}
+
+pub trait EsbPayloadLength {
+ type PayloadLengthErrorType;
+
+ /// Set the radio's static payload length.
+ ///
+ /// Note, this has no effect when dynamic payloads are enabled.
+ fn set_payload_length(&mut self, length: u8) -> Result<(), Self::PayloadLengthErrorType>;
+
+ /// Get the currently configured static payload length used on pipe 0
+ ///
+ /// Use [`RF24::get_dynamic_payload_length()`] instead when dynamic payloads are enabled.
+ fn get_payload_length(&mut self) -> Result;
+
+ /// Toggle the dynamic payloads feature for all pipes.
+ ///
+ /// Dynamic payloads are required to use ACK packets with payloads appended.
+ ///
+ /// Enabling dynamic payloads nullifies the effect of
+ /// [`EsbPayloadLength::set_payload_length()`] and
+ /// [`EsbPayloadLength::get_payload_length()`].
+ /// Use [`EsbPayloadLength::get_dynamic_payload_length()`] to
+ /// fetch the length of the next [`EsbFifo::available()`] payload in the RX FIFO.
+ ///
+ /// ```rust
+ /// radio.set(true).unwrap();
+ /// // ... then after or during RX role:
+ /// if radio.available().unwrap() {
+ /// let length = radio.get_dynamic_payload_length().unwrap();
+ /// let mut payload = [0; 32];
+ /// radio.read(&mut payload, length).unwrap();
+ /// }
+ /// ```
+ fn set_dynamic_payloads(&mut self, enable: bool) -> Result<(), Self::PayloadLengthErrorType>;
+
+ /// Get the dynamic length of the next available payload in the RX FIFO.
+ ///
+ /// This returns `0` when dynamic payloads are disabled.
+ ///
+ /// I specifically chose a [`usize`] return type for convenience when working with
+ /// slices. Due to the nRF24L01's maximum 32 byte payload restriction, this will
+ /// never return a value greater than 32.
+ fn get_dynamic_payload_length(&mut self) -> Result;
+}
+
+pub trait EsbAutoAck: EsbPayloadLength {
+ type AutoAckErrorType;
+
+ /// Allows appending payloads to automatic ACK (acknowledgement) packets.
+ ///
+ /// By default this feature is disabled.
+ /// Using payloads in the auto-ack packets requires enabling dynamic payloads feature
+ /// This function will only ensure dynamic payloads are enabled on pipes 0 and 1.
+ /// Use [`EsbPayloadLength::set_dynamic_payloads()`] to enable dynamic payloads on all pipes.
+ ///
+ /// Use [`EsbFifo::available()`] to see if there any ACK payloads in the RX FIFO.
+ /// Use [`EsbRadio::read()`] to fetch the payloads from the RX FIFO.
+ ///
+ /// To append a payload to an auto ack packet, use [`EsbAutoAck::write_ack_payload()`].
+ fn allow_ack_payloads(&mut self, enable: bool) -> Result<(), Self::AutoAckErrorType>;
+
+ /// Write a `buf` to the radio's TX FIFO for use with automatic ACK packets.
+ ///
+ /// The given `buf` will be the outgoing payload added to an automatic ACK packet
+ /// when acknowledging an incoming payload that was received with the specified
+ /// `pipe`. This function returns `true` if the payload was written to the TX FIFO.
+ /// It returns `false` under the following conditions:
+ ///
+ /// - the radio's TX FIFO is full.
+ /// - the specified `pipe` number is invalid (not in range [0, 5]).
+ /// - the ACK payload feature is not enabled (see [`EsbAutoAck::allow_ack_payloads()`])
+ ///
+ /// It is important to discard any non-ACK payloads in the TX FIFO (using
+ /// [`EsbFifo::flush_tx()`]) before writing the first ACK payload into the TX FIFO.
+ /// This function can be used before and after calling [`EsbRadio::start_listening()`].
+ ///
+ ///
+ ///
+ /// The payload must be loaded into the radio's TX FIFO _before_ the incoming
+ /// payload is received.
+ ///
+ /// Remember, the TX FIFO stack can store only 3 payloads,
+ /// and there are typically more pipes than TX FIFO occupancy.
+ /// Expected behavior is better assured when the ACK payloads are only used
+ /// for 1 pipe
+ ///
+ ///
+ ///
+ /// Since ACK payloads require the dynamic payloads feature enabled, the given
+ /// `buf`'s length will determine the length of the payload in the ACK packet.
+ ///
+ /// See also [`EsbAutoAck::allow_ack_payloads()`],
+ /// [`EsbPayloadLength::set_dynamic_payloads`], and [`EsbAutoAck::set_auto_ack()`].
+ fn write_ack_payload(&mut self, pipe: u8, buf: &[u8]) -> Result;
+
+ /// Enable or disable the auto-ack (automatic acknowledgement) feature for all
+ /// pipes.
+ ///
+ /// This feature is enabled by default. The auto-ack feature responds to every
+ /// received payload with an ACK packet. These ACK packets get sent
+ /// from the receiving radio back to the transmitting radio. To attach an
+ /// ACK payload to a ACK packet, use [`EsbAutoAck::write_ack_payload()`]`.
+ ///
+ /// If this feature is disabled on a transmitting radio, then the
+ /// transmitting radio will always report that the payload was received
+ /// (even if it was not). Please remember that this feature's configuration
+ /// needs to match for transmitting and receiving radios.
+ ///
+ /// When using the `ask_no_ack` parameter to [`EsbRadio::send()`] and [`EsbRadio::write()`],
+ /// this feature can be disabled for an individual payload. However, if this feature
+ /// is disabled, then the `ask_no_ack` parameter will have no effect.
+ ///
+ /// If disabling auto-acknowledgment packets, the ACK payloads
+ /// feature is also disabled as this feature is required to send ACK
+ /// payloads.
+ fn set_auto_ack(&mut self, enable: bool) -> Result<(), Self::AutoAckErrorType>;
+
+ /// Set the auto-ack feature for an individual `pipe`.
+ ///
+ /// Pipe 0 is used for TX operations, which include sending ACK packets. If
+ /// using this feature on both TX & RX nodes, then pipe 0 must have this
+ /// feature enabled for the RX & TX operations. If this feature is disabled
+ /// on a transmitting radio's pipe 0, then the transmitting radio will
+ /// always report that the payload was received (even if it was not).
+ /// Remember to also enable this feature for any pipe that is openly
+ /// listening to a transmitting radio with this feature enabled.
+ ///
+ /// If this feature is enabled for pipe 0, then the `ask_no_ack` parameter to
+ /// [`EsbRadio::send()`] and [`EsbRadio::write()`] can be used to disable this feature for
+ /// an individual payload. However, if this feature is disabled for pipe 0,
+ /// then the `ask_no_ack` parameter will have no effect.
+ ///
+ /// If disabling auto-acknowledgment packets on pipe 0, the ACK
+ /// payloads feature is also disabled as this feature is required on pipe 0
+ /// to send ACK payloads.
+ fn set_auto_ack_pipe(&mut self, enable: bool, pipe: u8) -> Result<(), Self::AutoAckErrorType>;
+
+ /// Set the number of retry attempts and delay between retry attempts when
+ /// transmitting a payload.
+ ///
+ /// The radio is waiting for an acknowledgement (ACK) packet during the delay between retry attempts.
+ ///
+ /// Both parameters are clamped to range [0, 15].
+ /// - `delay`: How long to wait between each retry, in multiples of
+ /// 250 us. The minimum of 0 means 250 us, and the maximum of 15 means
+ /// 4000 us. The default value of 5 means 1500us (5 * 250 + 250).
+ /// - `count`: How many retries before giving up. The default/maximum is 15. Use
+ /// 0 to disable the auto-retry feature all together.
+ ///
+ /// Disabling the auto-retry feature on a transmitter still uses the
+ /// auto-ack feature (if enabled), except it will not retry to transmit if
+ /// the payload was not acknowledged on the first attempt.
+ fn set_auto_retries(&mut self, delay: u8, count: u8) -> Result<(), Self::AutoAckErrorType>;
+
+ /// Allow the functionality of the `ask_no_ack` parameter in [`EsbRadio::send()`] and
+ /// [`EsbRadio::write()`].
+ ///
+ /// This only needs to called once before using the `ask_no_ack` parameter in
+ /// [`EsbRadio::send()`] and [`EsbRadio::write()`]. Enabling this feature will basically
+ /// allow disabling the auto-ack feature on a per-payload basis.
+ fn allow_ask_no_ack(&mut self, enable: bool) -> Result<(), Self::AutoAckErrorType>;
+}
+
+pub trait EsbPaLevel {
+ type PaLevelErrorType;
+
+ /// Get the currently configured Power Amplitude Level (PA Level)
+ fn get_pa_level(&mut self) -> Result;
+
+ /// Set the radio's Power Amplitude Level (PA Level)
+ fn set_pa_level(&mut self, pa_level: PaLevel) -> Result<(), Self::PaLevelErrorType>;
+}
+
+pub trait EsbPower {
+ type PowerErrorType;
+
+ /// Power down the radio.
+ ///
+ ///
+ ///
+ /// The nRF24L01 cannot receive nor transmit data when powered down.
+ ///
+ ///
+ fn power_down(&mut self) -> Result<(), Self::PowerErrorType>;
+
+ /// Power up the radio.
+ ///
+ /// This wakes the radio from a sleep state, resulting in a
+ /// power standby mode that allows the radio to receive or transmit data.
+ ///
+ /// To ensure proper operation, this function will `delay` after the radio is awaken.
+ /// If the `delay` parameter is given a [`Some`] value, then the this function
+ /// will wait for the specified number of microseconds. If `delay` is a [`None`]
+ /// value, this function will wait for 5 milliseconds.
+ ///
+ /// To perform other tasks will the radio is powering up:
+ /// ```rust
+ /// radio.power_up(Some(0)).unwrap();
+ /// // ... do something else for 5 milliseconds
+ /// radio.start_listening().unwrap();
+ /// ```
+ fn power_up(&mut self, delay: Option) -> Result<(), Self::PowerErrorType>;
+}
+
+pub trait EsbCrcLength {
+ type CrcLengthErrorType;
+
+ /// Get the currently configured CRC (Cyclical Redundancy Checksum) length
+ fn get_crc_length(&mut self) -> Result;
+
+ /// Set the radio's CRC (Cyclical Redundancy Checksum) length
+ fn set_crc_length(&mut self, data_rate: CrcLength) -> Result<(), Self::CrcLengthErrorType>;
+}
+
+pub trait EsbDataRate {
+ type DataRateErrorType;
+
+ /// Get the currently configured Data Rate
+ fn get_data_rate(&mut self) -> Result;
+
+ /// Set the radio's Data Rate
+ fn set_data_rate(&mut self, data_rate: DataRate) -> Result<(), Self::DataRateErrorType>;
+}
+
+pub trait EsbRadio:
+ EsbChannel
+ + EsbPipe
+ + EsbStatus
+ + EsbFifo
+ + EsbPayloadLength
+ + EsbAutoAck
+ + EsbPaLevel
+ + EsbPower
+ + EsbCrcLength
+ + EsbDataRate
+{
+ type RadioErrorType;
+
+ /// Initialize the radio's hardware
+ fn init(&mut self) -> Result<(), Self::RadioErrorType>;
+
+ /// Put the radio into RX role
+ fn start_listening(&mut self) -> Result<(), Self::RadioErrorType>;
+
+ /// Put the radio into TX role
+ fn stop_listening(&mut self) -> Result<(), Self::RadioErrorType>;
+
+ /// Blocking write.
+ ///
+ /// This transmits a payload (given by `buf`) and returns a bool describing if
+ /// the transmission was successful or not.
+ ///
+ /// See [`EsbRadio::write()`] for description of `ask_no_ack` parameter and more
+ /// detail about how the radio processes data in the TX FIFO.
+ fn send(&mut self, buf: &[u8], ask_no_ack: bool) -> Result;
+
+ /// Non-blocking write.
+ ///
+ /// This function does not wait for the radio to complete the transmission.
+ /// Instead it simply writes the given `buf` into the radio's TX FIFO.
+ /// If the TX FIFO is already full, this function just calls
+ /// [`EsbStatus::clear_status_flags()`] and returns `false`.
+ ///
+ /// If `ask_no_ack` is true, then the transmitted payload will not use the auto-ack
+ /// feature. This parameter is different from auto-ack because it controls the
+ /// auto-ack feature for only this payload, whereas the auto-ack feature controls
+ /// ACK packets for all payloads. If [`EsbAutoAck::allow_ask_no_ack()`] is not called
+ /// at least once prior to asserting this parameter, then it has no effect.
+ ///
+ /// This function's `start_tx` parameter determines if the radio should enter active
+ /// TX mode. This function does not deactivate TX mode.
+ ///
+ /// If the radio's remains in TX mode after successfully transmitting a payload,
+ /// then any subsequent payloads in the TX FIFO will automatically be processed.
+ /// Set the `start_tx` parameter `false` to prevent entering TX mode.
+ fn write(
+ &mut self,
+ buf: &[u8],
+ ask_no_ack: bool,
+ start_tx: bool,
+ ) -> Result;
+
+ fn resend(&mut self) -> Result;
+
+ fn rewrite(&mut self) -> Result<(), Self::RadioErrorType>;
+
+ fn get_last_arc(&mut self) -> Result;
+
+ /// Read data from the radio's RX FIFO into the specified `buf`.
+ ///
+ /// All payloads received by the radio are stored in the RX FIFO (a 3 layer stack).
+ /// Use [`EsbFifo::available()`] to determine if there is data ready to read.
+ ///
+ /// The `len` parameter determines how much data is stored to `buf`. Ultimately,
+ /// the value of `len` is restricted by the radio's maximum 32 byte limit and the
+ /// length of the given `buf`.
+ fn read(&mut self, buf: &mut [u8], len: usize) -> Result<(), Self::RadioErrorType>;
+}
diff --git a/src/radio/rf24/auto_ack.rs b/src/radio/rf24/auto_ack.rs
new file mode 100644
index 0000000..324f042
--- /dev/null
+++ b/src/radio/rf24/auto_ack.rs
@@ -0,0 +1,100 @@
+use embedded_hal::{delay::DelayNs, digital::OutputPin, spi::SpiDevice};
+
+use crate::{
+ radio::{EsbAutoAck, EsbPayloadLength},
+ Nrf24Error, RF24,
+};
+
+use super::{commands, mnemonics, registers};
+
+impl EsbAutoAck for RF24
+where
+ SPI: SpiDevice,
+ DO: OutputPin,
+ DELAY: DelayNs,
+{
+ type AutoAckErrorType = Nrf24Error;
+
+ fn allow_ack_payloads(&mut self, enable: bool) -> Result<(), Self::AutoAckErrorType> {
+ if self._ack_payloads_enabled != enable {
+ if enable {
+ self.spi_read(1, registers::FEATURE)?;
+ let mut reg_val = self._buf[1] | mnemonics::EN_ACK_PAY | mnemonics::EN_DPL;
+ self.spi_write_byte(registers::FEATURE, reg_val)?;
+
+ // Enable dynamic payload on pipes 0 & 1
+ self.spi_read(1, registers::DYNPD)?;
+ reg_val = self._buf[1] | 3;
+ self.spi_write_byte(registers::DYNPD, reg_val)?;
+ self._dynamic_payloads_enabled = true;
+ } else {
+ // disable ack payloads (leave dynamic payload features as is)
+ self.spi_read(1, registers::FEATURE)?;
+ let reg_val = self._buf[1] & !mnemonics::EN_ACK_PAY;
+ self.spi_write_byte(registers::FEATURE, reg_val)?;
+ }
+ self._ack_payloads_enabled = enable;
+ }
+ Ok(())
+ }
+
+ fn set_auto_ack(&mut self, enable: bool) -> Result<(), Nrf24Error<::Error, ::Error>> {
+ self.spi_write_byte(registers::EN_AA, 0x3F * enable as u8)?;
+ // accommodate ACK payloads feature
+ if !enable && self._ack_payloads_enabled {
+ self.set_dynamic_payloads(false)?;
+ }
+ Ok(())
+ }
+
+ fn set_auto_ack_pipe(&mut self, enable: bool, pipe: u8) -> Result<(), Self::AutoAckErrorType> {
+ if pipe > 5 {
+ return Ok(());
+ }
+ self.spi_read(1, registers::EN_AA)?;
+ let mask = 1 << pipe;
+ if !enable && self._ack_payloads_enabled && pipe == 0 {
+ self.allow_ack_payloads(enable)?;
+ }
+ let reg_val = self._buf[1] & !mask | (mask * enable as u8);
+ self.spi_write_byte(registers::EN_AA, reg_val)
+ }
+
+ fn allow_ask_no_ack(&mut self, enable: bool) -> Result<(), Self::AutoAckErrorType> {
+ self.spi_read(1, registers::FEATURE)?;
+ self.spi_write_byte(registers::FEATURE, self._buf[1] & !1 | enable as u8)
+ }
+
+ fn write_ack_payload(&mut self, pipe: u8, buf: &[u8]) -> Result {
+ if self._ack_payloads_enabled && pipe <= 5 {
+ let len = {
+ let buf_len = buf.len();
+ if buf_len > 32 {
+ 32usize
+ } else {
+ buf_len
+ }
+ };
+ self.spi_write_buf(commands::W_ACK_PAYLOAD | pipe, &buf[..len])?;
+ return Ok(0 == self._status & 1);
+ }
+ Ok(false)
+ }
+
+ fn set_auto_retries(&mut self, delay: u8, count: u8) -> Result<(), Self::AutoAckErrorType> {
+ let out = {
+ if count > 15 {
+ 15
+ } else {
+ count
+ }
+ } | ({
+ if delay > 15 {
+ 15
+ } else {
+ delay
+ }
+ } << 4);
+ self.spi_write_byte(registers::SETUP_RETR, out)
+ }
+}
diff --git a/src/radio/rf24/channel.rs b/src/radio/rf24/channel.rs
new file mode 100644
index 0000000..e1afe61
--- /dev/null
+++ b/src/radio/rf24/channel.rs
@@ -0,0 +1,31 @@
+use crate::radio::{rf24::registers, EsbChannel};
+use crate::{Nrf24Error, RF24};
+use embedded_hal::{delay::DelayNs, digital::OutputPin, spi::SpiDevice};
+
+impl EsbChannel for RF24
+where
+ SPI: SpiDevice,
+ DO: OutputPin,
+ DELAY: DelayNs,
+{
+ type ChannelErrorType = Nrf24Error;
+
+ /// The nRF24L01 support 126 channels. The specified `channel` is
+ /// clamped to the range [0, 125].
+ fn set_channel(&mut self, channel: u8) -> Result<(), Self::ChannelErrorType> {
+ let ch = {
+ if channel > 125 {
+ 125
+ } else {
+ channel
+ }
+ };
+ self.spi_write_byte(registers::RF_CH, ch)
+ }
+
+ /// See also [`RF24::set_channel()`].
+ fn get_channel(&mut self) -> Result {
+ self.spi_read(1, registers::RF_CH)?;
+ Ok(self._buf[1].clone())
+ }
+}
diff --git a/src/radio/rf24/crc_length.rs b/src/radio/rf24/crc_length.rs
new file mode 100644
index 0000000..f9e03d3
--- /dev/null
+++ b/src/radio/rf24/crc_length.rs
@@ -0,0 +1,39 @@
+use embedded_hal::{delay::DelayNs, digital::OutputPin, spi::SpiDevice};
+
+use crate::{radio::EsbCrcLength, CrcLength, Nrf24Error, RF24};
+
+use super::registers;
+
+impl EsbCrcLength for RF24
+where
+ SPI: SpiDevice,
+ DO: OutputPin,
+ DELAY: DelayNs,
+{
+ type CrcLengthErrorType = Nrf24Error;
+
+ fn get_crc_length(&mut self) -> Result {
+ let result = self.spi_read(1, registers::CONFIG);
+ result?;
+ let crc_bin = self._buf[1] >> 2 & 3;
+ match crc_bin {
+ 0 => Ok(CrcLength::DISABLED),
+ 5 => Ok(CrcLength::BIT8),
+ 6 => Ok(CrcLength::BIT16),
+ _ => Err(Nrf24Error::BinaryCorruption),
+ }
+ }
+
+ fn set_crc_length(&mut self, data_rate: CrcLength) -> Result<(), Self::CrcLengthErrorType> {
+ let crc_bin = {
+ match data_rate {
+ CrcLength::DISABLED => 0 as u8,
+ CrcLength::BIT8 => 5 as u8,
+ CrcLength::BIT16 => 6 as u8,
+ }
+ } << 2;
+ self.spi_read(1, registers::CONFIG)?;
+ let out = self._buf[1] & (3 << 2) | crc_bin;
+ self.spi_write_byte(registers::CONFIG, out)
+ }
+}
diff --git a/src/radio/rf24/data_rate.rs b/src/radio/rf24/data_rate.rs
new file mode 100644
index 0000000..ebbb9b3
--- /dev/null
+++ b/src/radio/rf24/data_rate.rs
@@ -0,0 +1,48 @@
+use embedded_hal::{delay::DelayNs, digital::OutputPin, spi::SpiDevice};
+
+use crate::{radio::EsbDataRate, DataRate, Nrf24Error, RF24};
+
+use super::registers;
+
+impl EsbDataRate for RF24
+where
+ SPI: SpiDevice,
+ DO: OutputPin,
+ DELAY: DelayNs,
+{
+ type DataRateErrorType = Nrf24Error;
+
+ fn get_data_rate(&mut self) -> Result {
+ let result = self.spi_read(1, registers::RF_SETUP);
+ result?;
+ let da_bin = self._buf[1] >> 3 & 3;
+ match da_bin {
+ 0 => Ok(DataRate::Mbps1),
+ 1 => Ok(DataRate::Mbps2),
+ 2 => Ok(DataRate::Kbps250),
+ _ => Err(Nrf24Error::BinaryCorruption),
+ }
+ }
+
+ fn set_data_rate(&mut self, data_rate: DataRate) -> Result<(), Self::DataRateErrorType> {
+ let da_bin = {
+ match data_rate {
+ DataRate::Mbps1 => {
+ self._tx_delay = 280;
+ 0 as u8
+ }
+ DataRate::Mbps2 => {
+ self._tx_delay = 240;
+ 1 as u8
+ }
+ DataRate::Kbps250 => {
+ self._tx_delay = 505;
+ 2 as u8
+ }
+ }
+ } << 3;
+ self.spi_read(1, registers::RF_SETUP)?;
+ let out = self._buf[1] & (3 << 3) | da_bin;
+ self.spi_write_byte(registers::RF_SETUP, out)
+ }
+}
diff --git a/src/radio/rf24/fifo.rs b/src/radio/rf24/fifo.rs
new file mode 100644
index 0000000..f0a5e88
--- /dev/null
+++ b/src/radio/rf24/fifo.rs
@@ -0,0 +1,54 @@
+use embedded_hal::{delay::DelayNs, digital::OutputPin, spi::SpiDevice};
+
+use crate::{radio::EsbFifo, FifoState, Nrf24Error, RF24};
+
+use super::{commands, registers};
+
+impl EsbFifo for RF24
+where
+ SPI: SpiDevice,
+ DO: OutputPin,
+ DELAY: DelayNs,
+{
+ type FifoErrorType = Nrf24Error;
+
+ fn available(&mut self) -> Result {
+ self.available_pipe(&mut None)
+ }
+
+ fn available_pipe(&mut self, pipe: &mut Option) -> Result {
+ self.spi_read(1, registers::FIFO_STATUS)?;
+ if self._buf[1] & 1 == 0 {
+ // RX FIFO is not empty
+ // get last used pipe (if pipe != None)
+ if let Some(rx_pipe) = pipe {
+ self.spi_read(1, registers::STATUS)?;
+ *rx_pipe = &self._buf[1].clone() >> 1 & 7;
+ }
+ return Ok(true);
+ }
+ Ok(false)
+ }
+
+ /// Use this to discard all 3 layers in the radio's RX FIFO.
+ fn flush_rx(&mut self) -> Result<(), Self::FifoErrorType> {
+ self.spi_read(0, commands::FLUSH_RX)
+ }
+
+ /// Use this to discard all 3 layers in the radio's TX FIFO.
+ fn flush_tx(&mut self) -> Result<(), Self::FifoErrorType> {
+ self.spi_read(0, commands::FLUSH_TX)
+ }
+
+ fn get_fifo_state(&mut self, about_tx: bool) -> Result {
+ self.spi_read(1, registers::FIFO_STATUS)?;
+ let offset = about_tx as u8 * 4;
+ let status = (self._buf[1] & (3 << offset)) >> offset;
+ match status {
+ 0 => Ok(FifoState::Occupied),
+ 1 => Ok(FifoState::Empty),
+ 2 => Ok(FifoState::Full),
+ _ => Err(Nrf24Error::BinaryCorruption),
+ }
+ }
+}
diff --git a/src/radio/rf24/mod.rs b/src/radio/rf24/mod.rs
new file mode 100644
index 0000000..612ac51
--- /dev/null
+++ b/src/radio/rf24/mod.rs
@@ -0,0 +1,480 @@
+use embedded_hal::{delay::DelayNs, digital::OutputPin, spi::SpiDevice};
+mod auto_ack;
+mod channel;
+mod crc_length;
+mod data_rate;
+mod fifo;
+mod pa_level;
+mod payload_length;
+mod pipe;
+mod power;
+pub mod registers;
+mod status;
+use super::{
+ EsbAutoAck, EsbChannel, EsbCrcLength, EsbDataRate, EsbFifo, EsbPaLevel, EsbPayloadLength,
+ EsbPipe, EsbPower, EsbStatus,
+};
+use crate::{
+ enums::{CrcLength, DataRate, PaLevel},
+ EsbRadio,
+};
+
+/// An collection of error types to describe hardware malfunctions.
+#[derive(Clone, Copy, Debug)]
+pub enum Nrf24Error {
+ /// Represents a SPI transaction error.
+ Spi(SPI),
+ /// Represents a DigitalOutput error.
+ Gpo(DO),
+ /// Represents a corruption of binary data (as it was transferred over the SPI bus' MISO)
+ BinaryCorruption,
+}
+
+/// A private module encapsulating SPI commands for the nRF24L01.
+mod commands {
+ pub const W_REGISTER: u8 = 0x20;
+ pub const ACTIVATE: u8 = 0x50;
+ pub const R_RX_PL_WID: u8 = 0x60;
+ pub const R_RX_PAYLOAD: u8 = 0x61;
+ pub const W_TX_PAYLOAD: u8 = 0xA0;
+ pub const W_ACK_PAYLOAD: u8 = 0xA8;
+ pub const FLUSH_TX: u8 = 0xE1;
+ pub const FLUSH_RX: u8 = 0xE2;
+ pub const REUSE_TX_PL: u8 = 0xE3;
+ pub const NOP: u8 = 0xFF;
+}
+
+/// A private module to encapsulate bit mnemonics
+mod mnemonics {
+ pub const MASK_RX_DR: u8 = 1 << 6;
+ pub const MASK_TX_DS: u8 = 1 << 5;
+ pub const MASK_MAX_RT: u8 = 1 << 4;
+ pub const EN_DPL: u8 = 1 << 2;
+ pub const EN_ACK_PAY: u8 = 1 << 1;
+}
+
+pub struct RF24 {
+ // private attributes
+ _spi: SPI,
+ _status: u8,
+ _ce_pin: DO,
+ _buf: [u8; 33],
+ _is_plus_variant: bool,
+ _ack_payloads_enabled: bool,
+ _dynamic_payloads_enabled: bool,
+ _config_reg: u8,
+ _wait: DELAY,
+ _pipe0_rx_addr: Option<[u8; 5]>,
+ _addr_length: u8,
+ _tx_delay: u32,
+ _payload_length: u8,
+}
+
+impl RF24
+where
+ SPI: SpiDevice,
+ DO: OutputPin,
+ DELAY: DelayNs,
+{
+ /// Instantiate an [`RF24`] object for use on the specified
+ /// `spi` bus with the given `ce_pin`.
+ ///
+ /// The radio's CSN pin (aka Chip Select pin) shall be defined
+ /// when instantiating the [`SpiDevice`] object (passed to the
+ /// `spi` parameter).
+ pub fn new(ce_pin: DO, spi: SPI, delay_fn: DELAY) -> RF24 {
+ RF24 {
+ _status: 0,
+ _ce_pin: ce_pin,
+ _spi: spi,
+ _buf: [0 as u8; 33],
+ _is_plus_variant: true,
+ _ack_payloads_enabled: false,
+ _dynamic_payloads_enabled: false,
+ _config_reg: 0,
+ _wait: delay_fn,
+ _pipe0_rx_addr: None,
+ _addr_length: 5,
+ _tx_delay: 250,
+ _payload_length: 32,
+ }
+ }
+
+ fn spi_transfer(&mut self, len: usize) -> Result<(), Nrf24Error> {
+ self._spi
+ .transfer_in_place(&mut self._buf[..len])
+ .map_err(Nrf24Error::Spi)?;
+ self._status = self._buf[0];
+ Ok(())
+ }
+
+ /// This is also used to write SPI commands that consist of 1 byte:
+ /// ```
+ /// self.spi_read(0, commands::NOP)?;
+ /// // STATUS register is now stored in self._status
+ /// ```
+ fn spi_read(
+ &mut self,
+ len: usize,
+ command: u8,
+ ) -> Result<(), Nrf24Error> {
+ self._buf[0] = command;
+ self.spi_transfer(len + 1)
+ }
+
+ fn spi_write_byte(
+ &mut self,
+ command: u8,
+ byte: u8,
+ ) -> Result<(), Nrf24Error> {
+ self._buf[0] = command | commands::W_REGISTER;
+ self._buf[1] = byte;
+ self.spi_transfer(2)
+ }
+
+ fn spi_write_buf(
+ &mut self,
+ command: u8,
+ buf: &[u8],
+ ) -> Result<(), Nrf24Error> {
+ self._buf[0] = command;
+ // buffers in rust are stored in Big Endian memory.
+ // the nRF24L01 expects multi-byte SPI transactions to be Little Endian.
+ // So, reverse the byteOrder when loading the user's `buf`` into the lib's `_buf``
+ let buf_len = buf.len();
+ for i in buf_len..0 {
+ self._buf[i + 1 - buf_len] = buf[i];
+ }
+ self.spi_transfer(buf_len + 1)
+ }
+
+ /// A private function to write a special SPI command specific to older
+ /// non-plus variants of the nRF24L01 radio module. It has no effect on plus variants.
+ fn toggle_features(&mut self) -> Result<(), Nrf24Error> {
+ self._buf[0] = commands::ACTIVATE;
+ self._buf[1] = 0x73;
+ self.spi_transfer(2)
+ }
+
+ /// Is this radio a nRF24L01+ variant?
+ ///
+ /// The bool that this function returns is only valid _after_ calling [`RF24::init()`].
+ pub fn is_plus_variant(&mut self) -> bool {
+ self._is_plus_variant
+ }
+
+ pub fn test_rpd(&mut self) -> Result> {
+ self.spi_read(1, registers::RPD)?;
+ Ok(self._buf[1] & 1 == 1)
+ }
+
+ pub fn start_carrier_wave(
+ &mut self,
+ level: PaLevel,
+ channel: u8,
+ ) -> Result<(), Nrf24Error> {
+ self.stop_listening()?;
+ self.spi_read(1, registers::RF_SETUP)?;
+ self.spi_write_byte(registers::RF_SETUP, self._buf[1] | 0x84)?;
+ if self._is_plus_variant {
+ self.set_auto_ack(false)?;
+ self.set_auto_retries(0, 0)?;
+ let buf = [0xFF; 32];
+
+ // use write_register() instead of openWritingPipe() to bypass
+ // truncation of the address with the current RF24::addr_width value
+ self.spi_write_buf(registers::TX_ADDR, &buf[..5])?;
+ self.flush_tx()?; // so we can write to top level
+
+ // use write_register() instead of write_payload() to bypass
+ // truncation of the payload with the current RF24::payload_size value
+ self.spi_write_buf(commands::W_TX_PAYLOAD, &buf)?;
+
+ self.set_crc_length(CrcLength::DISABLED)?;
+ }
+ self.set_pa_level(level)?;
+ self.set_channel(channel)?;
+ self._ce_pin.set_high().map_err(Nrf24Error::Gpo)?;
+ if self._is_plus_variant {
+ self._wait.delay_ms(1); // datasheet says 1 ms is ok in this instance
+ self._ce_pin.set_low().map_err(Nrf24Error::Gpo)?;
+ self.rewrite()?;
+ }
+ Ok(())
+ }
+
+ pub fn stop_carrier_wave(&mut self) -> Result<(), Nrf24Error> {
+ /*
+ * A note from the datasheet:
+ * Do not use REUSE_TX_PL together with CONT_WAVE=1. When both these
+ * registers are set the chip does not react when setting CE low. If
+ * however, both registers are set PWR_UP = 0 will turn TX mode off.
+ */
+ self.power_down()?; // per datasheet recommendation (just to be safe)
+ self.spi_read(1, registers::RF_SETUP)?;
+ self.spi_write_byte(registers::RF_SETUP, self._buf[1] & !0x84)?;
+ self._ce_pin.set_low().map_err(Nrf24Error::Gpo)
+ }
+}
+
+impl EsbRadio for RF24
+where
+ SPI: SpiDevice,
+ DO: OutputPin,
+ DELAY: DelayNs,
+{
+ type RadioErrorType = Nrf24Error;
+
+ /// Initialize the radio's hardware using the [`SpiDevice`] and [`OutputPin`] given
+ /// to [`RF24::new()`].
+ fn init(&mut self) -> Result<(), Self::RadioErrorType> {
+ self._ce_pin.set_low().map_err(Nrf24Error::Gpo)?;
+
+ // Must allow the radio time to settle else configuration bits will not necessarily stick.
+ // This is actually only required following power up but some settling time also appears to
+ // be required after resets too. For full coverage, we'll always assume the worst.
+ // Enabling 16b CRC is by far the most obvious case if the wrong timing is used - or skipped.
+ // Technically we require 4.5ms + 14us as a worst case. We'll just call it 5ms for good measure.
+ // WARNING: Delay is based on P-variant whereby non-P *may* require different timing.
+ self._wait.delay_ms(5);
+
+ // Set 1500uS (minimum for 32B payload in ESB@250KBPS) timeouts, to make testing a little easier
+ // WARNING: If this is ever lowered, either 250KBS mode with AutoAck is broken or maximum packet
+ // sizes must never be used. See datasheet for a more complete explanation.
+ self.set_auto_retries(5, 15)?;
+
+ // Then set the data rate to the slowest (and most reliable) speed supported by all hardware.
+ self.set_data_rate(DataRate::Mbps1)?;
+
+ // detect if is a plus variant & use old toggle features command accordingly
+ self.spi_read(1, registers::FEATURE)?;
+ let before_toggle = self._buf[1];
+ self.toggle_features()?;
+ self.spi_read(1, registers::FEATURE)?;
+ let after_toggle = self._buf[1];
+ self._is_plus_variant = before_toggle == after_toggle;
+ if after_toggle > 0 {
+ if self._is_plus_variant {
+ // module did not experience power-on-reset, so now features are disabled
+ // toggle them back on
+ self.toggle_features()?;
+ }
+ // allow use of ask_no_ack parameter and dynamic payloads by default
+ self.spi_write_byte(registers::FEATURE, 0)?;
+ }
+ self._ack_payloads_enabled = false; // ack payloads disabled by default
+
+ // disable dynamic payloads by default (for all pipes)
+ self.spi_write_byte(registers::DYNPD, 0)?;
+ self._dynamic_payloads_enabled = false;
+
+ // enable auto-ack on all pipes
+ self.spi_write_byte(registers::EN_AA, 0x3F)?;
+
+ // only open RX pipes 0 & 1
+ self.spi_write_byte(registers::EN_RXADDR, 3)?;
+
+ // set static payload size to 32 (max) bytes by default
+ self.set_payload_length(32)?;
+ // set default address length to (max) 5 bytes
+ self.set_address_length(5)?;
+
+ // This channel should be universally safe and not bleed over into adjacent spectrum.
+ self.set_channel(76)?;
+
+ // Reset current status
+ // Notice reset and flush is the last thing we do
+ self.clear_status_flags(true, true, true)?;
+
+ // Flush buffers
+ self.flush_rx()?;
+ self.flush_tx()?;
+
+ // Clear CONFIG register:
+ // Reflect all IRQ events on IRQ pin
+ // Enable PTX
+ // Power Up
+ // 16-bit CRC (CRC required by auto-ack)
+ // Do not write CE high so radio will remain in standby-I mode
+ // PTX should use only 22uA of power
+ self._config_reg = 12;
+ self.spi_write_byte(registers::CONFIG, self._config_reg)?;
+
+ self.power_up(None)?;
+
+ // if config is not set correctly then there was a bad response from module
+ self.spi_read(1, registers::CONFIG)?;
+ return if self._buf[1] == 14 {
+ Ok(())
+ } else {
+ Err(Nrf24Error::BinaryCorruption)
+ };
+ }
+
+ fn start_listening(&mut self) -> Result<(), Self::RadioErrorType> {
+ self._config_reg |= 1;
+ self.spi_write_byte(registers::CONFIG, self._config_reg)?;
+ self.clear_status_flags(true, true, true)?;
+ self._ce_pin.set_high().map_err(Nrf24Error::Gpo)?;
+
+ // Restore the pipe0 address, if exists
+ if let Some(addr) = self._pipe0_rx_addr {
+ self.spi_write_buf(registers::RX_ADDR_P0, &addr[..self._addr_length as usize])?;
+ } else {
+ self.close_rx_pipe(0)?;
+ }
+ Ok(())
+ }
+
+ fn stop_listening(&mut self) -> Result<(), Self::RadioErrorType> {
+ self._ce_pin.set_low().map_err(Nrf24Error::Gpo)?;
+
+ self._wait.delay_us(self._tx_delay);
+ if self._ack_payloads_enabled {
+ self.flush_tx()?;
+ }
+
+ self._config_reg &= 0xFE;
+ self.spi_write_byte(registers::CONFIG, self._config_reg)?;
+
+ self.spi_read(1, registers::EN_RXADDR)?;
+ let out = self._buf[1] | 1;
+ self.spi_write_byte(registers::EN_RXADDR, out)
+ }
+
+ /// This function calls [`RF24::flush_tx()`] upon entry, but it does not
+ /// deactivate the radio's CE pin upon exit.
+ fn send(&mut self, buf: &[u8], ask_no_ack: bool) -> Result {
+ self._ce_pin.set_low().map_err(Nrf24Error::Gpo)?;
+ // this function only handles 1 payload at a time
+ self.flush_tx()?; // flush the TX FIFO to ensure we are sending the given buf
+ if !self.write(buf, ask_no_ack, true)? {
+ return Ok(false);
+ }
+ self._wait.delay_us(10);
+ // now block until we get a tx_ds or tx_df event
+ while self._status & 0x30 == 0 {
+ self.spi_read(0, commands::NOP)?;
+ }
+ Ok(self._status & mnemonics::MASK_TX_DS == mnemonics::MASK_TX_DS)
+ }
+
+ ///
+ ///
+ /// To transmit a payload the radio's CE pin must be active for at least 10 microseconds.
+ /// The caller is required to ensure the CE pin has been active for at least 10
+ /// microseconds when using this function, thus non-blocking behavior.
+ ///
+ ///
+ ///
+ /// This function's `start_tx` parameter determines if the radio's CE pin should be
+ /// made active. This function does not deactivate the radio's CE pin.
+ ///
+ /// If the radio's CE pin remains active after successfully transmitting a payload,
+ /// then any subsequent payloads in the TX FIFO will automatically be processed.
+ /// Set the `start_tx` parameter `false` to skip activating the radio's CE pin.
+ fn write(
+ &mut self,
+ buf: &[u8],
+ ask_no_ack: bool,
+ start_tx: bool,
+ ) -> Result::Error, ::Error>> {
+ self.clear_status_flags(true, true, true)?;
+ if self._status & 1 == 1 {
+ // TX FIFO is full already
+ return Ok(false);
+ }
+ let mut buf_len = {
+ let len = buf.len();
+ if len > 32 {
+ 32
+ } else {
+ len
+ }
+ };
+ // to avoid resizing the given buf, we'll have to use self._buf directly
+ // TODO: reversed byte order may need attention here
+ self._buf[0] = commands::W_TX_PAYLOAD | ((ask_no_ack as u8) << 4);
+ for i in buf_len..0 {
+ self._buf[i + 1 - buf_len] = buf[i];
+ }
+ // ensure payload_length setting is respected
+ if !self._dynamic_payloads_enabled && buf_len < self._payload_length as usize {
+ // pad buf with zeros
+ for i in self._payload_length as usize..buf_len {
+ self._buf[i + 1 - self._payload_length as usize] = 0;
+ }
+ buf_len = self._payload_length as usize;
+ }
+ self.spi_transfer(buf_len + 1)?;
+ if start_tx {
+ self._ce_pin.set_high().map_err(Nrf24Error::Gpo)?;
+ }
+ Ok(true)
+ }
+
+ /// Remember that each call to [`RF24::read()`] fetches data from the
+ /// RX FIFO beginning with the first byte from the first available
+ /// payload. A payload is not removed from the RX FIFO until it's
+ /// entire length (or more) is fetched.
+ ///
+ /// - If `len` is less than the available payload's
+ /// length, then the payload remains in the RX FIFO.
+ /// - If `len` is greater than the first of multiple
+ /// available payloads, then the data saved to the `buf`
+ /// parameter's object will be supplemented with data from the next
+ /// available payload.
+ /// - If `len` is greater than the last available
+ /// payload's length, then the last byte in the payload is used as
+ /// padding for the data saved to the `buf` parameter's object.
+ /// The nRF24L01 will repeatedly use the last byte from the last
+ /// payload even when [`RF24::read()`] is called with an empty RX FIFO.
+ fn read(&mut self, buf: &mut [u8], len: usize) -> Result<(), Self::RadioErrorType> {
+ let buf_len = {
+ let max_len = buf.len();
+ if len > max_len {
+ max_len
+ } else if len > 32 {
+ 32usize
+ } else {
+ len
+ }
+ };
+ if buf_len == 0 {
+ return Ok(());
+ }
+ self.spi_read(buf_len, commands::R_RX_PAYLOAD)?;
+ // need to reverse the byte order from Little endian to Big Endian
+ for i in buf_len..0 {
+ buf[i] = self._buf[i + 1 - buf_len];
+ }
+ Ok(())
+ }
+
+ fn resend(&mut self) -> Result::Error, ::Error>> {
+ self.rewrite()?;
+ self._wait.delay_us(10);
+ // now block until a tx_ds or tx_df event occurs
+ while self._status & 0x30 == 0 {
+ self.spi_read(0, commands::NOP)?;
+ }
+ Ok(self._status & mnemonics::MASK_TX_DS == mnemonics::MASK_TX_DS)
+ }
+
+ fn rewrite(&mut self) -> Result<(), Nrf24Error<::Error, ::Error>> {
+ self._ce_pin.set_low().map_err(Nrf24Error::Gpo)?;
+ self.clear_status_flags(false, true, true)?;
+ self.spi_read(0, commands::REUSE_TX_PL)?;
+ self._ce_pin.set_high().map_err(Nrf24Error::Gpo)
+ }
+
+ /// Get the Auto-Retry Count (ARC) about the previous transmission.
+ ///
+ /// This data is reset for every payload attempted to transmit.
+ /// It cannot exceed 15 per the `count` parameter in [`RF24::set_auto_retries()`].
+ /// If auto-ack feature is disabled, then this function provides no useful data.
+ fn get_last_arc(&mut self) -> Result::Error, ::Error>> {
+ self.spi_read(1, registers::OBSERVE_TX)?;
+ Ok(self._buf[1] & 0xF)
+ }
+}
diff --git a/src/radio/rf24/pa_level.rs b/src/radio/rf24/pa_level.rs
new file mode 100644
index 0000000..1a15aed
--- /dev/null
+++ b/src/radio/rf24/pa_level.rs
@@ -0,0 +1,41 @@
+use embedded_hal::{delay::DelayNs, digital::OutputPin, spi::SpiDevice};
+
+use crate::{radio::EsbPaLevel, Nrf24Error, PaLevel, RF24};
+
+use super::registers;
+
+impl EsbPaLevel for RF24
+where
+ SPI: SpiDevice,
+ DO: OutputPin,
+ DELAY: DelayNs,
+{
+ type PaLevelErrorType = Nrf24Error;
+
+ fn get_pa_level(&mut self) -> Result {
+ let result = self.spi_read(1, registers::RF_SETUP);
+ result?;
+ let pa_bin = self._buf[1] >> 1 & 3;
+ match pa_bin {
+ 0 => Ok(PaLevel::MIN),
+ 1 => Ok(PaLevel::LOW),
+ 2 => Ok(PaLevel::HIGH),
+ 3 => Ok(PaLevel::MAX),
+ _ => Err(Nrf24Error::BinaryCorruption),
+ }
+ }
+
+ fn set_pa_level(&mut self, pa_level: PaLevel) -> Result<(), Self::PaLevelErrorType> {
+ let pa_bin = {
+ match pa_level {
+ PaLevel::MIN => 0 as u8,
+ PaLevel::LOW => 1 as u8,
+ PaLevel::HIGH => 2 as u8,
+ PaLevel::MAX => 3 as u8,
+ }
+ } << 1;
+ self.spi_read(1, registers::RF_SETUP)?;
+ let out = self._buf[1] & (3 << 1) | pa_bin;
+ self.spi_write_byte(registers::RF_SETUP, out)
+ }
+}
diff --git a/src/radio/rf24/payload_length.rs b/src/radio/rf24/payload_length.rs
new file mode 100644
index 0000000..3db7e40
--- /dev/null
+++ b/src/radio/rf24/payload_length.rs
@@ -0,0 +1,58 @@
+use crate::{radio::EsbPayloadLength, Nrf24Error, RF24};
+use embedded_hal::{delay::DelayNs, digital::OutputPin, spi::SpiDevice};
+
+use super::{commands, mnemonics, registers};
+
+impl EsbPayloadLength for RF24
+where
+ SPI: SpiDevice,
+ DO: OutputPin,
+ DELAY: DelayNs,
+{
+ type PayloadLengthErrorType = Nrf24Error;
+
+ fn set_payload_length(&mut self, length: u8) -> Result<(), Self::PayloadLengthErrorType> {
+ let len = {
+ if length > 32 {
+ 32
+ } else {
+ length
+ }
+ };
+ for i in 0..6 {
+ self.spi_write_byte(registers::RX_PW_P0 + i, len)?;
+ }
+ self._payload_length = len;
+ Ok(())
+ }
+
+ fn get_payload_length(&mut self) -> Result {
+ self.spi_read(1, registers::RX_PW_P0)?;
+ Ok(self._buf[1])
+ }
+
+ fn set_dynamic_payloads(&mut self, enable: bool) -> Result<(), Self::PayloadLengthErrorType> {
+ self.spi_read(1, registers::FEATURE)?;
+ let reg_val = self._buf[1];
+ if enable != (reg_val & mnemonics::EN_DPL == mnemonics::EN_DPL) {
+ self.spi_write_byte(
+ registers::FEATURE,
+ reg_val & !mnemonics::EN_DPL | (mnemonics::EN_DPL * enable as u8),
+ )?;
+ }
+ self.spi_write_byte(registers::DYNPD, 0x3F * enable as u8)?;
+ self._dynamic_payloads_enabled = enable;
+ Ok(())
+ }
+
+ fn get_dynamic_payload_length(&mut self) -> Result {
+ if !self._dynamic_payloads_enabled {
+ return Ok(0);
+ }
+ self.spi_read(1, commands::R_RX_PL_WID)?;
+ if self._buf[1] > 32 {
+ return Err(Nrf24Error::BinaryCorruption);
+ }
+ Ok(self._buf[1] as usize)
+ }
+}
diff --git a/src/radio/rf24/pipe.rs b/src/radio/rf24/pipe.rs
new file mode 100644
index 0000000..98b80ce
--- /dev/null
+++ b/src/radio/rf24/pipe.rs
@@ -0,0 +1,88 @@
+use embedded_hal::{delay::DelayNs, digital::OutputPin, spi::SpiDevice};
+
+use crate::{radio::EsbPipe, Nrf24Error, RF24};
+
+use super::registers;
+
+impl EsbPipe for RF24
+where
+ SPI: SpiDevice,
+ DO: OutputPin,
+ DELAY: DelayNs,
+{
+ type PipeErrorType = Nrf24Error;
+
+ fn open_rx_pipe(&mut self, pipe: u8, address: &[u8]) -> Result<(), Self::PipeErrorType> {
+ if pipe > 5 {
+ return Ok(());
+ }
+
+ if pipe < 2 {
+ // Clamp the address length used: min(self._addr_length, address.len());
+ // This means that we only write the bytes that were passed
+ let width = if address.len() < self._addr_length as usize {
+ address.len()
+ } else {
+ self._addr_length as usize
+ };
+
+ // If this is pipe 0, cache the address. This is needed because
+ // open_writing_pipe() will overwrite the pipe 0 address, so
+ // start_listening() will have to restore it.
+ if pipe == 0 {
+ let mut cached_addr = self._pipe0_rx_addr.unwrap_or_default();
+ for i in 0..width {
+ cached_addr[i] = address[i];
+ }
+ self._pipe0_rx_addr = Some(cached_addr);
+ }
+ self.spi_write_buf(registers::RX_ADDR_P0 + pipe, &address[..width])?;
+ }
+ // For pipes 2-5, only write the MSB
+ else {
+ self.spi_write_byte(registers::RX_ADDR_P0 + pipe, address[0])?;
+ }
+
+ self.spi_read(1, registers::EN_RXADDR)?;
+ let out = self._buf[1] | (1 << pipe);
+ self.spi_write_byte(registers::EN_RXADDR, out)
+ }
+
+ fn open_tx_pipe(&mut self, address: &[u8]) -> Result<(), Self::PipeErrorType> {
+ self.spi_write_buf(registers::TX_ADDR, address)?;
+ self.spi_write_buf(registers::RX_ADDR_P0, address)
+ }
+
+ /// If the given `pipe` number is not in range [0, 5], then this function does nothing.
+ fn close_rx_pipe(&mut self, pipe: u8) -> Result<(), Self::PipeErrorType> {
+ if pipe > 5 {
+ return Ok(());
+ }
+ self.spi_read(1, registers::EN_RXADDR)?;
+ let out = self._buf[1] & !(1 << pipe);
+ self.spi_write_byte(registers::EN_RXADDR, out)?;
+ if pipe == 0 {
+ self._pipe0_rx_addr = None;
+ }
+ Ok(())
+ }
+
+ fn set_address_length(&mut self, length: u8) -> Result<(), Self::PipeErrorType> {
+ let width = match length {
+ 2 => 0,
+ 3 => 1,
+ 4 => 2,
+ 5 => 3,
+ _ => 3,
+ };
+ self.spi_write_byte(registers::SETUP_AW, width)?;
+ self._addr_length = width;
+ Ok(())
+ }
+
+ fn get_address_length(&mut self) -> Result {
+ self.spi_read(1, registers::SETUP_AW)?;
+ self._addr_length = self._buf[1] + 2;
+ Ok(self._addr_length)
+ }
+}
diff --git a/src/radio/rf24/power.rs b/src/radio/rf24/power.rs
new file mode 100644
index 0000000..099a0bc
--- /dev/null
+++ b/src/radio/rf24/power.rs
@@ -0,0 +1,47 @@
+use embedded_hal::{delay::DelayNs, digital::OutputPin, spi::SpiDevice};
+
+use crate::{radio::EsbPower, Nrf24Error, RF24};
+
+use super::registers;
+
+impl EsbPower for RF24
+where
+ SPI: SpiDevice,
+ DO: OutputPin,
+ DELAY: DelayNs,
+{
+ type PowerErrorType = Nrf24Error;
+
+ /// After calling [`ESBRadio::start_listening()`](fn@crate::radio::EsbRadio::start_listening),
+ /// a non-PA/LNA radio will consume about
+ /// 13.5mA at [`PaLevel::MAX`](type@crate::enums::PaLevel::MAX).
+ /// During active transmission (including RX role when transmitting an auto-ACK
+ /// packet), a non-PA/LNA radio will consume about 11.5mA.
+ /// In power standby mode (when not receiving nor transmitting), a non-PA/LNA radio
+ /// will consume about 26uA (.026mA).
+ /// In full power down mode (a sleep state), the radio will consume approximately
+ /// 900nA (.0009mA).
+ fn power_down(&mut self) -> Result<(), Self::PowerErrorType> {
+ self._ce_pin.set_low().map_err(Nrf24Error::Gpo)?; // Guarantee CE is low on powerDown
+ self._config_reg &= 0xFD;
+ self.spi_write_byte(registers::CONFIG, self._config_reg)?;
+ Ok(())
+ }
+
+ fn power_up(&mut self, delay: Option) -> Result<(), Self::PowerErrorType> {
+ // if not powered up then power up and wait for the radio to initialize
+ if self._config_reg & 0xFD > 0 {
+ return Ok(());
+ }
+ self._config_reg |= 2;
+ self.spi_write_byte(registers::CONFIG, self._config_reg)?;
+
+ // For nRF24L01+ to go from power down mode to TX or RX mode it must first pass through stand-by mode.
+ // There must be a delay of Tpd2stby (see Table 16.) after the nRF24L01+ leaves power down mode before
+ // the CEis set high. - Tpd2stby can be up to 5ms per the 1.0 datasheet
+ if delay.is_some_and(|val| val > 0) || delay.is_none() {
+ self._wait.delay_us(delay.unwrap_or_else(|| 5000));
+ }
+ Ok(())
+ }
+}
diff --git a/src/radio/rf24/registers.rs b/src/radio/rf24/registers.rs
new file mode 100644
index 0000000..e704887
--- /dev/null
+++ b/src/radio/rf24/registers.rs
@@ -0,0 +1,18 @@
+///! A private module encapsulating register offsets for the nRF24L01.
+
+pub const CONFIG: u8 = 0x00;
+pub const EN_AA: u8 = 0x01;
+pub const EN_RXADDR: u8 = 0x02;
+pub const SETUP_AW: u8 = 0x03;
+pub const SETUP_RETR: u8 = 0x04;
+pub const RF_CH: u8 = 0x05;
+pub const RF_SETUP: u8 = 0x06;
+pub const STATUS: u8 = 0x07;
+pub const OBSERVE_TX: u8 = 0x08;
+pub const RPD: u8 = 0x09;
+pub const RX_ADDR_P0: u8 = 0x0A;
+pub const TX_ADDR: u8 = 0x10;
+pub const RX_PW_P0: u8 = 0x11;
+pub const FIFO_STATUS: u8 = 0x17;
+pub const DYNPD: u8 = 0x1C;
+pub const FEATURE: u8 = 0x1D;
diff --git a/src/radio/rf24/status.rs b/src/radio/rf24/status.rs
new file mode 100644
index 0000000..91ddfb8
--- /dev/null
+++ b/src/radio/rf24/status.rs
@@ -0,0 +1,98 @@
+use embedded_hal::{delay::DelayNs, digital::OutputPin, spi::SpiDevice};
+
+use crate::{radio::EsbStatus, Nrf24Error, RF24};
+
+use super::{commands, mnemonics, registers};
+
+impl EsbStatus for RF24
+where
+ SPI: SpiDevice,
+ DO: OutputPin,
+ DELAY: DelayNs,
+{
+ type StatusErrorType = Nrf24Error;
+
+ /// Configure which status flags trigger the radio's IRQ pin.
+ ///
+ /// The supported interrupt events correspond to the parameters:
+ /// - `rx_dr` means "RX Data Ready"
+ /// - `tx_ds` means "TX Data Sent"
+ /// - `tx_df` means "TX Data Failed" to send
+ ///
+ /// Set any parameter to `false` to have the IRQ pin ignore the corresponding event.
+ /// By default, all events are enabled and will trigger the IRQ pin, a behavior
+ /// equivalent to `set_status_flags(true, true, true)`.
+ fn set_status_flags(
+ &mut self,
+ rx_dr: bool,
+ tx_ds: bool,
+ tx_df: bool,
+ ) -> Result<(), Self::StatusErrorType> {
+ self.spi_read(1, registers::CONFIG)?;
+ let mut new_config = self._buf[1] & (3 << 4);
+ if !rx_dr {
+ new_config |= mnemonics::MASK_RX_DR;
+ }
+ if !tx_ds {
+ new_config |= mnemonics::MASK_TX_DS;
+ }
+ if !tx_df {
+ new_config |= mnemonics::MASK_MAX_RT;
+ }
+ self.spi_write_byte(registers::CONFIG, new_config)
+ }
+
+ /// Clear the radio's IRQ status flags
+ ///
+ /// This needs to be done when the event has been handled.
+ ///
+ /// The supported interrupt events correspond to the parameters:
+ /// - `rx_dr` means "RX Data Ready"
+ /// - `tx_ds` means "TX Data Sent"
+ /// - `tx_df` means "TX Data Failed" to send
+ ///
+ /// Set any parameter to `true` to clear the corresponding interrupt event.
+ /// Setting a parameter to `false` will leave the corresponding status flag untouched.
+ /// This means that the IRQ pin can remain active (LOW) when multiple events occurred
+ /// but only flag was cleared.
+ fn clear_status_flags(
+ &mut self,
+ rx_dr: bool,
+ tx_ds: bool,
+ tx_df: bool,
+ ) -> Result<(), Self::StatusErrorType> {
+ let mut new_config = 0;
+ if rx_dr {
+ new_config |= mnemonics::MASK_RX_DR;
+ }
+ if tx_ds {
+ new_config |= mnemonics::MASK_TX_DS;
+ }
+ if tx_df {
+ new_config |= mnemonics::MASK_MAX_RT;
+ }
+ self.spi_write_byte(registers::STATUS, new_config)
+ }
+
+ fn update(&mut self) -> Result<(), Self::StatusErrorType> {
+ self.spi_read(0, commands::NOP)
+ }
+
+ fn get_status_flags(
+ &mut self,
+ rx_dr: &mut Option,
+ tx_ds: &mut Option,
+ tx_df: &mut Option,
+ ) -> Result<(), Self::StatusErrorType> {
+ if let Some(f) = rx_dr {
+ *f = self._status & mnemonics::MASK_RX_DR > 0;
+ }
+ if let Some(f) = tx_ds {
+ *f = self._status & mnemonics::MASK_TX_DS > 0;
+ }
+ if let Some(f) = tx_df {
+ *f = self._status & mnemonics::MASK_MAX_RT > 0;
+ }
+ Ok(())
+ }
+}