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(()) + } +}