esb_ng/
app.rs

1use crate::{
2    payload::{EsbHeader, PayloadR, PayloadW},
3    //     peripherals::{Interrupt, NVIC},
4    Error,
5};
6use bbq2::{
7    queue::BBQueue,
8    traits::{coordination::cas::AtomicCoord, notifier::maitake::MaiNotSpsc, storage::Inline},
9
10};
11use core::default::Default;
12use cortex_m::peripheral::NVIC;
13use nrf_pac::Interrupt;
14
15pub(crate) type FramedProducer<const N: usize> = bbq2::prod_cons::framed::FramedProducer<
16    &'static BBQueue<Inline<N>, AtomicCoord, MaiNotSpsc>,
17    Inline<N>,
18    AtomicCoord,
19    MaiNotSpsc,
20    u16,
21>;
22pub(crate) type FramedConsumer<const N: usize> = bbq2::prod_cons::framed::FramedConsumer<
23    &'static BBQueue<Inline<N>, AtomicCoord, MaiNotSpsc>,
24    Inline<N>,
25    AtomicCoord,
26    MaiNotSpsc,
27    u16,
28>;
29
30/// This is the primary Application-side interface.
31///
32/// It is intended to be used outside of the `RADIO` interrupt,
33/// and allows for sending or receiving frames from the ESB Radio
34/// hardware.
35pub struct EsbApp<const OUT: usize, const IN: usize> {
36    // TODO(AJM): Make a constructor for this so we don't
37    // need to make these fields pub(crate)
38    pub(crate) prod_to_radio: FramedProducer<OUT>,
39    pub(crate) cons_from_radio: FramedConsumer<IN>,
40    pub(crate) maximum_payload: u8,
41}
42
43pub struct EsbAppSender<const OUT: usize> {
44    pub(crate) prod_to_radio: FramedProducer<OUT>,
45    pub(crate) maximum_payload: u8,
46}
47
48impl<const OUT: usize> EsbAppSender<OUT> {
49
50    /// Obtain a grant for an outgoing packet to be sent over the Radio
51    ///
52    /// When space is available, this function will return a [`PayloadW`],
53    /// which can be written into for data to be sent over the radio. If
54    /// the given parameters are incorrect, or if no space is available,
55    /// or if a grant is already in progress, an error will be returned.
56    ///
57    /// ## Notes
58    ///
59    /// Once a grant has been created, the maximum size of the grant can not
60    /// be increased, only shrunk. If a larger grant is needed, you must
61    /// `drop` the old grant, and create a new one.
62    ///
63    /// Only one grant may be active at a time.
64    pub fn grant_packet(&mut self, header: EsbHeader) -> Result<PayloadW<OUT>, Error> {
65        // Check we have not exceeded the configured packet max
66        if header.length > self.maximum_payload {
67            return Err(Error::MaximumPacketExceeded);
68        }
69
70        let grant_result = self
71            .prod_to_radio
72            .grant(header.payload_len() + EsbHeader::header_size());
73
74        let grant = grant_result.map_err(|err| match err {
75            // BbqError::GrantInProgress => Error::GrantInProgress,
76            // BbqError::InsufficientSize => Error::OutgoingQueueFull,
77            _ => Error::InternalError,
78        })?;
79        Ok(PayloadW::new_from_app(grant, header))
80    }
81
82    pub async fn wait_grant_packet(&mut self, header: EsbHeader) -> Result<PayloadW<OUT>, Error> {
83        // Check we have not exceeded the configured packet max
84        if header.length > self.maximum_payload {
85            return Err(Error::MaximumPacketExceeded);
86        }
87
88        let grant = self
89            .prod_to_radio
90            .wait_grant(header.payload_len() + EsbHeader::header_size()).await;
91
92        Ok(PayloadW::new_from_app(grant, header))
93    }
94
95    /// Starts the radio sending all packets in the queue.
96    ///
97    /// The radio will send until the queue has been drained. This method must be called again if
98    /// the queue is completely drained before the user commits new packets.
99    #[inline]
100    pub fn start_tx(&mut self) {
101        // TODO(AJM): Is this appropriate for PRX? Or is this a PTX-only
102        // sort of interface?
103
104        // Do we need to do anything other than pend the interrupt?
105        NVIC::pend(Interrupt::RADIO)
106    }
107
108    /// Gets the maximum payload size (in bytes) that the driver was configured to use.
109    #[inline]
110    pub fn maximum_payload_size(&self) -> usize {
111        self.maximum_payload.into()
112    }
113}
114
115pub struct EsbAppReceiver<const IN: usize> {
116    pub(crate) cons_from_radio: FramedConsumer<IN>,
117    pub(crate) maximum_payload: u8,
118}
119
120impl<const IN: usize> EsbAppReceiver<IN> {
121    /// Is there a received message that is ready to be read?
122    ///
123    /// Returns `true` if a call to `read_packet` would return `Some`.
124    pub fn msg_ready(&mut self) -> bool {
125        // Dropping the grant does not release it.
126        self.cons_from_radio.read().is_ok()
127    }
128
129    /// Attempt to read a packet that has been received via the radio.
130    ///
131    /// Returns `Some(PayloadR)` if a packet is ready to be read,
132    /// otherwise `None`.
133    pub fn read_packet(&mut self) -> Option<PayloadR<IN>> {
134        self.cons_from_radio.read().ok().map(PayloadR::new)
135    }
136
137    pub async fn wait_read_packet(&mut self) -> PayloadR<IN> {
138        PayloadR::new(self.cons_from_radio.wait_read().await)
139    }
140
141    /// Gets the maximum payload size (in bytes) that the driver was configured to use.
142    #[inline]
143    pub fn maximum_payload_size(&self) -> usize {
144        self.maximum_payload.into()
145    }
146}
147
148impl<const OUT: usize, const IN: usize> EsbApp<OUT, IN> {
149    pub fn split(self) -> (EsbAppSender<OUT>, EsbAppReceiver<IN>) {
150        let EsbApp { prod_to_radio, cons_from_radio, maximum_payload } = self;
151        (
152            EsbAppSender { prod_to_radio, maximum_payload },
153            EsbAppReceiver { cons_from_radio, maximum_payload },
154        )
155    }
156
157    /// Obtain a grant for an outgoing packet to be sent over the Radio
158    ///
159    /// When space is available, this function will return a [`PayloadW`],
160    /// which can be written into for data to be sent over the radio. If
161    /// the given parameters are incorrect, or if no space is available,
162    /// or if a grant is already in progress, an error will be returned.
163    ///
164    /// ## Notes
165    ///
166    /// Once a grant has been created, the maximum size of the grant can not
167    /// be increased, only shrunk. If a larger grant is needed, you must
168    /// `drop` the old grant, and create a new one.
169    ///
170    /// Only one grant may be active at a time.
171    pub fn grant_packet(&mut self, header: EsbHeader) -> Result<PayloadW<OUT>, Error> {
172        // Check we have not exceeded the configured packet max
173        if header.length > self.maximum_payload {
174            return Err(Error::MaximumPacketExceeded);
175        }
176
177        let grant_result = self
178            .prod_to_radio
179            .grant(header.payload_len() + EsbHeader::header_size());
180
181        let grant = grant_result.map_err(|err| match err {
182            // BbqError::GrantInProgress => Error::GrantInProgress,
183            // BbqError::InsufficientSize => Error::OutgoingQueueFull,
184            _ => Error::InternalError,
185        })?;
186        Ok(PayloadW::new_from_app(grant, header))
187    }
188
189    /// Starts the radio sending all packets in the queue.
190    ///
191    /// The radio will send until the queue has been drained. This method must be called again if
192    /// the queue is completely drained before the user commits new packets.
193    #[inline]
194    pub fn start_tx(&mut self) {
195        // TODO(AJM): Is this appropriate for PRX? Or is this a PTX-only
196        // sort of interface?
197
198        // Do we need to do anything other than pend the interrupt?
199        NVIC::pend(Interrupt::RADIO)
200    }
201
202    /// Is there a received message that is ready to be read?
203    ///
204    /// Returns `true` if a call to `read_packet` would return `Some`.
205    pub fn msg_ready(&mut self) -> bool {
206        // Dropping the grant does not release it.
207        self.cons_from_radio.read().is_ok()
208    }
209
210    /// Attempt to read a packet that has been received via the radio.
211    ///
212    /// Returns `Some(PayloadR)` if a packet is ready to be read,
213    /// otherwise `None`.
214    pub fn read_packet(&mut self) -> Option<PayloadR<IN>> {
215        self.cons_from_radio.read().ok().map(PayloadR::new)
216    }
217
218    pub async fn wait_read_packet(&mut self) -> PayloadR<IN> {
219        PayloadR::new(self.cons_from_radio.wait_read().await)
220    }
221
222    /// Gets the maximum payload size (in bytes) that the driver was configured to use.
223    #[inline]
224    pub fn maximum_payload_size(&self) -> usize {
225        self.maximum_payload.into()
226    }
227}
228
229/// Addresses used for communication.
230///
231/// ESB uses up to eight pipes to address communication, each pipe has an unique address which is
232/// composed by the base address and the prefix. Pipe 0 has an unique base and prefix, while the
233/// other pipes share a base address but have different prefixes.
234///
235/// Default values:
236///
237/// | Field      | Default Value            |
238/// | :---       | :---                     |
239/// | base0      | [0xE7, 0xE7, 0xE7, 0xE7] |
240/// | base1      | [0xC2, 0xC2, 0xC2, 0xC2] |
241/// | prefixes0  | [0xE7, 0xC2, 0xC3, 0xC4] |
242/// | prefixes1  | [0xC5, 0xC6, 0xC7, 0xC8] |
243/// | rf_channel | 2                        |
244///
245pub struct Addresses {
246    /// Base address for pipe 0
247    pub(crate) base0: [u8; 4],
248    /// Base address for pipe 1-7
249    pub(crate) base1: [u8; 4],
250    /// Prefixes for pipes 0-3, in order
251    pub(crate) prefixes0: [u8; 4],
252    /// `prefixes1` - Prefixes for pipes 4-7, in order
253    pub(crate) prefixes1: [u8; 4],
254    /// Channel to be used by the radio hardware (must be between 0 and 100)
255    pub(crate) rf_channel: u8,
256}
257
258impl Addresses {
259    /// Creates a new instance of `Addresses`
260    ///
261    /// * `base0` - Base address for pipe 0.
262    /// * `base1` - Base address for pipe 1-7.
263    /// * `prefixes0` - Prefixes for pipes 0-3, in order.
264    /// * `prefixes1` - Prefixes for pipes 4-7, in order.
265    /// * `rf_channel` - Channel to be used by the radio hardware (must be between 0 and 100).
266    ///
267    /// # Panics
268    ///
269    /// This function will panic if `rf_channel` is bigger than 100.
270    pub fn new(
271        base0: [u8; 4],
272        base1: [u8; 4],
273        prefixes0: [u8; 4],
274        prefixes1: [u8; 4],
275        rf_channel: u8,
276    ) -> Result<Self, Error> {
277        // TODO(AJM): Move to a builder pattern here?
278        if rf_channel > 100 {
279            return Err(Error::InvalidParameters);
280        }
281        Ok(Self {
282            base0,
283            base1,
284            prefixes0,
285            prefixes1,
286            rf_channel,
287        })
288    }
289}
290
291impl Default for Addresses {
292    fn default() -> Self {
293        Self {
294            base0: [0xE7, 0xE7, 0xE7, 0xE7],
295            base1: [0xC2, 0xC2, 0xC2, 0xC2],
296            prefixes0: [0xE7, 0xC2, 0xC3, 0xC4],
297            prefixes1: [0xC5, 0xC6, 0xC7, 0xC8],
298            rf_channel: 2,
299        }
300    }
301}