From 98de11e5e3ae437676198a105ffab8c0f4977513 Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Wed, 29 Oct 2025 17:49:55 +0100 Subject: net-esp-hosted: add Interface trait. --- embassy-net-esp-hosted/src/iface.rs | 62 +++++++++++++++++++++++++++++++++++++ embassy-net-esp-hosted/src/lib.rs | 47 ++++++++++------------------ 2 files changed, 79 insertions(+), 30 deletions(-) create mode 100644 embassy-net-esp-hosted/src/iface.rs (limited to 'embassy-net-esp-hosted') 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 @@ +use embassy_time::Timer; +use embedded_hal::digital::InputPin; +use embedded_hal_async::digital::Wait; +use embedded_hal_async::spi::SpiDevice; + +/// Physical interface trait for communicating with the ESP chip. +pub trait Interface { + /// Wait for the HANDSHAKE signal indicating the ESP is ready for a new transaction. + async fn wait_for_handshake(&mut self); + + /// Wait for the READY signal indicating the ESP has data to send. + async fn wait_for_ready(&mut self); + + /// Perform a SPI transfer, exchanging data with the ESP chip. + async fn transfer(&mut self, rx: &mut [u8], tx: &[u8]); +} + +/// Standard SPI interface. +/// +/// This interface is what's implemented in the upstream `esp-hosted-fg` firmware. It uses: +/// - An `SpiDevice` for SPI communication (CS is handled by the device) +/// - A handshake pin that signals when the ESP is ready for a new transaction +/// - A ready pin that indicates when the ESP has data to send +pub struct SpiInterface { + spi: SPI, + handshake: IN, + ready: IN, +} + +impl SpiInterface +where + SPI: SpiDevice, + IN: InputPin + Wait, +{ + /// Create a new SpiInterface. + pub fn new(spi: SPI, handshake: IN, ready: IN) -> Self { + Self { spi, handshake, ready } + } +} + +impl Interface for SpiInterface +where + SPI: SpiDevice, + IN: InputPin + Wait, +{ + async fn wait_for_handshake(&mut self) { + self.handshake.wait_for_high().await.unwrap(); + } + + async fn wait_for_ready(&mut self) { + self.ready.wait_for_high().await.unwrap(); + } + + async fn transfer(&mut self, rx: &mut [u8], tx: &[u8]) { + self.spi.transfer(rx, tx).await.unwrap(); + + // The esp-hosted firmware deasserts the HANDSHAKE pin a few us AFTER ending the SPI transfer + // If we check it again too fast, we'll see it's high from the previous transfer, and if we send it + // data it will get lost. + Timer::after_micros(100).await; + } +} 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 @@ #![no_std] #![doc = include_str!("../README.md")] #![warn(missing_docs)] +#![allow(async_fn_in_trait)] use embassy_futures::select::{Either4, select4}; use embassy_net_driver_channel as ch; use embassy_net_driver_channel::driver::LinkState; use embassy_time::{Duration, Instant, Timer}; -use embedded_hal::digital::{InputPin, OutputPin}; -use embedded_hal_async::digital::Wait; -use embedded_hal_async::spi::SpiDevice; +use embedded_hal::digital::OutputPin; use crate::ioctl::{PendingIoctl, Shared}; use crate::proto::{CtrlMsg, CtrlMsgPayload}; @@ -19,9 +18,11 @@ mod proto; mod fmt; mod control; +mod iface; mod ioctl; pub use control::*; +pub use iface::*; const MTU: usize = 1514; @@ -118,20 +119,17 @@ impl State { /// Type alias for network driver. pub type NetDriver<'a> = ch::Device<'a, MTU>; -/// Create a new esp-hosted driver using the provided state, SPI peripheral and pins. +/// Create a new esp-hosted driver using the provided state, interface, and reset pin. /// /// Returns a device handle for interfacing with embassy-net, a control handle for /// interacting with the driver, and a runner for communicating with the WiFi device. -pub async fn new<'a, SPI, IN, OUT>( +pub async fn new<'a, I, OUT>( state: &'a mut State, - spi: SPI, - handshake: IN, - ready: IN, + iface: I, reset: OUT, -) -> (NetDriver<'a>, Control<'a>, Runner<'a, SPI, IN, OUT>) +) -> (NetDriver<'a>, Control<'a>, Runner<'a, I, OUT>) where - SPI: SpiDevice, - IN: InputPin + Wait, + I: Interface, OUT: OutputPin, { let (ch_runner, device) = ch::new(&mut state.ch, ch::driver::HardwareAddress::Ethernet([0; 6])); @@ -142,10 +140,8 @@ where state_ch, shared: &state.shared, next_seq: 1, - handshake, - ready, reset, - spi, + iface, heartbeat_deadline: Instant::now() + HEARTBEAT_MAX_GAP, }; @@ -153,7 +149,7 @@ where } /// Runner for communicating with the WiFi device. -pub struct Runner<'a, SPI, IN, OUT> { +pub struct Runner<'a, I, OUT> { ch: ch::Runner<'a, MTU>, state_ch: ch::StateRunner<'a>, shared: &'a Shared, @@ -161,16 +157,13 @@ pub struct Runner<'a, SPI, IN, OUT> { next_seq: u16, heartbeat_deadline: Instant, - spi: SPI, - handshake: IN, - ready: IN, + iface: I, reset: OUT, } -impl<'a, SPI, IN, OUT> Runner<'a, SPI, IN, OUT> +impl<'a, I, OUT> Runner<'a, I, OUT> where - SPI: SpiDevice, - IN: InputPin + Wait, + I: Interface, OUT: OutputPin, { /// Run the packet processing. @@ -185,11 +178,11 @@ where let mut rx_buf = [0u8; MAX_SPI_BUFFER_SIZE]; loop { - self.handshake.wait_for_high().await.unwrap(); + self.iface.wait_for_handshake().await; let ioctl = self.shared.ioctl_wait_pending(); let tx = self.ch.tx_buf(); - let ev = async { self.ready.wait_for_high().await.unwrap() }; + let ev = async { self.iface.wait_for_ready().await }; let hb = Timer::at(self.heartbeat_deadline); match select4(ioctl, tx, ev, hb).await { @@ -243,15 +236,9 @@ where trace!("tx: {:02x}", &tx_buf[..40]); } - self.spi.transfer(&mut rx_buf, &tx_buf).await.unwrap(); + self.iface.transfer(&mut rx_buf, &tx_buf).await; - // The esp-hosted firmware deasserts the HANSHAKE pin a few us AFTER ending the SPI transfer - // If we check it again too fast, we'll see it's high from the previous transfer, and if we send it - // data it will get lost. - // Make sure we check it after 100us at minimum. - let delay_until = Instant::now() + Duration::from_micros(100); self.handle_rx(&mut rx_buf); - Timer::at(delay_until).await; } } -- cgit