feat: add display UI with splash screen, dashboard, and RSSI sparkline

Implement full SSD1306 OLED display support:
- Splash screen on boot (1.5s) with project name, version, board
- Dashboard showing radio state, config, counters, RSSI/SNR
- RSSI history sparkline (64-sample ring buffer, 2px-wide bars)
- DisplayOn/DisplayOff host commands routed via dedicated channel
- Pure render functions against generic DrawTarget (no hardware coupling)
- Display on/off control bypasses radio task entirely

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
swaitsandClaude Opus 4.6 committed 2026-03-29 20:42:54 -06:00
1 parent 7064110293
commit 3102fc747a
17624 files changed
+4564928 -38

No files matched your search

+412
View File
@@ -0,0 +1,412 @@
//! Radio driver implementation focused on Bluetooth Low-Energy transmission.
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};
pub use pac::radio::mode::MODE_A as Mode;
#[cfg(not(feature = "nrf51"))]
use pac::radio::pcnf0::PLEN_A as PreambleLength;
use crate::interrupt::typelevel::Interrupt;
use crate::radio::*;
pub use crate::radio::{Error, TxPower};
use crate::util::slice_in_ram_or;
/// 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<P = T> + 'd,
_irq: impl interrupt::typelevel::Binding<T::Interrupt, InterruptHandler<T>> + 'd,
) -> Self {
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, It is setted as the maximum value
// and the length of the packet is controlled only by the LENGTH field in the packet
.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()
});
// 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).
// It skips the address field
w.skipaddr()
.skip()
// In BLE 24-bit CRC = 3 bytes
.len()
.three()
});
// Ch map between 2400 MHZ .. 2500 MHz
// All modes use this range
#[cfg(not(feature = "nrf51"))]
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() };
Self { _p: radio }
}
fn state(&self) -> RadioState {
super::state(T::regs())
}
/// Set the radio mode
///
/// The radio must be disabled before calling this function
pub fn set_mode(&mut self, mode: Mode) {
assert!(self.state() == RadioState::DISABLED);
let r = T::regs();
r.mode.write(|w| w.mode().variant(mode));
#[cfg(not(feature = "nrf51"))]
r.pcnf0.write(|w| {
w.plen().variant(match mode {
Mode::BLE_1MBIT => PreambleLength::_8BIT,
Mode::BLE_2MBIT => PreambleLength::_16BIT,
#[cfg(any(
feature = "nrf52811",
feature = "nrf52820",
feature = "nrf52833",
feature = "nrf52840",
feature = "_nrf5340-net"
))]
Mode::BLE_LR125KBIT | Mode::BLE_LR500KBIT => PreambleLength::LONG_RANGE,
_ => unimplemented!(),
})
});
}
/// Set the header size changing the S1's len field
///
/// The radio must be disabled before calling this function
pub fn set_header_expansion(&mut self, use_s1_field: bool) {
assert!(self.state() == RadioState::DISABLED);
let r = T::regs();
// s1 len in bits
let s1len: u8 = match use_s1_field {
false => 0,
true => 8,
};
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)
});
}
/// Set initial data whitening value
/// Data whitening is used to avoid long sequences of zeros or ones, e.g., 0b0000000 or 0b1111111, in the data bit stream
/// On BLE the initial value is the channel index | 0x40
///
/// The radio must be disabled before calling this function
pub fn set_whitening_init(&mut self, whitening_init: u8) {
assert!(self.state() == RadioState::DISABLED);
let r = T::regs();
r.datawhiteiv.write(|w| unsafe { w.datawhiteiv().bits(whitening_init) });
}
/// Set the central frequency to be used
/// It should be in the range 2400..2500
///
/// [The radio must be disabled before calling this function](https://devzone.nordicsemi.com/f/nordic-q-a/15829/radio-frequency-change)
pub fn set_frequency(&mut self, frequency: u32) {
assert!(self.state() == RadioState::DISABLED);
assert!((2400..=2500).contains(&frequency));
let r = T::regs();
r.frequency
.write(|w| unsafe { w.frequency().bits((frequency - 2400) as u8) });
}
/// Set the acess address
/// This address is always constants for advertising
/// And a random value generate on each connection
/// It is used to filter the packages
///
/// The radio must be disabled before calling this function
pub fn set_access_address(&mut self, access_address: u32) {
assert!(self.state() == RadioState::DISABLED);
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 it shifts 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
// This config only filter the packets by the address,
// so only packages send to the previous address
// will finish the reception (TODO: check the explanation)
r.rxaddresses.write(|w| {
w.addr0()
.enabled()
.addr1()
.enabled()
.addr2()
.enabled()
.addr3()
.enabled()
.addr4()
.enabled()
});
}
/// Set the CRC polynomial
/// It only uses the 24 least significant bits
///
/// The radio must be disabled before calling this function
pub fn set_crc_poly(&mut self, crc_poly: u32) {
assert!(self.state() == RadioState::DISABLED);
let r = T::regs();
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)
});
}
/// Set the CRC init value
/// It only uses the 24 least significant bits
/// The CRC initial value varies depending of the PDU type
///
/// The radio must be disabled before calling this function
pub fn set_crc_init(&mut self, crc_init: u32) {
assert!(self.state() == RadioState::DISABLED);
let r = T::regs();
r.crcinit.write(|w| unsafe { w.crcinit().bits(crc_init & 0xFFFFFF) });
}
/// Set the radio tx power
///
/// The radio must be disabled before calling this function
pub fn set_tx_power(&mut self, tx_power: TxPower) {
assert!(self.state() == RadioState::DISABLED);
let r = T::regs();
r.txpower.write(|w| w.txpower().variant(tx_power));
}
/// Set buffer to read/write
///
/// This method is unsound. You should guarantee that the buffer will live
/// for the life time of the transmission or if the buffer will be modified.
/// Also if the buffer is smaller than the packet length, the radio will
/// read/write memory out of the buffer bounds.
fn set_buffer(&mut self, buffer: &[u8]) -> Result<(), Error> {
slice_in_ram_or(buffer, Error::BufferNotInRAM)?;
let r = T::regs();
// Here it consider that the length of the packet is
// correctly set in the buffer, otherwise it will send
// 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
/// If the length byte in the package is greater than the buffer length
/// the radio will read memory out of the buffer bounds
pub async fn transmit(&mut self, buffer: &[u8]) -> Result<(), Error> {
self.set_buffer(buffer)?;
let r = T::regs();
self.trigger_and_wait_end(move || {
// Initialize the transmission
// trace!("txen");
r.tasks_txen.write(|w| unsafe { w.bits(1) });
})
.await;
Ok(())
}
/// Receive packet
/// If the length byte in the received package is greater than the buffer length
/// the radio will write memory out of the buffer bounds
pub async fn receive(&mut self, buffer: &mut [u8]) -> Result<(), Error> {
self.set_buffer(buffer)?;
let r = T::regs();
self.trigger_and_wait_end(move || {
// Initialize the transmission
// trace!("rxen");
r.tasks_rxen.write(|w| unsafe { w.bits(1) });
})
.await;
Ok(())
}
async fn trigger_and_wait_end(&mut self, trigger: impl FnOnce()) {
let r = T::regs();
let s = T::state();
// If the Future is dropped before the end of the transmission
// it 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.tasks_stop.write(|w| unsafe { w.bits(1) });
r.events_end.reset();
trace!("radio drop: stopped");
});
// trace!("radio:enable interrupt");
// Clear some remnant side-effects (TODO: check if this is necessary)
r.events_end.reset();
// Enable interrupt
r.intenset.write(|w| w.end().set());
compiler_fence(Ordering::SeqCst);
// Trigger the transmission
trigger();
// On poll check if interrupt happen
poll_fn(|cx| {
s.event_waker.register(cx.waker());
if r.events_end.read().bits() == 1 {
// trace!("radio:end");
return core::task::Poll::Ready(());
}
Poll::Pending
})
.await;
compiler_fence(Ordering::SeqCst);
r.events_end.reset(); // ACK
// Everthing ends fine, so it disable the drop
drop.defuse();
}
/// Disable the radio
fn disable(&mut self) {
let r = T::regs();
compiler_fence(Ordering::SeqCst);
// If it is already disabled, do nothing
if self.state() != RadioState::DISABLED {
trace!("radio:disable");
// Trigger the disable task
r.tasks_disable.write(|w| unsafe { w.bits(1) });
// Wait until the radio is disabled
while r.events_disabled.read().bits() == 0 {}
compiler_fence(Ordering::SeqCst);
// Acknowledge it
r.events_disabled.reset();
}
}
}
impl<'d, T: Instance> Drop for Radio<'d, T> {
fn drop(&mut self) {
self.disable();
}
}
+546
View File
@@ -0,0 +1,546 @@
//! IEEE 802.15.4 radio driver
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 super::{state, Error, Instance, InterruptHandler, RadioState, TxPower};
use crate::interrupt::typelevel::Interrupt;
use crate::interrupt::{self};
use crate::Peripheral;
/// Default (IEEE compliant) Start of Frame Delimiter
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,
},
}
/// IEEE 802.15.4 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 IEEE 802.15.4 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));
r.pcnf0.write(|w| {
// 8-bit on air length
w.lflen()
.bits(8)
// Zero bytes S0 field length
.s0len()
.clear_bit()
// Zero bytes S1 field length
.s1len()
.bits(0)
// Do not include S1 field in RAM if S1 length > 0
.s1incl()
.clear_bit()
// Zero code Indicator length
.cilen()
.bits(0)
// 32-bit zero preamble
.plen()
._32bit_zero()
// Include CRC in length
.crcinc()
.include()
});
r.pcnf1.write(|w| {
// Maximum packet length
w.maxlen()
.bits(Packet::MAX_PSDU_LEN)
// Zero static length
.statlen()
.bits(0)
// Zero base address length
.balen()
.bits(0)
// Little-endian
.endian()
.clear_bit()
// Disable packet whitening
.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 (SFD)
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 {
#[cfg(not(any(feature = "nrf52811", feature = "_nrf5340-net")))]
8 => TxPower::POS8D_BM,
#[cfg(not(any(feature = "nrf52811", feature = "_nrf5340-net")))]
7 => TxPower::POS7D_BM,
#[cfg(not(any(feature = "nrf52811", feature = "_nrf5340-net")))]
6 => TxPower::POS6D_BM,
#[cfg(not(any(feature = "nrf52811", feature = "_nrf5340-net")))]
5 => TxPower::POS5D_BM,
#[cfg(not(feature = "_nrf5340-net"))]
4 => TxPower::POS4D_BM,
#[cfg(not(feature = "_nrf5340-net"))]
3 => TxPower::POS3D_BM,
#[cfg(not(any(feature = "nrf52811", feature = "_nrf5340-net")))]
2 => TxPower::POS2D_BM,
0 => TxPower::_0D_BM,
#[cfg(feature = "_nrf5340-net")]
-1 => TxPower::NEG1D_BM,
#[cfg(feature = "_nrf5340-net")]
-2 => TxPower::NEG2D_BM,
#[cfg(feature = "_nrf5340-net")]
-3 => TxPower::NEG3D_BM,
-4 => TxPower::NEG4D_BM,
#[cfg(feature = "_nrf5340-net")]
-5 => TxPower::NEG5D_BM,
#[cfg(feature = "_nrf5340-net")]
-6 => TxPower::NEG6D_BM,
#[cfg(feature = "_nrf5340-net")]
-7 => TxPower::NEG7D_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 {}
}
/// Get the current radio state
fn state(&self) -> RadioState {
state(T::regs())
}
/// 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,
// idle or ramping up
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) {
// clear related events
T::regs().events_ccabusy.reset();
T::regs().events_phyend.reset();
// NOTE to avoid errata 204 (see rev1 v1.4) we do TX_IDLE -> DISABLED -> RX_IDLE
let disable = match self.state() {
RadioState::DISABLED => false,
RadioState::RX_IDLE => self.needs_enable,
_ => true,
};
if disable {
self.disable();
}
self.needs_enable = false;
}
/// Prepare radio for receiving a packet
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();
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()),
}
}
/// Cancel receiving packet
fn receive_cancel() {
let r = T::regs();
r.shorts.reset();
r.tasks_stop.write(|w| w.tasks_stop().set_bit());
loop {
match 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<(), Error> {
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(Error::CrcFailed(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();
// 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);
}
+108
View File
@@ -0,0 +1,108 @@
//! 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;
#[cfg(any(
feature = "nrf52811",
feature = "nrf52820",
feature = "nrf52833",
feature = "nrf52840",
feature = "_nrf5340-net"
))]
/// IEEE 802.15.4
pub mod ieee802154;
use core::marker::PhantomData;
use embassy_sync::waitqueue::AtomicWaker;
use pac::radio::state::STATE_A as RadioState;
pub use pac::radio::txpower::TXPOWER_A as TxPower;
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,
/// CRC check failed
CrcFailed(u16),
}
/// Interrupt handler
pub struct InterruptHandler<T: Instance> {
_phantom: PhantomData<T>,
}
impl<T: Instance> interrupt::typelevel::Handler<T::Interrupt> for InterruptHandler<T> {
unsafe fn on_interrupt() {
let r = T::regs();
let s = T::state();
// clear all interrupts
r.intenclr.write(|w| w.bits(0xffff_ffff));
s.event_waker.wake();
}
}
pub(crate) struct State {
/// end packet transmission or reception
event_waker: AtomicWaker,
}
impl State {
pub(crate) const fn new() -> Self {
Self {
event_waker: AtomicWaker::new(),
}
}
}
pub(crate) trait SealedInstance {
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::SealedInstance for peripherals::$type {
fn regs() -> &'static pac::radio::RegisterBlock {
unsafe { &*pac::$pac_type::ptr() }
}
fn state() -> &'static crate::radio::State {
static STATE: crate::radio::State = crate::radio::State::new();
&STATE
}
}
impl crate::radio::Instance for peripherals::$type {
type Interrupt = crate::interrupt::typelevel::$irq;
}
};
}
/// Radio peripheral instance.
#[allow(private_bounds)]
pub trait Instance: Peripheral<P = Self> + SealedInstance + 'static + Send {
/// Interrupt for this peripheral.
type Interrupt: interrupt::typelevel::Interrupt;
}
/// Get the state of the radio
pub(crate) fn state(radio: &pac::radio::RegisterBlock) -> RadioState {
match radio.state.read().state().variant() {
Some(state) => state,
None => unreachable!(),
}
}