Merge pull request #1831 from vDorst/adin1110-part2

embassy-net-adin1110 more improvements
This commit is contained in:
Dario Nieuwenhuis 2023-09-02 00:49:17 +02:00 committed by GitHub
commit 9d8c527308
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 811 additions and 232 deletions

View file

@ -1,6 +1,6 @@
[package]
name = "embassy-net-adin1110"
version = "0.1.0"
version = "0.2.0"
description = "embassy-net driver for the ADIN1110 ethernet chip"
keywords = ["embedded", "ADIN1110", "embassy-net", "embedded-hal-async", "ethernet", "async"]
categories = ["embedded", "hardware-support", "no-std", "network-programming", "async"]
@ -12,30 +12,31 @@ edition = "2021"
[dependencies]
heapless = "0.7.16"
defmt = { version = "0.3", optional = true }
log = { version = "0.4.4", default-features = false, optional = true }
log = { version = "0.4", default-features = false, optional = true }
embedded-hal-1 = { package = "embedded-hal", version = "=1.0.0-rc.1" }
embedded-hal-async = { version = "=1.0.0-rc.1" }
embedded-hal-bus = { version = "=0.1.0-rc.1", features = ["async"] }
embassy-net-driver-channel = { version = "0.1.0", path = "../embassy-net-driver-channel" }
embassy-time = { version = "0.1.0" }
embassy-time = { version = "0.1.3" }
embassy-futures = { version = "0.1.0", path = "../embassy-futures" }
bitfield = "0.14.0"
[dev-dependencies]
# reenable when https://github.com/dbrgn/embedded-hal-mock/pull/86 is merged.
#embedded-hal-mock = { git = "https://github.com/dbrgn/embedded-hal-mock", branch = "1-alpha", features = ["embedded-hal-async", "eh1"] }] }
embedded-hal-mock = { git = "https://github.com/newAM/embedded-hal-mock", branch = "eh1-rc.1", features = ["embedded-hal-async", "eh1"] }
crc = "3.0.1"
env_logger = "0.10"
critical-section = { version = "1.1.1", features = ["std"] }
futures-test = "0.3.17"
critical-section = { version = "1.1.2", features = ["std"] }
futures-test = "0.3.28"
[features]
default = [ ]
defmt = [ "dep:defmt" ]
defmt = [ "dep:defmt", "embedded-hal-1/defmt-03" ]
log = ["dep:log"]
[package.metadata.embassy_docs]
src_base = "https://github.com/embassy-rs/embassy/blob/embassy-net-adin1110-v$VERSION/embassy-net-adin1110/src/"
src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-net-adin1110/src/"
target = "thumbv7em-none-eabi"
features = ["defmt"]

View file

