diff --git a/embassy-nrf/Cargo.toml b/embassy-nrf/Cargo.toml
index 7e161df9b..dcdc7f313 100644
--- a/embassy-nrf/Cargo.toml
+++ b/embassy-nrf/Cargo.toml
@@ -57,6 +57,9 @@ unstable-pac = []
## Enable GPIO tasks and events
gpiote = []
+## Enable radio driver
+radio = ["dep:jewel"]
+
## Use RTC1 as the time driver for `embassy-time`, with a tick rate of 32.768khz
time-driver-rtc1 = ["_time-driver"]
@@ -150,6 +153,8 @@ embedded-storage-async = "0.4.0"
cfg-if = "1.0.0"
document-features = "0.2.7"
+jewel = { version = "0.1.0", git = "https://github.com/jewel-rs/jewel", optional = true }
+
nrf51-pac = { version = "0.12.0", optional = true }
nrf52805-pac = { version = "0.12.0", optional = true }
nrf52810-pac = { version = "0.12.0", optional = true }
diff --git a/embassy-nrf/src/chips/nrf52805.rs b/embassy-nrf/src/chips/nrf52805.rs
index 624d6613d..b97c85f9e 100644
--- a/embassy-nrf/src/chips/nrf52805.rs
+++ b/embassy-nrf/src/chips/nrf52805.rs
@@ -129,6 +129,9 @@ embassy_hal_internal::peripherals! {
// QDEC
QDEC,
+
+ // RADIO
+ RADIO,
}
impl_uarte!(UARTE0, UARTE0, UARTE0_UART0);
@@ -209,6 +212,9 @@ impl_ppi_channel!(PPI_CH31, 31 => static);
impl_saadc_input!(P0_04, ANALOG_INPUT2);
impl_saadc_input!(P0_05, ANALOG_INPUT3);
+#[cfg(feature = "radio")]
+impl_radio!(RADIO, RADIO, RADIO);
+
embassy_hal_internal::interrupt_mod!(
POWER_CLOCK,
RADIO,
diff --git a/embassy-nrf/src/chips/nrf52810.rs b/embassy-nrf/src/chips/nrf52810.rs
index 002feab3b..03548d03f 100644
--- a/embassy-nrf/src/chips/nrf52810.rs
+++ b/embassy-nrf/src/chips/nrf52810.rs
@@ -135,6 +135,9 @@ embassy_hal_internal::peripherals! {
// PDM
PDM,
+
+ // Radio
+ RADIO,
}
impl_uarte!(UARTE0, UARTE0, UARTE0_UART0);
@@ -235,6 +238,9 @@ impl_saadc_input!(P0_29, ANALOG_INPUT5);
impl_saadc_input!(P0_30, ANALOG_INPUT6);
impl_saadc_input!(P0_31, ANALOG_INPUT7);
+#[cfg(feature = "radio")]
+impl_radio!(RADIO, RADIO, RADIO);
+
embassy_hal_internal::interrupt_mod!(
POWER_CLOCK,
RADIO,
diff --git a/embassy-nrf/src/chips/nrf52811.rs b/embassy-nrf/src/chips/nrf52811.rs
index 5952907f8..992fbd129 100644
--- a/embassy-nrf/src/chips/nrf52811.rs
+++ b/embassy-nrf/src/chips/nrf52811.rs
@@ -135,6 +135,9 @@ embassy_hal_internal::peripherals! {
// PDM
PDM,
+
+ // Radio
+ RADIO,
}
impl_uarte!(UARTE0, UARTE0, UARTE0_UART0);
@@ -237,6 +240,9 @@ impl_saadc_input!(P0_29, ANALOG_INPUT5);
impl_saadc_input!(P0_30, ANALOG_INPUT6);
impl_saadc_input!(P0_31, ANALOG_INPUT7);
+#[cfg(feature = "radio")]
+impl_radio!(RADIO, RADIO, RADIO);
+
embassy_hal_internal::interrupt_mod!(
POWER_CLOCK,
RADIO,
diff --git a/embassy-nrf/src/chips/nrf52820.rs b/embassy-nrf/src/chips/nrf52820.rs
index c2f792cb9..f241f4ea3 100644
--- a/embassy-nrf/src/chips/nrf52820.rs
+++ b/embassy-nrf/src/chips/nrf52820.rs
@@ -130,6 +130,9 @@ embassy_hal_internal::peripherals! {
// QDEC
QDEC,
+
+ // Radio
+ RADIO,
}
impl_usb!(USBD, USBD, USBD);
@@ -224,6 +227,9 @@ impl_ppi_channel!(PPI_CH29, 29 => static);
impl_ppi_channel!(PPI_CH30, 30 => static);
impl_ppi_channel!(PPI_CH31, 31 => static);
+#[cfg(feature = "radio")]
+impl_radio!(RADIO, RADIO, RADIO);
+
embassy_hal_internal::interrupt_mod!(
POWER_CLOCK,
RADIO,
diff --git a/embassy-nrf/src/chips/nrf52832.rs b/embassy-nrf/src/chips/nrf52832.rs
index 65d52364d..6bbdd9a63 100644
--- a/embassy-nrf/src/chips/nrf52832.rs
+++ b/embassy-nrf/src/chips/nrf52832.rs
@@ -150,6 +150,9 @@ embassy_hal_internal::peripherals! {
// PDM
PDM,
+
+ // Radio
+ RADIO,
}
impl_uarte!(UARTE0, UARTE0, UARTE0_UART0);
@@ -264,6 +267,9 @@ impl_saadc_input!(P0_31, ANALOG_INPUT7);
impl_i2s!(I2S, I2S, I2S);
+#[cfg(feature = "radio")]
+impl_radio!(RADIO, RADIO, RADIO);
+
embassy_hal_internal::interrupt_mod!(
POWER_CLOCK,
RADIO,
diff --git a/embassy-nrf/src/chips/nrf52833.rs b/embassy-nrf/src/chips/nrf52833.rs
index 7c9b66d69..e137e4dc6 100644
--- a/embassy-nrf/src/chips/nrf52833.rs
+++ b/embassy-nrf/src/chips/nrf52833.rs
@@ -170,6 +170,9 @@ embassy_hal_internal::peripherals! {
// I2S
I2S,
+
+ // Radio
+ RADIO,
}
impl_usb!(USBD, USBD, USBD);
@@ -306,6 +309,9 @@ impl_saadc_input!(P0_31, ANALOG_INPUT7);
impl_i2s!(I2S, I2S, I2S);
+#[cfg(feature = "radio")]
+impl_radio!(RADIO, RADIO, RADIO);
+
embassy_hal_internal::interrupt_mod!(
POWER_CLOCK,
RADIO,
diff --git a/embassy-nrf/src/chips/nrf52840.rs b/embassy-nrf/src/chips/nrf52840.rs
index 51c55cd4d..2d805f871 100644
--- a/embassy-nrf/src/chips/nrf52840.rs
+++ b/embassy-nrf/src/chips/nrf52840.rs
@@ -173,6 +173,9 @@ embassy_hal_internal::peripherals! {
// I2S
I2S,
+
+ // Radio
+ RADIO,
}
impl_usb!(USBD, USBD, USBD);
@@ -311,6 +314,9 @@ impl_saadc_input!(P0_31, ANALOG_INPUT7);
impl_i2s!(I2S, I2S, I2S);
+#[cfg(feature = "radio")]
+impl_radio!(RADIO, RADIO, RADIO);
+
embassy_hal_internal::interrupt_mod!(
POWER_CLOCK,
RADIO,
diff --git a/embassy-nrf/src/chips/nrf5340_net.rs b/embassy-nrf/src/chips/nrf5340_net.rs
index a7cf82872..3248bde52 100644
--- a/embassy-nrf/src/chips/nrf5340_net.rs
+++ b/embassy-nrf/src/chips/nrf5340_net.rs
@@ -248,6 +248,9 @@ embassy_hal_internal::peripherals! {
P1_13,
P1_14,
P1_15,
+
+ // Radio
+ RADIO,
}
impl_uarte!(SERIAL0, UARTE0, SERIAL0);
@@ -345,6 +348,9 @@ impl_ppi_channel!(PPI_CH29, 29 => configurable);
impl_ppi_channel!(PPI_CH30, 30 => configurable);
impl_ppi_channel!(PPI_CH31, 31 => configurable);
+#[cfg(feature = "radio")]
+impl_radio!(RADIO, RADIO, RADIO);
+
embassy_hal_internal::interrupt_mod!(
CLOCK_POWER,
RADIO,
diff --git a/embassy-nrf/src/lib.rs b/embassy-nrf/src/lib.rs
index 358a7cc27..961928d11 100644
--- a/embassy-nrf/src/lib.rs
+++ b/embassy-nrf/src/lib.rs
@@ -45,6 +45,12 @@ pub mod buffered_uarte;
pub mod gpio;
#[cfg(feature = "gpiote")]
pub mod gpiote;
+
+#[cfg(feature = "radio")]
+pub mod radio;
+#[cfg(all(feature = "radio", feature = "_nrf9160"))]
+compile_error!("feature `radio` is not valid for nRF91 series chips.");
+
#[cfg(any(feature = "nrf52832", feature = "nrf52833", feature = "nrf52840"))]
pub mod i2s;
pub mod nvmc;
diff --git a/embassy-nrf/src/radio/ble.rs b/embassy-nrf/src/radio/ble.rs
new file mode 100644
index 000000000..a5d9f447b
--- /dev/null
+++ b/embassy-nrf/src/radio/ble.rs
@@ -0,0 +1,432 @@
+//! Radio driver implementation focused on Bluetooth Low-Energy transmission.
+//!
+//! The radio can calculate the CRC, perform data whitening,
+//! automatically send the right preamble.
+//! Most of the configuration is done automatically when you choose the mode and this driver.
+//!
+//! Some configuration can just be done when de device is disabled,
+//! and the configuration varies depending if is a transmitter or a receiver.
+//! Because of that we have a state machine to keep track of the state of the radio.
+//! The Radio is the disable radio which configure the common parameters between
+//! the bluetooth protocols, like the package format, the CRC and the whitening.
+//! The TxRadio radio enable and configured as a transmitter with the specific parameters.
+
+use core::future::poll_fn;
+use core::sync::atomic::{compiler_fence, Ordering};
+use core::task::Poll;
+
+use embassy_hal_internal::drop::OnDrop;
+use embassy_hal_internal::{into_ref, PeripheralRef};
+use jewel::phy::{
+ AdvertisingChannel, Channel, ChannelTrait, HeaderSize, Mode, Radio as BleRadio, ADV_ADDRESS, ADV_CRC_INIT,
+ CRC_POLY, MAX_PDU_LENGTH,
+};
+use pac::radio::mode::MODE_A as PacMode;
+use pac::radio::pcnf0::PLEN_A as PreambleLength;
+// Re-export SVD variants to allow user to directly set values.
+pub use pac::radio::{state::STATE_A as RadioState, txpower::TXPOWER_A as TxPower};
+
+use crate::interrupt::typelevel::Interrupt;
+use crate::radio::*;
+use crate::util::slice_in_ram_or;
+
+/// UART error.
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+#[non_exhaustive]
+pub enum Error {
+ /// Buffer was too long.
+ BufferTooLong,
+ /// Buffer was to short.
+ BufferTooShort,
+ /// The buffer is not in data RAM. It's most likely in flash, and nRF's DMA cannot access flash.
+ BufferNotInRAM,
+}
+
+/// Radio driver.
+pub struct Radio<'d, T: Instance> {
+ _p: PeripheralRef<'d, T>,
+}
+
+impl<'d, T: Instance> Radio<'d, T> {
+ /// Create a new radio driver.
+ pub fn new(
+ radio: impl Peripheral
+ 'd,
+ _irq: impl interrupt::typelevel::Binding> + 'd,
+ ) -> Self {
+ // From 5.4.1 of the nRF52840 Product Specification:
+ // > The HFXO must be running to use the RADIO or the calibration mechanism associated with the 32.768 kHz RC oscillator.
+ // Currently the jewel crate don't implement the calibration mechanism, so we need to ensure that the HFXO is running
+ utils::check_xtal();
+
+ into_ref!(radio);
+
+ let r = T::regs();
+
+ r.pcnf1.write(|w| unsafe {
+ // It is 0 bytes long in a standard BLE packet
+ w.statlen()
+ .bits(0)
+ // MaxLen configures the maximum packet payload plus add-on size in
+ // number of bytes that can be transmitted or received by the RADIO. This feature can be used to ensure
+ // that the RADIO does not overwrite, or read beyond, the RAM assigned to the packet payload. This means
+ // that if the packet payload length defined by PCNF1.STATLEN and the LENGTH field in the packet specifies a
+ // packet larger than MAXLEN, the payload will be truncated at MAXLEN
+ //
+ // To simplify the implementation, I'm setting the max length to the maximum value
+ // and I'm using only the length field to truncate the payload
+ .maxlen()
+ .bits(255)
+ // Configure the length of the address field in the packet
+ // The prefix after the address fields is always appended, so is always 1 byte less than the size of the address
+ // The base address is truncated from the least significant byte if the BALEN is less than 4
+ //
+ // BLE address is always 4 bytes long
+ .balen()
+ .bits(3) // 3 bytes base address (+ 1 prefix);
+ // Configure the endianess
+ // For BLE is always little endian (LSB first)
+ .endian()
+ .little()
+ // Data whitening is used to avoid long sequences of zeros or
+ // ones, e.g., 0b0000000 or 0b1111111, in the data bit stream.
+ // The whitener and de-whitener are defined the same way,
+ // using a 7-bit linear feedback shift register with the
+ // polynomial x7 + x4 + 1.
+ //
+ // In BLE Whitening shall be applied on the PDU and CRC of all
+ // Link Layer packets and is performed after the CRC generation
+ // in the transmitter. No other parts of the packets are whitened.
+ // De-whitening is performed before the CRC checking in the receiver
+ // Before whitening or de-whitening, the shift register should be
+ // initialized based on the channel index.
+ .whiteen()
+ .set_bit() // Enable whitening
+ });
+
+ // Configure CRC
+ r.crccnf.write(|w| {
+ // In BLE the CRC shall be calculated on the PDU of all Link Layer
+ // packets (even if the packet is encrypted).
+ // So here we skip the address field
+ w.skipaddr()
+ .skip()
+ // In BLE 24-bit CRC = 3 bytes
+ .len()
+ .three()
+ });
+
+ r.crcpoly.write(|w| unsafe {
+ // Configure the CRC polynomial
+ // Each term in the CRC polynomial is mapped to a bit in this
+ // register which index corresponds to the term's exponent.
+ // The least significant term/bit is hard-wired internally to
+ // 1, and bit number 0 of the register content is ignored by
+ // the hardware. The following example is for an 8 bit CRC
+ // polynomial: x8 + x7 + x3 + x2 + 1 = 1 1000 1101 .
+ w.crcpoly().bits(CRC_POLY & 0xFFFFFF)
+ });
+ // The CRC initial value varies depending of the PDU type
+
+ // Ch map between 2400 MHZ .. 2500 MHz
+ // All modes use this range
+ r.frequency.write(|w| w.map().default());
+
+ // Configure shortcuts to simplify and speed up sending and receiving packets.
+ r.shorts.write(|w| {
+ // start transmission/recv immediately after ramp-up
+ // disable radio when transmission/recv is done
+ w.ready_start().enabled().end_disable().enabled()
+ });
+
+ // Enable NVIC interrupt
+ T::Interrupt::unpend();
+ unsafe { T::Interrupt::enable() };
+
+ let mut radio = Self { _p: radio };
+
+ // set defaults
+ radio.set_mode(Mode::Ble1mbit);
+ radio.set_tx_power(0);
+ radio.set_header_size(HeaderSize::TwoBytes);
+ radio.set_access_address(ADV_ADDRESS);
+ radio.set_crc_init(ADV_CRC_INIT);
+ radio.set_channel(AdvertisingChannel::Ch39.into());
+
+ radio
+ }
+
+ #[allow(dead_code)]
+ fn trace_state(&self) {
+ let r = T::regs();
+
+ match r.state.read().state().variant().unwrap() {
+ RadioState::DISABLED => trace!("radio:state:DISABLED"),
+ RadioState::RX_RU => trace!("radio:state:RX_RU"),
+ RadioState::RX_IDLE => trace!("radio:state:RX_IDLE"),
+ RadioState::RX => trace!("radio:state:RX"),
+ RadioState::RX_DISABLE => trace!("radio:state:RX_DISABLE"),
+ RadioState::TX_RU => trace!("radio:state:TX_RU"),
+ RadioState::TX_IDLE => trace!("radio:state:TX_IDLE"),
+ RadioState::TX => trace!("radio:state:TX"),
+ RadioState::TX_DISABLE => trace!("radio:state:TX_DISABLE"),
+ }
+ }
+
+ async fn trigger_and_wait_end(&mut self, trigger: impl FnOnce() -> ()) {
+ //self.trace_state();
+
+ let r = T::regs();
+ let s = T::state();
+
+ // If the Future is dropped before the end of the transmission
+ // we need to disable the interrupt and stop the transmission
+ // to keep the state consistent
+ let drop = OnDrop::new(|| {
+ trace!("radio drop: stopping");
+
+ r.intenclr.write(|w| w.end().clear());
+ r.events_end.reset();
+
+ r.tasks_stop.write(|w| w.tasks_stop().set_bit());
+
+ // The docs don't explicitly mention any event to acknowledge the stop task
+ // So I guess it's the same as end
+ while r.events_end.read().events_end().bit_is_clear() {}
+
+ trace!("radio drop: stopped");
+ });
+
+ /* Config interrupt */
+ // trace!("radio:enable interrupt");
+ // Clear some remnant side-effects (I'm unsure if this is needed)
+ r.events_end.reset();
+
+ // Enable interrupt
+ r.intenset.write(|w| w.end().set());
+
+ compiler_fence(Ordering::SeqCst);
+
+ // Trigger the transmission
+ trigger();
+ // self.trace_state();
+
+ // On poll check if interrupt happen
+ poll_fn(|cx| {
+ s.end_waker.register(cx.waker());
+ if r.events_end.read().events_end().bit_is_set() {
+ // trace!("radio:end");
+ return core::task::Poll::Ready(());
+ }
+ Poll::Pending
+ })
+ .await;
+
+ compiler_fence(Ordering::SeqCst);
+ r.events_disabled.reset(); // ACK
+
+ // Everthing ends fine, so we can disable the drop
+ drop.defuse();
+ }
+
+ /// Disable the radio.
+ fn disable(&mut self) {
+ let r = T::regs();
+
+ compiler_fence(Ordering::SeqCst);
+ // If is already disabled, do nothing
+ if !r.state.read().state().is_disabled() {
+ trace!("radio:disable");
+ // Trigger the disable task
+ r.tasks_disable.write(|w| w.tasks_disable().set_bit());
+
+ // Wait until the radio is disabled
+ while r.events_disabled.read().events_disabled().bit_is_clear() {}
+
+ compiler_fence(Ordering::SeqCst);
+
+ // Acknowledge it
+ r.events_disabled.reset();
+ }
+ }
+}
+
+impl<'d, T: Instance> BleRadio for Radio<'d, T> {
+ type Error = Error;
+
+ fn set_mode(&mut self, mode: Mode) {
+ let r = T::regs();
+ r.mode.write(|w| {
+ w.mode().variant(match mode {
+ Mode::Ble1mbit => PacMode::BLE_1MBIT,
+ //Mode::Ble2mbit => PacMode::BLE_2MBIT,
+ })
+ });
+
+ r.pcnf0.write(|w| {
+ w.plen().variant(match mode {
+ Mode::Ble1mbit => PreambleLength::_8BIT,
+ //Mode::Ble2mbit => PreambleLength::_16BIT,
+ })
+ });
+ }
+
+ fn set_header_size(&mut self, header_size: HeaderSize) {
+ let r = T::regs();
+
+ let s1len: u8 = match header_size {
+ HeaderSize::TwoBytes => 0,
+ HeaderSize::ThreeBytes => 8, // bits
+ };
+
+ r.pcnf0.write(|w| unsafe {
+ w
+ // Configure S0 to 1 byte length, this will represent the Data/Adv header flags
+ .s0len()
+ .set_bit()
+ // Configure the length (in bits) field to 1 byte length, this will represent the length of the payload
+ // and also be used to know how many bytes to read/write from/to the buffer
+ .lflen()
+ .bits(8)
+ // Configure the lengh (in bits) of bits in the S1 field. It could be used to represent the CTEInfo for data packages in BLE.
+ .s1len()
+ .bits(s1len)
+ });
+ }
+
+ fn set_channel(&mut self, channel: Channel) {
+ let r = T::regs();
+
+ r.frequency
+ .write(|w| unsafe { w.frequency().bits((channel.central_frequency() - 2400) as u8) });
+ r.datawhiteiv
+ .write(|w| unsafe { w.datawhiteiv().bits(channel.whitening_init()) });
+ }
+
+ fn set_access_address(&mut self, access_address: u32) {
+ let r = T::regs();
+
+ // Configure logical address
+ // The byte ordering on air is always least significant byte first for the address
+ // So for the address 0xAA_BB_CC_DD, the address on air will be DD CC BB AA
+ // The package order is BASE, PREFIX so BASE=0xBB_CC_DD and PREFIX=0xAA
+ r.prefix0
+ .write(|w| unsafe { w.ap0().bits((access_address >> 24) as u8) });
+
+ // The base address is truncated from the least significant byte (because the BALEN is less than 4)
+ // So we need to shift the address to the right
+ r.base0.write(|w| unsafe { w.bits(access_address << 8) });
+
+ // Don't match tx address
+ r.txaddress.write(|w| unsafe { w.txaddress().bits(0) });
+
+ // Match on logical address
+ // For what I understand, this config only filter the packets
+ // by the address, so only packages send to the previous address
+ // will finish the reception
+ r.rxaddresses.write(|w| {
+ w.addr0()
+ .enabled()
+ .addr1()
+ .enabled()
+ .addr2()
+ .enabled()
+ .addr3()
+ .enabled()
+ .addr4()
+ .enabled()
+ });
+ }
+
+ fn set_crc_init(&mut self, crc_init: u32) {
+ let r = T::regs();
+
+ r.crcinit.write(|w| unsafe { w.crcinit().bits(crc_init & 0xFFFFFF) });
+ }
+
+ fn set_tx_power(&mut self, power_db: i8) {
+ let r = T::regs();
+
+ let tx_power: TxPower = match power_db {
+ 8..=i8::MAX => TxPower::POS8D_BM,
+ 7 => TxPower::POS7D_BM,
+ 6 => TxPower::POS6D_BM,
+ 5 => TxPower::POS5D_BM,
+ 4 => TxPower::POS4D_BM,
+ 3 => TxPower::POS3D_BM,
+ 1..=2 => TxPower::POS2D_BM,
+ -3..=0 => TxPower::_0D_BM,
+ -7..=-4 => TxPower::NEG4D_BM,
+ -11..=-8 => TxPower::NEG8D_BM,
+ -15..=-12 => TxPower::NEG12D_BM,
+ -19..=-16 => TxPower::NEG16D_BM,
+ -29..=-20 => TxPower::NEG20D_BM,
+ -39..=-30 => TxPower::NEG30D_BM,
+ i8::MIN..=-40 => TxPower::NEG40D_BM,
+ };
+
+ r.txpower.write(|w| w.txpower().variant(tx_power));
+ }
+
+ fn set_buffer(&mut self, buffer: &[u8]) -> Result<(), Self::Error> {
+ // Because we are serializing the buffer, we should always have the buffer in RAM
+ slice_in_ram_or(buffer, Error::BufferNotInRAM)?;
+
+ if buffer.len() > MAX_PDU_LENGTH {
+ return Err(Error::BufferTooLong);
+ }
+
+ let r = T::regs();
+
+ // Here we are considering that the length of the packet is
+ // correctly set in the buffer, otherwise we will sending
+ // unowned regions of memory
+ let ptr = buffer.as_ptr();
+
+ // Configure the payload
+ r.packetptr.write(|w| unsafe { w.bits(ptr as u32) });
+
+ Ok(())
+ }
+
+ /// Send packet
+ async fn transmit(&mut self) {
+ let r = T::regs();
+
+ self.trigger_and_wait_end(move || {
+ // Initialize the transmission
+ // trace!("txen");
+ r.tasks_txen.write(|w| w.tasks_txen().set_bit());
+ })
+ .await;
+ }
+
+ /// Send packet
+ async fn receive(&mut self) {
+ let r = T::regs();
+
+ self.trigger_and_wait_end(move || {
+ // Initialize the transmission
+ // trace!("rxen");
+ r.tasks_rxen.write(|w| w.tasks_rxen().set_bit());
+
+ // Await until ready
+ while r.events_ready.read().events_ready().bit_is_clear() {}
+
+ compiler_fence(Ordering::SeqCst);
+
+ // Acknowledge it
+ r.events_ready.reset();
+
+ // trace!("radio:start");
+ r.tasks_start.write(|w| w.tasks_start().set_bit());
+ })
+ .await;
+ }
+}
+
+impl<'d, T: Instance> Drop for Radio<'d, T> {
+ fn drop(&mut self) {
+ self.disable();
+ }
+}
diff --git a/embassy-nrf/src/radio/mod.rs b/embassy-nrf/src/radio/mod.rs
new file mode 100644
index 000000000..91cc2c0a7
--- /dev/null
+++ b/embassy-nrf/src/radio/mod.rs
@@ -0,0 +1,89 @@
+//! Integrated 2.4 GHz Radio
+//!
+//! The 2.4 GHz radio transceiver is compatible with multiple radio standards
+//! such as 1Mbps, 2Mbps and Long Range Bluetooth Low Energy.
+
+#![macro_use]
+
+/// Bluetooth Low Energy Radio driver.
+pub mod ble;
+
+use core::marker::PhantomData;
+
+use crate::{interrupt, pac, Peripheral};
+
+/// Interrupt handler
+pub struct InterruptHandler {
+ _phantom: PhantomData,
+}
+
+impl interrupt::typelevel::Handler for InterruptHandler {
+ unsafe fn on_interrupt() {
+ let r = T::regs();
+ let s = T::state();
+
+ if r.events_end.read().events_end().bit_is_set() {
+ s.end_waker.wake();
+ r.intenclr.write(|w| w.end().clear());
+ }
+ }
+}
+
+pub(crate) mod utils {
+ use super::*;
+
+ // Check if the HFCLK is XTAL is enabled
+ pub fn check_xtal() {
+ // safe: only reading the value
+ let is_xtal = unsafe {
+ let r = &*pac::CLOCK::ptr();
+ r.hfclkstat.read().src().is_xtal()
+ };
+ assert!(is_xtal, "HFCLK must be XTAL");
+ }
+}
+
+pub(crate) mod sealed {
+ use embassy_sync::waitqueue::AtomicWaker;
+
+ pub struct State {
+ /// end packet transmission or reception
+ pub end_waker: AtomicWaker,
+ }
+ impl State {
+ pub const fn new() -> Self {
+ Self {
+ end_waker: AtomicWaker::new(),
+ }
+ }
+ }
+
+ pub trait Instance {
+ fn regs() -> &'static crate::pac::radio::RegisterBlock;
+ fn state() -> &'static State;
+ }
+}
+
+macro_rules! impl_radio {
+ ($type:ident, $pac_type:ident, $irq:ident) => {
+ impl crate::radio::sealed::Instance for peripherals::$type {
+ fn regs() -> &'static pac::radio::RegisterBlock {
+ unsafe { &*pac::$pac_type::ptr() }
+ }
+
+ fn state() -> &'static crate::radio::sealed::State {
+ static STATE: crate::radio::sealed::State = crate::radio::sealed::State::new();
+ &STATE
+ }
+ }
+ impl crate::radio::Instance for peripherals::$type {
+ type Interrupt = crate::interrupt::typelevel::$irq;
+ }
+ };
+}
+
+/// Radio peripheral instance.
+pub trait Instance: Peripheral + sealed::Instance + 'static + Send {
+ /// Interrupt for this peripheral.
+ type Interrupt: interrupt::typelevel::Interrupt;
+}
diff --git a/examples/nrf52840/Cargo.toml b/examples/nrf52840/Cargo.toml
index abb995be6..0239583cd 100644
--- a/examples/nrf52840/Cargo.toml
+++ b/examples/nrf52840/Cargo.toml
@@ -8,8 +8,8 @@ license = "MIT OR Apache-2.0"
embassy-futures = { version = "0.1.0", path = "../../embassy-futures" }
embassy-sync = { version = "0.5.0", path = "../../embassy-sync", features = ["defmt"] }
embassy-executor = { version = "0.5.0", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "executor-interrupt", "defmt", "integrated-timers"] }
-embassy-time = { version = "0.3.0", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime"] }
-embassy-nrf = { version = "0.1.0", path = "../../embassy-nrf", features = ["defmt", "nrf52840", "time-driver-rtc1", "gpiote", "unstable-pac", "time"] }
+embassy-time = { version = "0.3.0", features = ["defmt", "defmt-timestamp-uptime"] }
+embassy-nrf = { version = "0.1.0", path = "../../embassy-nrf", features = ["defmt", "nrf52840", "time-driver-rtc1", "gpiote", "unstable-pac", "time", "radio"]}
embassy-net = { version = "0.4.0", path = "../../embassy-net", features = ["defmt", "tcp", "dhcpv4", "medium-ethernet"] }
embassy-usb = { version = "0.1.0", path = "../../embassy-usb", features = ["defmt"] }
embedded-io = { version = "0.6.0", features = ["defmt-03"] }
@@ -35,6 +35,10 @@ embedded-hal-async = { version = "1.0" }
embedded-hal-bus = { version = "0.1", features = ["async"] }
num-integer = { version = "0.1.45", default-features = false }
microfft = "0.5.0"
+jewel = { version = "0.1.0", git = "https://github.com/jewel-rs/jewel"}
+
+[patch.crates-io]
+embassy-time = { version = "0.3.0", path = "../../embassy-time"}
[profile.release]
debug = 2
diff --git a/examples/nrf52840/src/bin/radio_ble_advertising.rs b/examples/nrf52840/src/bin/radio_ble_advertising.rs
new file mode 100644
index 000000000..8898c2418
--- /dev/null
+++ b/examples/nrf52840/src/bin/radio_ble_advertising.rs
@@ -0,0 +1,42 @@
+#![no_std]
+#![no_main]
+
+use defmt::{info, unwrap};
+use embassy_executor::Spawner;
+use embassy_nrf::{bind_interrupts, peripherals, radio};
+use embassy_time::Timer;
+use jewel::phy::Radio;
+use {defmt_rtt as _, panic_probe as _};
+
+bind_interrupts!(struct Irqs {
+ RADIO => radio::InterruptHandler;
+});
+
+// For a high-level API look on jewel examples
+#[embassy_executor::main]
+async fn main(_spawner: Spawner) {
+ let mut config = embassy_nrf::config::Config::default();
+ config.hfclk_source = embassy_nrf::config::HfclkSource::ExternalXtal;
+ let p = embassy_nrf::init(config);
+
+ info!("Starting BLE radio");
+ let mut radio = radio::ble::Radio::new(p.RADIO, Irqs);
+
+ let pdu = [
+ 0x46u8, // ADV_NONCONN_IND, Random address,
+ 0x18, // Length of payload
+ 0x27, 0xdc, 0xd0, 0xe8, 0xe1, 0xff, // Adress
+ 0x02, 0x01, 0x06, // Flags
+ 0x03, 0x03, 0x09, 0x18, // Complete list of 16-bit UUIDs available
+ 0x0A, 0x09, // Length, Type: Device name
+ b'H', b'e', b'l', b'l', b'o', b'R', b'u', b's', b't',
+ ];
+
+ unwrap!(radio.set_buffer(pdu.as_ref()));
+
+ loop {
+ info!("Sending packet");
+ radio.transmit().await;
+ Timer::after_millis(500).await;
+ }
+}