Skip to content

Commit 553bd2a

Browse files
committed
feat: embassy-net driver for nrf radio
1 parent 56572ef commit 553bd2a

File tree

5 files changed

+291
-1
lines changed

5 files changed

+291
-1
lines changed

embassy-net-nrf-802154/Cargo.toml

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
[package]
2+
name = "embassy-net-nrf-802154"
3+
version = "0.1.0"
4+
edition = "2021"
5+
description = "embassy-net driver for nrf52 802.15.4 radio"
6+
keywords = ["embedded", "802.15.4", "embassy-net", "embedded-hal-async"]
7+
categories = ["embedded", "hardware-support", "no-std", "network-programming", "asynchronous"]
8+
license = "MIT OR Apache-2.0"
9+
repository = "https://github.com/embassy-rs/embassy"
10+
documentation = "https://docs.embassy.dev/embassy-net-nrf-802154"
11+
12+
[package.metadata.embassy_docs]
13+
src_base = "https://github.com/embassy-rs/embassy/blob/embassy-net-nrf-802154-v$VERSION/embassy-net-nrf-802154/src/"
14+
src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-net-nrf-802154/src/"
15+
features = ["defmt","nrf52840"]
16+
target = "thumbv7em-none-eabi"
17+
18+
[package.metadata.docs.rs]
19+
features = ["defmt","nrf52840"]
20+
21+
[dependencies]
22+
embassy-time = { version = "0.4.0", path = "../embassy-time" }
23+
embassy-futures = { version = "0.1.1", path = "../embassy-futures"}
24+
embassy-net-driver-channel = { version = "0.3.0", path = "../embassy-net-driver-channel"}
25+
26+
embassy-nrf = { version = "0.3.1", path = "../embassy-nrf"}
27+
28+
[features]
29+
defmt = ["embassy-time/defmt", "embassy-futures/defmt", "embassy-net-driver-channel/defmt", "embassy-nrf/defmt"]
30+
31+
nrf51 = ["embassy-nrf/nrf51"]
32+
33+
nrf52805 = ["embassy-nrf/nrf52805"]
34+
nrf52810 = ["embassy-nrf/nrf52810"]
35+
nrf52811 = ["embassy-nrf/nrf52811"]
36+
nrf52820 = ["embassy-nrf/nrf52820"]
37+
nrf52832 = ["embassy-nrf/nrf52832"]
38+
nrf52833 = ["embassy-nrf/nrf52833"]
39+
nrf52840 = ["embassy-nrf/nrf52840"]