@ -30,8 +30,8 @@ Currently only `Generic` SPI with or without CRC is supported.
## Hardware
- Tested on [`Analog Devices EVAL-ADIN1110EBZ`](https://www.analog.com/en/design-center/evaluation-hardware-and-software/evaluation-boards-kits/eval-adin1110.html) with an `STM32L4S5QII3P`, see [`spe_adin1110_http_server`](../examples/stm32l4/src/bin/spe_adin1110_http_server.rs) dor an example.
- [`SparkFun MicroMod Single Pair Ethernet Function Board`](https://www.sparkfun.com/products/19038) or [`SparkFun MicroMod Single Pair Ethernet Kit`](https://www.sparkfun.com/products/19628), supporting multiple microcontrollers. **Make sure to check if it's a microcontroller that is supported by Embassy!**
- Tested on [`Analog Devices EVAL-ADIN1110EBZ`](https://www.analog.com/en/design-center/evaluation-hardware-and-software/evaluation-boards-kits/eval-adin1110.html) with an `STM32L4S5QII3P`, see [`spe_adin1110_http_server`](../examples/stm32l4/src/bin/spe_adin1110_http_server.rs) for an example.
- [`SparkFun MicroMod Single Pair Ethernet Function Board`](https://www.sparkfun.com/products/19038) or [`SparkFun MicroMod Single Pair Ethernet Kit (End Of Life)`](https://www.sparkfun.com/products/19628), supporting multiple microcontrollers. **Make sure to check if it's a microcontroller that is supported by Embassy!**
## Other SPE chips
@ -44,6 +44,39 @@ ADIN1110 library can tested on the host with a mock SPI driver.
$ `cargo test --target x86_64-unknown-linux-gnu`
## Benchmark
- Benchmarked on [`Analog Devices EVAL-ADIN1110EBZ`](https://www.analog.com/en/design-center/evaluation-hardware-and-software/evaluation-boards-kits/eval-adin1110.html), with [`spe_adin1110_http_server`](../examples/stm32l4/src/bin/spe_adin1110_http_server.rs) example.
Basic `ping` benchmark
```rust,ignore
# ping <IP> -c 60
60 packets transmitted, 60 received, 0% packet loss, time 59066ms
rtt min/avg/max/mdev = 1.089/1.161/1.237/0.018 ms
# ping <IP> -s 1472 -M do -c 60
60 packets transmitted, 60 received, 0% packet loss, time 59066ms
rtt min/avg/max/mdev = 5.122/5.162/6.177/0.133 ms
```
HTTP load generator benchmark with [`oha`](https://github.com/hatoo/oha)
```rust,ignore
# oha -c 1 http://<IP> -z 60s
Summary:
Success rate: 50.00%
Total: 60.0005 secs
Slowest: 0.0055 secs
Fastest: 0.0033 secs
Average: 0.0034 secs
Requests/sec: 362.1971
Total data: 2.99 MiB
Size/request: 289 B
Size/sec: 51.11 KiB
```
## License
This work is licensed under either of

View file

@ -257,29 +257,30 @@ pub const CRC32R_LOOKUP_TABLE: [u32; 256] = [
0x2D02_EF8D,
];
/// Generate Ethernet Frame Check Sequence
#[allow(non_camel_case_types)]
#[derive(Debug)]
pub struct ETH_FSC(pub u32);
pub struct ETH_FCS(pub u32);
impl ETH_FSC {
impl ETH_FCS {
pub const CRC32_OK: u32 = 0x2144_df1c;
#[must_use]
pub fn new(data: &[u8]) -> Self {
let fsc = data.iter().fold(u32::MAX, |crc, byte| {
let fcs = data.iter().fold(u32::MAX, |crc, byte| {
let idx = u8::try_from(crc & 0xFF).unwrap() ^ byte;
CRC32R_LOOKUP_TABLE[usize::from(idx)] ^ (crc >> 8)
}) ^ u32::MAX;
Self(fsc)
Self(fcs)
}
#[must_use]
pub fn update(self, data: &[u8]) -> Self {
let fsc = data.iter().fold(self.0 ^ u32::MAX, |crc, byte| {
let fcs = data.iter().fold(self.0 ^ u32::MAX, |crc, byte| {
let idx = u8::try_from(crc & 0xFF).unwrap() ^ byte;
CRC32R_LOOKUP_TABLE[usize::from(idx)] ^ (crc >> 8)
}) ^ u32::MAX;
Self(fsc)
Self(fcs)
}
#[must_use]
@ -319,24 +320,24 @@ mod tests {
];
// Packet A
let own_crc = ETH_FSC::new(&packet_a[0..60]);
let own_crc = ETH_FCS::new(&packet_a[0..60]);
let crc_bytes = own_crc.hton_bytes();
println!("{:08x} {:02x?}", own_crc.0, crc_bytes);
assert_eq!(&crc_bytes, &packet_a[60..64]);
let own_crc = ETH_FSC::new(packet_a);
let own_crc = ETH_FCS::new(packet_a);
println!("{:08x}", own_crc.0);
assert_eq!(own_crc.0, ETH_FSC::CRC32_OK);
assert_eq!(own_crc.0, ETH_FCS::CRC32_OK);
// Packet B
let own_crc = ETH_FSC::new(&packet_b[0..60]);
let own_crc = ETH_FCS::new(&packet_b[0..60]);
let crc_bytes = own_crc.hton_bytes();
println!("{:08x} {:02x?}", own_crc.0, crc_bytes);
assert_eq!(&crc_bytes, &packet_b[60..64]);
let own_crc = ETH_FSC::new(packet_b);
let own_crc = ETH_FCS::new(packet_b);
println!("{:08x}", own_crc.0);
assert_eq!(own_crc.0, ETH_FSC::CRC32_OK);
assert_eq!(own_crc.0, ETH_FCS::CRC32_OK);
}
#[test]
@ -349,9 +350,9 @@ mod tests {
];
let (part_a, part_b) = full_data.split_at(16);
let crc_partially = ETH_FSC::new(part_a).update(part_b);
let crc_partially = ETH_FCS::new(part_a).update(part_b);
let crc_full = ETH_FSC::new(full_data);
let crc_full = ETH_FCS::new(full_data);
assert_eq!(crc_full.0, crc_partially.0);
}

View file

@ -0,0 +1,254 @@
#![macro_use]
#![allow(unused_macros)]
use core::fmt::{Debug, Display, LowerHex};
#[cfg(all(feature = "defmt", feature = "log"))]
compile_error!("You may not enable both `defmt` and `log` features.");
macro_rules! assert {
($($x:tt)*) => {
{
#[cfg(not(feature = "defmt"))]
::core::assert!($($x)*);
#[cfg(feature = "defmt")]
::defmt::assert!($($x)*);
}
};
}
macro_rules! assert_eq {
($($x:tt)*) => {
{
#[cfg(not(feature = "defmt"))]
::core::assert_eq!($($x)*);
#[cfg(feature = "defmt")]
::defmt::assert_eq!($($x)*);
}
};
}
macro_rules! assert_ne {
($($x:tt)*) => {
{
#[cfg(not(feature = "defmt"))]
::core::assert_ne!($($x)*);
#[cfg(feature = "defmt")]
::defmt::assert_ne!($($x)*);
}
};
}
macro_rules! debug_assert {
($($x:tt)*) => {
{
#[cfg(not(feature = "defmt"))]
::core::debug_assert!($($x)*);
#[cfg(feature = "defmt")]
::defmt::debug_assert!($($x)*);
}
};
}
macro_rules! debug_assert_eq {
($($x:tt)*) => {
{
#[cfg(not(feature = "defmt"))]
::core::debug_assert_eq!($($x)*);
#[cfg(feature = "defmt")]
::defmt::debug_assert_eq!($($x)*);
}
};
}
macro_rules! debug_assert_ne {
($($x:tt)*) => {
{
#[cfg(not(feature = "defmt"))]
::core::debug_assert_ne!($($x)*);
#[cfg(feature = "defmt")]
::defmt::debug_assert_ne!($($x)*);
}
};
}
macro_rules! todo {
($($x:tt)*) => {
{
#[cfg(not(feature = "defmt"))]
::core::todo!($($x)*);
#[cfg(feature = "defmt")]
::defmt::todo!($($x)*);
}
};
}
macro_rules! unreachable {
($($x:tt)*) => {
{
#[cfg(not(feature = "defmt"))]
::core::unreachable!($($x)*);
#[cfg(feature = "defmt")]
::defmt::unreachable!($($x)*);
}
};
}
macro_rules! panic {
($($x:tt)*) => {
{
#[cfg(not(feature = "defmt"))]
::core::panic!($($x)*);
#[cfg(feature = "defmt")]
::defmt::panic!($($x)*);
}
};
}
macro_rules! trace {
($s:literal $(, $x:expr)* $(,)?) => {
{
#[cfg(feature = "log")]
::log::trace!($s $(, $x)*);
#[cfg(feature = "defmt")]
::defmt::trace!($s $(, $x)*);
#[cfg(not(any(feature = "log", feature="defmt")))]
let _ignored = ($( & $x ),*);
}
};
}
macro_rules! debug {
($s:literal $(, $x:expr)* $(,)?) => {
{
#[cfg(feature = "log")]
::log::debug!($s $(, $x)*);
#[cfg(feature = "defmt")]
::defmt::debug!($s $(, $x)*);
#[cfg(not(any(feature = "log", feature="defmt")))]
let _ignored = ($( & $x ),*);
}
};
}
macro_rules! info {
($s:literal $(, $x:expr)* $(,)?) => {
{
#[cfg(feature = "log")]
::log::info!($s $(, $x)*);
#[cfg(feature = "defmt")]
::defmt::info!($s $(, $x)*);
#[cfg(not(any(feature = "log", feature="defmt")))]
let _ignored = ($( & $x ),*);
}
};
}
macro_rules! warn {
($s:literal $(, $x:expr)* $(,)?) => {
{
#[cfg(feature = "log")]
::log::warn!($s $(, $x)*);
#[cfg(feature = "defmt")]
::defmt::warn!($s $(, $x)*);
#[cfg(not(any(feature = "log", feature="defmt")))]
let _ignored = ($( & $x ),*);
}
};
}
macro_rules! error {
($s:literal $(, $x:expr)* $(,)?) => {
{
#[cfg(feature = "log")]
::log::error!($s $(, $x)*);
#[cfg(feature = "defmt")]
::defmt::error!($s $(, $x)*);
#[cfg(not(any(feature = "log", feature="defmt")))]
let _ignored = ($( & $x ),*);
}
};
}
#[cfg(feature = "defmt")]
macro_rules! unwrap {
($($x:tt)*) => {
::defmt::unwrap!($($x)*)
};
}
#[cfg(not(feature = "defmt"))]
macro_rules! unwrap {
($arg:expr) => {
match $crate::fmt::Try::into_result($arg) {
::core::result::Result::Ok(t) => t,
::core::result::Result::Err(e) => {
::core::panic!("unwrap of `{}` failed: {:?}", ::core::stringify!($arg), e);
}
}
};
($arg:expr, $($msg:expr),+ $(,)? ) => {
match $crate::fmt::Try::into_result($arg) {
::core::result::Result::Ok(t) => t,
::core::result::Result::Err(e) => {
::core::panic!("unwrap of `{}` failed: {}: {:?}", ::core::stringify!($arg), ::core::format_args!($($msg,)*), e);
}
}
}
}
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub struct NoneError;
pub trait Try {
type Ok;
type Error;
fn into_result(self) -> Result<Self::Ok, Self::Error>;
}
impl<T> Try for Option<T> {
type Ok = T;
type Error = NoneError;
#[inline]
fn into_result(self) -> Result<T, NoneError> {
self.ok_or(NoneError)
}
}
impl<T, E> Try for Result<T, E> {
type Ok = T;
type Error = E;
#[inline]
fn into_result(self) -> Self {
self
}
}
pub struct Bytes<'a>(pub &'a [u8]);
impl<'a> Debug for Bytes<'a> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "{:#02x?}", self.0)
}
}
impl<'a> Display for Bytes<'a> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "{:#02x?}", self.0)
}
}
impl<'a> LowerHex for Bytes<'a> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "{:#02x?}", self.0)
}
}
#[cfg(feature = "defmt")]
impl<'a> defmt::Format for Bytes<'a> {
fn format(&self, fmt: defmt::Formatter) {
defmt::write!(fmt, "{:02x}", self.0)
}
}

