From 224d6b03125dd9c799611883914dd99c5431e749 Mon Sep 17 00:00:00 2001 From: Remmirad Date: Sat, 6 Sep 2025 11:32:23 +0200 Subject: [PATCH] nrf: 802.15.4 embassy-net-driver --- embassy-net/README.md | 1 + embassy-nrf/CHANGELOG.md | 1 + embassy-nrf/Cargo.toml | 7 ++ embassy-nrf/README.md | 4 + embassy-nrf/src/embassy_net_802154_driver.rs | 96 +++++++++++++++ embassy-nrf/src/lib.rs | 11 ++ examples/nrf52840/Cargo.toml | 4 +- examples/nrf52840/src/bin/sixlowpan.rs | 120 +++++++++++++++++++ 8 files changed, 242 insertions(+), 2 deletions(-) create mode 100644 embassy-nrf/src/embassy_net_802154_driver.rs create mode 100644 examples/nrf52840/src/bin/sixlowpan.rs diff --git a/embassy-net/README.md b/embassy-net/README.md index 1722ffc7b..1c5b30a9c 100644 --- a/embassy-net/README.md +++ b/embassy-net/README.md @@ -25,6 +25,7 @@ unimplemented features of the network protocols. - [`embassy-stm32`](https://github.com/embassy-rs/embassy/tree/main/embassy-stm32) for the builtin Ethernet MAC in all STM32 chips (STM32F1, STM32F2, STM32F4, STM32F7, STM32H7, STM32H5). - [`embassy-net-wiznet`](https://github.com/embassy-rs/embassy/tree/main/embassy-net-wiznet) for Wiznet SPI Ethernet MAC+PHY chips (W5100S, W5500) - [`embassy-net-esp-hosted`](https://github.com/embassy-rs/embassy/tree/main/embassy-net-esp-hosted) for using ESP32 chips with the [`esp-hosted`](https://github.com/espressif/esp-hosted) firmware as WiFi adapters for another non-ESP32 MCU. +- [`embassy-nrf`](https://github.com/embassy-rs/embassy/tree/main/embassy-nrf) for IEEE 802.15.4 support on nrf chips. ## Examples diff --git a/embassy-nrf/CHANGELOG.md b/embassy-nrf/CHANGELOG.md index 5dc941b25..befa34ecf 100644 --- a/embassy-nrf/CHANGELOG.md +++ b/embassy-nrf/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased - ReleaseDate - changed: nrf54l: Disable glitch detection and enable DC/DC in init. +- changed: Add embassy-net-driver-channel implementation for IEEE 802.15.4 ## 0.7.0 - 2025-08-26 diff --git a/embassy-nrf/Cargo.toml b/embassy-nrf/Cargo.toml index 2ce75cfac..4afd28fbd 100644 --- a/embassy-nrf/Cargo.toml +++ b/embassy-nrf/Cargo.toml @@ -79,6 +79,9 @@ gpiote = [] ## Use RTC1 as the time driver for `embassy-time`, with a tick rate of 32.768khz time-driver-rtc1 = ["_time-driver"] +## Enable embassy-net 802.15.4 driver +net-driver = ["_net-driver"] + ## Allow using the NFC pins as regular GPIO pins (P0_09/P0_10 on nRF52, P0_02/P0_03 on nRF53) nfc-pins-as-gpio = [] @@ -154,6 +157,8 @@ _nrf91 = [] _time-driver = ["dep:embassy-time-driver", "embassy-time-driver?/tick-hz-32_768", "dep:embassy-time-queue-utils", "embassy-embedded-hal/time"] +_net-driver = ["dep:embassy-net-driver-channel","dep:embassy-futures"] + # trustzone state. _s = [] _ns = [] @@ -177,6 +182,8 @@ embassy-sync = { version = "0.7.2", path = "../embassy-sync" } embassy-hal-internal = { version = "0.3.0", path = "../embassy-hal-internal", features = ["cortex-m", "prio-bits-3"] } embassy-embedded-hal = { version = "0.5.0", path = "../embassy-embedded-hal", default-features = false } embassy-usb-driver = { version = "0.2.0", path = "../embassy-usb-driver" } +embassy-net-driver-channel = { version = "0.3.2", path = "../embassy-net-driver-channel", optional = true} +embassy-futures = { version = "0.1.2", path = "../embassy-futures", optional = true} embedded-hal-02 = { package = "embedded-hal", version = "0.2.6", features = ["unproven"] } embedded-hal-1 = { package = "embedded-hal", version = "1.0" } diff --git a/embassy-nrf/README.md b/embassy-nrf/README.md index 3df5f1fa5..26b1fa5ea 100644 --- a/embassy-nrf/README.md +++ b/embassy-nrf/README.md @@ -28,6 +28,10 @@ allows running Rust code without a SPM or TF-M binary, saving flash space and si If the `time-driver-rtc1` feature is enabled, the HAL uses the RTC peripheral as a global time driver for [embassy-time](https://crates.io/crates/embassy-time), with a tick rate of 32768 Hz. +## Embassy-net-driver + +If the board supports IEEE 802.15.4 (see `src/radio/mod.rs`) the corresponding [embassy-net-driver](https://crates.io/crates/embassy-net-driver) implementation can be enabled with the feature `net-driver`. + ## Embedded-hal The `embassy-nrf` HAL implements the traits from [embedded-hal](https://crates.io/crates/embedded-hal) (v0.2 and 1.0) and [embedded-hal-async](https://crates.io/crates/embedded-hal-async), as well as [embedded-io](https://crates.io/crates/embedded-io) and [embedded-io-async](https://crates.io/crates/embedded-io-async). diff --git a/embassy-nrf/src/embassy_net_802154_driver.rs b/embassy-nrf/src/embassy_net_802154_driver.rs new file mode 100644 index 000000000..8662be787 --- /dev/null +++ b/embassy-nrf/src/embassy_net_802154_driver.rs @@ -0,0 +1,96 @@ +//! embassy-net IEEE 802.15.4 driver + +use embassy_futures::select::{select3, Either3}; +use embassy_net_driver_channel::driver::LinkState; +use embassy_net_driver_channel::{self as ch}; +use embassy_time::{Duration, Ticker}; + +use crate::radio::ieee802154::{Packet, Radio}; +use crate::radio::InterruptHandler; +use crate::{self as nrf, interrupt}; + +/// MTU for the nrf radio. +pub const MTU: usize = Packet::CAPACITY as usize; + +/// embassy-net device for the driver. +pub type Device<'d> = embassy_net_driver_channel::Device<'d, MTU>; + +/// Internal state for the embassy-net driver. +pub struct State { + ch_state: ch::State, +} + +impl State { + /// Create a new `State`. + pub const fn new() -> Self { + Self { + ch_state: ch::State::new(), + } + } +} + +/// Background runner for the driver. +/// +/// You must call `.run()` in a background task for the driver to operate. +pub struct Runner<'d, T: nrf::radio::Instance> { + radio: nrf::radio::ieee802154::Radio<'d, T>, + ch: ch::Runner<'d, MTU>, +} + +impl<'d, T: nrf::radio::Instance> Runner<'d, T> { + /// Drives the radio. Needs to run to use the driver. + pub async fn run(mut self) -> ! { + let (state_chan, mut rx_chan, mut tx_chan) = self.ch.split(); + let mut tick = Ticker::every(Duration::from_millis(500)); + let mut packet = Packet::new(); + state_chan.set_link_state(LinkState::Up); + loop { + match select3( + async { + let rx_buf = rx_chan.rx_buf().await; + self.radio.receive(&mut packet).await.ok().map(|_| rx_buf) + }, + tx_chan.tx_buf(), + tick.next(), + ) + .await + { + Either3::First(Some(rx_buf)) => { + let len = rx_buf.len().min(packet.len() as usize); + (&mut rx_buf[..len]).copy_from_slice(&*packet); + rx_chan.rx_done(len); + } + Either3::Second(tx_buf) => { + let len = tx_buf.len().min(Packet::CAPACITY as usize); + packet.copy_from_slice(&tx_buf[..len]); + self.radio.try_send(&mut packet).await.ok().unwrap(); + tx_chan.tx_done(); + } + _ => {} + } + } + } +} + +/// Make sure to use `HfclkSource::ExternalXtal` as the `hfclk_source` +/// to use the radio (nrf52840 product spec v1.11 5.4.1) +/// ``` +/// # use embassy_nrf::config::*; +/// let mut config = Config::default(); +/// config.hfclk_source = HfclkSource::ExternalXtal; +/// ``` +pub async fn new<'a, const N_RX: usize, const N_TX: usize, T: nrf::radio::Instance, Irq>( + mac_addr: [u8; 8], + radio: nrf::Peri<'a, T>, + irq: Irq, + state: &'a mut State, +) -> Result<(Device<'a>, Runner<'a, T>), ()> +where + Irq: interrupt::typelevel::Binding> + 'a, +{ + let radio = Radio::new(radio, irq); + + let (runner, device) = ch::new(&mut state.ch_state, ch::driver::HardwareAddress::Ieee802154(mac_addr)); + + Ok((device, Runner { ch: runner, radio })) +} diff --git a/embassy-nrf/src/lib.rs b/embassy-nrf/src/lib.rs index aa4801897..897e660b8 100644 --- a/embassy-nrf/src/lib.rs +++ b/embassy-nrf/src/lib.rs @@ -137,6 +137,17 @@ pub mod qspi; #[cfg(not(feature = "_nrf54l"))] // TODO #[cfg(not(any(feature = "_nrf91", feature = "_nrf5340-app")))] pub mod radio; + +#[cfg(any( + feature = "nrf52811", + feature = "nrf52820", + feature = "nrf52833", + feature = "nrf52840", + feature = "_nrf5340-net" +))] +#[cfg(feature = "_net-driver")] +pub mod embassy_net_802154_driver; + #[cfg(not(feature = "_nrf54l"))] // TODO #[cfg(feature = "_nrf5340")] pub mod reset; diff --git a/examples/nrf52840/Cargo.toml b/examples/nrf52840/Cargo.toml index a9339bcd3..452e83b7e 100644 --- a/examples/nrf52840/Cargo.toml +++ b/examples/nrf52840/Cargo.toml @@ -10,8 +10,8 @@ embassy-futures = { version = "0.1.2", path = "../../embassy-futures" } embassy-sync = { version = "0.7.2", path = "../../embassy-sync", features = ["defmt"] } embassy-executor = { version = "0.9.0", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "executor-interrupt", "defmt"] } embassy-time = { version = "0.5.0", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime"] } -embassy-nrf = { version = "0.7.0", path = "../../embassy-nrf", features = ["defmt", "nrf52840", "time-driver-rtc1", "gpiote", "unstable-pac", "time"] } -embassy-net = { version = "0.7.1", path = "../../embassy-net", features = ["defmt", "tcp", "dhcpv4", "medium-ethernet"] } +embassy-nrf = { version = "0.7.0", path = "../../embassy-nrf", features = ["defmt", "nrf52840", "time-driver-rtc1", "gpiote", "unstable-pac", "time", "net-driver"] } +embassy-net = { version = "0.7.1", path = "../../embassy-net", features = ["defmt", "tcp", "dhcpv4", "medium-ethernet","udp", "medium-ieee802154", "proto-ipv6"] } embassy-usb = { version = "0.5.1", path = "../../embassy-usb", features = ["defmt"] } embedded-io = { version = "0.6.0", features = ["defmt-03"] } embedded-io-async = { version = "0.6.1", features = ["defmt-03"] } diff --git a/examples/nrf52840/src/bin/sixlowpan.rs b/examples/nrf52840/src/bin/sixlowpan.rs new file mode 100644 index 000000000..00a597366 --- /dev/null +++ b/examples/nrf52840/src/bin/sixlowpan.rs @@ -0,0 +1,120 @@ +#![no_std] +#![no_main] + +use core::net::Ipv6Addr; + +use defmt::{info, unwrap, warn}; +use embassy_executor::Spawner; +use embassy_net::udp::{PacketMetadata, UdpMetadata, UdpSocket}; +use embassy_net::{IpAddress, IpEndpoint, IpListenEndpoint, Ipv6Cidr, StackResources, StaticConfigV6}; +use embassy_nrf::config::{Config, HfclkSource}; +use embassy_nrf::rng::Rng; +use embassy_nrf::{bind_interrupts, embassy_net_802154_driver as net, peripherals, radio}; +use embassy_time::Delay; +use embedded_hal_async::delay::DelayNs; +use static_cell::StaticCell; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + RADIO => radio::InterruptHandler; + RNG => embassy_nrf::rng::InterruptHandler; +}); + +#[embassy_executor::task] +async fn ieee802154_task(runner: net::Runner<'static, peripherals::RADIO>) -> ! { + runner.run().await +} + +#[embassy_executor::task] +async fn net_task(mut runner: embassy_net::Runner<'static, net::Device<'static>>) -> ! { + runner.run().await +} + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + let mut config = Config::default(); + // Necessary to run the radio nrf52840 v1.11 5.4.1 + config.hfclk_source = HfclkSource::ExternalXtal; + let p = embassy_nrf::init(config); + + let mac_addr: [u8; 8] = [2, 3, 4, 5, 6, 7, 8, 9]; + static NRF802154_STATE: StaticCell> = StaticCell::new(); + let (device, runner) = net::new(mac_addr, p.RADIO, Irqs, NRF802154_STATE.init(net::State::new())) + .await + .unwrap(); + + spawner.spawn(unwrap!(ieee802154_task(runner))); + + // Swap these when flashing a second board + let peer = Ipv6Addr::new(0xfe80, 0, 0, 0, 0xd701, 0xda3f, 0x3955, 0x82a4); + let local = Ipv6Addr::new(0xfe80, 0, 0, 0, 0xd701, 0xda3f, 0x3955, 0x82a5); + + let config = embassy_net::Config::ipv6_static(StaticConfigV6 { + address: Ipv6Cidr::new(local, 64), + gateway: None, + dns_servers: Default::default(), + }); + + // Generate random seed + let mut rng = Rng::new(p.RNG, Irqs); + let mut seed = [0; 8]; + rng.blocking_fill_bytes(&mut seed); + let seed = u64::from_le_bytes(seed); + + // Init network stack + static RESOURCES: StaticCell> = StaticCell::new(); + let (stack, runner) = embassy_net::new(device, config, RESOURCES.init(StackResources::new()), seed); + + spawner.spawn(unwrap!(net_task(runner))); + + let mut rx_buffer = [0; 2096]; + let mut tx_buffer = [0; 2096]; + let mut tx_m_buffer = [PacketMetadata::EMPTY; 5]; + let mut rx_m_buffer = [PacketMetadata::EMPTY; 5]; + + let mut delay = Delay; + loop { + let mut socket = UdpSocket::new( + stack, + &mut tx_m_buffer, + &mut rx_buffer, + &mut rx_m_buffer, + &mut tx_buffer, + ); + socket + .bind(IpListenEndpoint { + addr: Some(IpAddress::Ipv6(local)), + port: 1234, + }) + .unwrap(); + let rep = UdpMetadata { + endpoint: IpEndpoint { + addr: IpAddress::Ipv6(peer), + port: 1234, + }, + local_address: Some(IpAddress::Ipv6(local)), + meta: Default::default(), + }; + + info!("Listening on {:?} UDP:1234...", local); + + let mut recv_buf = [0; 12]; + loop { + delay.delay_ms(2000).await; + if socket.may_recv() { + let n = match socket.recv_from(&mut recv_buf).await { + Ok((0, _)) => panic!(), + Ok((n, _)) => n, + Err(e) => { + warn!("read error: {:?}", e); + break; + } + }; + info!("Received {:02x}", &recv_buf[..n]); + } + + info!("Sending"); + socket.send_to(b"Hello World", rep).await.unwrap(); + } + } +}