embassy-net-nrf-802154/README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# nrf52 802.15.4 radio `embassy-net` integration
2+
3+
[`embassy-net`](https://crates.io/crates/embassy-net) driver for nrf52 802.15.4 radio.
4+
5+
See [`examples`](https://github.com/embassy-rs/embassy/tree/main/examples/nrf52840/src/bin) directory `sixlowpan` example.

embassy-net-nrf-802154/src/lib.rs

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
#![no_std]
2+
#![warn(missing_docs)]
3+
#![doc = include_str!("../README.md")]
4+
5+
#[cfg(not(any(
6+
feature = "nrf51",
7+
feature = "nrf52805",
8+
feature = "nrf52810",
9+
feature = "nrf52811",
10+
feature = "nrf52820",
11+
feature = "nrf52832",
12+
feature = "nrf52833",
13+
feature = "nrf52840",
14+
)))]
15+
compile_error!(
16+
"No chip feature activated. You must activate exactly one of the following features:
17+
nrf51,
18+
nrf52805,
19+
nrf52810,
20+
nrf52811,
21+
nrf52820,
22+
nrf52832,
23+
nrf52833,
24+
nrf52840,
25+
"
26+
);
27+
28+
use embassy_futures::select::{select3, Either3};
29+
use embassy_net_driver_channel::driver::LinkState;
30+
use embassy_net_driver_channel::{self as ch};
31+
use embassy_nrf::radio::ieee802154::{Packet, Radio};
32+
use embassy_nrf::radio::InterruptHandler;
33+
use embassy_nrf::{self as nrf, interrupt};
34+
use embassy_time::{Duration, Ticker};
35+
36+
/// MTU for the nrf radio.
37+
pub const MTU: usize = Packet::CAPACITY as usize;
38+
39+
/// embassy-net device for the driver.
40+
pub type Device<'d> = embassy_net_driver_channel::Device<'d, MTU>;
41+
42+
/// Internal state for the embassy-net driver.
43+
pub struct State<const N_RX: usize, const N_TX: usize> {
44+
ch_state: ch::State<MTU, N_RX, N_TX>,
45+
}
46+
47+
impl<const N_RX: usize, const N_TX: usize> State<N_RX, N_TX> {
48+
/// Create a new `State`.
49+
pub const fn new() -> Self {
50+
Self {
51+
ch_state: ch::State::new(),
52+
}
53+
}
54+
}
55+
56+
/// Background runner for the driver.
57+
///
58+
/// You must call `.run()` in a background task for the driver to operate.
59+
pub struct Runner<'d, T: nrf::radio::Instance> {
60+
radio: nrf::radio::ieee802154::Radio<'d, T>,
61+
ch: ch::Runner<'d, MTU>,
62+
}
63+
64+
impl<'d, T: nrf::radio::Instance> Runner<'d, T> {
65+
/// Drives the radio. Needs to run to use the driver.
66+
pub async fn run(mut self) -> ! {
67+
let (state_chan, mut rx_chan, mut tx_chan) = self.ch.split();
68+
let mut tick = Ticker::every(Duration::from_millis(500));
69+
let mut packet = Packet::new();
70+
state_chan.set_link_state(LinkState::Up);
71+
loop {
72+
match select3(
73+
async {
74+
let rx_buf = rx_chan.rx_buf().await;
75+
self.radio.receive(&mut packet).await.ok().map(|_| rx_buf)
76+
},
77+
tx_chan.tx_buf(),
78+
tick.next(),
79+
)
80+
.await
81+
{
82+
Either3::First(Some(rx_buf)) => {
83+
let len = rx_buf.len().min(packet.len() as usize);
84+
(&mut rx_buf[..len]).copy_from_slice(&*packet);
85+
rx_chan.rx_done(len);
86+
}
87+
Either3::Second(tx_buf) => {
88+
let len = tx_buf.len().min(Packet::CAPACITY as usize);
89+
packet.copy_from_slice(&tx_buf[..len]);
90+
self.radio.try_send(&mut packet).await.ok().unwrap();
91+
tx_chan.tx_done();
92+
}
93+
_ => {}
94+
}
95+
}
96+
}
97+
}
98+
99+
/// Make sure to use `HfclkSource::ExternalXtal` as the `hfclk_source`
100+
/// to use the radio (nrf52840 product spec v1.11 5.4.1)
101+
/// ```
102+
/// # use embassy_nrf::config::*;
103+
/// let mut config = Config::default();
104+
/// config.hfclk_source = HfclkSource::ExternalXtal;
105+
/// ```
106+
pub async fn new<'a, const N_RX: usize, const N_TX: usize, T: nrf::radio::Instance, Irq>(
107+
mac_addr: [u8; 8],
108+
radio: nrf::Peri<'a, T>,
109+
irq: Irq,
110+
state: &'a mut State<N_RX, N_TX>,
111+
) -> Result<(Device<'a>, Runner<'a, T>), ()>
112+
where
113+
Irq: interrupt::typelevel::Binding<T::Interrupt, InterruptHandler<T>> + 'a,
114+
{
115+
let radio = Radio::new(radio, irq);
116+
117+
let (runner, device) = ch::new(&mut state.ch_state, ch::driver::HardwareAddress::Ieee802154(mac_addr));
118+
119+
Ok((device, Runner { ch: runner, radio }))
120+
}