File diff suppressed because it is too large Load diff

View file

@ -1,3 +1,5 @@
use core::fmt::{Debug, Display};
use bitfield::{bitfield, bitfield_bitrange, bitfield_fields};
#[allow(non_camel_case_types)]
@ -34,6 +36,12 @@ pub enum SpiRegisters {
RX = 0x91,
}
impl Display for SpiRegisters {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "{self:?}")
}
}
impl From<SpiRegisters> for u16 {
fn from(val: SpiRegisters) -> Self {
val as u16
@ -68,7 +76,7 @@ impl From<u16> for SpiRegisters {
0x73 => Self::ADDR_MSK_UPR1,
0x90 => Self::RX_FSIZE,
0x91 => Self::RX,
e => panic!("Unknown value {e}"),
e => panic!("Unknown value {}", e),
}
}
}
@ -174,7 +182,7 @@ bitfield! {
pub sdf_detect_src, set_sdf_detect_src : 7;
/// Statistics Clear on Reading
pub stats_clr_on_rd, set_stats_clr_on_rd : 6;
/// Enable CRC Append
/// Enable SPI CRC
pub crc_append, set_crc_append : 5;
/// Admit Frames with IFG Errors on Port 1 (P1)
pub p1_rcv_ifg_err_frm, set_p1_rcv_ifg_err_frm : 4;
@ -313,7 +321,7 @@ impl From<u8> for LedFunc {
26 => LedFunc::Clk25Ref,
27 => LedFunc::TxTCLK,
28 => LedFunc::Clk120MHz,
e => panic!("Invalid value {e}"),
e => panic!("Invalid value {}", e),
}
}
}
@ -369,7 +377,7 @@ impl From<u8> for LedPol {
0 => LedPol::AutoSense,
1 => LedPol::ActiveHigh,
2 => LedPol::ActiveLow,
e => panic!("Invalid value {e}"),
e => panic!("Invalid value {}", e),
}
}
}

