esb_ng/
lib.rs

1//! Rust implementation of Nordic's Enhanced ShockBurst communication protocol
2//!
3//! This crate implements the Enhanced ShockBurst protocol with dynamic payload size up to 252 bytes
4//! and acknowledgement support.
5//!
6//! The communication is performed by two devices in different roles, one acting as the
7//! Primary Transmitter (PTX) and a second one acting as the Primary Receiver (PRX). The
8//! transaction is always started by the PTX, and bi-directional communication is achieved via
9//! acknowledgement packets, where the PRX can embed a payload.
10//!
11//! This crate makes use of [`bbqueue`](https://docs.rs/bbqueue) to handle buffering of packets and
12//! to be able to achieve a zero-copy implementation. For timing requirements, the payload that must
13//! be sent together with an acknowledgement must be pre-buffered. When a packet that demands an
14//! acknowledgement is received in PRX mode the driver will try to dequeue a payload from the
15//! transmit queue to be sent, an acknowledgement with a zero sized payload will be sent if the
16//! transmit queue is empty.
17//!
18//! # Timing Requirements
19//!
20//! For better communication stability, both the radio and timer interrupts must be top priority,
21//! and the driver's methods should be called at the beginning of the interrupt handler. In
22//! the current implementation, the data rate is fixed at 2Mbps.
23//!
24//! There are three configurable options that directly affect the timing of the communication:
25//!
26//! - Wait for acknowledgement timeout (us) - Default: 120 microseconds.
27//!     - It is used in PTX mode while sending a packet that requested for an acknowledgement. It
28//!       must be bigger than the [Ramp-up](#ramp-up) time.
29//!
30//! - Retransmit delay offset (us) - Default: 500 microseconds.
31//!     - The delay between the end of a transmission and the start of a retransmission when an
32//!       acknowledgement was expected but not received. It must be bigger than the
33//!       `acknowledgement timeout` plus a constant offset of 62 microseconds.
34//!
35//! - Number of retransmit attempts - Default: 3 attempts.
36//!     - The number of times the driver will retransmit a packet that requires an acknowledgement.
37//!       After all the attempts are carried out, the driver will drop the packet and proceed to
38//!       transmit the next one in the queue.
39//!
40//! # Supported devices and crate features
41//!
42//! | Device   | Feature |
43//! | :---     | :---    |
44//! | nRF51822 | 51      |
45//! | nRF52810 | 52810   |
46//! | nRF52832 | 52832   |
47//! | nRF52840 | 52840   |
48//!
49//! Other devices might be compatible with this implementation, however, at this point, the only
50//! tested devices are the ones in the table above.
51//!
52//! # Ramp-up
53//!
54//! The radio's hardware requires a time before the start or reception of a transmission. This time
55//! is 140 microseconds in the nRF5 devices. However, nRF52 devices have a Fast Ramp-up feature,
56//! where this time is reduced to 40 microseconds. This feature can be enabled by using the
57//! `fast-ru` feature of this crate. Care must be taken when using the Fast Ramp-up while
58//! communicating with devices that do not support it, the timing configuration must take this case
59//! into account.
60//!
61//! # In-queue packet representation
62//!
63//! This crate uses some bytes of queue space to pass information between the user and the driver.
64//! The user must take this into account when choosing the size of the
65//! [EsbBuffer](buffer/struct.EsbBuffer.html). Moreover, the characteristics of the underlying
66//! BipBuffer must be considered, for more information refer to [bbqueue docs](https://docs.rs/bbqueue).
67//!
68//! | Used by bbqueue framed    | SW USE                         |               ACTUAL DMA PART                                      |
69//! | :---                      | :---                           | :---                                                               |
70//! | frame_size - 1 to 2 bytes | rssi - 1 byte \| pipe - 1 byte | length - 1 byte \| pid_no_ack - 1 byte \| payload - 1 to 252 bytes |
71//!
72//! The maximum in-queue packet size is 258 bytes (with a 252 bytes payload).
73//!
74//! # Compatibility with nRF24L01+
75//!
76//! This implementation is only compatible with nRF24L01+ devices when using a
77//! [configuration](struct.Config.html) with a maximum packet size no bigger than 32 bytes
78//! (inclusive). That is required because the nRF24L01+ only supports payloads up to that size and
79//! uses a 6-bits effective payload length that must be configured in the nRF5 radio.
80//!
81//! # Examples
82//!
83//! Usage examples can be found at the [demos repository](https://github.com/thalesfragoso/esb-demos).
84//!
85
86#![no_std]
87
88pub mod app;
89pub mod buffer;
90pub mod irq;
91pub mod payload;
92pub mod peripherals;
93
94// Export crate relevant items
95pub use crate::{
96    app::{Addresses, EsbApp},
97    buffer::EsbBuffer,
98    irq::{EsbIrq, IrqTimer},
99    payload::{EsbHeader, EsbHeaderBuilder},
100};
101
102use core::default::Default;
103// Export dependency items necessary to create a backing structure
104// pub use bbqueue::{consts, ArrayLength, BBBuffer, ConstBBBuffer};
105pub use bbq2;
106
107// TODO: Figure it out good values
108const RX_WAIT_FOR_ACK_TIMEOUT_US_2MBPS: u16 = 120;
109const RETRANSMIT_DELAY_US_OFFSET: u16 = 62;
110const RETRANSMIT_DELAY: u16 = 500;
111const MAXIMUM_TRANSMIT_ATTEMPTS: u8 = 3;
112const ENABLED_PIPES: u8 = 0xFF;
113
114#[cfg(not(feature = "fast-ru"))]
115pub(crate) const RAMP_UP_TIME: u16 = 140;
116
117// This is only true if we enable the fast ramp-up time, which we do
118#[cfg(feature = "fast-ru")]
119pub(crate) const RAMP_UP_TIME: u16 = 40;
120
121/// Crate-wide error type
122#[derive(Debug, PartialEq, Eq)]
123pub enum Error {
124    /// Unable to add item to the incoming queue, queue is full. After issuing this error,
125    /// [EsbIrq](irq/struct.EsbIrq.html) will be put in the Idle state
126    IncomingQueueFull,
127
128    /// Unable to add item to the outgoing queue, queue is full
129    OutgoingQueueFull,
130
131    /// Grant already in progress
132    GrantInProgress,
133
134    /// Unable to pop item from the queue, queue is empty
135    QueueEmpty,
136
137    /// Unable to split to producer/consumer halves, the
138    /// buffer has already been split
139    AlreadySplit,
140
141    /// Values out of range
142    InvalidParameters,
143
144    // The requested packet was larger than the configured max payload size
145    MaximumPacketExceeded,
146
147    /// Internal Error, if you encounter this error, please report it, it is a bug
148    InternalError,
149
150    /// [EsbIrq](irq/struct.EsbIrq.html) reached the maximum number of attempts to send a packet
151    /// that requested for an acknowledgement, the packet will be removed from the queue and
152    /// [EsbIrq](irq/struct.EsbIrq.html) will try to send the next one
153    MaximumAttempts,
154}
155
156/// Tx Power
157pub type TxPower = peripherals::Txpower;
158
159/// Protocol configuration
160#[derive(Copy, Clone)]
161pub struct Config {
162    /// Number of microseconds to wait for an acknowledgement before timing out
163    wait_for_ack_timeout: u16,
164    /// Delay, in microseconds, between retransmissions when the radio does not receive an
165    /// acknowledgement
166    retransmit_delay: u16,
167    /// Maximum number of transmit attempts when an acknowledgement is not received
168    maximum_transmit_attempts: u8,
169    /// A bit mask representing the pipes that the radio must listen while receiving, the LSb is
170    /// pipe zero
171    enabled_pipes: u8,
172    /// Tx Power
173    tx_power: TxPower,
174    /// Maximum payload size in bytes that the driver will send or receive.
175    ///
176    /// This allows for a more efficient usage of the receiver queue and makes this driver
177    /// compatible with nRF24L01+ modules when this size is 32 bytes or less
178    maximum_payload_size: u8,
179}
180
181impl Default for Config {
182    fn default() -> Self {
183        Self {
184            wait_for_ack_timeout: RX_WAIT_FOR_ACK_TIMEOUT_US_2MBPS,
185            retransmit_delay: RETRANSMIT_DELAY,
186            maximum_transmit_attempts: MAXIMUM_TRANSMIT_ATTEMPTS,
187            enabled_pipes: ENABLED_PIPES,
188            tx_power: TxPower::_0_DBM,
189            maximum_payload_size: 252,
190        }
191    }
192}
193
194/// A builder for an `Config` structure
195///
196/// The builder is converted into an `Config` by calling the
197/// `check()` method.
198///
199/// ## Example
200///
201/// ```rust
202/// use esb::ConfigBuilder;
203///
204/// let config_result = ConfigBuilder::default()
205///     .wait_for_ack_timeout(50)
206///     .retransmit_delay(240)
207///     .maximum_transmit_attempts(4)
208///     .enabled_pipes(0x01)
209///     .check();
210///
211/// assert!(config_result.is_ok());
212/// ```
213///
214/// ## Default Config Contents
215///
216/// By default, the following settings will be used:
217///
218/// | Field                               | Default Value |
219/// | :---                                | :---          |
220/// | Ack Timeout                         | 120 us        |
221/// | Retransmit Delay                    | 500 us        |
222/// | Maximum number of transmit attempts | 3             |
223/// | Enabled Pipes                       | 0xFF          |
224/// | Tx Power                            | 0dBm          |
225/// | Maximum payload size                | 252 bytes     |
226///
227#[derive(Default)]
228pub struct ConfigBuilder(Config);
229
230impl ConfigBuilder {
231    /// Sets number of microseconds to wait for an acknowledgement before timing out
232    pub fn wait_for_ack_timeout(mut self, micros: u16) -> Self {
233        self.0.wait_for_ack_timeout = micros;
234        self
235    }
236
237    // TODO: document 62
238    /// Sets retransmit delay, must be bigger than `wait_for_ack_timeout` field plus 62 and bigger
239    /// than the ramp-up time (140us without fast-ru and 40us with fast-ru)
240    pub fn retransmit_delay(mut self, micros: u16) -> Self {
241        self.0.retransmit_delay = micros;
242        self
243    }
244
245    /// Sets maximum number of transmit attempts
246    pub fn maximum_transmit_attempts(mut self, n: u8) -> Self {
247        self.0.maximum_transmit_attempts = n;
248        self
249    }
250
251    /// Sets enabled pipes for receiving
252    pub fn enabled_pipes(mut self, enabled_pipes: u8) -> Self {
253        self.0.enabled_pipes = enabled_pipes;
254        self
255    }
256
257    /// Sets the tx power
258    pub fn tx_power(mut self, tx_power: TxPower) -> Self {
259        self.0.tx_power = tx_power;
260        self
261    }
262
263    /// Sets the maximum payload size
264    pub fn max_payload_size(mut self, payload_size: u8) -> Self {
265        self.0.maximum_payload_size = payload_size;
266        self
267    }
268
269    pub fn check(self) -> Result<Config, Error> {
270        let bad_ack_timeout = self.0.wait_for_ack_timeout < 44;
271        let bad_retransmit_delay = self.0.retransmit_delay
272            <= self.0.wait_for_ack_timeout + RETRANSMIT_DELAY_US_OFFSET
273            || self.0.retransmit_delay <= RAMP_UP_TIME;
274        let bad_size = self.0.maximum_payload_size > 252;
275
276        if bad_ack_timeout || bad_retransmit_delay || bad_size {
277            Err(Error::InvalidParameters)
278        } else {
279            Ok(self.0)
280        }
281    }
282}