From 847b8be81480b249a2e8b1ff502e6188d2dadf04 Mon Sep 17 00:00:00 2001 From: "Guilherme S. Salustiano" Date: Wed, 7 Feb 2024 15:25:07 +0100 Subject: [PATCH 01/13] feat/implement ble radio on nrf --- embassy-nrf/Cargo.toml | 5 + embassy-nrf/src/chips/nrf52805.rs | 6 + embassy-nrf/src/chips/nrf52810.rs | 6 + embassy-nrf/src/chips/nrf52811.rs | 6 + embassy-nrf/src/chips/nrf52820.rs | 6 + embassy-nrf/src/chips/nrf52832.rs | 6 + embassy-nrf/src/chips/nrf52833.rs | 6 + embassy-nrf/src/chips/nrf52840.rs | 6 + embassy-nrf/src/chips/nrf5340_net.rs | 6 + embassy-nrf/src/lib.rs | 6 + embassy-nrf/src/radio/ble.rs | 432 ++++++++++++++++++ embassy-nrf/src/radio/mod.rs | 89 ++++ examples/nrf52840/Cargo.toml | 8 +- .../nrf52840/src/bin/radio_ble_advertising.rs | 42 ++ 14 files changed, 628 insertions(+), 2 deletions(-) create mode 100644 embassy-nrf/src/radio/ble.rs create mode 100644 embassy-nrf/src/radio/mod.rs create mode 100644 examples/nrf52840/src/bin/radio_ble_advertising.rs diff --git a/embassy-nrf/Cargo.toml b/embassy-nrf/Cargo.toml index 7e161df9..dcdc7f31 100644 --- a/embassy-nrf/Cargo.toml +++ b/embassy-nrf/Cargo.toml @@ -57,6 +57,9 @@ unstable-pac = [] ## Enable GPIO tasks and events gpiote = [] +## Enable radio driver +radio = ["dep:jewel"] + ## Use RTC1 as the time driver for `embassy-time`, with a tick rate of 32.768khz time-driver-rtc1 = ["_time-driver"] @@ -150,6 +153,8 @@ embedded-storage-async = "0.4.0" cfg-if = "1.0.0" document-features = "0.2.7" +jewel = { version = "0.1.0", git = "https://github.com/jewel-rs/jewel", optional = true } + nrf51-pac = { version = "0.12.0", optional = true } nrf52805-pac = { version = "0.12.0", optional = true } nrf52810-pac = { version = "0.12.0", optional = true } diff --git a/embassy-nrf/src/chips/nrf52805.rs b/embassy-nrf/src/chips/nrf52805.rs index 624d6613..b97c85f9 100644 --- a/embassy-nrf/src/chips/nrf52805.rs +++ b/embassy-nrf/src/chips/nrf52805.rs @@ -129,6 +129,9 @@ embassy_hal_internal::peripherals! { // QDEC QDEC, + + // RADIO + RADIO, } impl_uarte!(UARTE0, UARTE0, UARTE0_UART0); @@ -209,6 +212,9 @@ impl_ppi_channel!(PPI_CH31, 31 => static); impl_saadc_input!(P0_04, ANALOG_INPUT2); impl_saadc_input!(P0_05, ANALOG_INPUT3); +#[cfg(feature = "radio")] +impl_radio!(RADIO, RADIO, RADIO); + embassy_hal_internal::interrupt_mod!( POWER_CLOCK, RADIO, diff --git a/embassy-nrf/src/chips/nrf52810.rs b/embassy-nrf/src/chips/nrf52810.rs index 002feab3..03548d03 100644 --- a/embassy-nrf/src/chips/nrf52810.rs +++ b/embassy-nrf/src/chips/nrf52810.rs @@ -135,6 +135,9 @@ embassy_hal_internal::peripherals! { // PDM PDM, + + // Radio + RADIO, } impl_uarte!(UARTE0, UARTE0, UARTE0_UART0); @@ -235,6 +238,9 @@ impl_saadc_input!(P0_29, ANALOG_INPUT5); impl_saadc_input!(P0_30, ANALOG_INPUT6); impl_saadc_input!(P0_31, ANALOG_INPUT7); +#[cfg(feature = "radio")] +impl_radio!(RADIO, RADIO, RADIO); + embassy_hal_internal::interrupt_mod!( POWER_CLOCK, RADIO, diff --git a/embassy-nrf/src/chips/nrf52811.rs b/embassy-nrf/src/chips/nrf52811.rs index 5952907f..992fbd12 100644 --- a/embassy-nrf/src/chips/nrf52811.rs +++ b/embassy-nrf/src/chips/nrf52811.rs @@ -135,6 +135,9 @@ embassy_hal_internal::peripherals! { // PDM PDM, + + // Radio + RADIO, } impl_uarte!(UARTE0, UARTE0, UARTE0_UART0); @@ -237,6 +240,9 @@ impl_saadc_input!(P0_29, ANALOG_INPUT5); impl_saadc_input!(P0_30, ANALOG_INPUT6); impl_saadc_input!(P0_31, ANALOG_INPUT7); +#[cfg(feature = "radio")] +impl_radio!(RADIO, RADIO, RADIO); + embassy_hal_internal::interrupt_mod!( POWER_CLOCK, RADIO, diff --git a/embassy-nrf/src/chips/nrf52820.rs b/embassy-nrf/src/chips/nrf52820.rs index c2f792cb..f241f4ea 100644 --- a/embassy-nrf/src/chips/nrf52820.rs +++ b/embassy-nrf/src/chips/nrf52820.rs @@ -130,6 +130,9 @@ embassy_hal_internal::peripherals! { // QDEC QDEC, + + // Radio + RADIO, } impl_usb!(USBD, USBD, USBD); @@ -224,6 +227,9 @@ impl_ppi_channel!(PPI_CH29, 29 => static); impl_ppi_channel!(PPI_CH30, 30 => static); impl_ppi_channel!(PPI_CH31, 31 => static); +#[cfg(feature = "radio")] +impl_radio!(RADIO, RADIO, RADIO); + embassy_hal_internal::interrupt_mod!( POWER_CLOCK, RADIO, diff --git a/embassy-nrf/src/chips/nrf52832.rs b/embassy-nrf/src/chips/nrf52832.rs index 65d52364..6bbdd9a6 100644 --- a/embassy-nrf/src/chips/nrf52832.rs +++ b/embassy-nrf/src/chips/nrf52832.rs @@ -150,6 +150,9 @@ embassy_hal_internal::peripherals! { // PDM PDM, + + // Radio + RADIO, } impl_uarte!(UARTE0, UARTE0, UARTE0_UART0); @@ -264,6 +267,9 @@ impl_saadc_input!(P0_31, ANALOG_INPUT7); impl_i2s!(I2S, I2S, I2S); +#[cfg(feature = "radio")] +impl_radio!(RADIO, RADIO, RADIO); + embassy_hal_internal::interrupt_mod!( POWER_CLOCK, RADIO, diff --git a/embassy-nrf/src/chips/nrf52833.rs b/embassy-nrf/src/chips/nrf52833.rs index 7c9b66d6..e137e4dc 100644 --- a/embassy-nrf/src/chips/nrf52833.rs +++ b/embassy-nrf/src/chips/nrf52833.rs @@ -170,6 +170,9 @@ embassy_hal_internal::peripherals! { // I2S I2S, + + // Radio + RADIO, } impl_usb!(USBD, USBD, USBD); @@ -306,6 +309,9 @@ impl_saadc_input!(P0_31, ANALOG_INPUT7); impl_i2s!(I2S, I2S, I2S); +#[cfg(feature = "radio")] +impl_radio!(RADIO, RADIO, RADIO); + embassy_hal_internal::interrupt_mod!( POWER_CLOCK, RADIO, diff --git a/embassy-nrf/src/chips/nrf52840.rs b/embassy-nrf/src/chips/nrf52840.rs index 51c55cd4..2d805f87 100644 --- a/embassy-nrf/src/chips/nrf52840.rs +++ b/embassy-nrf/src/chips/nrf52840.rs @@ -173,6 +173,9 @@ embassy_hal_internal::peripherals! { // I2S I2S, + + // Radio + RADIO, } impl_usb!(USBD, USBD, USBD); @@ -311,6 +314,9 @@ impl_saadc_input!(P0_31, ANALOG_INPUT7); impl_i2s!(I2S, I2S, I2S); +#[cfg(feature = "radio")] +impl_radio!(RADIO, RADIO, RADIO); + embassy_hal_internal::interrupt_mod!( POWER_CLOCK, RADIO, diff --git a/embassy-nrf/src/chips/nrf5340_net.rs b/embassy-nrf/src/chips/nrf5340_net.rs index a7cf8287..3248bde5 100644 --- a/embassy-nrf/src/chips/nrf5340_net.rs +++ b/embassy-nrf/src/chips/nrf5340_net.rs @@ -248,6 +248,9 @@ embassy_hal_internal::peripherals! { P1_13, P1_14, P1_15, + + // Radio + RADIO, } impl_uarte!(SERIAL0, UARTE0, SERIAL0); @@ -345,6 +348,9 @@ impl_ppi_channel!(PPI_CH29, 29 => configurable); impl_ppi_channel!(PPI_CH30, 30 => configurable); impl_ppi_channel!(PPI_CH31, 31 => configurable); +#[cfg(feature = "radio")] +impl_radio!(RADIO, RADIO, RADIO); + embassy_hal_internal::interrupt_mod!( CLOCK_POWER, RADIO, diff --git a/embassy-nrf/src/lib.rs b/embassy-nrf/src/lib.rs index 358a7cc2..961928d1 100644 --- a/embassy-nrf/src/lib.rs +++ b/embassy-nrf/src/lib.rs @@ -45,6 +45,12 @@ pub mod buffered_uarte; pub mod gpio; #[cfg(feature = "gpiote")] pub mod gpiote; + +#[cfg(feature = "radio")] +pub mod radio; +#[cfg(all(feature = "radio", feature = "_nrf9160"))] +compile_error!("feature `radio` is not valid for nRF91 series chips."); + #[cfg(any(feature = "nrf52832", feature = "nrf52833", feature = "nrf52840"))] pub mod i2s; pub mod nvmc; diff --git a/embassy-nrf/src/radio/ble.rs b/embassy-nrf/src/radio/ble.rs new file mode 100644 index 00000000..a5d9f447 --- /dev/null +++ b/embassy-nrf/src/radio/ble.rs @@ -0,0 +1,432 @@ +//! Radio driver implementation focused on Bluetooth Low-Energy transmission. +//! +//! The radio can calculate the CRC, perform data whitening, +//! automatically send the right preamble. +//! Most of the configuration is done automatically when you choose the mode and this driver. +//! +//! Some configuration can just be done when de device is disabled, +//! and the configuration varies depending if is a transmitter or a receiver. +//! Because of that we have a state machine to keep track of the state of the radio. +//! The Radio is the disable radio which configure the common parameters between +//! the bluetooth protocols, like the package format, the CRC and the whitening. +//! The TxRadio radio enable and configured as a transmitter with the specific parameters. + +use core::future::poll_fn; +use core::sync::atomic::{compiler_fence, Ordering}; +use core::task::Poll; + +use embassy_hal_internal::drop::OnDrop; +use embassy_hal_internal::{into_ref, PeripheralRef}; +use jewel::phy::{ + AdvertisingChannel, Channel, ChannelTrait, HeaderSize, Mode, Radio as BleRadio, ADV_ADDRESS, ADV_CRC_INIT, + CRC_POLY, MAX_PDU_LENGTH, +}; +use pac::radio::mode::MODE_A as PacMode; +use pac::radio::pcnf0::PLEN_A as PreambleLength; +// Re-export SVD variants to allow user to directly set values. +pub use pac::radio::{state::STATE_A as RadioState, txpower::TXPOWER_A as TxPower}; + +use crate::interrupt::typelevel::Interrupt; +use crate::radio::*; +use crate::util::slice_in_ram_or; + +/// UART error. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[non_exhaustive] +pub enum Error { + /// Buffer was too long. + BufferTooLong, + /// Buffer was to short. + BufferTooShort, + /// The buffer is not in data RAM. It's most likely in flash, and nRF's DMA cannot access flash. + BufferNotInRAM, +} + +/// Radio driver. +pub struct Radio<'d, T: Instance> { + _p: PeripheralRef<'d, T>, +} + +impl<'d, T: Instance> Radio<'d, T> { + /// Create a new radio driver. + pub fn new( + radio: impl Peripheral