examples/nrf52840/Cargo.toml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,15 @@ embassy-sync = { version = "0.7.0", path = "../../embassy-sync", features = ["de
1010
embassy-executor = { version = "0.7.0", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "executor-interrupt", "defmt"] }
1111
embassy-time = { version = "0.4.0", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime"] }
1212
embassy-nrf = { version = "0.3.1", path = "../../embassy-nrf", features = ["defmt", "nrf52840", "time-driver-rtc1", "gpiote", "unstable-pac", "time"] }
13-
embassy-net = { version = "0.7.0", path = "../../embassy-net", features = ["defmt", "tcp", "dhcpv4", "medium-ethernet"] }
13+
embassy-net = { version = "0.7.0", path = "../../embassy-net", features = ["defmt", "tcp", "dhcpv4", "medium-ethernet", "udp","medium-ieee802154", "proto-ipv6"] }
1414
embassy-usb = { version = "0.4.0", path = "../../embassy-usb", features = ["defmt"] }
1515
embedded-io = { version = "0.6.0", features = ["defmt-03"] }
1616
embedded-io-async = { version = "0.6.1", features = ["defmt-03"] }
1717
embassy-net-esp-hosted = { version = "0.2.0", path = "../../embassy-net-esp-hosted", features = ["defmt"] }
1818
embassy-net-enc28j60 = { version = "0.2.0", path = "../../embassy-net-enc28j60", features = ["defmt"] }
1919

20+
embassy-net-nrf-802154 = { version = "0.1.0", path = "../../embassy-net-nrf-802154", features = ["defmt", "nrf52840"]}
21+
2022
defmt = "1.0.1"
2123
defmt-rtt = "1.0.0"
2224

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
#![no_std]
2+
#![no_main]
3+
4+
use core::net::Ipv6Addr;
5+
6+
use defmt::{info, unwrap, warn};
7+
use embassy_executor::Spawner;
8+
use embassy_net::udp::{PacketMetadata, UdpMetadata, UdpSocket};
9+
use embassy_net::{IpAddress, IpEndpoint, IpListenEndpoint, Ipv6Cidr, StackResources, StaticConfigV6};
10+
use embassy_nrf::config::{Config, HfclkSource};
11+
use embassy_nrf::rng::Rng;
12+
use embassy_nrf::{bind_interrupts, peripherals, radio};
13+
use embassy_time::Delay;
14+
use embedded_hal_async::delay::DelayNs;
15+
use static_cell::StaticCell;
16+
use {defmt_rtt as _, panic_probe as _};
17+
18+
bind_interrupts!(struct Irqs {
19+
RADIO => radio::InterruptHandler<peripherals::RADIO>;
20+
RNG => embassy_nrf::rng::InterruptHandler<peripherals::RNG>;
21+
});
22+
23+
#[embassy_executor::task]
24+
async fn ieee802154_task(runner: embassy_net_nrf_802154::Runner<'static, peripherals::RADIO>) -> ! {
25+
runner.run().await
26+
}
27+
28+
#[embassy_executor::task]
29+
async fn net_task(mut runner: embassy_net::Runner<'static, embassy_net_nrf_802154::Device<'static>>) -> ! {
30+
runner.run().await
31+
}
32+
33+
#[embassy_executor::main]
34+
async 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<embassy_net_nrf_802154::State<20, 20>> = StaticCell::new();
42+
let (device, runner) = embassy_net_nrf_802154::new(
43+
mac_addr,
44+
p.RADIO,
45+
Irqs,
46+
NRF802154_STATE.init(embassy_net_nrf_802154::State::new()),
47+
)
48+
.await
49+
.unwrap();
50+
51+
unwrap!(spawner.spawn(ieee802154_task(runner)));
52+
53+
let peer = Ipv6Addr::new(0xfe80, 0, 0, 0, 0xd701, 0xda3f, 0x3955, 0x82a4);
54+
let local = Ipv6Addr::new(0xfe80, 0, 0, 0, 0xd701, 0xda3f, 0x3955, 0x82a5);
55+
56+
let config = embassy_net::Config::ipv6_static(StaticConfigV6 {
57+
address: Ipv6Cidr::new(local, 64),
58+
gateway: None,
59+
dns_servers: Default::default(),
60+
});
61+
62+
// Generate random seed
63+
let mut rng = Rng::new(p.RNG, Irqs);
64+
let mut seed = [0; 8];
65+
rng.blocking_fill_bytes(&mut seed);
66+
let seed = u64::from_le_bytes(seed);
67+
68+
// Init network stack
69+
static RESOURCES: StaticCell<StackResources<3>> = StaticCell::new();
70+
let (stack, runner) = embassy_net::new(device, config, RESOURCES.init(StackResources::new()), seed);
71+
72+
unwrap!(spawner.spawn(net_task(runner)));
73+
74+
let mut rx_buffer = [0; 2096];
75+
let mut tx_buffer = [0; 2096];
76+
let mut tx_m_buffer = [PacketMetadata::EMPTY; 5];
77+
let mut rx_m_buffer = [PacketMetadata::EMPTY; 5];
78+
79+
let mut delay = Delay;
80+
loop {
81+
let mut socket = UdpSocket::new(
82+
stack,
83+
&mut tx_m_buffer,
84+
&mut rx_buffer,
85+
&mut rx_m_buffer,
86+
&mut tx_buffer,
87+
);
88+
socket
89+
.bind(IpListenEndpoint {
90+
addr: Some(IpAddress::Ipv6(local)),
91+
port: 1234,
92+
})
93+
.unwrap();
94+
let rep = UdpMetadata {
95+
endpoint: IpEndpoint {
96+
addr: IpAddress::Ipv6(peer),
97+
port: 1234,
98+
},
99+
local_address: Some(IpAddress::Ipv6(local)),
100+
meta: Default::default(),
101+
};
102+
103+
info!("Listening on {:?} UDP:1234...", local);
104+
105+
let mut recv_buf = [0; 12];
106+
loop {
107+
delay.delay_ms(2000).await;
108+
if socket.may_recv() {
109+
let n = match socket.recv_from(&mut recv_buf).await {
110+
Ok((0, _)) => panic!(),
111+
Ok((n, _)) => n,
112+
Err(e) => {
113+
warn!("read error: {:?}", e);
114+
break;
115+
}
116+
};
117+
info!("Received {:02x}", &recv_buf[..n]);
118+
}
119+
120+
info!("Sending");
121+
socket.send_to(b"Hello World", rep).await.unwrap();
122+
}
123+
}
124+
}

0 commit comments

Comments
 (0)