diff --git a/embassy-net-w5500/Cargo.toml b/embassy-net-w5500/Cargo.toml new file mode 100644 index 000000000..1921e812d --- /dev/null +++ b/embassy-net-w5500/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "embassy-net-w5500" +version = "0.1.0" +description = "embassy-net driver for the W5500 ethernet chip" +keywords = ["embedded", "w5500", "embassy-net", "embedded-hal-async", "ethernet", "async"] +categories = ["embedded", "hardware-support", "no-std", "network-programming", "async"] +license = "MIT OR Apache-2.0" +edition = "2021" + +[dependencies] +embedded-hal = { version = "1.0.0-alpha.10" } +embedded-hal-async = { version = "=0.2.0-alpha.1" } +embassy-net-driver-channel = { version = "0.1.0" } +embassy-time = { version = "0.1.0" } +embassy-futures = { version = "0.1.0" } +defmt = { version = "0.3", optional = true } + +[patch.crates-io] +embassy-executor = { git = "https://github.com/embassy-rs/embassy", rev = "e179e7cf85810f0aa7ef8027d8d48f6d21f64dac" } +embassy-time = { git = "https://github.com/embassy-rs/embassy", rev = "e179e7cf85810f0aa7ef8027d8d48f6d21f64dac" } +embassy-futures = { git = "https://github.com/embassy-rs/embassy", rev = "e179e7cf85810f0aa7ef8027d8d48f6d21f64dac" } +embassy-sync = { git = "https://github.com/embassy-rs/embassy", rev = "e179e7cf85810f0aa7ef8027d8d48f6d21f64dac" } +embassy-rp = { git = "https://github.com/embassy-rs/embassy", rev = "e179e7cf85810f0aa7ef8027d8d48f6d21f64dac" } +embassy-net = { git = "https://github.com/embassy-rs/embassy", rev = "e179e7cf85810f0aa7ef8027d8d48f6d21f64dac" } +embassy-net-driver = { git = "https://github.com/embassy-rs/embassy", rev = "e179e7cf85810f0aa7ef8027d8d48f6d21f64dac" } +embassy-net-driver-channel = { git = "https://github.com/embassy-rs/embassy", rev = "e179e7cf85810f0aa7ef8027d8d48f6d21f64dac" } diff --git a/embassy-net-w5500/README.md b/embassy-net-w5500/README.md new file mode 100644 index 000000000..9eaf4b700 --- /dev/null +++ b/embassy-net-w5500/README.md @@ -0,0 +1,7 @@ +# WIZnet W5500 `embassy-net` integration + +[`embassy-net`](https://crates.io/crates/embassy-net) integration for the WIZnet W5500 SPI ethernet chip, operating in MACRAW mode. + +Supports any SPI driver implementing [`embedded-hal-async`](https://crates.io/crates/embedded-hal-async) + +See [`examples`](https://github.com/kalkyl/embassy-net-w5500/tree/main/examples) directory for usage examples with the rp2040 [`WIZnet W5500-EVB-Pico`](https://www.wiznet.io/product-item/w5500-evb-pico/) module. \ No newline at end of file diff --git a/embassy-net-w5500/src/device.rs b/embassy-net-w5500/src/device.rs new file mode 100644 index 000000000..8158bc98e --- /dev/null +++ b/embassy-net-w5500/src/device.rs @@ -0,0 +1,144 @@ +use crate::socket; +use crate::spi::SpiInterface; +use embedded_hal_async::spi::SpiDevice; + +pub const MODE: u16 = 0x00; +pub const MAC: u16 = 0x09; +pub const SOCKET_INTR: u16 = 0x18; +pub const PHY_CFG: u16 = 0x2E; + +#[repr(u8)] +pub enum RegisterBlock { + Common = 0x00, + Socket0 = 0x01, + TxBuf = 0x02, + RxBuf = 0x03, +} + +/// W5500 in MACRAW mode +#[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct W5500 { + bus: SpiInterface, +} + +impl W5500 { + /// Create and initialize the W5500 driver + pub async fn new(spi: SPI, mac_addr: [u8; 6]) -> Result, SPI::Error> { + let mut bus = SpiInterface(spi); + // Reset device + bus.write_frame(RegisterBlock::Common, MODE, &[0x80]) + .await?; + + // Enable interrupt pin + bus.write_frame(RegisterBlock::Common, SOCKET_INTR, &[0x01]) + .await?; + // Enable receive interrupt + bus.write_frame( + RegisterBlock::Socket0, + socket::SOCKET_INTR_MASK, + &[socket::Interrupt::Receive as u8], + ) + .await?; + + // Set MAC address + bus.write_frame(RegisterBlock::Common, MAC, &mac_addr) + .await?; + + // Set the raw socket RX/TX buffer sizes to 16KB + bus.write_frame(RegisterBlock::Socket0, socket::TXBUF_SIZE, &[16]) + .await?; + bus.write_frame(RegisterBlock::Socket0, socket::RXBUF_SIZE, &[16]) + .await?; + + // MACRAW mode with MAC filtering. + let mode: u8 = (1 << 2) | (1 << 7); + bus.write_frame(RegisterBlock::Socket0, socket::MODE, &[mode]) + .await?; + socket::command(&mut bus, socket::Command::Open).await?; + + Ok(Self { bus }) + } + + /// Read bytes from the RX buffer. Returns the number of bytes read. + async fn read_bytes(&mut self, buffer: &mut [u8], offset: u16) -> Result { + let rx_size = socket::get_rx_size(&mut self.bus).await? as usize; + + let read_buffer = if rx_size > buffer.len() + offset as usize { + buffer + } else { + &mut buffer[..rx_size - offset as usize] + }; + + let read_ptr = socket::get_rx_read_ptr(&mut self.bus) + .await? + .wrapping_add(offset); + self.bus + .read_frame(RegisterBlock::RxBuf, read_ptr, read_buffer) + .await?; + socket::set_rx_read_ptr( + &mut self.bus, + read_ptr.wrapping_add(read_buffer.len() as u16), + ) + .await?; + + Ok(read_buffer.len()) + } + + /// Read an ethernet frame from the device. Returns the number of bytes read. + pub async fn read_frame(&mut self, frame: &mut [u8]) -> Result { + let rx_size = socket::get_rx_size(&mut self.bus).await? as usize; + if rx_size == 0 { + return Ok(0); + } + + socket::reset_interrupt(&mut self.bus, socket::Interrupt::Receive).await?; + + // First two bytes gives the size of the received ethernet frame + let expected_frame_size: usize = { + let mut frame_bytes = [0u8; 2]; + assert!(self.read_bytes(&mut frame_bytes[..], 0).await? == 2); + u16::from_be_bytes(frame_bytes) as usize - 2 + }; + + // Read the ethernet frame + let read_buffer = if frame.len() > expected_frame_size { + &mut frame[..expected_frame_size] + } else { + frame + }; + + let recvd_frame_size = self.read_bytes(read_buffer, 2).await?; + + // Register RX as completed + socket::command(&mut self.bus, socket::Command::Receive).await?; + + // If the whole frame wasn't read, drop it + if recvd_frame_size < expected_frame_size { + Ok(0) + } else { + Ok(recvd_frame_size) + } + } + + /// Write an ethernet frame to the device. Returns number of bytes written + pub async fn write_frame(&mut self, frame: &[u8]) -> Result { + while socket::get_tx_free_size(&mut self.bus).await? < frame.len() as u16 {} + let write_ptr = socket::get_tx_write_ptr(&mut self.bus).await?; + self.bus + .write_frame(RegisterBlock::TxBuf, write_ptr, frame) + .await?; + socket::set_tx_write_ptr(&mut self.bus, write_ptr.wrapping_add(frame.len() as u16)).await?; + socket::command(&mut self.bus, socket::Command::Send).await?; + Ok(frame.len()) + } + + pub async fn is_link_up(&mut self) -> bool { + let mut link = [0]; + self.bus + .read_frame(RegisterBlock::Common, PHY_CFG, &mut link) + .await + .ok(); + link[0] & 1 == 1 + } +} diff --git a/embassy-net-w5500/src/lib.rs b/embassy-net-w5500/src/lib.rs new file mode 100644 index 000000000..bf14b05b4 --- /dev/null +++ b/embassy-net-w5500/src/lib.rs @@ -0,0 +1,114 @@ +#![no_std] +/// [`embassy-net`](crates.io/crates/embassy-net) driver for the WIZnet W5500 ethernet chip. +mod device; +mod socket; +mod spi; + +use crate::device::W5500; +use embassy_futures::select::{select, Either}; +use embassy_net_driver_channel as ch; +use embassy_net_driver_channel::driver::LinkState; +use embassy_time::{Duration, Timer}; +use embedded_hal::digital::OutputPin; +use embedded_hal_async::digital::Wait; +use embedded_hal_async::spi::SpiDevice; +const MTU: usize = 1514; + +/// Type alias for the embassy-net driver for W5500 +pub type Device<'d> = embassy_net_driver_channel::Device<'d, MTU>; + +/// Internal state for the embassy-net integration. +pub struct State { + ch_state: ch::State, +} + +impl State { + /// Create a new `State`. + pub const fn new() -> Self { + Self { + ch_state: ch::State::new(), + } + } +} + +/// Background runner for the W5500. +/// +/// You must call `.run()` in a background task for the W5500 to operate. +pub struct Runner<'d, SPI: SpiDevice, INT: Wait, RST: OutputPin> { + mac: W5500, + ch: ch::Runner<'d, MTU>, + int: INT, + _reset: RST, +} + +/// You must call this in a background task for the W5500 to operate. +impl<'d, SPI: SpiDevice, INT: Wait, RST: OutputPin> Runner<'d, SPI, INT, RST> { + pub async fn run(mut self) -> ! { + let (state_chan, mut rx_chan, mut tx_chan) = self.ch.split(); + loop { + if self.mac.is_link_up().await { + state_chan.set_link_state(LinkState::Up); + loop { + match select( + async { + self.int.wait_for_low().await.ok(); + rx_chan.rx_buf().await + }, + tx_chan.tx_buf(), + ) + .await + { + Either::First(p) => { + if let Ok(n) = self.mac.read_frame(p).await { + rx_chan.rx_done(n); + } + } + Either::Second(p) => { + self.mac.write_frame(p).await.ok(); + tx_chan.tx_done(); + } + } + } + } else { + state_chan.set_link_state(LinkState::Down); + } + } + } +} + +/// Obtain a driver for using the W5500 with [`embassy-net`](crates.io/crates/embassy-net). +pub async fn new< + 'a, + const N_RX: usize, + const N_TX: usize, + SPI: SpiDevice, + INT: Wait, + RST: OutputPin, +>( + mac_addr: [u8; 6], + state: &'a mut State, + spi_dev: SPI, + int: INT, + mut reset: RST, +) -> (Device<'a>, Runner<'a, SPI, INT, RST>) { + // Reset the W5500. + reset.set_low().ok(); + // Ensure the reset is registered. + Timer::after(Duration::from_millis(1)).await; + reset.set_high().ok(); + // Wait for the W5500 to achieve PLL lock. + Timer::after(Duration::from_millis(2)).await; + + let mac = W5500::new(spi_dev, mac_addr).await.unwrap(); + + let (runner, device) = ch::new(&mut state.ch_state, mac_addr); + ( + device, + Runner { + ch: runner, + mac, + int, + _reset: reset, + }, + ) +} diff --git a/embassy-net-w5500/src/socket.rs b/embassy-net-w5500/src/socket.rs new file mode 100644 index 000000000..3f64d04d1 --- /dev/null +++ b/embassy-net-w5500/src/socket.rs @@ -0,0 +1,103 @@ +use crate::device::RegisterBlock; +use crate::spi::SpiInterface; +use embedded_hal_async::spi::SpiDevice; + +pub const MODE: u16 = 0x00; +pub const COMMAND: u16 = 0x01; +pub const RXBUF_SIZE: u16 = 0x1E; +pub const TXBUF_SIZE: u16 = 0x1F; +pub const TX_FREE_SIZE: u16 = 0x20; +pub const TX_DATA_WRITE_PTR: u16 = 0x24; +pub const RECVD_SIZE: u16 = 0x26; +pub const RX_DATA_READ_PTR: u16 = 0x28; +pub const SOCKET_INTR_MASK: u16 = 0x2C; + +#[repr(u8)] +pub enum Command { + Open = 0x01, + Send = 0x20, + Receive = 0x40, +} + +pub const INTR: u16 = 0x02; +#[repr(u8)] +pub enum Interrupt { + Receive = 0b00100_u8, +} + +pub async fn reset_interrupt( + bus: &mut SpiInterface, + code: Interrupt, +) -> Result<(), SPI::Error> { + let data = [code as u8]; + bus.write_frame(RegisterBlock::Socket0, INTR, &data).await +} + +pub async fn get_tx_write_ptr( + bus: &mut SpiInterface, +) -> Result { + let mut data = [0u8; 2]; + bus.read_frame(RegisterBlock::Socket0, TX_DATA_WRITE_PTR, &mut data) + .await?; + Ok(u16::from_be_bytes(data)) +} + +pub async fn set_tx_write_ptr( + bus: &mut SpiInterface, + ptr: u16, +) -> Result<(), SPI::Error> { + let data = ptr.to_be_bytes(); + bus.write_frame(RegisterBlock::Socket0, TX_DATA_WRITE_PTR, &data) + .await +} + +pub async fn get_rx_read_ptr( + bus: &mut SpiInterface, +) -> Result { + let mut data = [0u8; 2]; + bus.read_frame(RegisterBlock::Socket0, RX_DATA_READ_PTR, &mut data) + .await?; + Ok(u16::from_be_bytes(data)) +} + +pub async fn set_rx_read_ptr( + bus: &mut SpiInterface, + ptr: u16, +) -> Result<(), SPI::Error> { + let data = ptr.to_be_bytes(); + bus.write_frame(RegisterBlock::Socket0, RX_DATA_READ_PTR, &data) + .await +} + +pub async fn command( + bus: &mut SpiInterface, + command: Command, +) -> Result<(), SPI::Error> { + let data = [command as u8]; + bus.write_frame(RegisterBlock::Socket0, COMMAND, &data) + .await +} + +pub async fn get_rx_size(bus: &mut SpiInterface) -> Result { + loop { + // Wait until two sequential reads are equal + let mut res0 = [0u8; 2]; + bus.read_frame(RegisterBlock::Socket0, RECVD_SIZE, &mut res0) + .await?; + let mut res1 = [0u8; 2]; + bus.read_frame(RegisterBlock::Socket0, RECVD_SIZE, &mut res1) + .await?; + if res0 == res1 { + break Ok(u16::from_be_bytes(res0)); + } + } +} + +pub async fn get_tx_free_size( + bus: &mut SpiInterface, +) -> Result { + let mut data = [0; 2]; + bus.read_frame(RegisterBlock::Socket0, TX_FREE_SIZE, &mut data) + .await?; + Ok(u16::from_be_bytes(data)) +} diff --git a/embassy-net-w5500/src/spi.rs b/embassy-net-w5500/src/spi.rs new file mode 100644 index 000000000..55d311888 --- /dev/null +++ b/embassy-net-w5500/src/spi.rs @@ -0,0 +1,37 @@ +use crate::device::RegisterBlock; +use embedded_hal_async::spi::{Operation, SpiDevice}; + +#[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct SpiInterface(pub SPI); + +impl SpiInterface { + pub async fn read_frame( + &mut self, + block: RegisterBlock, + address: u16, + data: &mut [u8], + ) -> Result<(), SPI::Error> { + let address_phase = address.to_be_bytes(); + let control_phase = [(block as u8) << 3]; + let operations = &mut [ + Operation::Write(&address_phase), + Operation::Write(&control_phase), + Operation::TransferInPlace(data), + ]; + self.0.transaction(operations).await + } + + pub async fn write_frame( + &mut self, + block: RegisterBlock, + address: u16, + data: &[u8], + ) -> Result<(), SPI::Error> { + let address_phase = address.to_be_bytes(); + let control_phase = [(block as u8) << 3 | 0b0000_0100]; + let data_phase = data; + let operations = &[&address_phase[..], &control_phase, &data_phase]; + self.0.write_transaction(operations).await + } +} diff --git a/examples/rp/src/bin/ethernet_w5500_multisocket.rs b/examples/rp/src/bin/ethernet_w5500_multisocket.rs new file mode 100644 index 000000000..eb3b8de81 --- /dev/null +++ b/examples/rp/src/bin/ethernet_w5500_multisocket.rs @@ -0,0 +1,152 @@ +//! This example shows how you can allow multiple simultaneous TCP connections, by having multiple sockets listening on the same port. +//! +//! Example written for the [`WIZnet W5500-EVB-Pico`](https://www.wiznet.io/product-item/w5500-evb-pico/) board. + +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_futures::yield_now; +use embassy_net::{Stack, StackResources}; +use embassy_net_w5500::*; +use embassy_rp::clocks::RoscRng; +use embassy_rp::gpio::{Input, Level, Output, Pull}; +use embassy_rp::peripherals::{PIN_17, PIN_20, PIN_21, SPI0}; +use embassy_rp::spi::{Async, Config as SpiConfig, Spi}; +use embedded_hal_async::spi::ExclusiveDevice; +use embedded_io::asynch::Write; +use rand::RngCore; +use static_cell::StaticCell; +use {defmt_rtt as _, panic_probe as _}; + +macro_rules! singleton { + ($val:expr) => {{ + type T = impl Sized; + static STATIC_CELL: StaticCell = StaticCell::new(); + let (x,) = STATIC_CELL.init(($val,)); + x + }}; +} + +#[embassy_executor::task] +async fn ethernet_task( + runner: Runner< + 'static, + ExclusiveDevice, Output<'static, PIN_17>>, + Input<'static, PIN_21>, + Output<'static, PIN_20>, + >, +) -> ! { + runner.run().await +} + +#[embassy_executor::task] +async fn net_task(stack: &'static Stack>) -> ! { + stack.run().await +} + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + let mut rng = RoscRng; + + let mut spi_cfg = SpiConfig::default(); + spi_cfg.frequency = 50_000_000; + let (miso, mosi, clk) = (p.PIN_16, p.PIN_19, p.PIN_18); + let spi = Spi::new(p.SPI0, clk, mosi, miso, p.DMA_CH0, p.DMA_CH1, spi_cfg); + let cs = Output::new(p.PIN_17, Level::High); + let w5500_int = Input::new(p.PIN_21, Pull::Up); + let w5500_reset = Output::new(p.PIN_20, Level::High); + + let mac_addr = [0x02, 0x00, 0x00, 0x00, 0x00, 0x00]; + let state = singleton!(State::<8, 8>::new()); + let (device, runner) = embassy_net_w5500::new( + mac_addr, + state, + ExclusiveDevice::new(spi, cs), + w5500_int, + w5500_reset, + ) + .await; + unwrap!(spawner.spawn(ethernet_task(runner))); + + // Generate random seed + let seed = rng.next_u64(); + + // Init network stack + let stack = &*singleton!(Stack::new( + device, + embassy_net::Config::Dhcp(Default::default()), + singleton!(StackResources::<3>::new()), + seed + )); + + // Launch network task + unwrap!(spawner.spawn(net_task(&stack))); + + info!("Waiting for DHCP..."); + let cfg = wait_for_config(stack).await; + let local_addr = cfg.address.address(); + info!("IP address: {:?}", local_addr); + + // Create two sockets listening to the same port, to handle simultaneous connections + unwrap!(spawner.spawn(listen_task(&stack, 0, 1234))); + unwrap!(spawner.spawn(listen_task(&stack, 1, 1234))); +} + +#[embassy_executor::task(pool_size = 2)] +async fn listen_task(stack: &'static Stack>, id: u8, port: u16) { + let mut rx_buffer = [0; 4096]; + let mut tx_buffer = [0; 4096]; + let mut buf = [0; 4096]; + loop { + let mut socket = embassy_net::tcp::TcpSocket::new(stack, &mut rx_buffer, &mut tx_buffer); + socket.set_timeout(Some(embassy_net::SmolDuration::from_secs(10))); + + info!("SOCKET {}: Listening on TCP:{}...", id, port); + if let Err(e) = socket.accept(port).await { + warn!("accept error: {:?}", e); + continue; + } + info!( + "SOCKET {}: Received connection from {:?}", + id, + socket.remote_endpoint() + ); + + loop { + let n = match socket.read(&mut buf).await { + Ok(0) => { + warn!("read EOF"); + break; + } + Ok(n) => n, + Err(e) => { + warn!("SOCKET {}: {:?}", id, e); + break; + } + }; + info!( + "SOCKET {}: rxd {}", + id, + core::str::from_utf8(&buf[..n]).unwrap() + ); + + if let Err(e) = socket.write_all(&buf[..n]).await { + warn!("write error: {:?}", e); + break; + } + } + } +} + +async fn wait_for_config(stack: &'static Stack>) -> embassy_net::StaticConfig { + loop { + if let Some(config) = stack.config() { + return config.clone(); + } + yield_now().await; + } +} diff --git a/examples/rp/src/bin/ethernet_w5500_tcp_client.rs b/examples/rp/src/bin/ethernet_w5500_tcp_client.rs new file mode 100644 index 000000000..e166e0f35 --- /dev/null +++ b/examples/rp/src/bin/ethernet_w5500_tcp_client.rs @@ -0,0 +1,132 @@ +//! This example implements a TCP client that attempts to connect to a host on port 1234 and send it some data once per second. +//! +//! Example written for the [`WIZnet W5500-EVB-Pico`](https://www.wiznet.io/product-item/w5500-evb-pico/) board. + +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use core::str::FromStr; +use defmt::*; +use embassy_executor::Spawner; +use embassy_futures::yield_now; +use embassy_net::{Stack, StackResources}; +use embassy_net_w5500::*; +use embassy_rp::clocks::RoscRng; +use embassy_rp::gpio::{Input, Level, Output, Pull}; +use embassy_rp::peripherals::{PIN_17, PIN_20, PIN_21, SPI0}; +use embassy_rp::spi::{Async, Config as SpiConfig, Spi}; +use embassy_time::{Duration, Timer}; +use embedded_hal_async::spi::ExclusiveDevice; +use embedded_io::asynch::Write; +use rand::RngCore; +use static_cell::StaticCell; +use {defmt_rtt as _, panic_probe as _}; + +macro_rules! singleton { + ($val:expr) => {{ + type T = impl Sized; + static STATIC_CELL: StaticCell = StaticCell::new(); + let (x,) = STATIC_CELL.init(($val,)); + x + }}; +} + +#[embassy_executor::task] +async fn ethernet_task( + runner: Runner< + 'static, + ExclusiveDevice, Output<'static, PIN_17>>, + Input<'static, PIN_21>, + Output<'static, PIN_20>, + >, +) -> ! { + runner.run().await +} + +#[embassy_executor::task] +async fn net_task(stack: &'static Stack>) -> ! { + stack.run().await +} + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + let mut rng = RoscRng; + let mut led = Output::new(p.PIN_25, Level::Low); + + let mut spi_cfg = SpiConfig::default(); + spi_cfg.frequency = 50_000_000; + let (miso, mosi, clk) = (p.PIN_16, p.PIN_19, p.PIN_18); + let spi = Spi::new(p.SPI0, clk, mosi, miso, p.DMA_CH0, p.DMA_CH1, spi_cfg); + let cs = Output::new(p.PIN_17, Level::High); + let w5500_int = Input::new(p.PIN_21, Pull::Up); + let w5500_reset = Output::new(p.PIN_20, Level::High); + + let mac_addr = [0x02, 0x00, 0x00, 0x00, 0x00, 0x00]; + let state = singleton!(State::<8, 8>::new()); + let (device, runner) = embassy_net_w5500::new( + mac_addr, + state, + ExclusiveDevice::new(spi, cs), + w5500_int, + w5500_reset, + ) + .await; + unwrap!(spawner.spawn(ethernet_task(runner))); + + // Generate random seed + let seed = rng.next_u64(); + + // Init network stack + let stack = &*singleton!(Stack::new( + device, + embassy_net::Config::Dhcp(Default::default()), + singleton!(StackResources::<2>::new()), + seed + )); + + // Launch network task + unwrap!(spawner.spawn(net_task(&stack))); + + info!("Waiting for DHCP..."); + let cfg = wait_for_config(stack).await; + let local_addr = cfg.address.address(); + info!("IP address: {:?}", local_addr); + + let mut rx_buffer = [0; 4096]; + let mut tx_buffer = [0; 4096]; + loop { + let mut socket = embassy_net::tcp::TcpSocket::new(stack, &mut rx_buffer, &mut tx_buffer); + socket.set_timeout(Some(embassy_net::SmolDuration::from_secs(10))); + + led.set_low(); + info!("Connecting..."); + let host_addr = embassy_net::Ipv4Address::from_str("192.168.1.110").unwrap(); + if let Err(e) = socket.connect((host_addr, 1234)).await { + warn!("connect error: {:?}", e); + continue; + } + info!("Connected to {:?}", socket.remote_endpoint()); + led.set_high(); + + let msg = b"Hello world!\n"; + loop { + if let Err(e) = socket.write_all(msg).await { + warn!("write error: {:?}", e); + break; + } + info!("txd: {}", core::str::from_utf8(msg).unwrap()); + Timer::after(Duration::from_secs(1)).await; + } + } +} + +async fn wait_for_config(stack: &'static Stack>) -> embassy_net::StaticConfig { + loop { + if let Some(config) = stack.config() { + return config.clone(); + } + yield_now().await; + } +} diff --git a/examples/rp/src/bin/ethernet_w5500_tcp_server.rs b/examples/rp/src/bin/ethernet_w5500_tcp_server.rs new file mode 100644 index 000000000..ffd664d15 --- /dev/null +++ b/examples/rp/src/bin/ethernet_w5500_tcp_server.rs @@ -0,0 +1,141 @@ +//! This example implements a TCP echo server on port 1234 and using DHCP. +//! Send it some data, you should see it echoed back and printed in the console. +//! +//! Example written for the [`WIZnet W5500-EVB-Pico`](https://www.wiznet.io/product-item/w5500-evb-pico/) board. + +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_futures::yield_now; +use embassy_net::{Stack, StackResources}; +use embassy_net_w5500::*; +use embassy_rp::clocks::RoscRng; +use embassy_rp::gpio::{Input, Level, Output, Pull}; +use embassy_rp::peripherals::{PIN_17, PIN_20, PIN_21, SPI0}; +use embassy_rp::spi::{Async, Config as SpiConfig, Spi}; +use embedded_hal_async::spi::ExclusiveDevice; +use embedded_io::asynch::Write; +use rand::RngCore; +use static_cell::StaticCell; +use {defmt_rtt as _, panic_probe as _}; + +macro_rules! singleton { + ($val:expr) => {{ + type T = impl Sized; + static STATIC_CELL: StaticCell = StaticCell::new(); + let (x,) = STATIC_CELL.init(($val,)); + x + }}; +} + +#[embassy_executor::task] +async fn ethernet_task( + runner: Runner< + 'static, + ExclusiveDevice, Output<'static, PIN_17>>, + Input<'static, PIN_21>, + Output<'static, PIN_20>, + >, +) -> ! { + runner.run().await +} + +#[embassy_executor::task] +async fn net_task(stack: &'static Stack>) -> ! { + stack.run().await +} + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + let mut rng = RoscRng; + let mut led = Output::new(p.PIN_25, Level::Low); + + let mut spi_cfg = SpiConfig::default(); + spi_cfg.frequency = 50_000_000; + let (miso, mosi, clk) = (p.PIN_16, p.PIN_19, p.PIN_18); + let spi = Spi::new(p.SPI0, clk, mosi, miso, p.DMA_CH0, p.DMA_CH1, spi_cfg); + let cs = Output::new(p.PIN_17, Level::High); + let w5500_int = Input::new(p.PIN_21, Pull::Up); + let w5500_reset = Output::new(p.PIN_20, Level::High); + + let mac_addr = [0x02, 0x00, 0x00, 0x00, 0x00, 0x00]; + let state = singleton!(State::<8, 8>::new()); + let (device, runner) = embassy_net_w5500::new( + mac_addr, + state, + ExclusiveDevice::new(spi, cs), + w5500_int, + w5500_reset, + ) + .await; + unwrap!(spawner.spawn(ethernet_task(runner))); + + // Generate random seed + let seed = rng.next_u64(); + + // Init network stack + let stack = &*singleton!(Stack::new( + device, + embassy_net::Config::Dhcp(Default::default()), + singleton!(StackResources::<2>::new()), + seed + )); + + // Launch network task + unwrap!(spawner.spawn(net_task(&stack))); + + info!("Waiting for DHCP..."); + let cfg = wait_for_config(stack).await; + let local_addr = cfg.address.address(); + info!("IP address: {:?}", local_addr); + + let mut rx_buffer = [0; 4096]; + let mut tx_buffer = [0; 4096]; + let mut buf = [0; 4096]; + loop { + let mut socket = embassy_net::tcp::TcpSocket::new(stack, &mut rx_buffer, &mut tx_buffer); + socket.set_timeout(Some(embassy_net::SmolDuration::from_secs(10))); + + led.set_low(); + info!("Listening on TCP:1234..."); + if let Err(e) = socket.accept(1234).await { + warn!("accept error: {:?}", e); + continue; + } + info!("Received connection from {:?}", socket.remote_endpoint()); + led.set_high(); + + loop { + let n = match socket.read(&mut buf).await { + Ok(0) => { + warn!("read EOF"); + break; + } + Ok(n) => n, + Err(e) => { + warn!("{:?}", e); + break; + } + }; + info!("rxd {}", core::str::from_utf8(&buf[..n]).unwrap()); + + if let Err(e) = socket.write_all(&buf[..n]).await { + warn!("write error: {:?}", e); + break; + } + } + } +} + +async fn wait_for_config(stack: &'static Stack>) -> embassy_net::StaticConfig { + loop { + if let Some(config) = stack.config() { + return config.clone(); + } + yield_now().await; + } +} diff --git a/examples/rp/src/bin/ethernet_w5500_udp.rs b/examples/rp/src/bin/ethernet_w5500_udp.rs new file mode 100644 index 000000000..08ffeb244 --- /dev/null +++ b/examples/rp/src/bin/ethernet_w5500_udp.rs @@ -0,0 +1,127 @@ +//! This example implements a UDP server listening on port 1234 and echoing back the data. +//! +//! Example written for the [`WIZnet W5500-EVB-Pico`](https://www.wiznet.io/product-item/w5500-evb-pico/) board. + +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_futures::yield_now; +use embassy_net::udp::UdpSocket; +use embassy_net::{PacketMetadata, Stack, StackResources}; +use embassy_net_w5500::*; +use embassy_rp::clocks::RoscRng; +use embassy_rp::gpio::{Input, Level, Output, Pull}; +use embassy_rp::peripherals::{PIN_17, PIN_20, PIN_21, SPI0}; +use embassy_rp::spi::{Async, Config as SpiConfig, Spi}; +use embedded_hal_async::spi::ExclusiveDevice; +use rand::RngCore; +use static_cell::StaticCell; +use {defmt_rtt as _, panic_probe as _}; + +macro_rules! singleton { + ($val:expr) => {{ + type T = impl Sized; + static STATIC_CELL: StaticCell = StaticCell::new(); + let (x,) = STATIC_CELL.init(($val,)); + x + }}; +} + +#[embassy_executor::task] +async fn ethernet_task( + runner: Runner< + 'static, + ExclusiveDevice, Output<'static, PIN_17>>, + Input<'static, PIN_21>, + Output<'static, PIN_20>, + >, +) -> ! { + runner.run().await +} + +#[embassy_executor::task] +async fn net_task(stack: &'static Stack>) -> ! { + stack.run().await +} + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + let mut rng = RoscRng; + + let mut spi_cfg = SpiConfig::default(); + spi_cfg.frequency = 50_000_000; + let (miso, mosi, clk) = (p.PIN_16, p.PIN_19, p.PIN_18); + let spi = Spi::new(p.SPI0, clk, mosi, miso, p.DMA_CH0, p.DMA_CH1, spi_cfg); + let cs = Output::new(p.PIN_17, Level::High); + let w5500_int = Input::new(p.PIN_21, Pull::Up); + let w5500_reset = Output::new(p.PIN_20, Level::High); + + let mac_addr = [0x02, 0x00, 0x00, 0x00, 0x00, 0x00]; + let state = singleton!(State::<8, 8>::new()); + let (device, runner) = embassy_net_w5500::new( + mac_addr, + state, + ExclusiveDevice::new(spi, cs), + w5500_int, + w5500_reset, + ) + .await; + unwrap!(spawner.spawn(ethernet_task(runner))); + + // Generate random seed + let seed = rng.next_u64(); + + // Init network stack + let stack = &*singleton!(Stack::new( + device, + embassy_net::Config::Dhcp(Default::default()), + singleton!(StackResources::<2>::new()), + seed + )); + + // Launch network task + unwrap!(spawner.spawn(net_task(&stack))); + + info!("Waiting for DHCP..."); + let cfg = wait_for_config(stack).await; + let local_addr = cfg.address.address(); + info!("IP address: {:?}", local_addr); + + // Then we can use it! + let mut rx_buffer = [0; 4096]; + let mut tx_buffer = [0; 4096]; + let mut rx_meta = [PacketMetadata::EMPTY; 16]; + let mut tx_meta = [PacketMetadata::EMPTY; 16]; + let mut buf = [0; 4096]; + loop { + let mut socket = UdpSocket::new( + stack, + &mut rx_meta, + &mut rx_buffer, + &mut tx_meta, + &mut tx_buffer, + ); + socket.bind(1234).unwrap(); + + loop { + let (n, ep) = socket.recv_from(&mut buf).await.unwrap(); + if let Ok(s) = core::str::from_utf8(&buf[..n]) { + info!("rxd from {}: {}", ep, s); + } + socket.send_to(&buf[..n], ep).await.unwrap(); + } + } +} + +async fn wait_for_config(stack: &'static Stack>) -> embassy_net::StaticConfig { + loop { + if let Some(config) = stack.config() { + return config.clone(); + } + yield_now().await; + } +}