aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRemmirad <[email protected]>2025-09-06 11:32:23 +0200
committerRemmirad <[email protected]>2025-09-06 11:54:29 +0200
commit224d6b03125dd9c799611883914dd99c5431e749 (patch)
tree87ef2c4a8a3b90a94663cc7030a0fe86dbc1b271
parent25e0ebf5206fa2e2906f5826c0b1587739f628d8 (diff)
nrf: 802.15.4 embassy-net-driver
-rw-r--r--embassy-net/README.md1
-rw-r--r--embassy-nrf/CHANGELOG.md1
-rw-r--r--embassy-nrf/Cargo.toml7
-rw-r--r--embassy-nrf/README.md4
-rw-r--r--embassy-nrf/src/embassy_net_802154_driver.rs96
-rw-r--r--embassy-nrf/src/lib.rs11
-rw-r--r--examples/nrf52840/Cargo.toml4
-rw-r--r--examples/nrf52840/src/bin/sixlowpan.rs120
8 files changed, 242 insertions, 2 deletions
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.
25- [`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). 25- [`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).
26- [`embassy-net-wiznet`](https://github.com/embassy-rs/embassy/tree/main/embassy-net-wiznet) for Wiznet SPI Ethernet MAC+PHY chips (W5100S, W5500) 26- [`embassy-net-wiznet`](https://github.com/embassy-rs/embassy/tree/main/embassy-net-wiznet) for Wiznet SPI Ethernet MAC+PHY chips (W5100S, W5500)
27- [`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. 27- [`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.
28- [`embassy-nrf`](https://github.com/embassy-rs/embassy/tree/main/embassy-nrf) for IEEE 802.15.4 support on nrf chips.
28 29
29## Examples 30## Examples
30 31
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
9## Unreleased - ReleaseDate 9## Unreleased - ReleaseDate
10 10
11- changed: nrf54l: Disable glitch detection and enable DC/DC in init. 11- changed: nrf54l: Disable glitch detection and enable DC/DC in init.
12- changed: Add embassy-net-driver-channel implementation for IEEE 802.15.4
12 13
13## 0.7.0 - 2025-08-26 14## 0.7.0 - 2025-08-26
14 15
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 = []
79## Use RTC1 as the time driver for `embassy-time`, with a tick rate of 32.768khz 79## Use RTC1 as the time driver for `embassy-time`, with a tick rate of 32.768khz
80time-driver-rtc1 = ["_time-driver"] 80time-driver-rtc1 = ["_time-driver"]
81 81
82## Enable embassy-net 802.15.4 driver
83net-driver = ["_net-driver"]
84
82## Allow using the NFC pins as regular GPIO pins (P0_09/P0_10 on nRF52, P0_02/P0_03 on nRF53) 85## Allow using the NFC pins as regular GPIO pins (P0_09/P0_10 on nRF52, P0_02/P0_03 on nRF53)
83nfc-pins-as-gpio = [] 86nfc-pins-as-gpio = []
84 87
@@ -154,6 +157,8 @@ _nrf91 = []
154 157
155_time-driver = ["dep:embassy-time-driver", "embassy-time-driver?/tick-hz-32_768", "dep:embassy-time-queue-utils", "embassy-embedded-hal/time"] 158_time-driver = ["dep:embassy-time-driver", "embassy-time-driver?/tick-hz-32_768", "dep:embassy-time-queue-utils", "embassy-embedded-hal/time"]
156 159
160_net-driver = ["dep:embassy-net-driver-channel","dep:embassy-futures"]
161
157# trustzone state. 162# trustzone state.
158_s = [] 163_s = []
159_ns = [] 164_ns = []
@@ -177,6 +182,8 @@ embassy-sync = { version = "0.7.2", path = "../embassy-sync" }
177embassy-hal-internal = { version = "0.3.0", path = "../embassy-hal-internal", features = ["cortex-m", "prio-bits-3"] } 182embassy-hal-internal = { version = "0.3.0", path = "../embassy-hal-internal", features = ["cortex-m", "prio-bits-3"] }
178embassy-embedded-hal = { version = "0.5.0", path = "../embassy-embedded-hal", default-features = false } 183embassy-embedded-hal = { version = "0.5.0", path = "../embassy-embedded-hal", default-features = false }
179embassy-usb-driver = { version = "0.2.0", path = "../embassy-usb-driver" } 184embassy-usb-driver = { version = "0.2.0", path = "../embassy-usb-driver" }
185embassy-net-driver-channel = { version = "0.3.2", path = "../embassy-net-driver-channel", optional = true}
186embassy-futures = { version = "0.1.2", path = "../embassy-futures", optional = true}
180 187
181embedded-hal-02 = { package = "embedded-hal", version = "0.2.6", features = ["unproven"] } 188embedded-hal-02 = { package = "embedded-hal", version = "0.2.6", features = ["unproven"] }
182embedded-hal-1 = { package = "embedded-hal", version = "1.0" } 189embedded-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
28 28
29If 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. 29If 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.
30 30
31## Embassy-net-driver
32
33If 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`.
34
31## Embedded-hal 35## Embedded-hal
32 36
33The `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). 37The `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 @@
1//! embassy-net IEEE 802.15.4 driver
2
3use embassy_futures::select::{select3, Either3};
4use embassy_net_driver_channel::driver::LinkState;
5use embassy_net_driver_channel::{self as ch};
6use embassy_time::{Duration, Ticker};
7
8use crate::radio::ieee802154::{Packet, Radio};
9use crate::radio::InterruptHandler;
10use crate::{self as nrf, interrupt};
11
12/// MTU for the nrf radio.
13pub const MTU: usize = Packet::CAPACITY as usize;
14
15/// embassy-net device for the driver.
16pub type Device<'d> = embassy_net_driver_channel::Device<'d, MTU>;
17
18/// Internal state for the embassy-net driver.
19pub struct State<const N_RX: usize, const N_TX: usize> {
20 ch_state: ch::State<MTU, N_RX, N_TX>,
21}
22
23impl<const N_RX: usize, const N_TX: usize> State<N_RX, N_TX> {
24 /// Create a new `State`.
25 pub const fn new() -> Self {
26 Self {
27 ch_state: ch::State::new(),
28 }
29 }
30}
31
32/// Background runner for the driver.
33///
34/// You must call `.run()` in a background task for the driver to operate.
35pub struct Runner<'d, T: nrf::radio::Instance> {
36 radio: nrf::radio::ieee802154::Radio<'d, T>,
37 ch: ch::Runner<'d, MTU>,
38}
39
40impl<'d, T: nrf::radio::Instance> Runner<'d, T> {
41 /// Drives the radio. Needs to run to use the driver.
42 pub async fn run(mut self) -> ! {
43 let (state_chan, mut rx_chan, mut tx_chan) = self.ch.split();
44 let mut tick = Ticker::every(Duration::from_millis(500));
45 let mut packet = Packet::new();
46 state_chan.set_link_state(LinkState::Up);
47 loop {
48 match select3(
49 async {
50 let rx_buf = rx_chan.rx_buf().await;
51 self.radio.receive(&mut packet).await.ok().map(|_| rx_buf)
52 },
53 tx_chan.tx_buf(),
54 tick.next(),
55 )
56 .await
57 {
58 Either3::First(Some(rx_buf)) => {
59 let len = rx_buf.len().min(packet.len() as usize);
60 (&mut rx_buf[..len]).copy_from_slice(&*packet);
61 rx_chan.rx_done(len);
62 }
63 Either3::Second(tx_buf) => {
64 let len = tx_buf.len().min(Packet::CAPACITY as usize);
65 packet.copy_from_slice(&tx_buf[..len]);
66 self.radio.try_send(&mut packet).await.ok().unwrap();
67 tx_chan.tx_done();
68 }
69 _ => {}
70 }
71 }
72 }
73}
74
75/// Make sure to use `HfclkSource::ExternalXtal` as the `hfclk_source`
76/// to use the radio (nrf52840 product spec v1.11 5.4.1)
77/// ```
78/// # use embassy_nrf::config::*;
79/// let mut config = Config::default();
80/// config.hfclk_source = HfclkSource::ExternalXtal;
81/// ```
82pub async fn new<'a, const N_RX: usize, const N_TX: usize, T: nrf::radio::Instance, Irq>(
83 mac_addr: [u8; 8],
84 radio: nrf::Peri<'a, T>,
85 irq: Irq,
86 state: &'a mut State<N_RX, N_TX>,
87) -> Result<(Device<'a>, Runner<'a, T>), ()>
88where
89 Irq: interrupt::typelevel::Binding<T::Interrupt, InterruptHandler<T>> + 'a,
90{
91 let radio = Radio::new(radio, irq);
92
93 let (runner, device) = ch::new(&mut state.ch_state, ch::driver::HardwareAddress::Ieee802154(mac_addr));
94
95 Ok((device, Runner { ch: runner, radio }))
96}
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;
137#[cfg(not(feature = "_nrf54l"))] // TODO 137#[cfg(not(feature = "_nrf54l"))] // TODO
138#[cfg(not(any(feature = "_nrf91", feature = "_nrf5340-app")))] 138#[cfg(not(any(feature = "_nrf91", feature = "_nrf5340-app")))]
139pub mod radio; 139pub mod radio;
140
141#[cfg(any(
142 feature = "nrf52811",
143 feature = "nrf52820",
144 feature = "nrf52833",
145 feature = "nrf52840",
146 feature = "_nrf5340-net"
147))]
148#[cfg(feature = "_net-driver")]
149pub mod embassy_net_802154_driver;
150
140#[cfg(not(feature = "_nrf54l"))] // TODO 151#[cfg(not(feature = "_nrf54l"))] // TODO
141#[cfg(feature = "_nrf5340")] 152#[cfg(feature = "_nrf5340")]
142pub mod reset; 153pub 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" }
10embassy-sync = { version = "0.7.2", path = "../../embassy-sync", features = ["defmt"] } 10embassy-sync = { version = "0.7.2", path = "../../embassy-sync", features = ["defmt"] }
11embassy-executor = { version = "0.9.0", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "executor-interrupt", "defmt"] } 11embassy-executor = { version = "0.9.0", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "executor-interrupt", "defmt"] }
12embassy-time = { version = "0.5.0", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime"] } 12embassy-time = { version = "0.5.0", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime"] }
13embassy-nrf = { version = "0.7.0", path = "../../embassy-nrf", features = ["defmt", "nrf52840", "time-driver-rtc1", "gpiote", "unstable-pac", "time"] } 13embassy-nrf = { version = "0.7.0", path = "../../embassy-nrf", features = ["defmt", "nrf52840", "time-driver-rtc1", "gpiote", "unstable-pac", "time", "net-driver"] }
14embassy-net = { version = "0.7.1", path = "../../embassy-net", features = ["defmt", "tcp", "dhcpv4", "medium-ethernet"] } 14embassy-net = { version = "0.7.1", path = "../../embassy-net", features = ["defmt", "tcp", "dhcpv4", "medium-ethernet","udp", "medium-ieee802154", "proto-ipv6"] }
15embassy-usb = { version = "0.5.1", path = "../../embassy-usb", features = ["defmt"] } 15embassy-usb = { version = "0.5.1", path = "../../embassy-usb", features = ["defmt"] }
16embedded-io = { version = "0.6.0", features = ["defmt-03"] } 16embedded-io = { version = "0.6.0", features = ["defmt-03"] }
17embedded-io-async = { version = "0.6.1", features = ["defmt-03"] } 17embedded-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 @@
1#![no_std]
2#![no_main]
3
4use core::net::Ipv6Addr;
5
6use defmt::{info, unwrap, warn};
7use embassy_executor::Spawner;
8use embassy_net::udp::{PacketMetadata, UdpMetadata, UdpSocket};
9use embassy_net::{IpAddress, IpEndpoint, IpListenEndpoint, Ipv6Cidr, StackResources, StaticConfigV6};
10use embassy_nrf::config::{Config, HfclkSource};
11use embassy_nrf::rng::Rng;
12use embassy_nrf::{bind_interrupts, embassy_net_802154_driver as net, peripherals, radio};
13use embassy_time::Delay;
14use embedded_hal_async::delay::DelayNs;
15use static_cell::StaticCell;
16use {defmt_rtt as _, panic_probe as _};
17
18bind_interrupts!(struct Irqs {
19 RADIO => radio::InterruptHandler<peripherals::RADIO>;
20 RNG => embassy_nrf::rng::InterruptHandler<peripherals::RNG>;
21});
22
23#[embassy_executor::task]
24async fn ieee802154_task(runner: net::Runner<'static, peripherals::RADIO>) -> ! {
25 runner.run().await
26}
27
28#[embassy_executor::task]
29async fn net_task(mut runner: embassy_net::Runner<'static, net::Device<'static>>) -> ! {
30 runner.run().await
31}
32
33#[embassy_executor::main]
34async fn main(spawner: Spawner) {
35 let mut config = Config::default();
36 // Necessary to run the radio nrf52840 v1.11 5.4.1
37 config.hfclk_source = HfclkSource::ExternalXtal;
38 let p = embassy_nrf::init(config);
39
40 let mac_addr: [u8; 8] = [2, 3, 4, 5, 6, 7, 8, 9];
41 static NRF802154_STATE: StaticCell<net::State<20, 20>> = StaticCell::new();
42 let (device, runner) = net::new(mac_addr, p.RADIO, Irqs, NRF802154_STATE.init(net::State::new()))
43 .await
44 .unwrap();
45
46 spawner.spawn(unwrap!(ieee802154_task(runner)));
47
48 // Swap these when flashing a second board
49 let peer = Ipv6Addr::new(0xfe80, 0, 0, 0, 0xd701, 0xda3f, 0x3955, 0x82a4);
50 let local = Ipv6Addr::new(0xfe80, 0, 0, 0, 0xd701, 0xda3f, 0x3955, 0x82a5);
51
52 let config = embassy_net::Config::ipv6_static(StaticConfigV6 {
53 address: Ipv6Cidr::new(local, 64),
54 gateway: None,
55 dns_servers: Default::default(),
56 });
57
58 // Generate random seed
59 let mut rng = Rng::new(p.RNG, Irqs);
60 let mut seed = [0; 8];
61 rng.blocking_fill_bytes(&mut seed);
62 let seed = u64::from_le_bytes(seed);
63
64 // Init network stack
65 static RESOURCES: StaticCell<StackResources<3>> = StaticCell::new();
66 let (stack, runner) = embassy_net::new(device, config, RESOURCES.init(StackResources::new()), seed);
67
68 spawner.spawn(unwrap!(net_task(runner)));
69
70 let mut rx_buffer = [0; 2096];
71 let mut tx_buffer = [0; 2096];
72 let mut tx_m_buffer = [PacketMetadata::EMPTY; 5];
73 let mut rx_m_buffer = [PacketMetadata::EMPTY; 5];
74
75 let mut delay = Delay;
76 loop {
77 let mut socket = UdpSocket::new(
78 stack,
79 &mut tx_m_buffer,
80 &mut rx_buffer,
81 &mut rx_m_buffer,
82 &mut tx_buffer,
83 );
84 socket
85 .bind(IpListenEndpoint {
86 addr: Some(IpAddress::Ipv6(local)),
87 port: 1234,
88 })
89 .unwrap();
90 let rep = UdpMetadata {
91 endpoint: IpEndpoint {
92 addr: IpAddress::Ipv6(peer),
93 port: 1234,
94 },
95 local_address: Some(IpAddress::Ipv6(local)),
96 meta: Default::default(),
97 };
98
99 info!("Listening on {:?} UDP:1234...", local);
100
101 let mut recv_buf = [0; 12];
102 loop {
103 delay.delay_ms(2000).await;
104 if socket.may_recv() {
105 let n = match socket.recv_from(&mut recv_buf).await {
106 Ok((0, _)) => panic!(),
107 Ok((n, _)) => n,
108 Err(e) => {
109 warn!("read error: {:?}", e);
110 break;
111 }
112 };
113 info!("Received {:02x}", &recv_buf[..n]);
114 }
115
116 info!("Sending");
117 socket.send_to(b"Hello World", rep).await.unwrap();
118 }
119 }
120}