Merge pull request #4304 from Remmirad/embassy-net-nrf-802154

embassy-net driver for nrf52 802.15.4 radio
This commit is contained in:
Dario Nieuwenhuis 2025-09-07 12:38:57 +00:00 committed by GitHub
commit a6cd24907a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 242 additions and 2 deletions

View File

@ -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

View File

@ -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

View File

@ -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" }

View File

@ -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).

View File

@ -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<const N_RX: usize, const N_TX: usize> {
ch_state: ch::State<MTU, N_RX, N_TX>,
}
impl<const N_RX: usize, const N_TX: usize> State<N_RX, N_TX> {
/// 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<N_RX, N_TX>,
) -> Result<(Device<'a>, Runner<'a, T>), ()>
where
Irq: interrupt::typelevel::Binding<T::Interrupt, InterruptHandler<T>> + '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 }))
}

View File

@ -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;

View File

@ -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"] }

View File

@ -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<peripherals::RADIO>;
RNG => embassy_nrf::rng::InterruptHandler<peripherals::RNG>;
});
#[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<net::State<20, 20>> = 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<StackResources<3>> = 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();
}
}
}