Added IEEE 802.15.4 radio
This commit is contained in:
parent
263d1b024c
commit
4294bc5e4b
6 changed files with 917 additions and 22 deletions
embassy-nrf/src
|
@ -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,
|
||||
|
|
|
@ -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"))]
|
||||
|
|
|
@ -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(());
|
||||
|
|
310
embassy-nrf/src/radio/event.rs
Normal file
310
embassy-nrf/src/radio/event.rs
Normal file
|
@ -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 { "__" },
|
||||
)
|
||||
}
|
||||
}
|
573
embassy-nrf/src/radio/ieee802154.rs
Normal file
573
embassy-nrf/src/radio/ieee802154.rs
Normal file
|
@ -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<P = T> + 'd,
|
||||
_irq: impl interrupt::typelevel::Binding<T::Interrupt, InterruptHandler<T>> + '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<usize> = 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);
|
||||
}
|
|
@ -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<T: Instance> {
|
||||
_phantom: PhantomData<T>,
|
||||
|
@ -21,11 +42,10 @@ impl<T: Instance> interrupt::typelevel::Handler<T::Interrupt> 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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue