mirror of
https://github.com/embassy-rs/embassy.git
synced 2025-09-27 12:20:37 +00:00
Merge pull request #4304 from Remmirad/embassy-net-nrf-802154
embassy-net driver for nrf52 802.15.4 radio
This commit is contained in:
commit
a6cd24907a
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
@ -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" }
|
||||
|
@ -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).
|
||||
|
96
embassy-nrf/src/embassy_net_802154_driver.rs
Normal file
96
embassy-nrf/src/embassy_net_802154_driver.rs
Normal 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 }))
|
||||
}
|
@ -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;
|
||||
|
@ -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"] }
|
||||
|
120
examples/nrf52840/src/bin/sixlowpan.rs
Normal file
120
examples/nrf52840/src/bin/sixlowpan.rs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user