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}