aboutsummaryrefslogtreecommitdiff
path: root/embassy-net-esp-hosted
diff options
context:
space:
mode:
authorDario Nieuwenhuis <[email protected]>2025-10-29 17:49:55 +0100
committerDario Nieuwenhuis <[email protected]>2025-10-29 19:33:41 +0100
commit98de11e5e3ae437676198a105ffab8c0f4977513 (patch)
tree227599f3b7ee23d892491ccdbfe428babfaa16f2 /embassy-net-esp-hosted
parent1bbf35bdf47fa031181a8e8539f0641585672c81 (diff)
net-esp-hosted: add Interface trait.
Diffstat (limited to 'embassy-net-esp-hosted')
-rw-r--r--embassy-net-esp-hosted/src/iface.rs62
-rw-r--r--embassy-net-esp-hosted/src/lib.rs47
2 files changed, 79 insertions, 30 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 @@
1use embassy_time::Timer;
2use embedded_hal::digital::InputPin;
3use embedded_hal_async::digital::Wait;
4use embedded_hal_async::spi::SpiDevice;
5
6/// Physical interface trait for communicating with the ESP chip.
7pub 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
24pub struct SpiInterface<SPI, IN> {
25 spi: SPI,
26 handshake: IN,
27 ready: IN,
28}
29
30impl<SPI, IN> SpiInterface<SPI, IN>
31where
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
41impl<SPI, IN> Interface for SpiInterface<SPI, IN>
42where
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
5use embassy_futures::select::{Either4, select4}; 6use embassy_futures::select::{Either4, select4};
6use embassy_net_driver_channel as ch; 7use embassy_net_driver_channel as ch;
7use embassy_net_driver_channel::driver::LinkState; 8use embassy_net_driver_channel::driver::LinkState;
8use embassy_time::{Duration, Instant, Timer}; 9use embassy_time::{Duration, Instant, Timer};
9use embedded_hal::digital::{InputPin, OutputPin}; 10use embedded_hal::digital::OutputPin;
10use embedded_hal_async::digital::Wait;
11use embedded_hal_async::spi::SpiDevice;
12 11
13use crate::ioctl::{PendingIoctl, Shared}; 12use crate::ioctl::{PendingIoctl, Shared};
14use crate::proto::{CtrlMsg, CtrlMsgPayload}; 13use crate::proto::{CtrlMsg, CtrlMsgPayload};
@@ -19,9 +18,11 @@ mod proto;
19mod fmt; 18mod fmt;
20 19
21mod control; 20mod control;
21mod iface;
22mod ioctl; 22mod ioctl;
23 23
24pub use control::*; 24pub use control::*;
25pub use iface::*;
25 26
26const MTU: usize = 1514; 27const MTU: usize = 1514;
27 28
@@ -118,20 +119,17 @@ impl State {
118/// Type alias for network driver. 119/// Type alias for network driver.
119pub type NetDriver<'a> = ch::Device<'a, MTU>; 120pub 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.
125pub async fn new<'a, SPI, IN, OUT>( 126pub 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>)
132where 131where
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.
156pub struct Runner<'a, SPI, IN, OUT> { 152pub 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
170impl<'a, SPI, IN, OUT> Runner<'a, SPI, IN, OUT> 164impl<'a, I, OUT> Runner<'a, I, OUT>
171where 165where
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