aboutsummaryrefslogtreecommitdiff
path: root/embassy-nrf
diff options
context:
space:
mode:
authorUlf Lilleengen <[email protected]>2024-02-28 14:40:18 +0000
committerGitHub <[email protected]>2024-02-28 14:40:18 +0000
commitb806800f237a7951a86fc3eeb9785a74cbc2169e (patch)
tree776f140fcb64e4658cde033475dfd6c6f971aba7 /embassy-nrf
parent5ced938184e141471e921d235975e95725d6be53 (diff)
parent368b3a9aaf97a7662217b9ff2784e3648368586c (diff)
Merge pull request #2351 from jewel-rs/feat/radio
[embassry_nrf]: add BLE Radio driver
Diffstat (limited to 'embassy-nrf')
-rw-r--r--embassy-nrf/src/chips/nrf52840.rs5
-rw-r--r--embassy-nrf/src/lib.rs5
-rw-r--r--embassy-nrf/src/radio/ble.rs438
-rw-r--r--embassy-nrf/src/radio/mod.rs75
4 files changed, 523 insertions, 0 deletions
diff --git a/embassy-nrf/src/chips/nrf52840.rs b/embassy-nrf/src/chips/nrf52840.rs
index 51c55cd4d..d3272b2e8 100644
--- a/embassy-nrf/src/chips/nrf52840.rs
+++ b/embassy-nrf/src/chips/nrf52840.rs
@@ -173,6 +173,9 @@ embassy_hal_internal::peripherals! {
173 173
174 // I2S 174 // I2S
175 I2S, 175 I2S,
176
177 // Radio
178 RADIO,
176} 179}
177 180
178impl_usb!(USBD, USBD, USBD); 181impl_usb!(USBD, USBD, USBD);
@@ -311,6 +314,8 @@ impl_saadc_input!(P0_31, ANALOG_INPUT7);
311 314
312impl_i2s!(I2S, I2S, I2S); 315impl_i2s!(I2S, I2S, I2S);
313 316
317impl_radio!(RADIO, RADIO, RADIO);
318
314embassy_hal_internal::interrupt_mod!( 319embassy_hal_internal::interrupt_mod!(
315 POWER_CLOCK, 320 POWER_CLOCK,
316 RADIO, 321 RADIO,
diff --git a/embassy-nrf/src/lib.rs b/embassy-nrf/src/lib.rs
index 358a7cc27..04a6293a4 100644
--- a/embassy-nrf/src/lib.rs
+++ b/embassy-nrf/src/lib.rs
@@ -45,6 +45,11 @@ pub mod buffered_uarte;
45pub mod gpio; 45pub mod gpio;
46#[cfg(feature = "gpiote")] 46#[cfg(feature = "gpiote")]
47pub mod gpiote; 47pub mod gpiote;
48
49// TODO: tested on other chips
50#[cfg(any(feature = "nrf52840"))]
51pub mod radio;
52
48#[cfg(any(feature = "nrf52832", feature = "nrf52833", feature = "nrf52840"))] 53#[cfg(any(feature = "nrf52832", feature = "nrf52833", feature = "nrf52840"))]
49pub mod i2s; 54pub mod i2s;
50pub mod nvmc; 55pub mod nvmc;
diff --git a/embassy-nrf/src/radio/ble.rs b/embassy-nrf/src/radio/ble.rs
new file mode 100644
index 000000000..24dba582f
--- /dev/null
+++ b/embassy-nrf/src/radio/ble.rs
@@ -0,0 +1,438 @@
1//! Radio driver implementation focused on Bluetooth Low-Energy transmission.
2
3use core::future::poll_fn;
4use core::sync::atomic::{compiler_fence, Ordering};
5use core::task::Poll;
6
7use embassy_hal_internal::drop::OnDrop;
8use embassy_hal_internal::{into_ref, PeripheralRef};
9pub use pac::radio::mode::MODE_A as Mode;
10use pac::radio::pcnf0::PLEN_A as PreambleLength;
11use pac::radio::state::STATE_A as RadioState;
12pub use pac::radio::txpower::TXPOWER_A as TxPower;
13
14use crate::interrupt::typelevel::Interrupt;
15use crate::radio::*;
16use crate::util::slice_in_ram_or;
17
18/// RADIO error.
19#[derive(Debug, Clone, Copy, PartialEq, Eq)]
20#[cfg_attr(feature = "defmt", derive(defmt::Format))]
21#[non_exhaustive]
22pub enum Error {
23 /// Buffer was too long.
24 BufferTooLong,
25 /// Buffer was to short.
26 BufferTooShort,
27 /// The buffer is not in data RAM. It is most likely in flash, and nRF's DMA cannot access flash.
28 BufferNotInRAM,
29}
30
31/// Radio driver.
32pub struct Radio<'d, T: Instance> {
33 _p: PeripheralRef<'d, T>,
34}
35
36impl<'d, T: Instance> Radio<'d, T> {
37 /// Create a new radio driver.
38 pub fn new(
39 radio: impl Peripheral<P = T> + 'd,
40 _irq: impl interrupt::typelevel::Binding<T::Interrupt, InterruptHandler<T>> + 'd,
41 ) -> Self {
42 into_ref!(radio);
43
44 let r = T::regs();
45
46 r.pcnf1.write(|w| unsafe {
47 // It is 0 bytes long in a standard BLE packet
48 w.statlen()
49 .bits(0)
50 // MaxLen configures the maximum packet payload plus add-on size in
51 // number of bytes that can be transmitted or received by the RADIO. This feature can be used to ensure
52 // that the RADIO does not overwrite, or read beyond, the RAM assigned to the packet payload. This means
53 // that if the packet payload length defined by PCNF1.STATLEN and the LENGTH field in the packet specifies a
54 // packet larger than MAXLEN, the payload will be truncated at MAXLEN
55 //
56 // To simplify the implementation, It is setted as the maximum value
57 // and the length of the packet is controlled only by the LENGTH field in the packet
58 .maxlen()
59 .bits(255)
60 // Configure the length of the address field in the packet
61 // The prefix after the address fields is always appended, so is always 1 byte less than the size of the address
62 // The base address is truncated from the least significant byte if the BALEN is less than 4
63 //
64 // BLE address is always 4 bytes long
65 .balen()
66 .bits(3) // 3 bytes base address (+ 1 prefix);
67 // Configure the endianess
68 // For BLE is always little endian (LSB first)
69 .endian()
70 .little()
71 // Data whitening is used to avoid long sequences of zeros or
72 // ones, e.g., 0b0000000 or 0b1111111, in the data bit stream.
73 // The whitener and de-whitener are defined the same way,
74 // using a 7-bit linear feedback shift register with the
75 // polynomial x7 + x4 + 1.
76 //
77 // In BLE Whitening shall be applied on the PDU and CRC of all
78 // Link Layer packets and is performed after the CRC generation
79 // in the transmitter. No other parts of the packets are whitened.
80 // De-whitening is performed before the CRC checking in the receiver
81 // Before whitening or de-whitening, the shift register should be
82 // initialized based on the channel index.
83 .whiteen()
84 .set_bit()
85 });
86
87 // Configure CRC
88 r.crccnf.write(|w| {
89 // In BLE the CRC shall be calculated on the PDU of all Link Layer
90 // packets (even if the packet is encrypted).
91 // It skips the address field
92 w.skipaddr()
93 .skip()
94 // In BLE 24-bit CRC = 3 bytes
95 .len()
96 .three()
97 });
98
99 // Ch map between 2400 MHZ .. 2500 MHz
100 // All modes use this range
101 r.frequency.write(|w| w.map().default());
102
103 // Configure shortcuts to simplify and speed up sending and receiving packets.
104 r.shorts.write(|w| {
105 // start transmission/recv immediately after ramp-up
106 // disable radio when transmission/recv is done
107 w.ready_start().enabled().end_disable().enabled()
108 });
109
110 // Enable NVIC interrupt
111 T::Interrupt::unpend();
112 unsafe { T::Interrupt::enable() };
113
114 Self { _p: radio }
115 }
116
117 fn state(&self) -> RadioState {
118 match T::regs().state.read().state().variant() {
119 Some(s) => s,
120 None => unreachable!(),
121 }
122 }
123
124 #[allow(dead_code)]
125 fn trace_state(&self) {
126 match self.state() {
127 RadioState::DISABLED => trace!("radio:state:DISABLED"),
128 RadioState::RX_RU => trace!("radio:state:RX_RU"),
129 RadioState::RX_IDLE => trace!("radio:state:RX_IDLE"),
130 RadioState::RX => trace!("radio:state:RX"),
131 RadioState::RX_DISABLE => trace!("radio:state:RX_DISABLE"),
132 RadioState::TX_RU => trace!("radio:state:TX_RU"),
133 RadioState::TX_IDLE => trace!("radio:state:TX_IDLE"),
134 RadioState::TX => trace!("radio:state:TX"),
135 RadioState::TX_DISABLE => trace!("radio:state:TX_DISABLE"),
136 }
137 }
138
139 /// Set the radio mode
140 ///
141 /// The radio must be disabled before calling this function
142 pub fn set_mode(&mut self, mode: Mode) {
143 assert!(self.state() == RadioState::DISABLED);
144
145 let r = T::regs();
146 r.mode.write(|w| w.mode().variant(mode));
147
148 r.pcnf0.write(|w| {
149 w.plen().variant(match mode {
150 Mode::BLE_1MBIT => PreambleLength::_8BIT,
151 Mode::BLE_2MBIT => PreambleLength::_16BIT,
152 Mode::BLE_LR125KBIT | Mode::BLE_LR500KBIT => PreambleLength::LONG_RANGE,
153 _ => unimplemented!(),
154 })
155 });
156 }
157
158 /// Set the header size changing the S1's len field
159 ///
160 /// The radio must be disabled before calling this function
161 pub fn set_header_expansion(&mut self, use_s1_field: bool) {
162 assert!(self.state() == RadioState::DISABLED);
163
164 let r = T::regs();
165
166 // s1 len in bits
167 let s1len: u8 = match use_s1_field {
168 false => 0,
169 true => 8,
170 };
171
172 r.pcnf0.write(|w| unsafe {
173 w
174 // Configure S0 to 1 byte length, this will represent the Data/Adv header flags
175 .s0len()
176 .set_bit()
177 // Configure the length (in bits) field to 1 byte length, this will represent the length of the payload
178 // and also be used to know how many bytes to read/write from/to the buffer
179 .lflen()
180 .bits(8)
181 // Configure the lengh (in bits) of bits in the S1 field. It could be used to represent the CTEInfo for data packages in BLE.
182 .s1len()
183 .bits(s1len)
184 });
185 }
186
187 /// Set initial data whitening value
188 /// Data whitening is used to avoid long sequences of zeros or ones, e.g., 0b0000000 or 0b1111111, in the data bit stream
189 /// On BLE the initial value is the channel index | 0x40
190 ///
191 /// The radio must be disabled before calling this function
192 pub fn set_whitening_init(&mut self, whitening_init: u8) {
193 assert!(self.state() == RadioState::DISABLED);
194
195 let r = T::regs();
196
197 r.datawhiteiv.write(|w| unsafe { w.datawhiteiv().bits(whitening_init) });
198 }
199
200 /// Set the central frequency to be used
201 /// It should be in the range 2400..2500
202 ///
203 /// [The radio must be disabled before calling this function](https://devzone.nordicsemi.com/f/nordic-q-a/15829/radio-frequency-change)
204 pub fn set_frequency(&mut self, frequency: u32) {
205 assert!(self.state() == RadioState::DISABLED);
206 assert!((2400..=2500).contains(&frequency));
207
208 let r = T::regs();
209
210 r.frequency
211 .write(|w| unsafe { w.frequency().bits((frequency - 2400) as u8) });
212 }
213
214 /// Set the acess address
215 /// This address is always constants for advertising
216 /// And a random value generate on each connection
217 /// It is used to filter the packages
218 ///
219 /// The radio must be disabled before calling this function
220 pub fn set_access_address(&mut self, access_address: u32) {
221 assert!(self.state() == RadioState::DISABLED);
222
223 let r = T::regs();
224
225 // Configure logical address
226 // The byte ordering on air is always least significant byte first for the address
227 // So for the address 0xAA_BB_CC_DD, the address on air will be DD CC BB AA
228 // The package order is BASE, PREFIX so BASE=0xBB_CC_DD and PREFIX=0xAA
229 r.prefix0
230 .write(|w| unsafe { w.ap0().bits((access_address >> 24) as u8) });
231
232 // The base address is truncated from the least significant byte (because the BALEN is less than 4)
233 // So it shifts the address to the right
234 r.base0.write(|w| unsafe { w.bits(access_address << 8) });
235
236 // Don't match tx address
237 r.txaddress.write(|w| unsafe { w.txaddress().bits(0) });
238
239 // Match on logical address
240 // This config only filter the packets by the address,
241 // so only packages send to the previous address
242 // will finish the reception (TODO: check the explanation)
243 r.rxaddresses.write(|w| {
244 w.addr0()
245 .enabled()
246 .addr1()
247 .enabled()
248 .addr2()
249 .enabled()
250 .addr3()
251 .enabled()
252 .addr4()
253 .enabled()
254 });
255 }
256
257 /// Set the CRC polynomial
258 /// It only uses the 24 least significant bits
259 ///
260 /// The radio must be disabled before calling this function
261 pub fn set_crc_poly(&mut self, crc_poly: u32) {
262 assert!(self.state() == RadioState::DISABLED);
263
264 let r = T::regs();
265
266 r.crcpoly.write(|w| unsafe {
267 // Configure the CRC polynomial
268 // Each term in the CRC polynomial is mapped to a bit in this
269 // register which index corresponds to the term's exponent.
270 // The least significant term/bit is hard-wired internally to
271 // 1, and bit number 0 of the register content is ignored by
272 // the hardware. The following example is for an 8 bit CRC
273 // polynomial: x8 + x7 + x3 + x2 + 1 = 1 1000 1101 .
274 w.crcpoly().bits(crc_poly & 0xFFFFFF)
275 });
276 }
277
278 /// Set the CRC init value
279 /// It only uses the 24 least significant bits
280 /// The CRC initial value varies depending of the PDU type
281 ///
282 /// The radio must be disabled before calling this function
283 pub fn set_crc_init(&mut self, crc_init: u32) {
284 assert!(self.state() == RadioState::DISABLED);
285
286 let r = T::regs();
287
288 r.crcinit.write(|w| unsafe { w.crcinit().bits(crc_init & 0xFFFFFF) });
289 }
290
291 /// Set the radio tx power
292 ///
293 /// The radio must be disabled before calling this function
294 pub fn set_tx_power(&mut self, tx_power: TxPower) {
295 assert!(self.state() == RadioState::DISABLED);
296
297 let r = T::regs();
298
299 r.txpower.write(|w| w.txpower().variant(tx_power));
300 }
301
302 /// Set buffer to read/write
303 ///
304 /// This method is unsound. You should guarantee that the buffer will live
305 /// for the life time of the transmission or if the buffer will be modified.
306 /// Also if the buffer is smaller than the packet length, the radio will
307 /// read/write memory out of the buffer bounds.
308 fn set_buffer(&mut self, buffer: &[u8]) -> Result<(), Error> {
309 slice_in_ram_or(buffer, Error::BufferNotInRAM)?;
310
311 let r = T::regs();
312
313 // Here it consider that the length of the packet is
314 // correctly set in the buffer, otherwise it will send
315 // unowned regions of memory
316 let ptr = buffer.as_ptr();
317
318 // Configure the payload
319 r.packetptr.write(|w| unsafe { w.bits(ptr as u32) });
320
321 Ok(())
322 }
323
324 /// Send packet
325 /// If the length byte in the package is greater than the buffer length
326 /// the radio will read memory out of the buffer bounds
327 pub async fn transmit(&mut self, buffer: &[u8]) -> Result<(), Error> {
328 self.set_buffer(buffer)?;
329
330 let r = T::regs();
331 self.trigger_and_wait_end(move || {
332 // Initialize the transmission
333 // trace!("txen");
334 r.tasks_txen.write(|w| w.tasks_txen().set_bit());
335 })
336 .await;
337
338 Ok(())
339 }
340
341 /// Receive packet
342 /// If the length byte in the received package is greater than the buffer length
343 /// the radio will write memory out of the buffer bounds
344 pub async fn receive(&mut self, buffer: &mut [u8]) -> Result<(), Error> {
345 self.set_buffer(buffer)?;
346
347 let r = T::regs();
348 self.trigger_and_wait_end(move || {
349 // Initialize the transmission
350 // trace!("rxen");
351 r.tasks_rxen.write(|w| w.tasks_rxen().set_bit());
352 })
353 .await;
354
355 Ok(())
356 }
357
358 async fn trigger_and_wait_end(&mut self, trigger: impl FnOnce()) {
359 //self.trace_state();
360
361 let r = T::regs();
362 let s = T::state();
363
364 // If the Future is dropped before the end of the transmission
365 // it disable the interrupt and stop the transmission
366 // to keep the state consistent
367 let drop = OnDrop::new(|| {
368 trace!("radio drop: stopping");
369
370 r.intenclr.write(|w| w.end().clear());
371 r.events_end.reset();
372
373 r.tasks_stop.write(|w| w.tasks_stop().set_bit());
374
375 // The docs don't explicitly mention any event to acknowledge the stop task
376 while r.events_end.read().events_end().bit_is_clear() {}
377
378 trace!("radio drop: stopped");
379 });
380
381 // trace!("radio:enable interrupt");
382 // Clear some remnant side-effects (TODO: check if this is necessary)
383 r.events_end.reset();
384
385 // Enable interrupt
386 r.intenset.write(|w| w.end().set());
387
388 compiler_fence(Ordering::SeqCst);
389
390 // Trigger the transmission
391 trigger();
392 // self.trace_state();
393
394 // On poll check if interrupt happen
395 poll_fn(|cx| {
396 s.end_waker.register(cx.waker());
397 if r.events_end.read().events_end().bit_is_set() {
398 // trace!("radio:end");
399 return core::task::Poll::Ready(());
400 }
401 Poll::Pending
402 })
403 .await;
404
405 compiler_fence(Ordering::SeqCst);
406 r.events_disabled.reset(); // ACK
407
408 // Everthing ends fine, so it disable the drop
409 drop.defuse();
410 }
411
412 /// Disable the radio
413 fn disable(&mut self) {
414 let r = T::regs();
415
416 compiler_fence(Ordering::SeqCst);
417 // If it is already disabled, do nothing
418 if self.state() != RadioState::DISABLED {
419 trace!("radio:disable");
420 // Trigger the disable task
421 r.tasks_disable.write(|w| w.tasks_disable().set_bit());
422
423 // Wait until the radio is disabled
424 while r.events_disabled.read().events_disabled().bit_is_clear() {}
425
426 compiler_fence(Ordering::SeqCst);
427
428 // Acknowledge it
429 r.events_disabled.reset();
430 }
431 }
432}
433
434impl<'d, T: Instance> Drop for Radio<'d, T> {
435 fn drop(&mut self) {
436 self.disable();
437 }
438}
diff --git a/embassy-nrf/src/radio/mod.rs b/embassy-nrf/src/radio/mod.rs
new file mode 100644
index 000000000..03f967f87
--- /dev/null
+++ b/embassy-nrf/src/radio/mod.rs
@@ -0,0 +1,75 @@
1//! Integrated 2.4 GHz Radio
2//!
3//! The 2.4 GHz radio transceiver is compatible with multiple radio standards
4//! such as 1Mbps, 2Mbps and Long Range Bluetooth Low Energy.
5
6#![macro_use]
7
8/// Bluetooth Low Energy Radio driver.
9pub mod ble;
10
11use core::marker::PhantomData;
12
13use crate::{interrupt, pac, Peripheral};
14
15/// Interrupt handler
16pub struct InterruptHandler<T: Instance> {
17 _phantom: PhantomData<T>,
18}
19
20impl<T: Instance> interrupt::typelevel::Handler<T::Interrupt> for InterruptHandler<T> {
21 unsafe fn on_interrupt() {
22 let r = T::regs();
23 let s = T::state();
24
25 if r.events_end.read().events_end().bit_is_set() {
26 s.end_waker.wake();
27 r.intenclr.write(|w| w.end().clear());
28 }
29 }
30}
31
32pub(crate) mod sealed {
33 use embassy_sync::waitqueue::AtomicWaker;
34
35 pub struct State {
36 /// end packet transmission or reception
37 pub end_waker: AtomicWaker,
38 }
39 impl State {
40 pub const fn new() -> Self {
41 Self {
42 end_waker: AtomicWaker::new(),
43 }
44 }
45 }
46
47 pub trait Instance {
48 fn regs() -> &'static crate::pac::radio::RegisterBlock;
49 fn state() -> &'static State;
50 }
51}
52
53macro_rules! impl_radio {
54 ($type:ident, $pac_type:ident, $irq:ident) => {
55 impl crate::radio::sealed::Instance for peripherals::$type {
56 fn regs() -> &'static pac::radio::RegisterBlock {
57 unsafe { &*pac::$pac_type::ptr() }
58 }
59
60 fn state() -> &'static crate::radio::sealed::State {
61 static STATE: crate::radio::sealed::State = crate::radio::sealed::State::new();
62 &STATE
63 }
64 }
65 impl crate::radio::Instance for peripherals::$type {
66 type Interrupt = crate::interrupt::typelevel::$irq;
67 }
68 };
69}
70
71/// Radio peripheral instance.
72pub trait Instance: Peripheral<P = Self> + sealed::Instance + 'static + Send {
73 /// Interrupt for this peripheral.
74 type Interrupt: interrupt::typelevel::Interrupt;
75}