diff options
| author | Dario Nieuwenhuis <[email protected]> | 2025-10-29 17:49:55 +0100 |
|---|---|---|
| committer | Dario Nieuwenhuis <[email protected]> | 2025-10-29 19:33:41 +0100 |
| commit | 98de11e5e3ae437676198a105ffab8c0f4977513 (patch) | |
| tree | 227599f3b7ee23d892491ccdbfe428babfaa16f2 | |
| parent | 1bbf35bdf47fa031181a8e8539f0641585672c81 (diff) | |
net-esp-hosted: add Interface trait.
| -rw-r--r-- | embassy-net-esp-hosted/src/iface.rs | 62 | ||||
| -rw-r--r-- | embassy-net-esp-hosted/src/lib.rs | 47 | ||||
| -rw-r--r-- | examples/nrf52840/src/bin/wifi_esp_hosted.rs | 16 | ||||
| -rw-r--r-- | tests/nrf/src/bin/wifi_esp_hosted_perf.rs | 15 |
4 files changed, 89 insertions, 51 deletions
diff --git a/embassy-net-esp-hosted/src/iface.rs b/embassy-net-esp-hosted/src/iface.rs new file mode 100644 index 000000000..1f57851e0 --- /dev/null +++ b/embassy-net-esp-hosted/src/iface.rs | |||
| @@ -0,0 +1,62 @@ | |||
| 1 | use embassy_time::Timer; | ||
| 2 | use embedded_hal::digital::InputPin; | ||
| 3 | use embedded_hal_async::digital::Wait; | ||
| 4 | use embedded_hal_async::spi::SpiDevice; | ||
| 5 | |||
| 6 | /// Physical interface trait for communicating with the ESP chip. | ||
| 7 | pub trait Interface { | ||
| 8 | /// Wait for the HANDSHAKE signal indicating the ESP is ready for a new transaction. | ||
| 9 | async fn wait_for_handshake(&mut self); | ||
| 10 | |||
| 11 | /// Wait for the READY signal indicating the ESP has data to send. | ||
| 12 | async fn wait_for_ready(&mut self); | ||
| 13 | |||
| 14 | /// Perform a SPI transfer, exchanging data with the ESP chip. | ||
| 15 | async fn transfer(&mut self, rx: &mut [u8], tx: &[u8]); | ||
| 16 | } | ||
| 17 | |||
| 18 | /// Standard SPI interface. | ||
| 19 | /// | ||
| 20 | /// This interface is what's implemented in the upstream `esp-hosted-fg` firmware. It uses: | ||
| 21 | /// - An `SpiDevice` for SPI communication (CS is handled by the device) | ||
| 22 | /// - A handshake pin that signals when the ESP is ready for a new transaction | ||
| 23 | /// - A ready pin that indicates when the ESP has data to send | ||
| 24 | pub struct SpiInterface<SPI, IN> { | ||
| 25 | spi: SPI, | ||
| 26 | handshake: IN, | ||
| 27 | ready: IN, | ||
| 28 | } | ||
| 29 | |||
| 30 | impl<SPI, IN> SpiInterface<SPI, IN> | ||
| 31 | where | ||
| 32 | SPI: SpiDevice, | ||
| 33 | IN: InputPin + Wait, | ||
| 34 | { | ||
| 35 | /// Create a new SpiInterface. | ||
| 36 | pub fn new(spi: SPI, handshake: IN, ready: IN) -> Self { | ||
| 37 | Self { spi, handshake, ready } | ||
| 38 | } | ||
| 39 | } | ||
| 40 | |||
| 41 | impl<SPI, IN> Interface for SpiInterface<SPI, IN> | ||
| 42 | where | ||
| 43 | SPI: SpiDevice, | ||
| 44 | IN: InputPin + Wait, | ||
| 45 | { | ||
| 46 | async fn wait_for_handshake(&mut self) { | ||
| 47 | self.handshake.wait_for_high().await.unwrap(); | ||
| 48 | } | ||
| 49 | |||
| 50 | async fn wait_for_ready(&mut self) { | ||
| 51 | self.ready.wait_for_high().await.unwrap(); | ||
| 52 | } | ||
| 53 | |||
| 54 | async fn transfer(&mut self, rx: &mut [u8], tx: &[u8]) { | ||
| 55 | self.spi.transfer(rx, tx).await.unwrap(); | ||
| 56 | |||
| 57 | // The esp-hosted firmware deasserts the HANDSHAKE pin a few us AFTER ending the SPI transfer | ||
| 58 | // If we check it again too fast, we'll see it's high from the previous transfer, and if we send it | ||
| 59 | // data it will get lost. | ||
| 60 | Timer::after_micros(100).await; | ||
| 61 | } | ||
| 62 | } | ||
diff --git a/embassy-net-esp-hosted/src/lib.rs b/embassy-net-esp-hosted/src/lib.rs index 4405d9f77..8da0cd4dc 100644 --- a/embassy-net-esp-hosted/src/lib.rs +++ b/embassy-net-esp-hosted/src/lib.rs | |||
| @@ -1,14 +1,13 @@ | |||
| 1 | #![no_std] | 1 | #![no_std] |
| 2 | #![doc = include_str!("../README.md")] | 2 | #![doc = include_str!("../README.md")] |
| 3 | #![warn(missing_docs)] | 3 | #![warn(missing_docs)] |
| 4 | #![allow(async_fn_in_trait)] | ||
| 4 | 5 | ||
| 5 | use embassy_futures::select::{Either4, select4}; | 6 | use embassy_futures::select::{Either4, select4}; |
| 6 | use embassy_net_driver_channel as ch; | 7 | use embassy_net_driver_channel as ch; |
| 7 | use embassy_net_driver_channel::driver::LinkState; | 8 | use embassy_net_driver_channel::driver::LinkState; |
| 8 | use embassy_time::{Duration, Instant, Timer}; | 9 | use embassy_time::{Duration, Instant, Timer}; |
| 9 | use embedded_hal::digital::{InputPin, OutputPin}; | 10 | use embedded_hal::digital::OutputPin; |
| 10 | use embedded_hal_async::digital::Wait; | ||
| 11 | use embedded_hal_async::spi::SpiDevice; | ||
| 12 | 11 | ||
| 13 | use crate::ioctl::{PendingIoctl, Shared}; | 12 | use crate::ioctl::{PendingIoctl, Shared}; |
| 14 | use crate::proto::{CtrlMsg, CtrlMsgPayload}; | 13 | use crate::proto::{CtrlMsg, CtrlMsgPayload}; |
| @@ -19,9 +18,11 @@ mod proto; | |||
| 19 | mod fmt; | 18 | mod fmt; |
| 20 | 19 | ||
| 21 | mod control; | 20 | mod control; |
| 21 | mod iface; | ||
| 22 | mod ioctl; | 22 | mod ioctl; |
| 23 | 23 | ||
| 24 | pub use control::*; | 24 | pub use control::*; |
| 25 | pub use iface::*; | ||
| 25 | 26 | ||
| 26 | const MTU: usize = 1514; | 27 | const MTU: usize = 1514; |
| 27 | 28 | ||
| @@ -118,20 +119,17 @@ impl State { | |||
| 118 | /// Type alias for network driver. | 119 | /// Type alias for network driver. |
| 119 | pub type NetDriver<'a> = ch::Device<'a, MTU>; | 120 | pub type NetDriver<'a> = ch::Device<'a, MTU>; |
| 120 | 121 | ||
| 121 | /// Create a new esp-hosted driver using the provided state, SPI peripheral and pins. | 122 | /// Create a new esp-hosted driver using the provided state, interface, and reset pin. |
| 122 | /// | 123 | /// |
| 123 | /// Returns a device handle for interfacing with embassy-net, a control handle for | 124 | /// Returns a device handle for interfacing with embassy-net, a control handle for |
| 124 | /// interacting with the driver, and a runner for communicating with the WiFi device. | 125 | /// interacting with the driver, and a runner for communicating with the WiFi device. |
| 125 | pub async fn new<'a, SPI, IN, OUT>( | 126 | pub async fn new<'a, I, OUT>( |
| 126 | state: &'a mut State, | 127 | state: &'a mut State, |
| 127 | spi: SPI, | 128 | iface: I, |
| 128 | handshake: IN, | ||
| 129 | ready: IN, | ||
| 130 | reset: OUT, | 129 | reset: OUT, |
| 131 | ) -> (NetDriver<'a>, Control<'a>, Runner<'a, SPI, IN, OUT>) | 130 | ) -> (NetDriver<'a>, Control<'a>, Runner<'a, I, OUT>) |
| 132 | where | 131 | where |
| 133 | SPI: SpiDevice, | 132 | I: Interface, |
| 134 | IN: InputPin + Wait, | ||
| 135 | OUT: OutputPin, | 133 | OUT: OutputPin, |
| 136 | { | 134 | { |
| 137 | let (ch_runner, device) = ch::new(&mut state.ch, ch::driver::HardwareAddress::Ethernet([0; 6])); | 135 | let (ch_runner, device) = ch::new(&mut state.ch, ch::driver::HardwareAddress::Ethernet([0; 6])); |
| @@ -142,10 +140,8 @@ where | |||
| 142 | state_ch, | 140 | state_ch, |
| 143 | shared: &state.shared, | 141 | shared: &state.shared, |
| 144 | next_seq: 1, | 142 | next_seq: 1, |
| 145 | handshake, | ||
| 146 | ready, | ||
| 147 | reset, | 143 | reset, |
| 148 | spi, | 144 | iface, |
| 149 | heartbeat_deadline: Instant::now() + HEARTBEAT_MAX_GAP, | 145 | heartbeat_deadline: Instant::now() + HEARTBEAT_MAX_GAP, |
| 150 | }; | 146 | }; |
| 151 | 147 | ||
| @@ -153,7 +149,7 @@ where | |||
| 153 | } | 149 | } |
| 154 | 150 | ||
| 155 | /// Runner for communicating with the WiFi device. | 151 | /// Runner for communicating with the WiFi device. |
| 156 | pub struct Runner<'a, SPI, IN, OUT> { | 152 | pub struct Runner<'a, I, OUT> { |
| 157 | ch: ch::Runner<'a, MTU>, | 153 | ch: ch::Runner<'a, MTU>, |
| 158 | state_ch: ch::StateRunner<'a>, | 154 | state_ch: ch::StateRunner<'a>, |
| 159 | shared: &'a Shared, | 155 | shared: &'a Shared, |
| @@ -161,16 +157,13 @@ pub struct Runner<'a, SPI, IN, OUT> { | |||
| 161 | next_seq: u16, | 157 | next_seq: u16, |
| 162 | heartbeat_deadline: Instant, | 158 | heartbeat_deadline: Instant, |
| 163 | 159 | ||
| 164 | spi: SPI, | 160 | iface: I, |
| 165 | handshake: IN, | ||
| 166 | ready: IN, | ||
| 167 | reset: OUT, | 161 | reset: OUT, |
| 168 | } | 162 | } |
| 169 | 163 | ||
| 170 | impl<'a, SPI, IN, OUT> Runner<'a, SPI, IN, OUT> | 164 | impl<'a, I, OUT> Runner<'a, I, OUT> |
| 171 | where | 165 | where |
| 172 | SPI: SpiDevice, | 166 | I: Interface, |
| 173 | IN: InputPin + Wait, | ||
| 174 | OUT: OutputPin, | 167 | OUT: OutputPin, |
| 175 | { | 168 | { |
| 176 | /// Run the packet processing. | 169 | /// Run the packet processing. |
| @@ -185,11 +178,11 @@ where | |||
| 185 | let mut rx_buf = [0u8; MAX_SPI_BUFFER_SIZE]; | 178 | let mut rx_buf = [0u8; MAX_SPI_BUFFER_SIZE]; |
| 186 | 179 | ||
| 187 | loop { | 180 | loop { |
| 188 | self.handshake.wait_for_high().await.unwrap(); | 181 | self.iface.wait_for_handshake().await; |
| 189 | 182 | ||
| 190 | let ioctl = self.shared.ioctl_wait_pending(); | 183 | let ioctl = self.shared.ioctl_wait_pending(); |
| 191 | let tx = self.ch.tx_buf(); | 184 | let tx = self.ch.tx_buf(); |
| 192 | let ev = async { self.ready.wait_for_high().await.unwrap() }; | 185 | let ev = async { self.iface.wait_for_ready().await }; |
| 193 | let hb = Timer::at(self.heartbeat_deadline); | 186 | let hb = Timer::at(self.heartbeat_deadline); |
| 194 | 187 | ||
| 195 | match select4(ioctl, tx, ev, hb).await { | 188 | match select4(ioctl, tx, ev, hb).await { |
| @@ -243,15 +236,9 @@ where | |||
| 243 | trace!("tx: {:02x}", &tx_buf[..40]); | 236 | trace!("tx: {:02x}", &tx_buf[..40]); |
| 244 | } | 237 | } |
| 245 | 238 | ||
| 246 | self.spi.transfer(&mut rx_buf, &tx_buf).await.unwrap(); | 239 | self.iface.transfer(&mut rx_buf, &tx_buf).await; |
| 247 | 240 | ||
| 248 | // The esp-hosted firmware deasserts the HANSHAKE pin a few us AFTER ending the SPI transfer | ||
| 249 | // If we check it again too fast, we'll see it's high from the previous transfer, and if we send it | ||
| 250 | // data it will get lost. | ||
| 251 | // Make sure we check it after 100us at minimum. | ||
| 252 | let delay_until = Instant::now() + Duration::from_micros(100); | ||
| 253 | self.handle_rx(&mut rx_buf); | 241 | self.handle_rx(&mut rx_buf); |
| 254 | Timer::at(delay_until).await; | ||
| 255 | } | 242 | } |
| 256 | } | 243 | } |
| 257 | 244 | ||
diff --git a/examples/nrf52840/src/bin/wifi_esp_hosted.rs b/examples/nrf52840/src/bin/wifi_esp_hosted.rs index 07752ffc4..2f9c06b56 100644 --- a/examples/nrf52840/src/bin/wifi_esp_hosted.rs +++ b/examples/nrf52840/src/bin/wifi_esp_hosted.rs | |||
| @@ -27,14 +27,12 @@ bind_interrupts!(struct Irqs { | |||
| 27 | async fn wifi_task( | 27 | async fn wifi_task( |
| 28 | runner: hosted::Runner< | 28 | runner: hosted::Runner< |
| 29 | 'static, | 29 | 'static, |
| 30 | ExclusiveDevice<Spim<'static>, Output<'static>, Delay>, | 30 | hosted::SpiInterface<ExclusiveDevice<Spim<'static>, Output<'static>, Delay>, Input<'static>>, |
| 31 | Input<'static>, | ||
| 32 | Output<'static>, | 31 | Output<'static>, |
| 33 | >, | 32 | >, |
| 34 | ) -> ! { | 33 | ) -> ! { |
| 35 | runner.run().await | 34 | runner.run().await |
| 36 | } | 35 | } |
| 37 | |||
| 38 | #[embassy_executor::task] | 36 | #[embassy_executor::task] |
| 39 | async fn net_task(mut runner: embassy_net::Runner<'static, hosted::NetDriver<'static>>) -> ! { | 37 | async fn net_task(mut runner: embassy_net::Runner<'static, hosted::NetDriver<'static>>) -> ! { |
| 40 | runner.run().await | 38 | runner.run().await |
| @@ -60,15 +58,11 @@ async fn main(spawner: Spawner) { | |||
| 60 | let spi = spim::Spim::new(p.SPI3, Irqs, sck, miso, mosi, config); | 58 | let spi = spim::Spim::new(p.SPI3, Irqs, sck, miso, mosi, config); |
| 61 | let spi = ExclusiveDevice::new(spi, cs, Delay); | 59 | let spi = ExclusiveDevice::new(spi, cs, Delay); |
| 62 | 60 | ||
| 61 | let iface = hosted::SpiInterface::new(spi, handshake, ready); | ||
| 62 | |||
| 63 | static ESP_STATE: StaticCell<embassy_net_esp_hosted::State> = StaticCell::new(); | 63 | static ESP_STATE: StaticCell<embassy_net_esp_hosted::State> = StaticCell::new(); |
| 64 | let (device, mut control, runner) = embassy_net_esp_hosted::new( | 64 | let (device, mut control, runner) = |
| 65 | ESP_STATE.init(embassy_net_esp_hosted::State::new()), | 65 | embassy_net_esp_hosted::new(ESP_STATE.init(embassy_net_esp_hosted::State::new()), iface, reset).await; |
| 66 | spi, | ||
| 67 | handshake, | ||
| 68 | ready, | ||
| 69 | reset, | ||
| 70 | ) | ||
| 71 | .await; | ||
| 72 | 66 | ||
| 73 | spawner.spawn(unwrap!(wifi_task(runner))); | 67 | spawner.spawn(unwrap!(wifi_task(runner))); |
| 74 | 68 | ||
diff --git a/tests/nrf/src/bin/wifi_esp_hosted_perf.rs b/tests/nrf/src/bin/wifi_esp_hosted_perf.rs index 091a70ce9..ac082dbb8 100644 --- a/tests/nrf/src/bin/wifi_esp_hosted_perf.rs +++ b/tests/nrf/src/bin/wifi_esp_hosted_perf.rs | |||
| @@ -29,8 +29,7 @@ const WIFI_PASSWORD: &str = "V8YxhKt5CdIAJFud"; | |||
| 29 | async fn wifi_task( | 29 | async fn wifi_task( |
| 30 | runner: hosted::Runner< | 30 | runner: hosted::Runner< |
| 31 | 'static, | 31 | 'static, |
| 32 | ExclusiveDevice<Spim<'static>, Output<'static>, Delay>, | 32 | hosted::SpiInterface<ExclusiveDevice<Spim<'static>, Output<'static>, Delay>, Input<'static>>, |
| 33 | Input<'static>, | ||
| 34 | Output<'static>, | 33 | Output<'static>, |
| 35 | >, | 34 | >, |
| 36 | ) -> ! { | 35 | ) -> ! { |
| @@ -64,15 +63,11 @@ async fn main(spawner: Spawner) { | |||
| 64 | let spi = spim::Spim::new(p.SPI3, Irqs, sck, miso, mosi, config); | 63 | let spi = spim::Spim::new(p.SPI3, Irqs, sck, miso, mosi, config); |
| 65 | let spi = ExclusiveDevice::new(spi, cs, Delay); | 64 | let spi = ExclusiveDevice::new(spi, cs, Delay); |
| 66 | 65 | ||
| 66 | let iface = hosted::SpiInterface::new(spi, handshake, ready); | ||
| 67 | |||
| 67 | static STATE: StaticCell<embassy_net_esp_hosted::State> = StaticCell::new(); | 68 | static STATE: StaticCell<embassy_net_esp_hosted::State> = StaticCell::new(); |
| 68 | let (device, mut control, runner) = embassy_net_esp_hosted::new( | 69 | let (device, mut control, runner) = |
| 69 | STATE.init(embassy_net_esp_hosted::State::new()), | 70 | embassy_net_esp_hosted::new(STATE.init(embassy_net_esp_hosted::State::new()), iface, reset).await; |
| 70 | spi, | ||
| 71 | handshake, | ||
| 72 | ready, | ||
| 73 | reset, | ||
| 74 | ) | ||
| 75 | .await; | ||
| 76 | 71 | ||
| 77 | spawner.spawn(unwrap!(wifi_task(runner))); | 72 | spawner.spawn(unwrap!(wifi_task(runner))); |
| 78 | 73 | ||
