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}