From c289b9c37438db93c845ede0b51d34dcba45eaec Mon Sep 17 00:00:00 2001 From: Peter Hartley Date: Tue, 9 Jul 2024 09:05:35 +0100 Subject: [PATCH 1/3] Update w5500 dependency to 0.5 The 0.5.0 release of w5500 contains my patch for interrupt-driven mode. --- cotton-w5500/Cargo.toml | 4 ++- cotton-w5500/README.md | 31 +++++++++++++++++++ cotton-w5500/src/lib.rs | 17 +++++++++- cotton-w5500/src/smoltcp.rs | 6 +++- cross/rp2040-w5500/Cargo.toml | 2 +- .../src/bin/rp2040-w5500macraw-dhcp-rtic.rs | 7 +++-- .../src/bin/rp2040-w5500macraw-ssdp-rtic.rs | 7 +++-- 7 files changed, 66 insertions(+), 8 deletions(-) create mode 100644 cotton-w5500/README.md diff --git a/cotton-w5500/Cargo.toml b/cotton-w5500/Cargo.toml index 0feea1a..896cf00 100644 --- a/cotton-w5500/Cargo.toml +++ b/cotton-w5500/Cargo.toml @@ -14,7 +14,7 @@ rust-version = "1.75" all-features = true [dependencies] -w5500 = { git = "https://github.com/kellerkindt/w5500", rev = "74ef8391", version = "0.4.1" } +w5500 = "0.5" # defmt 0.3.7 has msrv too big defmt = ">=0.3.2, <0.3.7" smoltcp = { version = "0.11", default-features = false, features = [ @@ -24,6 +24,8 @@ smoltcp = { version = "0.11", default-features = false, features = [ ], optional = true } rp2040-hal = { version = "0.10", optional = true } mockall = { version = "0.12.1", optional = true } +# embedded-hal-bus 0.2 assumes compare_exchange, which Cortex-M0 doesn't have +embedded-hal-bus = "0.1" [features] default = ["smoltcp", "std"] diff --git a/cotton-w5500/README.md b/cotton-w5500/README.md new file mode 100644 index 0000000..d8914ca --- /dev/null +++ b/cotton-w5500/README.md @@ -0,0 +1,31 @@ +[![CI status](https://github.com/pdh11/cotton/actions/workflows/ci.yml/badge.svg)](https://github.com/pdh11/cotton/actions) +[![codecov](https://codecov.io/gh/pdh11/cotton/branch/main/graph/badge.svg?token=SMSZEPGRHA)](https://codecov.io/gh/pdh11/cotton) +[![dependency status](https://deps.rs/repo/github/pdh11/cotton/status.svg)](https://deps.rs/repo/github/pdh11/cotton) +[![Crates.io](https://img.shields.io/crates/v/cotton-w5500)](https://crates.io/crates/cotton-w5500) +[![Crates.io](https://img.shields.io/crates/d/cotton-w5500)](https://crates.io/crates/cotton-w5500) +[![docs.rs](https://img.shields.io/docsrs/cotton-w5500)](https://docs.rs/cotton-w5500/latest/cotton_unique/) +[![License: CC0-1.0](https://img.shields.io/badge/License-CC0_1.0-lightgrey.svg)](http://creativecommons.org/publicdomain/zero/1.0/) + +# cotton-w5500 + +Part of the [Cotton](https://github.com/pdh11/cotton) project. + +## A Wiznet W5500 driver for smoltcp + +This crate includes an implementation of `smoltcp::phy::Device` which +uses the [W5500](https://crates.io/crates/w5500) crate to target +[smoltcp](https://crates.io/crates/smoltcp) to the Wiznet W5500 +SPI-to-Ethernet chip, as found on the +[W5500-EVB-Pico](https://thepihut.com/products/wiznet-w5100s-evb-pico-rp2040-board-with-ethernet) +board (and in many other places). The W5500 is operated in "MACRAW" +(raw packet) mode, which allows more flexible networking (via smoltcp) +than is possible using the W5500's onboard TCP/UDP mode -- for +instance, it enables IPv6 support, which would otherwise require the +somewhat rarer W6100 chip. + +Although cotton-w5500 works well with cotton-unique, it is relatively +stand-alone: it does not depend on cotton-unique nor on any other part +of the Cotton project. + +Library documentation is [on +docs.rs](https://docs.rs/cotton-w5500/latest/cotton_w5500/). diff --git a/cotton-w5500/src/lib.rs b/cotton-w5500/src/lib.rs index c698438..5f30e4c 100644 --- a/cotton-w5500/src/lib.rs +++ b/cotton-w5500/src/lib.rs @@ -1,4 +1,19 @@ -//! Helpers for using the W5500 SPI Ethernet controller +//! A Wiznet W5500 driver for smoltcp +//! +//! This crate includes an implementation of `smoltcp::phy::Device` +//! which uses the [W5500](https://crates.io/crates/w5500) crate to +//! target [smoltcp](https://crates.io/crates/smoltcp) to the Wiznet +//! W5500 SPI-to-Ethernet chip, as found on the +//! [W5500-EVB-Pico](https://thepihut.com/products/wiznet-w5100s-evb-pico-rp2040-board-with-ethernet) +//! board (and in many other places). The W5500 is operated in +//! "MACRAW" (raw packet) mode, which allows more flexible networking +//! (via smoltcp) than is possible using the W5500's onboard TCP/UDP +//! mode -- for instance, it enables IPv6 support, which would +//! otherwise require the somewhat rarer W6100 chip. +//! +//! Although cotton-w5500 works well with cotton-unique, it is +//! relatively stand-alone: it does not depend on cotton-unique nor on +//! any other part of the Cotton project. #![cfg_attr(not(feature = "std"), no_std)] #![warn(missing_docs)] #![warn(rustdoc::missing_crate_level_docs)] diff --git a/cotton-w5500/src/smoltcp.rs b/cotton-w5500/src/smoltcp.rs index dbe879d..bc3bab2 100644 --- a/cotton-w5500/src/smoltcp.rs +++ b/cotton-w5500/src/smoltcp.rs @@ -151,6 +151,8 @@ pub mod w5500_evb_pico { use rp2040_hal::gpio::PullNone; use rp2040_hal::gpio::SioOutput; use rp2040_hal::pac::SPI0; + use embedded_hal_bus::spi::ExclusiveDevice; + use embedded_hal_bus::spi::NoDelay; type Spi0 = rp2040_hal::Spi< rp2040_hal::spi::Enabled, @@ -165,7 +167,9 @@ pub mod w5500_evb_pico { type SpiChipSelect = rp2040_hal::gpio::Pin, PullNone>; - type SpiBus = w5500::bus::FourWire; + type SpiDevice = ExclusiveDevice; + + type SpiBus = w5500::bus::FourWire; /// A W5500 driver specialised for the SPI setup on the W5500-EVB-Pico board pub type Device = super::Device; diff --git a/cross/rp2040-w5500/Cargo.toml b/cross/rp2040-w5500/Cargo.toml index 01da97f..79808ee 100644 --- a/cross/rp2040-w5500/Cargo.toml +++ b/cross/rp2040-w5500/Cargo.toml @@ -64,7 +64,7 @@ embedded-hal-bus = "0.1" fugit = "0.3" systick-monotonic = "1.0" -w5500 = { git = "https://github.com/kellerkindt/w5500", rev = "74ef8391", version = "0.4.1" } +w5500 = "0.5" smoltcp = { version = "0.11", features = [ "medium-ethernet", "proto-ipv4", diff --git a/cross/rp2040-w5500/src/bin/rp2040-w5500macraw-dhcp-rtic.rs b/cross/rp2040-w5500/src/bin/rp2040-w5500macraw-dhcp-rtic.rs index 58607ea..cee8570 100644 --- a/cross/rp2040-w5500/src/bin/rp2040-w5500macraw-dhcp-rtic.rs +++ b/cross/rp2040-w5500/src/bin/rp2040-w5500macraw-dhcp-rtic.rs @@ -128,14 +128,17 @@ mod app { (spi_mosi, spi_miso, spi_sclk), ); - let spi = spi.init( + let spi_bus = spi.init( &mut resets, clocks.peripheral_clock.freq(), 16u32.MHz(), hal::spi::FrameFormat::MotorolaSpi(embedded_hal::spi::MODE_0), ); - let bus = w5500::bus::FourWire::new(spi, spi_ncs); + let spi_device = + embedded_hal_bus::spi::ExclusiveDevice::new_no_delay(spi_bus, spi_ncs); + + let bus = w5500::bus::FourWire::new(spi_device); let w5500_irq = pins.gpio21.into_pull_up_input(); w5500_irq.set_interrupt_enabled(EdgeLow, true); diff --git a/cross/rp2040-w5500/src/bin/rp2040-w5500macraw-ssdp-rtic.rs b/cross/rp2040-w5500/src/bin/rp2040-w5500macraw-ssdp-rtic.rs index 88c7b9b..72cc72b 100644 --- a/cross/rp2040-w5500/src/bin/rp2040-w5500macraw-ssdp-rtic.rs +++ b/cross/rp2040-w5500/src/bin/rp2040-w5500macraw-ssdp-rtic.rs @@ -186,14 +186,17 @@ mod app { (spi_mosi, spi_miso, spi_sclk), ); - let spi = spi.init( + let spi_bus = spi.init( &mut resets, clocks.peripheral_clock.freq(), 16u32.MHz(), hal::spi::FrameFormat::MotorolaSpi(embedded_hal::spi::MODE_0), ); - let bus = w5500::bus::FourWire::new(spi, spi_ncs); + let spi_device = + embedded_hal_bus::spi::ExclusiveDevice::new_no_delay(spi_bus, spi_ncs); + + let bus = w5500::bus::FourWire::new(spi_device); let w5500_irq = pins.gpio21.into_pull_up_input(); w5500_irq.set_interrupt_enabled(EdgeLow, true); From 8f05cae35237953fbf924a1e39efc1ee5f9fda0e Mon Sep 17 00:00:00 2001 From: Peter Hartley Date: Tue, 9 Jul 2024 11:31:55 +0100 Subject: [PATCH 2/3] w5500: Update readme files ready to publish --- README.md | 9 +++++---- cotton-w5500/README.md | 2 +- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 006e4e3..af87247 100644 --- a/README.md +++ b/README.md @@ -32,10 +32,11 @@ So far: [![docs.rs](https://img.shields.io/docsrs/cotton-unique)](https://docs.rs/cotton-unique/latest/cotton_unique/): creating deterministic but per-device unique identifiers such as MAC addresses. - - cotton-w5500: smoltcp driver for the Wiznet W5500 Ethernet - controller in MACRAW mode, including interrupt-driven mode. Not yet - on crates.io as it depends on a git prerelease of the w5500 crate, - but released here in case anyone needs example code. + - [cotton-w5500](https://crates.io/crates/cotton-w5500) + [![Crates.io](https://img.shields.io/crates/v/cotton-w5500)](https://crates.io/crates/cotton-w5500) + [![Crates.io](https://img.shields.io/crates/d/cotton-w5500)](https://crates.io/crates/cotton-w5500) + [![docs.rs](https://img.shields.io/docsrs/cotton-w5500)](https://docs.rs/cotton-w5500/latest/cotton_w5500/): smoltcp driver for the Wiznet W5500 Ethernet + controller in MACRAW mode, including interrupt-driven mode. These crates are `no_std`-compatible, meaning that they can be used on embedded systems. In fact, all pushes to my local (not Github) diff --git a/cotton-w5500/README.md b/cotton-w5500/README.md index d8914ca..72e8ec7 100644 --- a/cotton-w5500/README.md +++ b/cotton-w5500/README.md @@ -3,7 +3,7 @@ [![dependency status](https://deps.rs/repo/github/pdh11/cotton/status.svg)](https://deps.rs/repo/github/pdh11/cotton) [![Crates.io](https://img.shields.io/crates/v/cotton-w5500)](https://crates.io/crates/cotton-w5500) [![Crates.io](https://img.shields.io/crates/d/cotton-w5500)](https://crates.io/crates/cotton-w5500) -[![docs.rs](https://img.shields.io/docsrs/cotton-w5500)](https://docs.rs/cotton-w5500/latest/cotton_unique/) +[![docs.rs](https://img.shields.io/docsrs/cotton-w5500)](https://docs.rs/cotton-w5500/latest/cotton_w5500/) [![License: CC0-1.0](https://img.shields.io/badge/License-CC0_1.0-lightgrey.svg)](http://creativecommons.org/publicdomain/zero/1.0/) # cotton-w5500 From 03ab2af146c59daf8b7df2bfc38697b89e3976c9 Mon Sep 17 00:00:00 2001 From: Peter Hartley Date: Tue, 9 Jul 2024 13:24:38 +0100 Subject: [PATCH 3/3] Attempt to make systemtests more reliable By always reading both stdout and stderr in case one fills its pipe and stalls. Wonder whether we'd be better off using probe-rs as an in-process library rather than shelling out to it? --- .../src/bin/rp2040-w5500macraw-ssdp-rtic.rs | 2 +- .../src/bin/stm32f746-nucleo-ssdp-rtic.rs | 2 +- systemtests/tests/device/device_test.rs | 36 +++++++++++++------ systemtests/tests/device/rp2040_w5500.rs | 5 +-- systemtests/tests/device/ssdp_test.rs | 25 ++++++------- systemtests/tests/device/stm32f746_nucleo.rs | 7 ++-- 6 files changed, 48 insertions(+), 29 deletions(-) diff --git a/cross/rp2040-w5500/src/bin/rp2040-w5500macraw-ssdp-rtic.rs b/cross/rp2040-w5500/src/bin/rp2040-w5500macraw-ssdp-rtic.rs index 72cc72b..ed5e3f1 100644 --- a/cross/rp2040-w5500/src/bin/rp2040-w5500macraw-ssdp-rtic.rs +++ b/cross/rp2040-w5500/src/bin/rp2040-w5500macraw-ssdp-rtic.rs @@ -321,7 +321,7 @@ mod app { &ws, ); - ssdp.subscribe("ssdp:all".to_string(), Listener {}, &ws); + ssdp.subscribe("cotton-test-server-rp2040".to_string(), Listener {}, &ws); defmt::println!("Advertising!"); ssdp.advertise( diff --git a/cross/stm32f746-nucleo/src/bin/stm32f746-nucleo-ssdp-rtic.rs b/cross/stm32f746-nucleo/src/bin/stm32f746-nucleo-ssdp-rtic.rs index 988b2c5..7e66ecb 100644 --- a/cross/stm32f746-nucleo/src/bin/stm32f746-nucleo-ssdp-rtic.rs +++ b/cross/stm32f746-nucleo/src/bin/stm32f746-nucleo-ssdp-rtic.rs @@ -140,7 +140,7 @@ mod app { ); let ws = WrappedSocket::new(&mut udp_socket); _ = ssdp.on_network_event(&ev, &wi, &ws); - ssdp.subscribe("ssdp:all".to_string(), Listener {}, &ws); + ssdp.subscribe("cotton-test-server-stm32f746".to_string(), Listener {}, &ws); let uuid = alloc::format!( "{:032x}", diff --git a/systemtests/tests/device/device_test.rs b/systemtests/tests/device/device_test.rs index 7940785..60d65eb 100644 --- a/systemtests/tests/device/device_test.rs +++ b/systemtests/tests/device/device_test.rs @@ -15,6 +15,24 @@ struct DeviceTestInner { errors: String, } +impl DeviceTestInner { + fn poll(&mut self) { + let mut s = String::new(); + self.stdout.read_available_to_string(&mut s).unwrap(); + self.output.push_str(&s); + if !s.is_empty() { + eprintln!("{:?}: stdout {s}", Instant::now()); + } + + let mut s = String::new(); + self.stderr.read_available_to_string(&mut s).unwrap(); + self.errors.push_str(&s); + if !s.is_empty() { + eprintln!("{:?}: stderr {s}", Instant::now()); + } + } +} + pub struct DeviceTest { inner: Mutex, } @@ -60,14 +78,12 @@ impl DeviceTest { pub fn expect(&self, needle: &str, timeout: Duration) { let start = Instant::now(); + eprintln!("{:?}: searching stdout for {needle}", Instant::now()); loop { { let mut inner = self.inner.lock().unwrap(); - let mut s = String::new(); - inner.stdout.read_available_to_string(&mut s).unwrap(); - inner.output.push_str(&s); - print!("{s}"); + inner.poll(); if let Some((_before, after)) = inner.output.split_once(needle) { eprintln!("OK: {needle}"); @@ -76,6 +92,7 @@ impl DeviceTest { } if start.elapsed() > timeout { + eprintln!("{:?}: stdout {}", Instant::now(), inner.output); assert_contains!(inner.output, needle); return; } @@ -86,22 +103,21 @@ impl DeviceTest { pub fn expect_stderr(&self, needle: &str, timeout: Duration) { let start = Instant::now(); + eprintln!("{:?}: searching stderr for {needle}", Instant::now()); loop { { let mut inner = self.inner.lock().unwrap(); - let mut s = String::new(); - inner.stderr.read_available_to_string(&mut s).unwrap(); - inner.errors.push_str(&s); - print!("{s}"); - if let Some((_before, after)) = inner.output.split_once(needle) + inner.poll(); + if let Some((_before, after)) = inner.errors.split_once(needle) { eprintln!("OK: {needle}"); - inner.output = after.to_string(); + inner.errors = after.to_string(); return; } if start.elapsed() > timeout { + eprintln!("{:?}: stderr {}", Instant::now(), inner.errors); assert_contains!(inner.errors, needle); return; } diff --git a/systemtests/tests/device/rp2040_w5500.rs b/systemtests/tests/device/rp2040_w5500.rs index 2e453b3..997d7fe 100644 --- a/systemtests/tests/device/rp2040_w5500.rs +++ b/systemtests/tests/device/rp2040_w5500.rs @@ -14,7 +14,7 @@ fn rp2040_test () + panic::UnwindSafe>( #[test] #[serial(rp2040_w5500)] #[cfg_attr(miri, ignore)] -fn arm_rp2040_w5500_hello() { +fn arm_rp2040_w5500_0hello() { rp2040_test( "../cross/rp2040-w5500/target/thumbv6m-none-eabi/debug/hello", |t| { @@ -59,7 +59,8 @@ fn arm_rp2040_w5500macraw_ssdp_rtic() { nt.expect_stderr("Finished in", Duration::from_secs(45)); nt.expect("DHCP config acquired!", Duration::from_secs(10)); ssdp_test( - Some("cotton-test-server-rp2040".to_string()), + "cotton-test-server-rp2040", + "rp2040-w5500-test", |st| { nt.expect("SSDP! cotton-test-server-rp2040", Duration::from_secs(20)); diff --git a/systemtests/tests/device/ssdp_test.rs b/systemtests/tests/device/ssdp_test.rs index a88eedc..b2063b9 100644 --- a/systemtests/tests/device/ssdp_test.rs +++ b/systemtests/tests/device/ssdp_test.rs @@ -20,6 +20,7 @@ impl SsdpTest { pub fn expect_seen(&self, notification_type: &str, timeout: Duration) { let start = Instant::now(); + eprintln!("{:?}: Looking for {notification_type}", Instant::now()); loop { { @@ -28,6 +29,7 @@ impl SsdpTest { return; } if start.elapsed() > timeout { + eprintln!("{:?}: Didn't find it", Instant::now()); assert_contains!(v, notification_type); return; } @@ -39,7 +41,8 @@ impl SsdpTest { } pub fn ssdp_test () + panic::UnwindSafe>( - advertise: Option, + my_service: &'static str, + device_service: &'static str, f: F, ) { let t = SsdpTest::new(); @@ -58,19 +61,17 @@ pub fn ssdp_test () + panic::UnwindSafe>( Service::new(poll.registry(), (SSDP_TOKEN1, SSDP_TOKEN2)) .unwrap(); - if let Some(nt) = advertise { - let uuid = uuid::Uuid::new_v4(); - ssdp.advertise( - uuid.to_string(), - cotton_ssdp::Advertisement { - notification_type: nt.to_string(), - location: "http://127.0.0.1/test".to_string(), - }, - ); - } + let uuid = uuid::Uuid::new_v4(); + ssdp.advertise( + uuid.to_string(), + cotton_ssdp::Advertisement { + notification_type: my_service.to_string(), + location: "http://127.0.0.1/test".to_string(), + }, + ); ssdp.subscribe( - "ssdp:all", + device_service, Box::new(move |r| { println!("HOST GOT {r:?}"); if let cotton_ssdp::Notification::Alive { diff --git a/systemtests/tests/device/stm32f746_nucleo.rs b/systemtests/tests/device/stm32f746_nucleo.rs index 4eedecc..bb50752 100644 --- a/systemtests/tests/device/stm32f746_nucleo.rs +++ b/systemtests/tests/device/stm32f746_nucleo.rs @@ -19,7 +19,7 @@ fn nucleo_test () + panic::UnwindSafe>( #[test] #[serial(stm32f746_nucleo)] #[cfg_attr(miri, ignore)] -fn arm_stm32f746_nucleo_hello() { +fn arm_stm32f746_nucleo_0hello() { nucleo_test( "../cross/stm32f746-nucleo/target/thumbv7em-none-eabi/debug/stm32f746-nucleo-hello", |t| { @@ -51,12 +51,13 @@ fn arm_stm32f746_nucleo_ssdp() { nt.expect_stderr("Finished in", Duration::from_secs(45)); nt.expect("DHCP config acquired!", Duration::from_secs(10)); ssdp_test( - Some("cotton-test-server-stm32f746".to_string()), + "cotton-test-server-stm32f746", // host service + "stm32f746-nucleo-test", // device service |st| { nt.expect("SSDP! cotton-test-server-stm32f746", Duration::from_secs(20)); st.expect_seen("stm32f746-nucleo-test", - Duration::from_secs(10)); + Duration::from_secs(20)); } ); }