diff --git a/embassy-nrf/src/chips/nrf52833.rs b/embassy-nrf/src/chips/nrf52833.rs
index 7c9b66d69..20f14e2d6 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,8 @@ impl_saadc_input!(P0_31, ANALOG_INPUT7);
impl_i2s!(I2S, I2S, I2S);
+impl_radio!(RADIO, RADIO, RADIO);
+
embassy_hal_internal::interrupt_mod!(
POWER_CLOCK,
RADIO,
diff --git a/embassy-nrf/src/lib.rs b/embassy-nrf/src/lib.rs
index 04a6293a4..132bffa8b 100644
--- a/embassy-nrf/src/lib.rs
+++ b/embassy-nrf/src/lib.rs
@@ -47,7 +47,7 @@ pub mod gpio;
pub mod gpiote;
// TODO: tested on other chips
-#[cfg(any(feature = "nrf52840"))]
+#[cfg(any(feature = "nrf52833", feature = "nrf52840"))]
pub mod radio;
#[cfg(any(feature = "nrf52832", feature = "nrf52833", feature = "nrf52840"))]
diff --git a/embassy-nrf/src/radio/ble.rs b/embassy-nrf/src/radio/ble.rs
index 24dba582f..ecf8cc5eb 100644
--- a/embassy-nrf/src/radio/ble.rs
+++ b/embassy-nrf/src/radio/ble.rs
@@ -15,19 +15,6 @@ use crate::interrupt::typelevel::Interrupt;
use crate::radio::*;
use crate::util::slice_in_ram_or;
-/// RADIO 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 is most likely in flash, and nRF's DMA cannot access flash.
- BufferNotInRAM,
-}
-
/// Radio driver.
pub struct Radio<'d, T: Instance> {
_p: PeripheralRef<'d, T>,
@@ -393,7 +380,7 @@ impl<'d, T: Instance> Radio<'d, T> {
// On poll check if interrupt happen
poll_fn(|cx| {
- s.end_waker.register(cx.waker());
+ s.event_waker.register(cx.waker());
if r.events_end.read().events_end().bit_is_set() {
// trace!("radio:end");
return core::task::Poll::Ready(());
diff --git a/embassy-nrf/src/radio/event.rs b/embassy-nrf/src/radio/event.rs
new file mode 100644
index 000000000..11056b4d8
--- /dev/null
+++ b/embassy-nrf/src/radio/event.rs
@@ -0,0 +1,310 @@
+use crate::pac;
+use bitflags;
+
+bitflags::bitflags! {
+ /// Event as bit flags
+ pub struct Event : u32 {
+ /// Radio ready
+ const READY = 1u32 << 0;
+ /// Address operation done
+ const ADDRESS = 1u32 << 1;
+ /// Payload operation done
+ const PAYLOAD = 1u32 << 2;
+ /// Packet operation done
+ const END = 1u32 << 3;
+ /// Radio has been disabled
+ const DISABLED = 1u32 << 4;
+ /// Device address match in last received packet
+ const DEV_MATCH = 1u32 << 5;
+ /// No device address match in last received packet
+ const DEV_MISS = 1u32 << 6;
+ /// RSSI sampling complete
+ const RSSI_END = 1u32 << 7;
+ /// Bit counter reached target
+ const BC_MATCH = 1u32 << 10;
+ /// CRC ok in last received packet
+ const CRC_OK = 1u32 << 12;
+ /// CRC error in last received packet
+ const CRC_ERROR = 1u32 << 13;
+ /// IEEE 802.15.4 length field received
+ const FRAME_START = 1u32 << 14;
+ /// Sampling of energy detect complete
+ const ED_END = 1u32 << 15;
+ /// Sampling of energy detect stopped
+ const ED_STOPPED = 1u32 << 16;
+ /// Wireless medium in idle, ready to sent
+ const CCA_IDLE = 1u32 << 17;
+ /// Wireless medium busy, do not send
+ const CCA_BUSY = 1u32 << 18;
+ /// Clear channel assessment stopped
+ const CCA_STOPPED = 1u32 << 19;
+ /// BLE LR rate boost received
+ const RATE_BOOST = 1u32 << 20;
+ /// Radio has ramped up transmitter
+ const TX_READY = 1u32 << 21;
+ /// Radio has ramped up receiver
+ const RX_READY = 1u32 << 22;
+ /// MAC header match found
+ const MHR_MATCH = 1u32 << 23;
+ /// Preamble received, possible false triggering
+ const SYNC = 1u32 << 26;
+ /// Last bit sent / received
+ const PHY_END = 1u32 << 27;
+ /// Continuous tone extension is present
+ const CTE_PRESENT = 1u32 << 28;
+ }
+}
+
+impl Event {
+ /// Read events from radio
+ #[cfg(not(feature = "nrf52832"))]
+ pub fn from_radio(radio: &pac::radio::RegisterBlock) -> Self {
+ let mut value = Self::empty();
+ if radio.events_ready.read().events_ready().bit_is_set() {
+ value |= Self::READY;
+ }
+ if radio.events_address.read().events_address().bit_is_set() {
+ value |= Self::ADDRESS;
+ }
+ if radio.events_payload.read().events_payload().bit_is_set() {
+ value |= Self::PAYLOAD;
+ }
+ if radio.events_end.read().events_end().bit_is_set() {
+ value |= Self::END;
+ }
+ if radio.events_disabled.read().events_disabled().bit_is_set() {
+ value |= Self::DISABLED;
+ }
+ if radio.events_devmatch.read().events_devmatch().bit_is_set() {
+ value |= Self::DEV_MATCH;
+ }
+ if radio.events_devmiss.read().events_devmiss().bit_is_set() {
+ value |= Self::DEV_MISS;
+ }
+ if radio.events_rssiend.read().events_rssiend().bit_is_set() {
+ value |= Self::RSSI_END;
+ }
+ if radio.events_bcmatch.read().events_bcmatch().bit_is_set() {
+ value |= Self::BC_MATCH;
+ }
+ if radio.events_crcok.read().events_crcok().bit_is_set() {
+ value |= Self::CRC_OK;
+ }
+ if radio.events_crcerror.read().events_crcerror().bit_is_set() {
+ value |= Self::CRC_ERROR;
+ }
+ #[cfg(any(
+ feature = "nrf52811",
+ feature = "nrf52820",
+ feature = "nrf52833",
+ feature = "_nrf5340-net"
+ ))]
+ if radio.events_framestart.read().events_framestart().bit_is_set() {
+ value |= Self::FRAME_START;
+ }
+ #[cfg(any(
+ feature = "nrf52811",
+ feature = "nrf52820",
+ feature = "nrf52833",
+ feature = "_nrf5340-net"
+ ))]
+ if radio.events_edend.read().events_edend().bit_is_set() {
+ value |= Self::ED_END;
+ }
+ #[cfg(any(
+ feature = "nrf52811",
+ feature = "nrf52820",
+ feature = "nrf52833",
+ feature = "_nrf5340-net"
+ ))]
+ if radio.events_edstopped.read().events_edstopped().bit_is_set() {
+ value |= Self::ED_STOPPED;
+ }
+ #[cfg(any(feature = "nrf52820", feature = "nrf52833", feature = "_nrf5340-net"))]
+ if radio.events_ccaidle.read().events_ccaidle().bit_is_set() {
+ value |= Self::CCA_IDLE;
+ }
+ #[cfg(any(feature = "nrf52820", feature = "nrf52833", feature = "_nrf5340-net"))]
+ if radio.events_ccabusy.read().events_ccabusy().bit_is_set() {
+ value |= Self::CCA_BUSY;
+ }
+ #[cfg(any(feature = "nrf52820", feature = "nrf52833", feature = "_nrf5340-net"))]
+ if radio.events_ccastopped.read().events_ccastopped().bit_is_set() {
+ value |= Self::CCA_STOPPED;
+ }
+ #[cfg(any(
+ feature = "nrf52811",
+ feature = "nrf52820",
+ feature = "nrf52833",
+ feature = "_nrf5340-net"
+ ))]
+ if radio.events_rateboost.read().events_rateboost().bit_is_set() {
+ value |= Self::RATE_BOOST;
+ }
+ #[cfg(any(
+ feature = "nrf52805",
+ feature = "nrf52811",
+ feature = "nrf52820",
+ feature = "nrf52833",
+ feature = "_nrf5340-net"
+ ))]
+ if radio.events_txready.read().events_txready().bit_is_set() {
+ value |= Self::TX_READY;
+ }
+ #[cfg(any(
+ feature = "nrf52805",
+ feature = "nrf52811",
+ feature = "nrf52820",
+ feature = "nrf52833",
+ feature = "_nrf5340-net"
+ ))]
+ if radio.events_rxready.read().events_rxready().bit_is_set() {
+ value |= Self::RX_READY;
+ }
+ #[cfg(any(
+ feature = "nrf52811",
+ feature = "nrf52820",
+ feature = "nrf52833",
+ feature = "_nrf5340-net"
+ ))]
+ if radio.events_mhrmatch.read().events_mhrmatch().bit_is_set() {
+ value |= Self::MHR_MATCH;
+ }
+ #[cfg(any(feature = "nrf52820", feature = "nrf52833", feature = "_nrf5340-net"))]
+ if radio.events_sync.read().events_sync().bit_is_set() {
+ value |= Self::SYNC;
+ }
+ #[cfg(any(
+ feature = "nrf52805",
+ feature = "nrf52811",
+ feature = "nrf52820",
+ feature = "nrf52833",
+ feature = "_nrf5340-net"
+ ))]
+ if radio.events_phyend.read().events_phyend().bit_is_set() {
+ value |= Self::PHY_END;
+ }
+ #[cfg(any(
+ feature = "nrf52811",
+ feature = "nrf52820",
+ feature = "nrf52833",
+ feature = "_nrf5340-net"
+ ))]
+ if radio.events_ctepresent.read().events_ctepresent().bit_is_set() {
+ value |= Self::CTE_PRESENT;
+ }
+ value
+ }
+ // The nRF52832 SVD probably is a bit broken
+ /// Read events from radio
+ #[cfg(feature = "nrf52832")]
+ pub fn from_radio(radio: &pac::radio::RegisterBlock) -> Self {
+ let mut value = Self::empty();
+ if radio.events_ready.read().bits() == 1 {
+ value |= Self::READY;
+ }
+ if radio.events_address.read().bits() == 1 {
+ value |= Self::ADDRESS;
+ }
+ if radio.events_payload.read().bits() == 1 {
+ value |= Self::PAYLOAD;
+ }
+ if radio.events_end.read().bits() == 1 {
+ value |= Self::END;
+ }
+ if radio.events_disabled.read().bits() == 1 {
+ value |= Self::DISABLED;
+ }
+ if radio.events_devmatch.read().bits() == 1 {
+ value |= Self::DEV_MATCH;
+ }
+ if radio.events_devmiss.read().bits() == 1 {
+ value |= Self::DEV_MISS;
+ }
+ if radio.events_rssiend.read().bits() == 1 {
+ value |= Self::RSSI_END;
+ }
+ if radio.events_bcmatch.read().bits() == 1 {
+ value |= Self::BC_MATCH;
+ }
+ if radio.events_crcok.read().bits() == 1 {
+ value |= Self::CRC_OK;
+ }
+ if radio.events_crcerror.read().bits() == 1 {
+ value |= Self::CRC_ERROR;
+ }
+ value
+ }
+
+ /// Read events from radio, mask with set interrupts
+ pub fn from_radio_masked(radio: &pac::radio::RegisterBlock) -> Self {
+ Self::from_radio(radio) & Self::from_bits_truncate(radio.intenset.read().bits())
+ }
+}
+
+#[cfg(feature = "defmt")]
+impl defmt::Format for Event {
+ fn format(&self, fmt: defmt::Formatter) {
+ defmt::write!(
+ fmt,
+ "{} {} {} {} {} {} {} {} {} {} {} {} {} {} {} {} {} {} {} {} {} {} {} {}",
+ if self.contains(Self::READY) { "RD" } else { "__" },
+ if self.contains(Self::ADDRESS) { "AD" } else { "__" },
+ if self.contains(Self::PAYLOAD) { "PL" } else { "__" },
+ if self.contains(Self::END) { " E" } else { "__" },
+ if self.contains(Self::DISABLED) { "DI" } else { "__" },
+ if self.contains(Self::DEV_MATCH) { "D+" } else { "__" },
+ if self.contains(Self::DEV_MISS) { "D-" } else { "__" },
+ if self.contains(Self::RSSI_END) { "RE" } else { "__" },
+ if self.contains(Self::BC_MATCH) { "CM" } else { "__" },
+ if self.contains(Self::CRC_OK) { "CO" } else { "__" },
+ if self.contains(Self::CRC_ERROR) { "CE" } else { "__" },
+ if self.contains(Self::FRAME_START) { "FS" } else { "__" },
+ if self.contains(Self::ED_END) { "EE" } else { "__" },
+ if self.contains(Self::ED_STOPPED) { "ES" } else { "__" },
+ if self.contains(Self::CCA_IDLE) { "CI" } else { "__" },
+ if self.contains(Self::CCA_BUSY) { "CB" } else { "__" },
+ if self.contains(Self::CCA_STOPPED) { "CS" } else { "__" },
+ if self.contains(Self::RATE_BOOST) { "RB" } else { "__" },
+ if self.contains(Self::TX_READY) { "TX" } else { "__" },
+ if self.contains(Self::RX_READY) { "RX" } else { "__" },
+ if self.contains(Self::MHR_MATCH) { "MM" } else { "__" },
+ if self.contains(Self::SYNC) { "SY" } else { "__" },
+ if self.contains(Self::PHY_END) { "PE" } else { "__" },
+ if self.contains(Self::CTE_PRESENT) { "CP" } else { "__" },
+ )
+ }
+}
+
+impl core::fmt::Display for Event {
+ fn fmt(&self, fmt: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
+ write!(
+ fmt,
+ "{} {} {} {} {} {} {} {} {} {} {} {} {} {} {} {} {} {} {} {} {} {} {} {}",
+ if self.contains(Self::READY) { "RD" } else { "__" },
+ if self.contains(Self::ADDRESS) { "AD" } else { "__" },
+ if self.contains(Self::PAYLOAD) { "PL" } else { "__" },
+ if self.contains(Self::END) { " E" } else { "__" },
+ if self.contains(Self::DISABLED) { "DI" } else { "__" },
+ if self.contains(Self::DEV_MATCH) { "D+" } else { "__" },
+ if self.contains(Self::DEV_MISS) { "D-" } else { "__" },
+ if self.contains(Self::RSSI_END) { "RE" } else { "__" },
+ if self.contains(Self::BC_MATCH) { "CM" } else { "__" },
+ if self.contains(Self::CRC_OK) { "CO" } else { "__" },
+ if self.contains(Self::CRC_ERROR) { "CE" } else { "__" },
+ if self.contains(Self::FRAME_START) { "FS" } else { "__" },
+ if self.contains(Self::ED_END) { "EE" } else { "__" },
+ if self.contains(Self::ED_STOPPED) { "ES" } else { "__" },
+ if self.contains(Self::CCA_IDLE) { "CI" } else { "__" },
+ if self.contains(Self::CCA_BUSY) { "CB" } else { "__" },
+ if self.contains(Self::CCA_STOPPED) { "CS" } else { "__" },
+ if self.contains(Self::RATE_BOOST) { "RB" } else { "__" },
+ if self.contains(Self::TX_READY) { "TX" } else { "__" },
+ if self.contains(Self::RX_READY) { "RX" } else { "__" },
+ if self.contains(Self::MHR_MATCH) { "MM" } else { "__" },
+ if self.contains(Self::SYNC) { "SY" } else { "__" },
+ if self.contains(Self::PHY_END) { "PE" } else { "__" },
+ if self.contains(Self::CTE_PRESENT) { "CP" } else { "__" },
+ )
+ }
+}
diff --git a/embassy-nrf/src/radio/ieee802154.rs b/embassy-nrf/src/radio/ieee802154.rs
new file mode 100644
index 000000000..4d7949cb3
--- /dev/null
+++ b/embassy-nrf/src/radio/ieee802154.rs
@@ -0,0 +1,573 @@
+//! IEEE 802.15.4 radio
+
+use core::sync::atomic::{compiler_fence, Ordering};
+use core::task::Poll;
+
+use super::{Error, Event, Instance, InterruptHandler};
+use crate::{
+ interrupt::{self, typelevel::Interrupt},
+ pac, Peripheral,
+};
+use pac::radio::{state::STATE_A as RadioState, txpower::TXPOWER_A as TxPower};
+
+use embassy_hal_internal::drop::OnDrop;
+use embassy_hal_internal::{into_ref, PeripheralRef};
+
+/// Default Start of Frame Delimiter = `0xA7` (IEEE compliant)
+pub const DEFAULT_SFD: u8 = 0xA7;
+
+// TODO expose the other variants in `pac::CCAMODE_A`
+/// Clear Channel Assessment method
+pub enum Cca {
+ /// Carrier sense
+ CarrierSense,
+ /// Energy Detection / Energy Above Threshold
+ EnergyDetection {
+ /// Energy measurements above this value mean that the channel is assumed to be busy.
+ /// Note the measurement range is 0..0xFF - where 0 means that the received power was
+ /// less than 10 dB above the selected receiver sensitivity. This value is not given in dBm,
+ /// but can be converted. See the nrf52840 Product Specification Section 6.20.12.4
+ /// for details.
+ ed_threshold: u8,
+ },
+}
+
+fn get_state(radio: &pac::radio::RegisterBlock) -> RadioState {
+ match radio.state.read().state().variant() {
+ Some(state) => state,
+ None => unreachable!(),
+ }
+}
+
+fn trace_state(state: RadioState) {
+ match state {
+ 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"),
+ }
+}
+
+/// Radio driver.
+pub struct Radio<'d, T: Instance> {
+ _p: PeripheralRef<'d, T>,
+ needs_enable: bool,
+}
+
+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 {
+ into_ref!(radio);
+
+ let r = T::regs();
+
+ // Disable and enable to reset peripheral
+ r.power.write(|w| w.power().disabled());
+ r.power.write(|w| w.power().enabled());
+
+ // Enable 802.15.4 mode
+ r.mode.write(|w| w.mode().ieee802154_250kbit());
+ // Configure CRC skip address
+ r.crccnf.write(|w| w.len().two().skipaddr().ieee802154());
+ unsafe {
+ // Configure CRC polynomial and init
+ r.crcpoly.write(|w| w.crcpoly().bits(0x0001_1021));
+ r.crcinit.write(|w| w.crcinit().bits(0));
+ // Configure packet layout
+ // 8-bit on air length
+ // S0 length, zero bytes
+ // S1 length, zero bytes
+ // S1 included in RAM if S1 length > 0, No.
+ // Code Indicator length, 0
+ // Preamble length 32-bit zero
+ // Exclude CRC
+ // No TERM field
+ r.pcnf0.write(|w| {
+ w.lflen()
+ .bits(8)
+ .s0len()
+ .clear_bit()
+ .s1len()
+ .bits(0)
+ .s1incl()
+ .clear_bit()
+ .cilen()
+ .bits(0)
+ .plen()
+ ._32bit_zero()
+ .crcinc()
+ .include()
+ });
+ r.pcnf1.write(|w| {
+ w.maxlen()
+ .bits(Packet::MAX_PSDU_LEN)
+ .statlen()
+ .bits(0)
+ .balen()
+ .bits(0)
+ .endian()
+ .clear_bit()
+ .whiteen()
+ .clear_bit()
+ });
+ }
+
+ // Enable NVIC interrupt
+ T::Interrupt::unpend();
+ unsafe { T::Interrupt::enable() };
+
+ let mut radio = Self {
+ _p: radio,
+ needs_enable: false,
+ };
+
+ radio.set_sfd(DEFAULT_SFD);
+ radio.set_transmission_power(0);
+ radio.set_channel(11);
+ radio.set_cca(Cca::CarrierSense);
+
+ radio
+ }
+
+ /// Changes the radio channel
+ pub fn set_channel(&mut self, channel: u8) {
+ let r = T::regs();
+ if channel < 11 || channel > 26 {
+ panic!("Bad 802.15.4 channel");
+ }
+ let frequency_offset = (channel - 10) * 5;
+ self.needs_enable = true;
+ r.frequency
+ .write(|w| unsafe { w.frequency().bits(frequency_offset).map().default() });
+ }
+
+ /// Changes the Clear Channel Assessment method
+ pub fn set_cca(&mut self, cca: Cca) {
+ let r = T::regs();
+ self.needs_enable = true;
+ match cca {
+ Cca::CarrierSense => r.ccactrl.write(|w| w.ccamode().carrier_mode()),
+ Cca::EnergyDetection { ed_threshold } => {
+ // "[ED] is enabled by first configuring the field CCAMODE=EdMode in CCACTRL
+ // and writing the CCAEDTHRES field to a chosen value."
+ r.ccactrl
+ .write(|w| unsafe { w.ccamode().ed_mode().ccaedthres().bits(ed_threshold) });
+ }
+ }
+ }
+
+ /// Changes the Start of Frame Delimiter
+ pub fn set_sfd(&mut self, sfd: u8) {
+ let r = T::regs();
+ r.sfd.write(|w| unsafe { w.sfd().bits(sfd) });
+ }
+
+ /// Clear interrupts
+ pub fn clear_all_interrupts(&mut self) {
+ let r = T::regs();
+ r.intenclr.write(|w| unsafe { w.bits(0xffff_ffff) });
+ }
+
+ /// Changes the radio transmission power
+ pub fn set_transmission_power(&mut self, power: i8) {
+ let r = T::regs();
+ self.needs_enable = true;
+
+ let tx_power: TxPower = match power {
+ 8 => TxPower::POS8D_BM,
+ 7 => TxPower::POS7D_BM,
+ 6 => TxPower::POS6D_BM,
+ 5 => TxPower::POS5D_BM,
+ 4 => TxPower::POS4D_BM,
+ 3 => TxPower::POS3D_BM,
+ 2 => TxPower::POS2D_BM,
+ 0 => TxPower::_0D_BM,
+ -4 => TxPower::NEG4D_BM,
+ -8 => TxPower::NEG8D_BM,
+ -12 => TxPower::NEG12D_BM,
+ -16 => TxPower::NEG16D_BM,
+ -20 => TxPower::NEG20D_BM,
+ -30 => TxPower::NEG30D_BM,
+ -40 => TxPower::NEG40D_BM,
+ _ => panic!("Invalid transmission power value"),
+ };
+
+ r.txpower.write(|w| w.txpower().variant(tx_power));
+ }
+
+ /// Waits until the radio state matches the given `state`
+ fn wait_for_radio_state(&self, state: RadioState) {
+ while self.state() != state {}
+ }
+
+ fn state(&self) -> RadioState {
+ let r = T::regs();
+ match r.state.read().state().variant() {
+ Some(state) => state,
+ None => unreachable!(),
+ }
+ }
+
+ fn trace_state(&self) {
+ trace_state(self.state());
+ }
+
+ /// Moves the radio from any state to the DISABLED state
+ fn disable(&mut self) {
+ let r = T::regs();
+ // See figure 110 in nRF52840-PS
+ loop {
+ match self.state() {
+ RadioState::DISABLED => return,
+
+ RadioState::RX_RU | RadioState::RX_IDLE | RadioState::TX_RU | RadioState::TX_IDLE => {
+ r.tasks_disable.write(|w| w.tasks_disable().set_bit());
+
+ self.wait_for_radio_state(RadioState::DISABLED);
+ return;
+ }
+
+ // ramping down
+ RadioState::RX_DISABLE | RadioState::TX_DISABLE => {
+ self.wait_for_radio_state(RadioState::DISABLED);
+ return;
+ }
+
+ // cancel ongoing transfer or ongoing CCA
+ RadioState::RX => {
+ r.tasks_ccastop.write(|w| w.tasks_ccastop().set_bit());
+ r.tasks_stop.write(|w| w.tasks_stop().set_bit());
+ self.wait_for_radio_state(RadioState::RX_IDLE);
+ }
+ RadioState::TX => {
+ r.tasks_stop.write(|w| w.tasks_stop().set_bit());
+ self.wait_for_radio_state(RadioState::TX_IDLE);
+ }
+ }
+ }
+ }
+
+ fn set_buffer(&mut self, buffer: &[u8]) {
+ let r = T::regs();
+ r.packetptr.write(|w| unsafe { w.bits(buffer.as_ptr() as u32) });
+ }
+
+ /// Moves the radio to the RXIDLE state
+ fn receive_prepare(&mut self) {
+ let state = self.state();
+
+ let disable = match state {
+ RadioState::DISABLED => false,
+ RadioState::RX_DISABLE => true,
+ RadioState::TX_DISABLE => true,
+ RadioState::RX_IDLE => self.needs_enable,
+ // NOTE to avoid errata 204 (see rev1 v1.4) we do TX_IDLE -> DISABLED -> RX_IDLE
+ RadioState::TX_IDLE => true,
+ _ => unreachable!(),
+ };
+ if disable {
+ trace!("Receive Setup");
+ self.trace_state();
+ self.disable();
+ }
+ self.needs_enable = false;
+ }
+
+ fn receive_start(&mut self, packet: &mut Packet) {
+ // NOTE we do NOT check the address of `packet` because the mutable reference ensures it's
+ // allocated in RAM
+ let r = T::regs();
+
+ // clear related events
+ r.events_framestart.reset();
+ r.events_ccabusy.reset();
+ r.events_phyend.reset();
+
+ self.receive_prepare();
+
+ // Configure shortcuts
+ //
+ // The radio goes through following states when receiving a 802.15.4 packet
+ //
+ // enable RX → ramp up RX → RX idle → Receive → end (PHYEND)
+ r.shorts.write(|w| w.rxready_start().enabled());
+
+ // set up RX buffer
+ self.set_buffer(packet.buffer.as_mut());
+
+ // start transfer
+ dma_start_fence();
+
+ match self.state() {
+ // Re-start receiver
+ RadioState::RX_IDLE => r.tasks_start.write(|w| w.tasks_start().set_bit()),
+ // Enable receiver
+ _ => r.tasks_rxen.write(|w| w.tasks_rxen().set_bit()),
+ }
+ }
+
+ fn receive_cancel() {
+ let r = T::regs();
+ r.shorts.reset();
+ if r.events_framestart.read().events_framestart().bit_is_set() {
+ // TODO: Is there a way to finish receiving this frame
+ trace!("EVENTS {}", Event::from_radio(r));
+ }
+ r.tasks_stop.write(|w| w.tasks_stop().set_bit());
+ loop {
+ match get_state(r) {
+ RadioState::DISABLED | RadioState::RX_IDLE => break,
+ _ => (),
+ }
+ }
+ // DMA transfer may have been in progress so synchronize with its memory operations
+ dma_end_fence();
+ }
+
+ /// Receives one radio packet and copies its contents into the given `packet` buffer
+ ///
+ /// This methods returns the `Ok` variant if the CRC included the packet was successfully
+ /// validated by the hardware; otherwise it returns the `Err` variant. In either case, `packet`
+ /// will be updated with the received packet's data
+ pub async fn receive(&mut self, packet: &mut Packet) -> Result<(), u16> {
+ let s = T::state();
+ let r = T::regs();
+
+ // Start the read
+ self.receive_start(packet);
+
+ let dropper = OnDrop::new(|| Self::receive_cancel());
+
+ self.clear_all_interrupts();
+ // wait until we have received something
+ core::future::poll_fn(|cx| {
+ s.event_waker.register(cx.waker());
+
+ if r.events_phyend.read().events_phyend().bit_is_set() {
+ r.events_phyend.reset();
+ trace!("RX done poll");
+ return Poll::Ready(());
+ } else {
+ r.intenset.write(|w| w.phyend().set());
+ };
+
+ Poll::Pending
+ })
+ .await;
+
+ dma_end_fence();
+ dropper.defuse();
+
+ let crc = r.rxcrc.read().rxcrc().bits() as u16;
+ if r.crcstatus.read().crcstatus().bit_is_set() {
+ Ok(())
+ } else {
+ Err(crc)
+ }
+ }
+
+ /// Tries to send the given `packet`
+ ///
+ /// This method performs Clear Channel Assessment (CCA) first and sends the `packet` only if the
+ /// channel is observed to be *clear* (no transmission is currently ongoing), otherwise no
+ /// packet is transmitted and the `Err` variant is returned
+ ///
+ /// NOTE this method will *not* modify the `packet` argument. The mutable reference is used to
+ /// ensure the `packet` buffer is allocated in RAM, which is required by the RADIO peripheral
+ // NOTE we do NOT check the address of `packet` because the mutable reference ensures it's
+ // allocated in RAM
+ pub async fn try_send(&mut self, packet: &mut Packet) -> Result<(), Error> {
+ let s = T::state();
+ let r = T::regs();
+
+ // clear related events
+ r.events_framestart.reset();
+ r.events_ccabusy.reset();
+ r.events_phyend.reset();
+
+ // enable radio to perform cca
+ self.receive_prepare();
+
+ /// transmit result
+ #[derive(Debug, Clone, Copy, PartialEq, Eq)]
+ #[cfg_attr(feature = "defmt", derive(defmt::Format))]
+ pub enum TransmitResult {
+ /// Success
+ Success,
+ /// Clear channel assessment reported channel in use
+ ChannelInUse,
+ }
+
+ // Configure shortcuts
+ //
+ // The radio goes through following states when sending a 802.15.4 packet
+ //
+ // enable RX → ramp up RX → clear channel assessment (CCA) → CCA result
+ // CCA idle → enable TX → start TX → TX → end (PHYEND) → disabled
+ //
+ // CCA might end up in the event CCABUSY in which there will be no transmission
+ r.shorts.write(|w| {
+ w.rxready_ccastart()
+ .enabled()
+ .ccaidle_txen()
+ .enabled()
+ .txready_start()
+ .enabled()
+ .ccabusy_disable()
+ .enabled()
+ .phyend_disable()
+ .enabled()
+ });
+
+ // Set transmission buffer
+ self.set_buffer(packet.buffer.as_mut());
+
+ // the DMA transfer will start at some point after the following write operation so
+ // we place the compiler fence here
+ dma_start_fence();
+ // start CCA. In case the channel is clear, the data at packetptr will be sent automatically
+
+ match self.state() {
+ // Re-start receiver
+ RadioState::RX_IDLE => r.tasks_ccastart.write(|w| w.tasks_ccastart().set_bit()),
+ // Enable receiver
+ _ => r.tasks_rxen.write(|w| w.tasks_rxen().set_bit()),
+ }
+
+ self.clear_all_interrupts();
+ let result = core::future::poll_fn(|cx| {
+ s.event_waker.register(cx.waker());
+
+ if r.events_phyend.read().events_phyend().bit_is_set() {
+ r.events_phyend.reset();
+ r.events_ccabusy.reset();
+ trace!("TX done poll");
+ return Poll::Ready(TransmitResult::Success);
+ } else if r.events_ccabusy.read().events_ccabusy().bit_is_set() {
+ r.events_ccabusy.reset();
+ trace!("TX no CCA");
+ return Poll::Ready(TransmitResult::ChannelInUse);
+ }
+
+ r.intenset.write(|w| w.phyend().set().ccabusy().set());
+
+ Poll::Pending
+ })
+ .await;
+
+ match result {
+ TransmitResult::Success => Ok(()),
+ TransmitResult::ChannelInUse => Err(Error::ChannelInUse),
+ }
+ }
+}
+
+/// An IEEE 802.15.4 packet
+///
+/// This `Packet` is a PHY layer packet. It's made up of the physical header (PHR) and the PSDU
+/// (PHY service data unit). The PSDU of this `Packet` will always include the MAC level CRC, AKA
+/// the FCS (Frame Control Sequence) -- the CRC is fully computed in hardware and automatically
+/// appended on transmission and verified on reception.
+///
+/// The API lets users modify the usable part (not the CRC) of the PSDU via the `deref` and
+/// `copy_from_slice` methods. These methods will automatically update the PHR.
+///
+/// See figure 119 in the Product Specification of the nRF52840 for more details
+pub struct Packet {
+ buffer: [u8; Self::SIZE],
+}
+
+// See figure 124 in nRF52840-PS
+impl Packet {
+ // for indexing purposes
+ const PHY_HDR: usize = 0;
+ const DATA: core::ops::RangeFrom = 1..;
+
+ /// Maximum amount of usable payload (CRC excluded) a single packet can contain, in bytes
+ pub const CAPACITY: u8 = 125;
+ const CRC: u8 = 2; // size of the CRC, which is *never* copied to / from RAM
+ const MAX_PSDU_LEN: u8 = Self::CAPACITY + Self::CRC;
+ const SIZE: usize = 1 /* PHR */ + Self::MAX_PSDU_LEN as usize;
+
+ /// Returns an empty packet (length = 0)
+ pub fn new() -> Self {
+ let mut packet = Self {
+ buffer: [0; Self::SIZE],
+ };
+ packet.set_len(0);
+ packet
+ }
+
+ /// Fills the packet payload with given `src` data
+ ///
+ /// # Panics
+ ///
+ /// This function panics if `src` is larger than `Self::CAPACITY`
+ pub fn copy_from_slice(&mut self, src: &[u8]) {
+ assert!(src.len() <= Self::CAPACITY as usize);
+ let len = src.len() as u8;
+ self.buffer[Self::DATA][..len as usize].copy_from_slice(&src[..len.into()]);
+ self.set_len(len);
+ }
+
+ /// Returns the size of this packet's payload
+ pub fn len(&self) -> u8 {
+ self.buffer[Self::PHY_HDR] - Self::CRC
+ }
+
+ /// Changes the size of the packet's payload
+ ///
+ /// # Panics
+ ///
+ /// This function panics if `len` is larger than `Self::CAPACITY`
+ pub fn set_len(&mut self, len: u8) {
+ assert!(len <= Self::CAPACITY);
+ self.buffer[Self::PHY_HDR] = len + Self::CRC;
+ }
+
+ /// Returns the LQI (Link Quality Indicator) of the received packet
+ ///
+ /// Note that the LQI is stored in the `Packet`'s internal buffer by the hardware so the value
+ /// returned by this method is only valid after a `Radio.recv` operation. Operations that
+ /// modify the `Packet`, like `copy_from_slice` or `set_len`+`deref_mut`, will overwrite the
+ /// stored LQI value.
+ ///
+ /// Also note that the hardware will *not* compute a LQI for packets smaller than 3 bytes so
+ /// this method will return an invalid value for those packets.
+ pub fn lqi(&self) -> u8 {
+ self.buffer[1 /* PHY_HDR */ + self.len() as usize /* data */]
+ }
+}
+
+impl core::ops::Deref for Packet {
+ type Target = [u8];
+
+ fn deref(&self) -> &[u8] {
+ &self.buffer[Self::DATA][..self.len() as usize]
+ }
+}
+
+impl core::ops::DerefMut for Packet {
+ fn deref_mut(&mut self) -> &mut [u8] {
+ let len = self.len();
+ &mut self.buffer[Self::DATA][..len as usize]
+ }
+}
+
+/// NOTE must be followed by a volatile write operation
+fn dma_start_fence() {
+ compiler_fence(Ordering::Release);
+}
+
+/// NOTE must be preceded by a volatile read operation
+fn dma_end_fence() {
+ compiler_fence(Ordering::Acquire);
+}
diff --git a/embassy-nrf/src/radio/mod.rs b/embassy-nrf/src/radio/mod.rs
index 03f967f87..430078e8a 100644
--- a/embassy-nrf/src/radio/mod.rs
+++ b/embassy-nrf/src/radio/mod.rs
@@ -7,11 +7,32 @@
/// Bluetooth Low Energy Radio driver.
pub mod ble;
+mod event;
+#[cfg(any(feature = "nrf52840", feature = "nrf52833", feature = "_nrf5340-net"))]
+/// IEEE 802.15.4
+pub mod ieee802154;
+
+pub use event::Event;
use core::marker::PhantomData;
use crate::{interrupt, pac, Peripheral};
+/// RADIO 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 too short.
+ BufferTooShort,
+ /// The buffer is not in data RAM. It's most likely in flash, and nRF's DMA cannot access flash.
+ BufferNotInRAM,
+ /// Clear channel assessment reported channel in use
+ ChannelInUse,
+}
+
/// Interrupt handler
pub struct InterruptHandler {
_phantom: PhantomData,
@@ -21,11 +42,10 @@ impl interrupt::typelevel::Handler for InterruptHandl
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());
- }
+ let events = Event::from_radio_masked(r);
+ // clear active interrupts
+ r.intenclr.write(|w| w.bits(events.bits()));
+ s.event_waker.wake();
}
}
@@ -34,12 +54,12 @@ pub(crate) mod sealed {
pub struct State {
/// end packet transmission or reception
- pub end_waker: AtomicWaker,
+ pub event_waker: AtomicWaker,
}
impl State {
pub const fn new() -> Self {
Self {
- end_waker: AtomicWaker::new(),
+ event_waker: AtomicWaker::new(),
}
}
}