View file

@ -1,7 +1,7 @@
[package]
edition = "2021"
name = "embassy-stm32l4-examples"
version = "0.1.0"
version = "0.1.1"
license = "MIT OR Apache-2.0"
[dependencies]
@ -12,7 +12,7 @@ embassy-executor = { version = "0.3.0", path = "../../embassy-executor", feature
embassy-time = { version = "0.1.3", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768", "unstable-traits", "nightly"] }
embassy-embedded-hal = { version = "0.1.0", path = "../../embassy-embedded-hal" }
embassy-usb = { version = "0.1.0", path = "../../embassy-usb", features = ["defmt"] }
embassy-net-adin1110 = { version = "0.1.0", path = "../../embassy-net-adin1110", default-features = false }
embassy-net-adin1110 = { version = "0.2.0", path = "../../embassy-net-adin1110" }
embassy-net = { version = "0.1.0", path = "../../embassy-net", features = ["defmt", "nightly", "udp", "tcp", "dhcpv4", "medium-ethernet"] }
embassy-futures = { version = "0.1.0", path = "../../embassy-futures" }
embedded-io-async = { version = "0.5.0", features = ["defmt-03"] }

View file

@ -11,7 +11,9 @@
// Settings switch S201 "HW CFG":
// - Without SPI CRC: OFF-ON-OFF-OFF-OFF
// - With SPI CRC: ON -ON-OFF-OFF-OFF
// Settings switch S303 "uC CFG": CFG0: On = static ip, Off = Dhcp
// Settings switch S303 "uC CFG":
// - CFG0: On = static ip, Off = Dhcp
// - CFG1: Ethernet `FCS` on TX path: On, Off
// The webserver shows the actual temperature of the onboard i2c temp sensor.
use core::marker::PhantomData;
@ -107,7 +109,7 @@ async fn main(spawner: Spawner) {
// Read the uc_cfg switches
let uc_cfg0 = Input::new(dp.PB2, Pull::None);
let _uc_cfg1 = Input::new(dp.PF11, Pull::None);
let uc_cfg1 = Input::new(dp.PF11, Pull::None);
let _uc_cfg2 = Input::new(dp.PG6, Pull::None);
let _uc_cfg3 = Input::new(dp.PG11, Pull::None);
@ -154,11 +156,13 @@ async fn main(spawner: Spawner) {
let cfg0_without_crc = spe_cfg0.is_high();
let cfg1_spi_mode = spe_cfg1.is_high();
let uc_cfg1_fcs_en = uc_cfg1.is_low();
defmt::println!(
"ADIN1110: CFG SPI-MODE 1-{}, CRC-bit 0-{}",
"ADIN1110: CFG SPI-MODE 1-{}, CRC-bit 0-{} FCS-{}",
cfg1_spi_mode,
cfg0_without_crc
cfg0_without_crc,
uc_cfg1_fcs_en
);
// Check the SPI mode selected with the "HW CFG" dip-switch
@ -172,8 +176,16 @@ async fn main(spawner: Spawner) {
let state = make_static!(embassy_net_adin1110::State::<8, 8>::new());
let (device, runner) =
embassy_net_adin1110::new(MAC, state, spe_spi, spe_int, spe_reset_n, !cfg0_without_crc).await;
let (device, runner) = embassy_net_adin1110::new(
MAC,
state,
spe_spi,
spe_int,
spe_reset_n,
!cfg0_without_crc,
uc_cfg1_fcs_en,
)
.await;
// Start task blink_led
unwrap!(spawner.spawn(heartbeat_led(led_uc3_yellow)));