+ 'd, + _irq: impl interrupt::typelevel::Binding> + 'd, + ) -> Self { + // From 5.4.1 of the nRF52840 Product Specification: + // > The HFXO must be running to use the RADIO or the calibration mechanism associated with the 32.768 kHz RC oscillator. + // Currently the jewel crate don't implement the calibration mechanism, so we need to ensure that the HFXO is running + utils::check_xtal(); + + into_ref!(radio); + + let r = T::regs(); + + r.pcnf1.write(|w| unsafe { + // It is 0 bytes long in a standard BLE packet + w.statlen() + .bits(0) + // MaxLen configures the maximum packet payload plus add-on size in + // number of bytes that can be transmitted or received by the RADIO. This feature can be used to ensure + // that the RADIO does not overwrite, or read beyond, the RAM assigned to the packet payload. This means + // that if the packet payload length defined by PCNF1.STATLEN and the LENGTH field in the packet specifies a + // packet larger than MAXLEN, the payload will be truncated at MAXLEN + // + // To simplify the implementation, I'm setting the max length to the maximum value + // and I'm using only the length field to truncate the payload + .maxlen() + .bits(255) + // Configure the length of the address field in the packet + // The prefix after the address fields is always appended, so is always 1 byte less than the size of the address + // The base address is truncated from the least significant byte if the BALEN is less than 4 + // + // BLE address is always 4 bytes long + .balen() + .bits(3) // 3 bytes base address (+ 1 prefix); + // Configure the endianess + // For BLE is always little endian (LSB first) + .endian() + .little() + // Data whitening is used to avoid long sequences of zeros or + // ones, e.g., 0b0000000 or 0b1111111, in the data bit stream. + // The whitener and de-whitener are defined the same way, + // using a 7-bit linear feedback shift register with the + // polynomial x7 + x4 + 1. + // + // In BLE Whitening shall be applied on the PDU and CRC of all + // Link Layer packets and is performed after the CRC generation + // in the transmitter. No other parts of the packets are whitened. + // De-whitening is performed before the CRC checking in the receiver + // Before whitening or de-whitening, the shift register should be + // initialized based on the channel index. + .whiteen() + .set_bit() // Enable whitening + }); + + // Configure CRC + r.crccnf.write(|w| { + // In BLE the CRC shall be calculated on the PDU of all Link Layer + // packets (even if the packet is encrypted). + // So here we skip the address field + w.skipaddr() + .skip() + // In BLE 24-bit CRC = 3 bytes + .len() + .three() + }); + + r.crcpoly.write(|w| unsafe { + // Configure the CRC polynomial + // Each term in the CRC polynomial is mapped to a bit in this + // register which index corresponds to the term's exponent. + // The least significant term/bit is hard-wired internally to + // 1, and bit number 0 of the register content is ignored by + // the hardware. The following example is for an 8 bit CRC + // polynomial: x8 + x7 + x3 + x2 + 1 = 1 1000 1101 . + w.crcpoly().bits(CRC_POLY & 0xFFFFFF) + }); + // The CRC initial value varies depending of the PDU type + + // Ch map between 2400 MHZ .. 2500 MHz + // All modes use this range + r.frequency.write(|w| w.map().default()); + + // Configure shortcuts to simplify and speed up sending and receiving packets. + r.shorts.write(|w| { + // start transmission/recv immediately after ramp-up + // disable radio when transmission/recv is done + w.ready_start().enabled().end_disable().enabled() + }); + + // Enable NVIC interrupt + T::Interrupt::unpend(); + unsafe { T::Interrupt::enable() }; + + let mut radio = Self { _p: radio }; + + // set defaults + radio.set_mode(Mode::Ble1mbit); + radio.set_tx_power(0); + radio.set_header_size(HeaderSize::TwoBytes); + radio.set_access_address(ADV_ADDRESS); + radio.set_crc_init(ADV_CRC_INIT); + radio.set_channel(AdvertisingChannel::Ch39.into()); + + radio + } + + #[allow(dead_code)] + fn trace_state(&self) { + let r = T::regs(); + + match r.state.read().state().variant().unwrap() { + RadioState::DISABLED => trace!("radio:state:DISABLED"), + RadioState::RX_RU => trace!("radio:state:RX_RU"), + RadioState::RX_IDLE => trace!("radio:state:RX_IDLE"), + RadioState::RX => trace!("radio:state:RX"), + RadioState::RX_DISABLE => trace!("radio:state:RX_DISABLE"), + RadioState::TX_RU => trace!("radio:state:TX_RU"), + RadioState::TX_IDLE => trace!("radio:state:TX_IDLE"), + RadioState::TX => trace!("radio:state:TX"), + RadioState::TX_DISABLE => trace!("radio:state:TX_DISABLE"), + } + } + + async fn trigger_and_wait_end(&mut self, trigger: impl FnOnce() -> ()) { + //self.trace_state(); + + let r = T::regs(); + let s = T::state(); + + // If the Future is dropped before the end of the transmission + // we need to disable the interrupt and stop the transmission + // to keep the state consistent + let drop = OnDrop::new(|| { + trace!("radio drop: stopping"); + + r.intenclr.write(|w| w.end().clear()); + r.events_end.reset(); + + r.tasks_stop.write(|w| w.tasks_stop().set_bit()); + + // The docs don't explicitly mention any event to acknowledge the stop task + // So I guess it's the same as end + while r.events_end.read().events_end().bit_is_clear() {} + + trace!("radio drop: stopped"); + }); + + /* Config interrupt */ + // trace!("radio:enable interrupt"); + // Clear some remnant side-effects (I'm unsure if this is needed) + r.events_end.reset(); + + // Enable interrupt + r.intenset.write(|w| w.end().set()); + + compiler_fence(Ordering::SeqCst); + + // Trigger the transmission + trigger(); + // self.trace_state(); + + // On poll check if interrupt happen + poll_fn(|cx| { + s.end_waker.register(cx.waker()); + if r.events_end.read().events_end().bit_is_set() { + // trace!("radio:end"); + return core::task::Poll::Ready(()); + } + Poll::Pending + }) + .await; + + compiler_fence(Ordering::SeqCst); + r.events_disabled.reset(); // ACK + + // Everthing ends fine, so we can disable the drop + drop.defuse(); + } + + /// Disable the radio. + fn disable(&mut self) { + let r = T::regs(); + + compiler_fence(Ordering::SeqCst); + // If is already disabled, do nothing + if !r.state.read().state().is_disabled() { + trace!("radio:disable"); + // Trigger the disable task + r.tasks_disable.write(|w| w.tasks_disable().set_bit()); + + // Wait until the radio is disabled + while r.events_disabled.read().events_disabled().bit_is_clear() {} + + compiler_fence(Ordering::SeqCst); + + // Acknowledge it + r.events_disabled.reset(); + } + } +} + +impl<'d, T: Instance> BleRadio for Radio<'d, T> { + type Error = Error; + + fn set_mode(&mut self, mode: Mode) { + let r = T::regs(); + r.mode.write(|w| { + w.mode().variant(match mode { + Mode::Ble1mbit => PacMode::BLE_1MBIT, + //Mode::Ble2mbit => PacMode::BLE_2MBIT, + }) + }); + + r.pcnf0.write(|w| { + w.plen().variant(match mode { + Mode::Ble1mbit => PreambleLength::_8BIT, + //Mode::Ble2mbit => PreambleLength::_16BIT, + }) + }); + } + + fn set_header_size(&mut self, header_size: HeaderSize) { + let r = T::regs(); + + let s1len: u8 = match header_size { + HeaderSize::TwoBytes => 0, + HeaderSize::ThreeBytes => 8, // bits + }; + + r.pcnf0.write(|w| unsafe { + w + // Configure S0 to 1 byte length, this will represent the Data/Adv header flags + .s0len() + .set_bit() + // Configure the length (in bits) field to 1 byte length, this will represent the length of the payload + // and also be used to know how many bytes to read/write from/to the buffer + .lflen() + .bits(8) + // Configure the lengh (in bits) of bits in the S1 field. It could be used to represent the CTEInfo for data packages in BLE. + .s1len() + .bits(s1len) + }); + } + + fn set_channel(&mut self, channel: Channel) { + let r = T::regs(); + + r.frequency + .write(|w| unsafe { w.frequency().bits((channel.central_frequency() - 2400) as u8) }); + r.datawhiteiv + .write(|w| unsafe { w.datawhiteiv().bits(channel.whitening_init()) }); + } + + fn set_access_address(&mut self, access_address: u32) { + let r = T::regs(); + + // Configure logical address + // The byte ordering on air is always least significant byte first for the address + // So for the address 0xAA_BB_CC_DD, the address on air will be DD CC BB AA + // The package order is BASE, PREFIX so BASE=0xBB_CC_DD and PREFIX=0xAA + r.prefix0 + .write(|w| unsafe { w.ap0().bits((access_address >> 24) as u8) }); + + // The base address is truncated from the least significant byte (because the BALEN is less than 4) + // So we need to shift the address to the right + r.base0.write(|w| unsafe { w.bits(access_address << 8) }); + + // Don't match tx address + r.txaddress.write(|w| unsafe { w.txaddress().bits(0) }); + + // Match on logical address + // For what I understand, this config only filter the packets + // by the address, so only packages send to the previous address + // will finish the reception + r.rxaddresses.write(|w| { + w.addr0() + .enabled() + .addr1() + .enabled() + .addr2() + .enabled() + .addr3() + .enabled() + .addr4() + .enabled() + }); + } + + fn set_crc_init(&mut self, crc_init: u32) { + let r = T::regs(); + + r.crcinit.write(|w| unsafe { w.crcinit().bits(crc_init & 0xFFFFFF) }); + } + + fn set_tx_power(&mut self, power_db: i8) { + let r = T::regs(); + + let tx_power: TxPower = match power_db { + 8..=i8::MAX => TxPower::POS8D_BM, + 7 => TxPower::POS7D_BM, + 6 => TxPower::POS6D_BM, + 5 => TxPower::POS5D_BM, + 4 => TxPower::POS4D_BM, + 3 => TxPower::POS3D_BM, + 1..=2 => TxPower::POS2D_BM, + -3..=0 => TxPower::_0D_BM, + -7..=-4 => TxPower::NEG4D_BM, + -11..=-8 => TxPower::NEG8D_BM, + -15..=-12 => TxPower::NEG12D_BM, + -19..=-16 => TxPower::NEG16D_BM, + -29..=-20 => TxPower::NEG20D_BM, + -39..=-30 => TxPower::NEG30D_BM, + i8::MIN..=-40 => TxPower::NEG40D_BM, + }; + + r.txpower.write(|w| w.txpower().variant(tx_power)); + } + + fn set_buffer(&mut self, buffer: &[u8]) -> Result<(), Self::Error> { + // Because we are serializing the buffer, we should always have the buffer in RAM + slice_in_ram_or(buffer, Error::BufferNotInRAM)?; + + if buffer.len() > MAX_PDU_LENGTH { + return Err(Error::BufferTooLong); + } + + let r = T::regs(); + + // Here we are considering that the length of the packet is + // correctly set in the buffer, otherwise we will sending + // unowned regions of memory + let ptr = buffer.as_ptr(); + + // Configure the payload + r.packetptr.write(|w| unsafe { w.bits(ptr as u32) }); + + Ok(()) + } + + /// Send packet + async fn transmit(&mut self) { + let r = T::regs(); + + self.trigger_and_wait_end(move || { + // Initialize the transmission + // trace!("txen"); + r.tasks_txen.write(|w| w.tasks_txen().set_bit()); + }) + .await; + } + + /// Send packet + async fn receive(&mut self) { + let r = T::regs(); + + self.trigger_and_wait_end(move || { + // Initialize the transmission + // trace!("rxen"); + r.tasks_rxen.write(|w| w.tasks_rxen().set_bit()); + + // Await until ready + while r.events_ready.read().events_ready().bit_is_clear() {} + + compiler_fence(Ordering::SeqCst); + + // Acknowledge it + r.events_ready.reset(); + + // trace!("radio:start"); + r.tasks_start.write(|w| w.tasks_start().set_bit()); + }) + .await; + } +} + +impl<'d, T: Instance> Drop for Radio<'d, T> { + fn drop(&mut self) { + self.disable(); + } +} diff --git a/embassy-nrf/src/radio/mod.rs b/embassy-nrf/src/radio/mod.rs new file mode 100644 index 00000000..91cc2c0a --- /dev/null +++ b/embassy-nrf/src/radio/mod.rs @@ -0,0 +1,89 @@ +//! Integrated 2.4 GHz Radio +//! +//! The 2.4 GHz radio transceiver is compatible with multiple radio standards +//! such as 1Mbps, 2Mbps and Long Range Bluetooth Low Energy. + +#![macro_use] + +/// Bluetooth Low Energy Radio driver. +pub mod ble; + +use core::marker::PhantomData; + +use crate::{interrupt, pac, Peripheral}; + +/// Interrupt handler +pub struct InterruptHandler { + _phantom: PhantomData, +} + +impl interrupt::typelevel::Handler for InterruptHandler { + unsafe fn on_interrupt() { + let r = T::regs(); + let s = T::state(); + + if r.events_end.read().events_end().bit_is_set() { + s.end_waker.wake(); + r.intenclr.write(|w| w.end().clear()); + } + } +} + +pub(crate) mod utils { + use super::*; + + // Check if the HFCLK is XTAL is enabled + pub fn check_xtal() { + // safe: only reading the value + let is_xtal = unsafe { + let r = &*pac::CLOCK::ptr(); + r.hfclkstat.read().src().is_xtal() + }; + assert!(is_xtal, "HFCLK must be XTAL"); + } +} + +pub(crate) mod sealed { + use embassy_sync::waitqueue::AtomicWaker; + + pub struct State { + /// end packet transmission or reception + pub end_waker: AtomicWaker, + } + impl State { + pub const fn new() -> Self { + Self { + end_waker: AtomicWaker::new(), + } + } + } + + pub trait Instance { + fn regs() -> &'static crate::pac::radio::RegisterBlock; + fn state() -> &'static State; + } +} + +macro_rules! impl_radio { + ($type:ident, $pac_type:ident, $irq:ident) => { + impl crate::radio::sealed::Instance for peripherals::$type { + fn regs() -> &'static pac::radio::RegisterBlock { + unsafe { &*pac::$pac_type::ptr() } + } + + fn state() -> &'static crate::radio::sealed::State { + static STATE: crate::radio::sealed::State = crate::radio::sealed::State::new(); + &STATE + } + } + impl crate::radio::Instance for peripherals::$type { + type Interrupt = crate::interrupt::typelevel::$irq; + } + }; +} + +/// Radio peripheral instance. +pub trait Instance: Peripheral

+ sealed::Instance + 'static + Send { + /// Interrupt for this peripheral. + type Interrupt: interrupt::typelevel::Interrupt; +} diff --git a/examples/nrf52840/Cargo.toml b/examples/nrf52840/Cargo.toml index abb995be..0239583c 100644 --- a/examples/nrf52840/Cargo.toml +++ b/examples/nrf52840/Cargo.toml @@ -8,8 +8,8 @@ license = "MIT OR Apache-2.0" embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } embassy-sync = { version = "0.5.0", path = "../../embassy-sync", features = ["defmt"] } embassy-executor = { version = "0.5.0", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "executor-interrupt", "defmt", "integrated-timers"] } -embassy-time = { version = "0.3.0", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime"] } -embassy-nrf = { version = "0.1.0", path = "../../embassy-nrf", features = ["defmt", "nrf52840", "time-driver-rtc1", "gpiote", "unstable-pac", "time"] } +embassy-time = { version = "0.3.0", features = ["defmt", "defmt-timestamp-uptime"] } +embassy-nrf = { version = "0.1.0", path = "../../embassy-nrf", features = ["defmt", "nrf52840", "time-driver-rtc1", "gpiote", "unstable-pac", "time", "radio"]} embassy-net = { version = "0.4.0", path = "../../embassy-net", features = ["defmt", "tcp", "dhcpv4", "medium-ethernet"] } embassy-usb = { version = "0.1.0", path = "../../embassy-usb", features = ["defmt"] } embedded-io = { version = "0.6.0", features = ["defmt-03"] } @@ -35,6 +35,10 @@ embedded-hal-async = { version = "1.0" } embedded-hal-bus = { version = "0.1", features = ["async"] } num-integer = { version = "0.1.45", default-features = false } microfft = "0.5.0" +jewel = { version = "0.1.0", git = "https://github.com/jewel-rs/jewel"} + +[patch.crates-io] +embassy-time = { version = "0.3.0", path = "../../embassy-time"} [profile.release] debug = 2 diff --git a/examples/nrf52840/src/bin/radio_ble_advertising.rs b/examples/nrf52840/src/bin/radio_ble_advertising.rs new file mode 100644 index 00000000..8898c241 --- /dev/null +++ b/examples/nrf52840/src/bin/radio_ble_advertising.rs @@ -0,0 +1,42 @@ +#![no_std] +#![no_main] + +use defmt::{info, unwrap}; +use embassy_executor::Spawner; +use embassy_nrf::{bind_interrupts, peripherals, radio}; +use embassy_time::Timer; +use jewel::phy::Radio; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + RADIO => radio::InterruptHandler; +}); + +// For a high-level API look on jewel examples +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let mut config = embassy_nrf::config::Config::default(); + config.hfclk_source = embassy_nrf::config::HfclkSource::ExternalXtal; + let p = embassy_nrf::init(config); + + info!("Starting BLE radio"); + let mut radio = radio::ble::Radio::new(p.RADIO, Irqs); + + let pdu = [ + 0x46u8, // ADV_NONCONN_IND, Random address, + 0x18, // Length of payload + 0x27, 0xdc, 0xd0, 0xe8, 0xe1, 0xff, // Adress + 0x02, 0x01, 0x06, // Flags + 0x03, 0x03, 0x09, 0x18, // Complete list of 16-bit UUIDs available + 0x0A, 0x09, // Length, Type: Device name + b'H', b'e', b'l', b'l', b'o', b'R', b'u', b's', b't', + ]; + + unwrap!(radio.set_buffer(pdu.as_ref())); + + loop { + info!("Sending packet"); + radio.transmit().await; + Timer::after_millis(500).await; + } +} From d408056a66be2183dbcde6840e69e53beb4a764b Mon Sep 17 00:00:00 2001 From: "Guilherme S. Salustiano" Date: Wed, 7 Feb 2024 17:16:46 +0100 Subject: [PATCH 02/13] remove default on radio --- embassy-nrf/src/radio/ble.rs | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/embassy-nrf/src/radio/ble.rs b/embassy-nrf/src/radio/ble.rs index a5d9f447..64428fff 100644 --- a/embassy-nrf/src/radio/ble.rs +++ b/embassy-nrf/src/radio/ble.rs @@ -17,10 +17,7 @@ use core::task::Poll; use embassy_hal_internal::drop::OnDrop; use embassy_hal_internal::{into_ref, PeripheralRef}; -use jewel::phy::{ - AdvertisingChannel, Channel, ChannelTrait, HeaderSize, Mode, Radio as BleRadio, ADV_ADDRESS, ADV_CRC_INIT, - CRC_POLY, MAX_PDU_LENGTH, -}; +use jewel::phy::{Channel, ChannelTrait, HeaderSize, Mode, Radio as BleRadio, CRC_POLY, MAX_PDU_LENGTH}; use pac::radio::mode::MODE_A as PacMode; use pac::radio::pcnf0::PLEN_A as PreambleLength; // Re-export SVD variants to allow user to directly set values. @@ -145,14 +142,6 @@ impl<'d, T: Instance> Radio<'d, T> { let mut radio = Self { _p: radio }; - // set defaults - radio.set_mode(Mode::Ble1mbit); - radio.set_tx_power(0); - radio.set_header_size(HeaderSize::TwoBytes); - radio.set_access_address(ADV_ADDRESS); - radio.set_crc_init(ADV_CRC_INIT); - radio.set_channel(AdvertisingChannel::Ch39.into()); - radio } From 5f1b80d40b6d2ae1636fa46e8e5334c03adeb67d Mon Sep 17 00:00:00 2001 From: "Guilherme S. Salustiano" Date: Wed, 7 Feb 2024 18:04:29 +0100 Subject: [PATCH 03/13] remove jewel dependency --- embassy-nrf/Cargo.toml | 4 +- embassy-nrf/src/radio/ble.rs | 160 ++++++++++-------- embassy-nrf/src/radio/mod.rs | 14 -- examples/nrf52840/Cargo.toml | 4 - .../nrf52840/src/bin/radio_ble_advertising.rs | 42 ----- 5 files changed, 86 insertions(+), 138 deletions(-) delete mode 100644 examples/nrf52840/src/bin/radio_ble_advertising.rs diff --git a/embassy-nrf/Cargo.toml b/embassy-nrf/Cargo.toml index dcdc7f31..9f35bd24 100644 --- a/embassy-nrf/Cargo.toml +++ b/embassy-nrf/Cargo.toml @@ -58,7 +58,7 @@ unstable-pac = [] gpiote = [] ## Enable radio driver -radio = ["dep:jewel"] +radio = [] ## Use RTC1 as the time driver for `embassy-time`, with a tick rate of 32.768khz time-driver-rtc1 = ["_time-driver"] @@ -153,8 +153,6 @@ embedded-storage-async = "0.4.0" cfg-if = "1.0.0" document-features = "0.2.7" -jewel = { version = "0.1.0", git = "https://github.com/jewel-rs/jewel", optional = true } - nrf51-pac = { version = "0.12.0", optional = true } nrf52805-pac = { version = "0.12.0", optional = true } nrf52810-pac = { version = "0.12.0", optional = true } diff --git a/embassy-nrf/src/radio/ble.rs b/embassy-nrf/src/radio/ble.rs index 64428fff..def94179 100644 --- a/embassy-nrf/src/radio/ble.rs +++ b/embassy-nrf/src/radio/ble.rs @@ -17,11 +17,10 @@ use core::task::Poll; use embassy_hal_internal::drop::OnDrop; use embassy_hal_internal::{into_ref, PeripheralRef}; -use jewel::phy::{Channel, ChannelTrait, HeaderSize, Mode, Radio as BleRadio, CRC_POLY, MAX_PDU_LENGTH}; -use pac::radio::mode::MODE_A as PacMode; +pub use pac::radio::mode::MODE_A as Mode; use pac::radio::pcnf0::PLEN_A as PreambleLength; -// Re-export SVD variants to allow user to directly set values. -pub use pac::radio::{state::STATE_A as RadioState, txpower::TXPOWER_A as TxPower}; +use pac::radio::state::STATE_A as RadioState; +pub use pac::radio::txpower::TXPOWER_A as TxPower; use crate::interrupt::typelevel::Interrupt; use crate::radio::*; @@ -51,11 +50,6 @@ impl<'d, T: Instance> Radio<'d, T> { radio: impl Peripheral

+ 'd, _irq: impl interrupt::typelevel::Binding> + 'd, ) -> Self { - // From 5.4.1 of the nRF52840 Product Specification: - // > The HFXO must be running to use the RADIO or the calibration mechanism associated with the 32.768 kHz RC oscillator. - // Currently the jewel crate don't implement the calibration mechanism, so we need to ensure that the HFXO is running - utils::check_xtal(); - into_ref!(radio); let r = T::regs(); @@ -113,18 +107,6 @@ impl<'d, T: Instance> Radio<'d, T> { .three() }); - r.crcpoly.write(|w| unsafe { - // Configure the CRC polynomial - // Each term in the CRC polynomial is mapped to a bit in this - // register which index corresponds to the term's exponent. - // The least significant term/bit is hard-wired internally to - // 1, and bit number 0 of the register content is ignored by - // the hardware. The following example is for an 8 bit CRC - // polynomial: x8 + x7 + x3 + x2 + 1 = 1 1000 1101 . - w.crcpoly().bits(CRC_POLY & 0xFFFFFF) - }); - // The CRC initial value varies depending of the PDU type - // Ch map between 2400 MHZ .. 2500 MHz // All modes use this range r.frequency.write(|w| w.map().default()); @@ -140,9 +122,7 @@ impl<'d, T: Instance> Radio<'d, T> { T::Interrupt::unpend(); unsafe { T::Interrupt::enable() }; - let mut radio = Self { _p: radio }; - - radio + Self { _p: radio } } #[allow(dead_code)] @@ -186,7 +166,6 @@ impl<'d, T: Instance> Radio<'d, T> { trace!("radio drop: stopped"); }); - /* Config interrupt */ // trace!("radio:enable interrupt"); // Clear some remnant side-effects (I'm unsure if this is needed) r.events_end.reset(); @@ -238,34 +217,34 @@ impl<'d, T: Instance> Radio<'d, T> { r.events_disabled.reset(); } } -} -impl<'d, T: Instance> BleRadio for Radio<'d, T> { - type Error = Error; - - fn set_mode(&mut self, mode: Mode) { + /// Set the radio mode + /// + /// The radio must be disabled before calling this function + pub fn set_mode(&mut self, mode: Mode) { let r = T::regs(); - r.mode.write(|w| { - w.mode().variant(match mode { - Mode::Ble1mbit => PacMode::BLE_1MBIT, - //Mode::Ble2mbit => PacMode::BLE_2MBIT, - }) - }); + r.mode.write(|w| w.mode().variant(mode)); r.pcnf0.write(|w| { w.plen().variant(match mode { - Mode::Ble1mbit => PreambleLength::_8BIT, - //Mode::Ble2mbit => PreambleLength::_16BIT, + Mode::BLE_1MBIT => PreambleLength::_8BIT, + Mode::BLE_2MBIT => PreambleLength::_16BIT, + Mode::BLE_LR125KBIT | Mode::BLE_LR500KBIT => PreambleLength::LONG_RANGE, + _ => unimplemented!(), }) }); } - fn set_header_size(&mut self, header_size: HeaderSize) { + /// Set the header size changing the S1 field + /// + /// The radio must be disabled before calling this function + pub fn set_header_expansion(&mut self, use_s1_field: bool) { let r = T::regs(); - let s1len: u8 = match header_size { - HeaderSize::TwoBytes => 0, - HeaderSize::ThreeBytes => 8, // bits + // s1 len in bits + let s1len: u8 = match use_s1_field { + false => 0, + true => 8, }; r.pcnf0.write(|w| unsafe { @@ -283,16 +262,36 @@ impl<'d, T: Instance> BleRadio for Radio<'d, T> { }); } - fn set_channel(&mut self, channel: Channel) { + /// 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) { + 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 + pub fn set_frequency(&mut self, frequency: u32) { + assert!(2400 <= frequency && frequency <= 2500); let r = T::regs(); r.frequency - .write(|w| unsafe { w.frequency().bits((channel.central_frequency() - 2400) as u8) }); - r.datawhiteiv - .write(|w| unsafe { w.datawhiteiv().bits(channel.whitening_init()) }); + .write(|w| unsafe { w.frequency().bits((frequency - 2400) as u8) }); } - fn set_access_address(&mut self, access_address: u32) { + /// 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) { let r = T::regs(); // Configure logical address @@ -327,44 +326,55 @@ impl<'d, T: Instance> BleRadio for Radio<'d, T> { }); } - fn set_crc_init(&mut self, crc_init: u32) { + /// 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) { + 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) { let r = T::regs(); r.crcinit.write(|w| unsafe { w.crcinit().bits(crc_init & 0xFFFFFF) }); } - fn set_tx_power(&mut self, power_db: i8) { + /// Set the radio tx power + /// + /// The radio must be disabled before calling this function + pub fn set_tx_power(&mut self, tx_power: TxPower) { let r = T::regs(); - let tx_power: TxPower = match power_db { - 8..=i8::MAX => TxPower::POS8D_BM, - 7 => TxPower::POS7D_BM, - 6 => TxPower::POS6D_BM, - 5 => TxPower::POS5D_BM, - 4 => TxPower::POS4D_BM, - 3 => TxPower::POS3D_BM, - 1..=2 => TxPower::POS2D_BM, - -3..=0 => TxPower::_0D_BM, - -7..=-4 => TxPower::NEG4D_BM, - -11..=-8 => TxPower::NEG8D_BM, - -15..=-12 => TxPower::NEG12D_BM, - -19..=-16 => TxPower::NEG16D_BM, - -29..=-20 => TxPower::NEG20D_BM, - -39..=-30 => TxPower::NEG30D_BM, - i8::MIN..=-40 => TxPower::NEG40D_BM, - }; - r.txpower.write(|w| w.txpower().variant(tx_power)); } - fn set_buffer(&mut self, buffer: &[u8]) -> Result<(), Self::Error> { + /// 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. + pub fn set_buffer(&mut self, buffer: &[u8]) -> Result<(), Error> { // Because we are serializing the buffer, we should always have the buffer in RAM slice_in_ram_or(buffer, Error::BufferNotInRAM)?; - if buffer.len() > MAX_PDU_LENGTH { - return Err(Error::BufferTooLong); - } - let r = T::regs(); // Here we are considering that the length of the packet is @@ -379,7 +389,7 @@ impl<'d, T: Instance> BleRadio for Radio<'d, T> { } /// Send packet - async fn transmit(&mut self) { + pub async fn transmit(&mut self) { let r = T::regs(); self.trigger_and_wait_end(move || { @@ -390,8 +400,8 @@ impl<'d, T: Instance> BleRadio for Radio<'d, T> { .await; } - /// Send packet - async fn receive(&mut self) { + /// Receive packet + pub async fn receive(&mut self) { let r = T::regs(); self.trigger_and_wait_end(move || { diff --git a/embassy-nrf/src/radio/mod.rs b/embassy-nrf/src/radio/mod.rs index 91cc2c0a..03f967f8 100644 --- a/embassy-nrf/src/radio/mod.rs +++ b/embassy-nrf/src/radio/mod.rs @@ -29,20 +29,6 @@ impl interrupt::typelevel::Handler for InterruptHandl } } -pub(crate) mod utils { - use super::*; - - // Check if the HFCLK is XTAL is enabled - pub fn check_xtal() { - // safe: only reading the value - let is_xtal = unsafe { - let r = &*pac::CLOCK::ptr(); - r.hfclkstat.read().src().is_xtal() - }; - assert!(is_xtal, "HFCLK must be XTAL"); - } -} - pub(crate) mod sealed { use embassy_sync::waitqueue::AtomicWaker; diff --git a/examples/nrf52840/Cargo.toml b/examples/nrf52840/Cargo.toml index 0239583c..78dabe34 100644 --- a/examples/nrf52840/Cargo.toml +++ b/examples/nrf52840/Cargo.toml @@ -35,10 +35,6 @@ embedded-hal-async = { version = "1.0" } embedded-hal-bus = { version = "0.1", features = ["async"] } num-integer = { version = "0.1.45", default-features = false } microfft = "0.5.0" -jewel = { version = "0.1.0", git = "https://github.com/jewel-rs/jewel"} - -[patch.crates-io] -embassy-time = { version = "0.3.0", path = "../../embassy-time"} [profile.release] debug = 2 diff --git a/examples/nrf52840/src/bin/radio_ble_advertising.rs b/examples/nrf52840/src/bin/radio_ble_advertising.rs deleted file mode 100644 index 8898c241..00000000 --- a/examples/nrf52840/src/bin/radio_ble_advertising.rs +++ /dev/null @@ -1,42 +0,0 @@ -#![no_std] -#![no_main] - -use defmt::{info, unwrap}; -use embassy_executor::Spawner; -use embassy_nrf::{bind_interrupts, peripherals, radio}; -use embassy_time::Timer; -use jewel::phy::Radio; -use {defmt_rtt as _, panic_probe as _}; - -bind_interrupts!(struct Irqs { - RADIO => radio::InterruptHandler; -}); - -// For a high-level API look on jewel examples -#[embassy_executor::main] -async fn main(_spawner: Spawner) { - let mut config = embassy_nrf::config::Config::default(); - config.hfclk_source = embassy_nrf::config::HfclkSource::ExternalXtal; - let p = embassy_nrf::init(config); - - info!("Starting BLE radio"); - let mut radio = radio::ble::Radio::new(p.RADIO, Irqs); - - let pdu = [ - 0x46u8, // ADV_NONCONN_IND, Random address, - 0x18, // Length of payload - 0x27, 0xdc, 0xd0, 0xe8, 0xe1, 0xff, // Adress - 0x02, 0x01, 0x06, // Flags - 0x03, 0x03, 0x09, 0x18, // Complete list of 16-bit UUIDs available - 0x0A, 0x09, // Length, Type: Device name - b'H', b'e', b'l', b'l', b'o', b'R', b'u', b's', b't', - ]; - - unwrap!(radio.set_buffer(pdu.as_ref())); - - loop { - info!("Sending packet"); - radio.transmit().await; - Timer::after_millis(500).await; - } -} From add78943146cb929d51830c1f1c74c51dd3a5d57 Mon Sep 17 00:00:00 2001 From: "Guilherme S. Salustiano" Date: Wed, 7 Feb 2024 18:09:10 +0100 Subject: [PATCH 04/13] remove radio feature --- embassy-nrf/Cargo.toml | 3 --- embassy-nrf/src/chips/nrf52805.rs | 1 - embassy-nrf/src/chips/nrf52810.rs | 1 - embassy-nrf/src/chips/nrf52811.rs | 1 - embassy-nrf/src/chips/nrf52820.rs | 1 - embassy-nrf/src/chips/nrf52832.rs | 1 - embassy-nrf/src/chips/nrf52833.rs | 1 - embassy-nrf/src/chips/nrf52840.rs | 1 - embassy-nrf/src/chips/nrf5340_net.rs | 1 - embassy-nrf/src/lib.rs | 13 ++++++++++--- examples/nrf52840/Cargo.toml | 4 ++-- 11 files changed, 12 insertions(+), 16 deletions(-) diff --git a/embassy-nrf/Cargo.toml b/embassy-nrf/Cargo.toml index 9f35bd24..7e161df9 100644 --- a/embassy-nrf/Cargo.toml +++ b/embassy-nrf/Cargo.toml @@ -57,9 +57,6 @@ unstable-pac = [] ## Enable GPIO tasks and events gpiote = [] -## Enable radio driver -radio = [] - ## Use RTC1 as the time driver for `embassy-time`, with a tick rate of 32.768khz time-driver-rtc1 = ["_time-driver"] diff --git a/embassy-nrf/src/chips/nrf52805.rs b/embassy-nrf/src/chips/nrf52805.rs index b97c85f9..c172dd10 100644 --- a/embassy-nrf/src/chips/nrf52805.rs +++ b/embassy-nrf/src/chips/nrf52805.rs @@ -212,7 +212,6 @@ impl_ppi_channel!(PPI_CH31, 31 => static); impl_saadc_input!(P0_04, ANALOG_INPUT2); impl_saadc_input!(P0_05, ANALOG_INPUT3); -#[cfg(feature = "radio")] impl_radio!(RADIO, RADIO, RADIO); embassy_hal_internal::interrupt_mod!( diff --git a/embassy-nrf/src/chips/nrf52810.rs b/embassy-nrf/src/chips/nrf52810.rs index 03548d03..c607586d 100644 --- a/embassy-nrf/src/chips/nrf52810.rs +++ b/embassy-nrf/src/chips/nrf52810.rs @@ -238,7 +238,6 @@ impl_saadc_input!(P0_29, ANALOG_INPUT5); impl_saadc_input!(P0_30, ANALOG_INPUT6); impl_saadc_input!(P0_31, ANALOG_INPUT7); -#[cfg(feature = "radio")] impl_radio!(RADIO, RADIO, RADIO); embassy_hal_internal::interrupt_mod!( diff --git a/embassy-nrf/src/chips/nrf52811.rs b/embassy-nrf/src/chips/nrf52811.rs index 992fbd12..5f70365b 100644 --- a/embassy-nrf/src/chips/nrf52811.rs +++ b/embassy-nrf/src/chips/nrf52811.rs @@ -240,7 +240,6 @@ impl_saadc_input!(P0_29, ANALOG_INPUT5); impl_saadc_input!(P0_30, ANALOG_INPUT6); impl_saadc_input!(P0_31, ANALOG_INPUT7); -#[cfg(feature = "radio")] impl_radio!(RADIO, RADIO, RADIO); embassy_hal_internal::interrupt_mod!( diff --git a/embassy-nrf/src/chips/nrf52820.rs b/embassy-nrf/src/chips/nrf52820.rs index f241f4ea..82d09740 100644 --- a/embassy-nrf/src/chips/nrf52820.rs +++ b/embassy-nrf/src/chips/nrf52820.rs @@ -227,7 +227,6 @@ impl_ppi_channel!(PPI_CH29, 29 => static); impl_ppi_channel!(PPI_CH30, 30 => static); impl_ppi_channel!(PPI_CH31, 31 => static); -#[cfg(feature = "radio")] impl_radio!(RADIO, RADIO, RADIO); embassy_hal_internal::interrupt_mod!( diff --git a/embassy-nrf/src/chips/nrf52832.rs b/embassy-nrf/src/chips/nrf52832.rs index 6bbdd9a6..67b32fe5 100644 --- a/embassy-nrf/src/chips/nrf52832.rs +++ b/embassy-nrf/src/chips/nrf52832.rs @@ -267,7 +267,6 @@ impl_saadc_input!(P0_31, ANALOG_INPUT7); impl_i2s!(I2S, I2S, I2S); -#[cfg(feature = "radio")] impl_radio!(RADIO, RADIO, RADIO); embassy_hal_internal::interrupt_mod!( diff --git a/embassy-nrf/src/chips/nrf52833.rs b/embassy-nrf/src/chips/nrf52833.rs index e137e4dc..20f14e2d 100644 --- a/embassy-nrf/src/chips/nrf52833.rs +++ b/embassy-nrf/src/chips/nrf52833.rs @@ -309,7 +309,6 @@ impl_saadc_input!(P0_31, ANALOG_INPUT7); impl_i2s!(I2S, I2S, I2S); -#[cfg(feature = "radio")] impl_radio!(RADIO, RADIO, RADIO); embassy_hal_internal::interrupt_mod!( diff --git a/embassy-nrf/src/chips/nrf52840.rs b/embassy-nrf/src/chips/nrf52840.rs index 2d805f87..d3272b2e 100644 --- a/embassy-nrf/src/chips/nrf52840.rs +++ b/embassy-nrf/src/chips/nrf52840.rs @@ -314,7 +314,6 @@ impl_saadc_input!(P0_31, ANALOG_INPUT7); impl_i2s!(I2S, I2S, I2S); -#[cfg(feature = "radio")] impl_radio!(RADIO, RADIO, RADIO); embassy_hal_internal::interrupt_mod!( diff --git a/embassy-nrf/src/chips/nrf5340_net.rs b/embassy-nrf/src/chips/nrf5340_net.rs index 3248bde5..65e8f965 100644 --- a/embassy-nrf/src/chips/nrf5340_net.rs +++ b/embassy-nrf/src/chips/nrf5340_net.rs @@ -348,7 +348,6 @@ impl_ppi_channel!(PPI_CH29, 29 => configurable); impl_ppi_channel!(PPI_CH30, 30 => configurable); impl_ppi_channel!(PPI_CH31, 31 => configurable); -#[cfg(feature = "radio")] impl_radio!(RADIO, RADIO, RADIO); embassy_hal_internal::interrupt_mod!( diff --git a/embassy-nrf/src/lib.rs b/embassy-nrf/src/lib.rs index 961928d1..0f64f30f 100644 --- a/embassy-nrf/src/lib.rs +++ b/embassy-nrf/src/lib.rs @@ -46,10 +46,17 @@ pub mod gpio; #[cfg(feature = "gpiote")] pub mod gpiote; -#[cfg(feature = "radio")] +#[cfg(any( + feature = "nrf52805", + feature = "nrf52810", + feature = "nrf52811", + feature = "nrf52820", + feature = "nrf52832", + feature = "nrf52833", + feature = "nrf52840", + feature = "_nrf5340-net" +))] pub mod radio; -#[cfg(all(feature = "radio", feature = "_nrf9160"))] -compile_error!("feature `radio` is not valid for nRF91 series chips."); #[cfg(any(feature = "nrf52832", feature = "nrf52833", feature = "nrf52840"))] pub mod i2s; diff --git a/examples/nrf52840/Cargo.toml b/examples/nrf52840/Cargo.toml index 78dabe34..abb995be 100644 --- a/examples/nrf52840/Cargo.toml +++ b/examples/nrf52840/Cargo.toml @@ -8,8 +8,8 @@ license = "MIT OR Apache-2.0" embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } embassy-sync = { version = "0.5.0", path = "../../embassy-sync", features = ["defmt"] } embassy-executor = { version = "0.5.0", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "executor-interrupt", "defmt", "integrated-timers"] } -embassy-time = { version = "0.3.0", features = ["defmt", "defmt-timestamp-uptime"] } -embassy-nrf = { version = "0.1.0", path = "../../embassy-nrf", features = ["defmt", "nrf52840", "time-driver-rtc1", "gpiote", "unstable-pac", "time", "radio"]} +embassy-time = { version = "0.3.0", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime"] } +embassy-nrf = { version = "0.1.0", path = "../../embassy-nrf", features = ["defmt", "nrf52840", "time-driver-rtc1", "gpiote", "unstable-pac", "time"] } embassy-net = { version = "0.4.0", path = "../../embassy-net", features = ["defmt", "tcp", "dhcpv4", "medium-ethernet"] } embassy-usb = { version = "0.1.0", path = "../../embassy-usb", features = ["defmt"] } embedded-io = { version = "0.6.0", features = ["defmt-03"] } From 3ad45655ecea36d54f8024b5fbdad461dc07ed69 Mon Sep 17 00:00:00 2001 From: "Guilherme S. Salustiano" Date: Wed, 7 Feb 2024 18:22:47 +0100 Subject: [PATCH 05/13] ci rerun --- embassy-nrf/src/radio/ble.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/embassy-nrf/src/radio/ble.rs b/embassy-nrf/src/radio/ble.rs index def94179..369b49c5 100644 --- a/embassy-nrf/src/radio/ble.rs +++ b/embassy-nrf/src/radio/ble.rs @@ -35,7 +35,7 @@ pub enum Error { BufferTooLong, /// Buffer was to short. BufferTooShort, - /// The buffer is not in data RAM. It's most likely in flash, and nRF's DMA cannot access flash. + /// The buffer is not in data RAM. It is most likely in flash, and nRF's DMA cannot access flash. BufferNotInRAM, } From ea8bfb4f382bddda2ca5fbe5ce4c595cd0421bd1 Mon Sep 17 00:00:00 2001 From: "Guilherme S. Salustiano" Date: Wed, 7 Feb 2024 18:25:06 +0100 Subject: [PATCH 06/13] remove some supports --- embassy-nrf/src/lib.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/embassy-nrf/src/lib.rs b/embassy-nrf/src/lib.rs index 0f64f30f..76729323 100644 --- a/embassy-nrf/src/lib.rs +++ b/embassy-nrf/src/lib.rs @@ -47,11 +47,11 @@ pub mod gpio; pub mod gpiote; #[cfg(any( - feature = "nrf52805", - feature = "nrf52810", + //feature = "nrf52805", + //feature = "nrf52810", feature = "nrf52811", feature = "nrf52820", - feature = "nrf52832", + //feature = "nrf52832", feature = "nrf52833", feature = "nrf52840", feature = "_nrf5340-net" From 9527d1d934a46a50bfc8f26bd942c6f6e5c5b31b Mon Sep 17 00:00:00 2001 From: "Guilherme S. Salustiano" Date: Wed, 7 Feb 2024 18:31:05 +0100 Subject: [PATCH 07/13] remove radio implementation on chips not tested --- embassy-nrf/src/chips/nrf52805.rs | 5 ----- embassy-nrf/src/chips/nrf52810.rs | 5 ----- embassy-nrf/src/chips/nrf52811.rs | 5 ----- embassy-nrf/src/chips/nrf52820.rs | 5 ----- embassy-nrf/src/chips/nrf52832.rs | 5 ----- embassy-nrf/src/chips/nrf52833.rs | 5 ----- embassy-nrf/src/chips/nrf5340_net.rs | 5 ----- embassy-nrf/src/lib.rs | 11 +---------- 8 files changed, 1 insertion(+), 45 deletions(-) diff --git a/embassy-nrf/src/chips/nrf52805.rs b/embassy-nrf/src/chips/nrf52805.rs index c172dd10..624d6613 100644 --- a/embassy-nrf/src/chips/nrf52805.rs +++ b/embassy-nrf/src/chips/nrf52805.rs @@ -129,9 +129,6 @@ embassy_hal_internal::peripherals! { // QDEC QDEC, - - // RADIO - RADIO, } impl_uarte!(UARTE0, UARTE0, UARTE0_UART0); @@ -212,8 +209,6 @@ impl_ppi_channel!(PPI_CH31, 31 => static); impl_saadc_input!(P0_04, ANALOG_INPUT2); impl_saadc_input!(P0_05, ANALOG_INPUT3); -impl_radio!(RADIO, RADIO, RADIO); - embassy_hal_internal::interrupt_mod!( POWER_CLOCK, RADIO, diff --git a/embassy-nrf/src/chips/nrf52810.rs b/embassy-nrf/src/chips/nrf52810.rs index c607586d..002feab3 100644 --- a/embassy-nrf/src/chips/nrf52810.rs +++ b/embassy-nrf/src/chips/nrf52810.rs @@ -135,9 +135,6 @@ embassy_hal_internal::peripherals! { // PDM PDM, - - // Radio - RADIO, } impl_uarte!(UARTE0, UARTE0, UARTE0_UART0); @@ -238,8 +235,6 @@ impl_saadc_input!(P0_29, ANALOG_INPUT5); impl_saadc_input!(P0_30, ANALOG_INPUT6); impl_saadc_input!(P0_31, ANALOG_INPUT7); -impl_radio!(RADIO, RADIO, RADIO); - embassy_hal_internal::interrupt_mod!( POWER_CLOCK, RADIO, diff --git a/embassy-nrf/src/chips/nrf52811.rs b/embassy-nrf/src/chips/nrf52811.rs index 5f70365b..5952907f 100644 --- a/embassy-nrf/src/chips/nrf52811.rs +++ b/embassy-nrf/src/chips/nrf52811.rs @@ -135,9 +135,6 @@ embassy_hal_internal::peripherals! { // PDM PDM, - - // Radio - RADIO, } impl_uarte!(UARTE0, UARTE0, UARTE0_UART0); @@ -240,8 +237,6 @@ impl_saadc_input!(P0_29, ANALOG_INPUT5); impl_saadc_input!(P0_30, ANALOG_INPUT6); impl_saadc_input!(P0_31, ANALOG_INPUT7); -impl_radio!(RADIO, RADIO, RADIO); - embassy_hal_internal::interrupt_mod!( POWER_CLOCK, RADIO, diff --git a/embassy-nrf/src/chips/nrf52820.rs b/embassy-nrf/src/chips/nrf52820.rs index 82d09740..c2f792cb 100644 --- a/embassy-nrf/src/chips/nrf52820.rs +++ b/embassy-nrf/src/chips/nrf52820.rs @@ -130,9 +130,6 @@ embassy_hal_internal::peripherals! { // QDEC QDEC, - - // Radio - RADIO, } impl_usb!(USBD, USBD, USBD); @@ -227,8 +224,6 @@ impl_ppi_channel!(PPI_CH29, 29 => static); impl_ppi_channel!(PPI_CH30, 30 => static); impl_ppi_channel!(PPI_CH31, 31 => static); -impl_radio!(RADIO, RADIO, RADIO); - embassy_hal_internal::interrupt_mod!( POWER_CLOCK, RADIO, diff --git a/embassy-nrf/src/chips/nrf52832.rs b/embassy-nrf/src/chips/nrf52832.rs index 67b32fe5..65d52364 100644 --- a/embassy-nrf/src/chips/nrf52832.rs +++ b/embassy-nrf/src/chips/nrf52832.rs @@ -150,9 +150,6 @@ embassy_hal_internal::peripherals! { // PDM PDM, - - // Radio - RADIO, } impl_uarte!(UARTE0, UARTE0, UARTE0_UART0); @@ -267,8 +264,6 @@ 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/chips/nrf52833.rs b/embassy-nrf/src/chips/nrf52833.rs index 20f14e2d..7c9b66d6 100644 --- a/embassy-nrf/src/chips/nrf52833.rs +++ b/embassy-nrf/src/chips/nrf52833.rs @@ -170,9 +170,6 @@ embassy_hal_internal::peripherals! { // I2S I2S, - - // Radio - RADIO, } impl_usb!(USBD, USBD, USBD); @@ -309,8 +306,6 @@ 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/chips/nrf5340_net.rs b/embassy-nrf/src/chips/nrf5340_net.rs index 65e8f965..a7cf8287 100644 --- a/embassy-nrf/src/chips/nrf5340_net.rs +++ b/embassy-nrf/src/chips/nrf5340_net.rs @@ -248,9 +248,6 @@ embassy_hal_internal::peripherals! { P1_13, P1_14, P1_15, - - // Radio - RADIO, } impl_uarte!(SERIAL0, UARTE0, SERIAL0); @@ -348,8 +345,6 @@ impl_ppi_channel!(PPI_CH29, 29 => configurable); impl_ppi_channel!(PPI_CH30, 30 => configurable); impl_ppi_channel!(PPI_CH31, 31 => configurable); -impl_radio!(RADIO, RADIO, RADIO); - embassy_hal_internal::interrupt_mod!( CLOCK_POWER, RADIO, diff --git a/embassy-nrf/src/lib.rs b/embassy-nrf/src/lib.rs index 76729323..5dba6f97 100644 --- a/embassy-nrf/src/lib.rs +++ b/embassy-nrf/src/lib.rs @@ -46,16 +46,7 @@ pub mod gpio; #[cfg(feature = "gpiote")] pub mod gpiote; -#[cfg(any( - //feature = "nrf52805", - //feature = "nrf52810", - feature = "nrf52811", - feature = "nrf52820", - //feature = "nrf52832", - feature = "nrf52833", - feature = "nrf52840", - feature = "_nrf5340-net" -))] +#[cfg(any(feature = "nrf52840"))] // needs to be tested on other chips pub mod radio; #[cfg(any(feature = "nrf52832", feature = "nrf52833", feature = "nrf52840"))] From 802bdd1af86829482d6b847f73f02e6973bb68ea Mon Sep 17 00:00:00 2001 From: "Guilherme S. Salustiano" Date: Sat, 10 Feb 2024 08:09:08 +0100 Subject: [PATCH 08/13] remove first person comments and assert disable state when it's necessary --- embassy-nrf/src/radio/ble.rs | 201 ++++++++++++++++++----------------- 1 file changed, 106 insertions(+), 95 deletions(-) diff --git a/embassy-nrf/src/radio/ble.rs b/embassy-nrf/src/radio/ble.rs index 369b49c5..b0d37457 100644 --- a/embassy-nrf/src/radio/ble.rs +++ b/embassy-nrf/src/radio/ble.rs @@ -64,13 +64,13 @@ impl<'d, T: Instance> Radio<'d, T> { // that if the packet payload length defined by PCNF1.STATLEN and the LENGTH field in the packet specifies a // packet larger than MAXLEN, the payload will be truncated at MAXLEN // - // To simplify the implementation, I'm setting the max length to the maximum value - // and I'm using only the length field to truncate the payload + // 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 + // 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() @@ -92,14 +92,14 @@ impl<'d, T: Instance> Radio<'d, T> { // Before whitening or de-whitening, the shift register should be // initialized based on the channel index. .whiteen() - .set_bit() // Enable whitening + .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). - // So here we skip the address field + // It skips the address field w.skipaddr() .skip() // In BLE 24-bit CRC = 3 bytes @@ -125,11 +125,18 @@ impl<'d, T: Instance> Radio<'d, T> { Self { _p: radio } } + fn state(&self) -> RadioState { + match T::regs().state.read().state().variant() { + Ok(s) => s, + None => unreachable!(), + } + } + #[allow(dead_code)] fn trace_state(&self) { let r = T::regs(); - match r.state.read().state().variant().unwrap() { + match self.state() { RadioState::DISABLED => trace!("radio:state:DISABLED"), RadioState::RX_RU => trace!("radio:state:RX_RU"), RadioState::RX_IDLE => trace!("radio:state:RX_IDLE"), @@ -142,86 +149,12 @@ impl<'d, T: Instance> Radio<'d, T> { } } - async fn trigger_and_wait_end(&mut self, trigger: impl FnOnce() -> ()) { - //self.trace_state(); - - let r = T::regs(); - let s = T::state(); - - // If the Future is dropped before the end of the transmission - // we need to disable the interrupt and stop the transmission - // to keep the state consistent - let drop = OnDrop::new(|| { - trace!("radio drop: stopping"); - - r.intenclr.write(|w| w.end().clear()); - r.events_end.reset(); - - r.tasks_stop.write(|w| w.tasks_stop().set_bit()); - - // The docs don't explicitly mention any event to acknowledge the stop task - // So I guess it's the same as end - while r.events_end.read().events_end().bit_is_clear() {} - - trace!("radio drop: stopped"); - }); - - // trace!("radio:enable interrupt"); - // Clear some remnant side-effects (I'm unsure if this is needed) - r.events_end.reset(); - - // Enable interrupt - r.intenset.write(|w| w.end().set()); - - compiler_fence(Ordering::SeqCst); - - // Trigger the transmission - trigger(); - // self.trace_state(); - - // On poll check if interrupt happen - poll_fn(|cx| { - s.end_waker.register(cx.waker()); - if r.events_end.read().events_end().bit_is_set() { - // trace!("radio:end"); - return core::task::Poll::Ready(()); - } - Poll::Pending - }) - .await; - - compiler_fence(Ordering::SeqCst); - r.events_disabled.reset(); // ACK - - // Everthing ends fine, so we can disable the drop - drop.defuse(); - } - - /// Disable the radio. - fn disable(&mut self) { - let r = T::regs(); - - compiler_fence(Ordering::SeqCst); - // If is already disabled, do nothing - if !r.state.read().state().is_disabled() { - trace!("radio:disable"); - // Trigger the disable task - r.tasks_disable.write(|w| w.tasks_disable().set_bit()); - - // Wait until the radio is disabled - while r.events_disabled.read().events_disabled().bit_is_clear() {} - - compiler_fence(Ordering::SeqCst); - - // Acknowledge it - r.events_disabled.reset(); - } - } - /// 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)); @@ -235,10 +168,12 @@ impl<'d, T: Instance> Radio<'d, T> { }); } - /// Set the header size changing the S1 field + /// 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 @@ -268,6 +203,8 @@ impl<'d, T: Instance> Radio<'d, T> { /// /// 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) }); @@ -276,9 +213,11 @@ impl<'d, T: Instance> Radio<'d, T> { /// Set the central frequency to be used /// It should be in the range 2400..2500 /// - /// The radio must be disabled before calling this function + /// [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 <= frequency && frequency <= 2500); + let r = T::regs(); r.frequency @@ -292,6 +231,8 @@ impl<'d, T: Instance> Radio<'d, T> { /// /// 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 @@ -309,9 +250,9 @@ impl<'d, T: Instance> Radio<'d, T> { r.txaddress.write(|w| unsafe { w.txaddress().bits(0) }); // Match on logical address - // For what I understand, this config only filter the packets - // by the address, so only packages send to the previous address - // will finish the reception + // 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() @@ -331,6 +272,8 @@ impl<'d, T: Instance> Radio<'d, T> { /// /// 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 { @@ -351,6 +294,8 @@ impl<'d, T: Instance> Radio<'d, T> { /// /// 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) }); @@ -360,6 +305,8 @@ impl<'d, T: Instance> Radio<'d, T> { /// /// 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)); @@ -408,19 +355,83 @@ impl<'d, T: Instance> Radio<'d, T> { // Initialize the transmission // trace!("rxen"); r.tasks_rxen.write(|w| w.tasks_rxen().set_bit()); + }) + .await; + } - // Await until ready - while r.events_ready.read().events_ready().bit_is_clear() {} + async fn trigger_and_wait_end(&mut self, trigger: impl FnOnce()) { + //self.trace_state(); + + let r = T::regs(); + let s = T::state(); + + // If the Future is dropped before the end of the transmission + // we need to disable the interrupt and stop the transmission + // to keep the state consistent + let drop = OnDrop::new(|| { + trace!("radio drop: stopping"); + + r.intenclr.write(|w| w.end().clear()); + r.events_end.reset(); + + r.tasks_stop.write(|w| w.tasks_stop().set_bit()); + + // The docs don't explicitly mention any event to acknowledge the stop task + while r.events_end.read().events_end().bit_is_clear() {} + + 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(); + // self.trace_state(); + + // On poll check if interrupt happen + poll_fn(|cx| { + s.end_waker.register(cx.waker()); + if r.events_end.read().events_end().bit_is_set() { + // trace!("radio:end"); + return core::task::Poll::Ready(()); + } + Poll::Pending + }) + .await; + + compiler_fence(Ordering::SeqCst); + r.events_disabled.reset(); // ACK + + // Everthing ends fine, so we can disable the drop + drop.defuse(); + } + + /// Disable the radio + fn disable(&mut self) { + let r = T::regs(); + + compiler_fence(Ordering::SeqCst); + // If it is already disabled, do nothing + if self.state() != RadioState::DISABLED { + trace!("radio:disable"); + // Trigger the disable task + r.tasks_disable.write(|w| w.tasks_disable().set_bit()); + + // Wait until the radio is disabled + while r.events_disabled.read().events_disabled().bit_is_clear() {} compiler_fence(Ordering::SeqCst); // Acknowledge it - r.events_ready.reset(); - - // trace!("radio:start"); - r.tasks_start.write(|w| w.tasks_start().set_bit()); - }) - .await; + r.events_disabled.reset(); + } } } From 37739284253699d3f087fd8e1f9282090e6aaf95 Mon Sep 17 00:00:00 2001 From: "Guilherme S. Salustiano" Date: Sat, 10 Feb 2024 08:18:32 +0100 Subject: [PATCH 09/13] apply clippy sugestions --- embassy-nrf/src/radio/ble.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/embassy-nrf/src/radio/ble.rs b/embassy-nrf/src/radio/ble.rs index b0d37457..c74676f5 100644 --- a/embassy-nrf/src/radio/ble.rs +++ b/embassy-nrf/src/radio/ble.rs @@ -127,15 +127,13 @@ impl<'d, T: Instance> Radio<'d, T> { fn state(&self) -> RadioState { match T::regs().state.read().state().variant() { - Ok(s) => s, + Some(s) => s, None => unreachable!(), } } #[allow(dead_code)] fn trace_state(&self) { - let r = T::regs(); - match self.state() { RadioState::DISABLED => trace!("radio:state:DISABLED"), RadioState::RX_RU => trace!("radio:state:RX_RU"), @@ -216,7 +214,7 @@ impl<'d, T: Instance> Radio<'d, T> { /// [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 <= frequency && frequency <= 2500); + assert!((2400..=2500).contains(&frequency)); let r = T::regs(); From 40282c2666f8b8bda5785b30a62ab352ff8e7498 Mon Sep 17 00:00:00 2001 From: "Guilherme S. Salustiano" Date: Sat, 10 Feb 2024 08:49:14 +0100 Subject: [PATCH 10/13] add buffer input on transmit/receive --- embassy-nrf/src/radio/ble.rs | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/embassy-nrf/src/radio/ble.rs b/embassy-nrf/src/radio/ble.rs index c74676f5..7718cfe1 100644 --- a/embassy-nrf/src/radio/ble.rs +++ b/embassy-nrf/src/radio/ble.rs @@ -316,7 +316,7 @@ impl<'d, T: Instance> Radio<'d, T> { /// 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. - pub fn set_buffer(&mut self, buffer: &[u8]) -> Result<(), Error> { + fn set_buffer(&mut self, buffer: &[u8]) -> Result<(), Error> { // Because we are serializing the buffer, we should always have the buffer in RAM slice_in_ram_or(buffer, Error::BufferNotInRAM)?; @@ -334,27 +334,33 @@ impl<'d, T: Instance> Radio<'d, T> { } /// Send packet - pub async fn transmit(&mut self) { - let r = T::regs(); + 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| w.tasks_txen().set_bit()); }) .await; + + Ok(()) } /// Receive packet - pub async fn receive(&mut self) { - let r = T::regs(); + 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| w.tasks_rxen().set_bit()); }) .await; + + Ok(()) } async fn trigger_and_wait_end(&mut self, trigger: impl FnOnce()) { From fdb15b205424e71107f18b43553d43691f3d8fb7 Mon Sep 17 00:00:00 2001 From: "Guilherme S. Salustiano" Date: Sat, 10 Feb 2024 08:58:31 +0100 Subject: [PATCH 11/13] add comments about buffer unsound --- embassy-nrf/src/radio/ble.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/embassy-nrf/src/radio/ble.rs b/embassy-nrf/src/radio/ble.rs index 7718cfe1..ba9ac5f2 100644 --- a/embassy-nrf/src/radio/ble.rs +++ b/embassy-nrf/src/radio/ble.rs @@ -334,6 +334,8 @@ impl<'d, T: Instance> Radio<'d, T> { } /// 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)?; @@ -349,6 +351,8 @@ impl<'d, T: Instance> Radio<'d, T> { } /// 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)?; From b4f0f575388df405e4e7330f2e8cdac30c4d60d9 Mon Sep 17 00:00:00 2001 From: "Guilherme S. Salustiano" Date: Mon, 12 Feb 2024 13:21:01 +0100 Subject: [PATCH 12/13] remove first person comments --- embassy-nrf/src/lib.rs | 3 ++- embassy-nrf/src/radio/ble.rs | 22 +++++----------------- 2 files changed, 7 insertions(+), 18 deletions(-) diff --git a/embassy-nrf/src/lib.rs b/embassy-nrf/src/lib.rs index 5dba6f97..04a6293a 100644 --- a/embassy-nrf/src/lib.rs +++ b/embassy-nrf/src/lib.rs @@ -46,7 +46,8 @@ pub mod gpio; #[cfg(feature = "gpiote")] pub mod gpiote; -#[cfg(any(feature = "nrf52840"))] // needs to be tested on other chips +// TODO: tested on other chips +#[cfg(any(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 ba9ac5f2..81ef96b6 100644 --- a/embassy-nrf/src/radio/ble.rs +++ b/embassy-nrf/src/radio/ble.rs @@ -1,15 +1,4 @@ //! Radio driver implementation focused on Bluetooth Low-Energy transmission. -//! -//! The radio can calculate the CRC, perform data whitening, -//! automatically send the right preamble. -//! Most of the configuration is done automatically when you choose the mode and this driver. -//! -//! Some configuration can just be done when de device is disabled, -//! and the configuration varies depending if is a transmitter or a receiver. -//! Because of that we have a state machine to keep track of the state of the radio. -//! The Radio is the disable radio which configure the common parameters between -//! the bluetooth protocols, like the package format, the CRC and the whitening. -//! The TxRadio radio enable and configured as a transmitter with the specific parameters. use core::future::poll_fn; use core::sync::atomic::{compiler_fence, Ordering}; @@ -241,7 +230,7 @@ impl<'d, T: Instance> Radio<'d, T> { .write(|w| unsafe { w.ap0().bits((access_address >> 24) as u8) }); // The base address is truncated from the least significant byte (because the BALEN is less than 4) - // So we need to shift the address to the right + // So it shifts the address to the right r.base0.write(|w| unsafe { w.bits(access_address << 8) }); // Don't match tx address @@ -317,13 +306,12 @@ impl<'d, T: Instance> Radio<'d, T> { /// 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> { - // Because we are serializing the buffer, we should always have the buffer in RAM slice_in_ram_or(buffer, Error::BufferNotInRAM)?; let r = T::regs(); - // Here we are considering that the length of the packet is - // correctly set in the buffer, otherwise we will sending + // 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(); @@ -374,7 +362,7 @@ impl<'d, T: Instance> Radio<'d, T> { let s = T::state(); // If the Future is dropped before the end of the transmission - // we need to disable the interrupt and stop the transmission + // it disable the interrupt and stop the transmission // to keep the state consistent let drop = OnDrop::new(|| { trace!("radio drop: stopping"); @@ -417,7 +405,7 @@ impl<'d, T: Instance> Radio<'d, T> { compiler_fence(Ordering::SeqCst); r.events_disabled.reset(); // ACK - // Everthing ends fine, so we can disable the drop + // Everthing ends fine, so it disable the drop drop.defuse(); } From 368b3a9aaf97a7662217b9ff2784e3648368586c Mon Sep 17 00:00:00 2001 From: "Guilherme S. Salustiano" Date: Wed, 28 Feb 2024 14:54:54 +0100 Subject: [PATCH 13/13] fix: radio error --- embassy-nrf/src/radio/ble.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/embassy-nrf/src/radio/ble.rs b/embassy-nrf/src/radio/ble.rs index 81ef96b6..24dba582 100644 --- a/embassy-nrf/src/radio/ble.rs +++ b/embassy-nrf/src/radio/ble.rs @@ -15,7 +15,7 @@ use crate::interrupt::typelevel::Interrupt; use crate::radio::*; use crate::util::slice_in_ram_or; -/// UART error. +/// RADIO error. #[derive(Debug, Clone, Copy, PartialEq, Eq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] #[non_exhaustive]