From 05c3bd7172ae9785f6a4beafdd69256f3298d6c4 Mon Sep 17 00:00:00 2001 From: Zachary Crockett Date: Sun, 19 Oct 2025 13:53:35 -0700 Subject: [PATCH 01/12] Copy adin1110 example from stm32l4 to stm32u5 Modify clocks and pins for Bristlemouth dev kit --- examples/stm32u5/Cargo.toml | 9 +- .../src/bin/spe_adin1110_http_server.rs | 386 ++++++++++++++++++ 2 files changed, 394 insertions(+), 1 deletion(-) create mode 100644 examples/stm32u5/src/bin/spe_adin1110_http_server.rs diff --git a/examples/stm32u5/Cargo.toml b/examples/stm32u5/Cargo.toml index 7a1e624060..3fa773a2ef 100644 --- a/examples/stm32u5/Cargo.toml +++ b/examples/stm32u5/Cargo.toml @@ -7,12 +7,16 @@ publish = false [dependencies] # Change stm32u5g9zj to your chip name, if necessary. -embassy-stm32 = { version = "0.4.0", path = "../../embassy-stm32", features = ["defmt", "unstable-pac", "stm32u5g9zj", "time-driver-any", "memory-x" ] } +embassy-stm32 = { version = "0.4.0", path = "../../embassy-stm32", features = ["defmt", "unstable-pac", "stm32u5g9zj", "time-driver-any", "memory-x", "exti"] } embassy-sync = { version = "0.7.2", path = "../../embassy-sync", features = ["defmt"] } embassy-executor = { version = "0.9.0", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "defmt"] } embassy-time = { version = "0.5.0", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } embassy-usb = { version = "0.5.1", path = "../../embassy-usb", features = ["defmt"] } embassy-futures = { version = "0.1.2", path = "../../embassy-futures" } +embassy-net-adin1110 = { version = "0.3.1", path = "../../embassy-net-adin1110" } +embassy-net = { version = "0.7.1", path = "../../embassy-net", features = ["defmt", "udp", "tcp", "proto-ipv6", "medium-ethernet"] } +embedded-io-async = { version = "0.6.1", features = ["defmt-03"] } +embedded-io = { version = "0.6.0", features = ["defmt-03"] } defmt = "1.0.1" defmt-rtt = "1.0.0" @@ -20,10 +24,13 @@ defmt-rtt = "1.0.0" cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } cortex-m-rt = "0.7.0" embedded-hal = "0.2.6" +embedded-hal-async = { version = "1.0" } +embedded-hal-bus = { version = "0.1", features = ["async"] } panic-probe = { version = "1.0.0", features = ["print-defmt"] } heapless = { version = "0.8", default-features = false } embedded-graphics = { version = "0.8.1" } tinybmp = { version = "0.6.0" } +static_cell = "2" micromath = "2.0.0" diff --git a/examples/stm32u5/src/bin/spe_adin1110_http_server.rs b/examples/stm32u5/src/bin/spe_adin1110_http_server.rs new file mode 100644 index 0000000000..3b7a5fa8fa --- /dev/null +++ b/examples/stm32u5/src/bin/spe_adin1110_http_server.rs @@ -0,0 +1,386 @@ +#![no_main] +#![no_std] +#![deny(clippy::pedantic)] +#![allow(clippy::doc_markdown)] +#![allow(clippy::missing_errors_doc)] + +// This example works on a Bristlemouth dev kit from Sofar Ocean. +// The webserver shows the actual temperature of the onboard i2c temp sensor. + +use core::marker::PhantomData; +use core::sync::atomic::{AtomicI32, Ordering}; + +use defmt::{Format, error, info, println, unwrap}; +use defmt_rtt as _; // global logger +use embassy_executor::Spawner; +use embassy_futures::select::{Either, select}; +use embassy_futures::yield_now; +use embassy_net::tcp::TcpSocket; +use embassy_net::{Ipv6Address, Ipv6Cidr, Stack, StackResources, StaticConfigV6}; +use embassy_net_adin1110::{ADIN1110, Device, Runner}; +use embassy_stm32::gpio::{Level, Output, Pull, Speed}; +use embassy_stm32::i2c::{self, Config as I2C_Config, I2c}; +use embassy_stm32::mode::Async; +use embassy_stm32::rng::{self, Rng}; +use embassy_stm32::spi::{Config as SPI_Config, Spi}; +use embassy_stm32::time::Hertz; +use embassy_stm32::{bind_interrupts, exti, pac, peripherals}; +use embassy_time::{Delay, Duration, Ticker, Timer}; +use embedded_hal_async::i2c::I2c as I2cBus; +use embedded_hal_bus::spi::ExclusiveDevice; +use embedded_io::Write as bWrite; +use embedded_io_async::Write; +use heapless::Vec; +use panic_probe as _; +use static_cell::StaticCell; + +bind_interrupts!(struct Irqs { + I2C1_EV => i2c::EventInterruptHandler; + I2C1_ER => i2c::ErrorInterruptHandler; + RNG => rng::InterruptHandler; +}); + +// Basic settings +// MAC-address used by the adin1110 +const MAC: [u8; 6] = [0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff]; +// Static IP settings +const IP_ADDRESS: Ipv6Cidr = Ipv6Cidr::new(Ipv6Address::new(0xfd00, 0, 0, 0, 0xc0ff, 0xeef0, 0xcacc, 0x1a99), 64); +// Listen port for the webserver +const HTTP_LISTEN_PORT: u16 = 80; + +pub type SpeSpi = Spi<'static, Async>; +pub type SpeSpiCs = ExclusiveDevice, Delay>; +pub type SpeInt = exti::ExtiInput<'static>; +pub type SpeRst = Output<'static>; +pub type Adin1110T = ADIN1110; +pub type TempSensI2c = I2c<'static, Async, i2c::Master>; + +static TEMP: AtomicI32 = AtomicI32::new(0); + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + defmt::println!("Start main()"); + + let mut config = embassy_stm32::Config::default(); + { + use embassy_stm32::rcc::{ + Hse, HseMode, Hsi48Config, Msirange, Pll, PllDiv, PllMul, PllPreDiv, PllSource, Sysclk, + }; + config.rcc.sys = Sysclk::PLL1_R; + config.rcc.hse = Some(Hse { + freq: Hertz(16_000_000), + mode: HseMode::Oscillator, + }); + config.rcc.pll1 = Some(Pll { + source: PllSource::MSIS, + prediv: PllPreDiv::DIV3, + mul: PllMul::MUL10, + divp: Some(PllDiv::DIV2), + divq: Some(PllDiv::DIV2), + divr: None, + }); + config.rcc.hsi48 = Some(Hsi48Config::default()); + config.rcc.msis = Some(Msirange::RANGE_48MHZ); + } + + let dp = embassy_stm32::init(config); + + let reset_status = pac::RCC.bdcr().read().0; + defmt::println!("bdcr before: 0x{:X}", reset_status); + + defmt::println!("Setup IO pins"); + + // Setup LEDs + // TODO add pca9535 crate + // let _led_uc1_green = Output::new(dp.PC13, Level::Low, Speed::Low); // expander IO1 1 + let mut led_uc1_red = Output::new(dp.PE2, Level::High, Speed::Low); // expander IO1 2 + let led_uc2_green = Output::new(dp.PE6, Level::High, Speed::Low); // expander IO1 3 + // let _led_uc2_red = Output::new(dp.PG15, Level::High, Speed::Low); // expander IO1 4 + + // Setup I2C pins + let _temp_sens_i2c = I2c::new( + dp.I2C1, + dp.PB6, + dp.PB7, + Irqs, + dp.GPDMA1_CH9, + dp.GPDMA1_CH8, + I2C_Config::default(), + ); + + // Setup IO and SPI for the SPE chip + let spe_reset_n = Output::new(dp.PA0, Level::Low, Speed::Low); + let spe_int = exti::ExtiInput::new(dp.PB8, dp.EXTI8, Pull::None); + let spe_spi_cs_n = Output::new(dp.PA15, Level::High, Speed::High); + let spe_spi_sclk = dp.PB3; + let spe_spi_miso = dp.PB4; + let spe_spi_mosi = dp.PB5; + + let mut spi_config = SPI_Config::default(); + spi_config.frequency = Hertz(20_000_000); + + let spe_spi: SpeSpi = Spi::new( + dp.SPI3, + spe_spi_sclk, + spe_spi_mosi, + spe_spi_miso, + dp.GPDMA1_CH13, + dp.GPDMA1_CH12, + spi_config, + ); + let spe_spi = SpeSpiCs::new(spe_spi, spe_spi_cs_n, Delay); + + static STATE: StaticCell> = StaticCell::new(); + let state = STATE.init(embassy_net_adin1110::State::<8, 8>::new()); + + let (device, runner) = embassy_net_adin1110::new(MAC, state, spe_spi, spe_int, spe_reset_n, true, false).await; + + // Start task blink_led + spawner.spawn(unwrap!(heartbeat_led(led_uc2_green))); + // Start ethernet task + spawner.spawn(unwrap!(ethernet_task(runner))); + + let mut rng = Rng::new(dp.RNG, Irqs); + // Generate random seed + let seed = rng.next_u64(); + + let ip_cfg = embassy_net::Config::ipv6_static(StaticConfigV6 { + address: IP_ADDRESS, + gateway: None, + dns_servers: Vec::new(), + }); + + // Init network stack + static RESOURCES: StaticCell> = StaticCell::new(); + let (stack, runner) = embassy_net::new(device, ip_cfg, RESOURCES.init(StackResources::new()), seed); + + // Launch network task + spawner.spawn(unwrap!(net_task(runner))); + + let cfg = wait_for_config(stack).await; + let local_addr = cfg.address.address(); + + // Then we can use it! + let mut rx_buffer = [0; 4096]; + let mut tx_buffer = [0; 4096]; + let mut mb_buf = [0; 4096]; + loop { + let mut socket = TcpSocket::new(stack, &mut rx_buffer, &mut tx_buffer); + socket.set_timeout(Some(Duration::from_secs(1))); + + info!("Listening on http://{}:{}...", local_addr, HTTP_LISTEN_PORT); + if let Err(e) = socket.accept(HTTP_LISTEN_PORT).await { + defmt::error!("accept error: {:?}", e); + continue; + } + + loop { + let _n = match socket.read(&mut mb_buf).await { + Ok(0) => { + defmt::info!("read EOF"); + break; + } + Ok(n) => n, + Err(e) => { + defmt::error!("{:?}", e); + break; + } + }; + led_uc1_red.set_low(); + + let status_line = "HTTP/1.1 200 OK"; + let contents = PAGE; + let length = contents.len(); + + let _ = write!( + &mut mb_buf[..], + "{status_line}\r\nContent-Length: {length}\r\n\r\n{contents}\r\n\0" + ); + let loc = mb_buf.iter().position(|v| *v == b'#').unwrap(); + + let temp = TEMP.load(Ordering::Relaxed); + let cel = temp / 1000; + let mcel = temp % 1000; + + info!("{}.{}", cel, mcel); + + let _ = write!(&mut mb_buf[loc..loc + 7], "{cel}.{mcel}"); + + let n = mb_buf.iter().position(|v| *v == 0).unwrap(); + + if let Err(e) = socket.write_all(&mb_buf[..n]).await { + error!("write error: {:?}", e); + break; + } + + led_uc1_red.set_high(); + } + } +} + +async fn wait_for_config(stack: Stack<'static>) -> embassy_net::StaticConfigV6 { + loop { + if let Some(config) = stack.config_v6() { + return config; + } + yield_now().await; + } +} + +#[embassy_executor::task] +async fn heartbeat_led(mut led: Output<'static>) { + let mut tmr = Ticker::every(Duration::from_hz(3)); + loop { + led.toggle(); + tmr.next().await; + } +} + +// ADT7422 +#[embassy_executor::task] +async fn temp_task(temp_dev_i2c: TempSensI2c, mut led: Output<'static>) -> ! { + let mut tmr = Ticker::every(Duration::from_hz(1)); + let mut temp_sens = ADT7422::new(temp_dev_i2c, 0x48).unwrap(); + + loop { + led.set_low(); + match select(temp_sens.read_temp(), Timer::after_millis(500)).await { + Either::First(i2c_ret) => match i2c_ret { + Ok(value) => { + led.set_high(); + let temp = i32::from(value); + println!("TEMP: {:04x}, {}", temp, temp * 78 / 10); + TEMP.store(temp * 78 / 10, Ordering::Relaxed); + } + Err(e) => defmt::println!("ADT7422: {}", e), + }, + Either::Second(()) => println!("Timeout"), + } + + tmr.next().await; + } +} + +#[embassy_executor::task] +async fn ethernet_task(runner: Runner<'static, SpeSpiCs, SpeInt, SpeRst>) -> ! { + runner.run().await +} + +#[embassy_executor::task] +async fn net_task(mut runner: embassy_net::Runner<'static, Device<'static>>) -> ! { + runner.run().await +} + +// same panicking *behavior* as `panic-probe` but doesn't print a panic message +// this prevents the panic message being printed *twice* when `defmt::panic` is invoked +#[defmt::panic_handler] +fn panic() -> ! { + cortex_m::asm::udf() +} + +#[allow(non_camel_case_types)] +#[repr(C)] +pub enum Registers { + Temp_MSB = 0x00, + Temp_LSB, + Status, + Cfg, + T_HIGH_MSB, + T_HIGH_LSB, + T_LOW_MSB, + T_LOW_LSB, + T_CRIT_MSB, + T_CRIT_LSB, + T_HYST, + ID, + SW_RESET = 0x2F, +} + +pub struct ADT7422<'d, BUS: I2cBus> { + addr: u8, + phantom: PhantomData<&'d ()>, + bus: BUS, +} + +#[derive(Debug, Format, PartialEq, Eq)] +pub enum Error { + I2c(I2cError), + Address, +} + +impl ADT7422<'_, BUS> +where + BUS: I2cBus, + BUS::Error: Format, +{ + pub fn new(bus: BUS, addr: u8) -> Result> { + if !(0x48..=0x4A).contains(&addr) { + return Err(Error::Address); + } + + Ok(Self { + bus, + phantom: PhantomData, + addr, + }) + } + + pub async fn init(&mut self) -> Result<(), Error> { + let mut cfg = 0b000_0000; + // if self.int.is_some() { + // // Set 1 SPS mode + // cfg |= 0b10 << 5; + // } else { + // One shot mode + cfg |= 0b01 << 5; + // } + + self.write_cfg(cfg).await + } + + pub async fn read(&mut self, reg: Registers) -> Result> { + let mut buffer = [0u8; 1]; + self.bus + .write_read(self.addr, &[reg as u8], &mut buffer) + .await + .map_err(Error::I2c)?; + Ok(buffer[0]) + } + + pub async fn write_cfg(&mut self, cfg: u8) -> Result<(), Error> { + let buf = [Registers::Cfg as u8, cfg]; + self.bus.write(self.addr, &buf).await.map_err(Error::I2c) + } + + pub async fn read_temp(&mut self) -> Result> { + let mut buffer = [0u8; 2]; + + // if let Some(int) = &mut self.int { + // // Wait for interrupt + // int.wait_for_low().await.unwrap(); + // } else { + // Start: One shot + let cfg = 0b01 << 5; + self.write_cfg(cfg).await?; + Timer::after_millis(250).await; + self.bus + .write_read(self.addr, &[Registers::Temp_MSB as u8], &mut buffer) + .await + .map_err(Error::I2c)?; + Ok(i16::from_be_bytes(buffer)) + } +} + +// Web page +const PAGE: &str = r#" + + + + + ADIN1110 with Rust + + +

EVAL-ADIN1110EBZ

+
Temp Sensor ADT7422: #00.00 °C
+ +"#; From 6d7b8a7f67af64d4019d58c5beb9ba7cd99c877e Mon Sep 17 00:00:00 2001 From: Zachary Crockett Date: Sun, 19 Oct 2025 16:59:38 -0700 Subject: [PATCH 02/12] Revert and rename dir to more specific chip --- .../src/bin/spe_adin1110_http_server.rs | 386 ------------------ .../{stm32u5 => stm32u5g9}/.cargo/config.toml | 0 examples/{stm32u5 => stm32u5g9}/Cargo.toml | 9 +- examples/{stm32u5 => stm32u5g9}/build.rs | 0 .../{stm32u5 => stm32u5g9}/src/bin/adc.rs | 0 .../{stm32u5 => stm32u5g9}/src/bin/blinky.rs | 0 .../{stm32u5 => stm32u5g9}/src/bin/boot.rs | 0 .../{stm32u5 => stm32u5g9}/src/bin/ferris.bmp | Bin .../{stm32u5 => stm32u5g9}/src/bin/flash.rs | 0 .../src/bin/hspi_memory_mapped.rs | 0 .../{stm32u5 => stm32u5g9}/src/bin/i2c.rs | 0 .../{stm32u5 => stm32u5g9}/src/bin/ltdc.rs | 0 .../{stm32u5 => stm32u5g9}/src/bin/rng.rs | 0 .../{stm32u5 => stm32u5g9}/src/bin/tsc.rs | 0 .../src/bin/usb_hs_serial.rs | 0 .../src/bin/usb_serial.rs | 0 16 files changed, 1 insertion(+), 394 deletions(-) delete mode 100644 examples/stm32u5/src/bin/spe_adin1110_http_server.rs rename examples/{stm32u5 => stm32u5g9}/.cargo/config.toml (100%) rename examples/{stm32u5 => stm32u5g9}/Cargo.toml (75%) rename examples/{stm32u5 => stm32u5g9}/build.rs (100%) rename examples/{stm32u5 => stm32u5g9}/src/bin/adc.rs (100%) rename examples/{stm32u5 => stm32u5g9}/src/bin/blinky.rs (100%) rename examples/{stm32u5 => stm32u5g9}/src/bin/boot.rs (100%) rename examples/{stm32u5 => stm32u5g9}/src/bin/ferris.bmp (100%) rename examples/{stm32u5 => stm32u5g9}/src/bin/flash.rs (100%) rename examples/{stm32u5 => stm32u5g9}/src/bin/hspi_memory_mapped.rs (100%) rename examples/{stm32u5 => stm32u5g9}/src/bin/i2c.rs (100%) rename examples/{stm32u5 => stm32u5g9}/src/bin/ltdc.rs (100%) rename examples/{stm32u5 => stm32u5g9}/src/bin/rng.rs (100%) rename examples/{stm32u5 => stm32u5g9}/src/bin/tsc.rs (100%) rename examples/{stm32u5 => stm32u5g9}/src/bin/usb_hs_serial.rs (100%) rename examples/{stm32u5 => stm32u5g9}/src/bin/usb_serial.rs (100%) diff --git a/examples/stm32u5/src/bin/spe_adin1110_http_server.rs b/examples/stm32u5/src/bin/spe_adin1110_http_server.rs deleted file mode 100644 index 3b7a5fa8fa..0000000000 --- a/examples/stm32u5/src/bin/spe_adin1110_http_server.rs +++ /dev/null @@ -1,386 +0,0 @@ -#![no_main] -#![no_std] -#![deny(clippy::pedantic)] -#![allow(clippy::doc_markdown)] -#![allow(clippy::missing_errors_doc)] - -// This example works on a Bristlemouth dev kit from Sofar Ocean. -// The webserver shows the actual temperature of the onboard i2c temp sensor. - -use core::marker::PhantomData; -use core::sync::atomic::{AtomicI32, Ordering}; - -use defmt::{Format, error, info, println, unwrap}; -use defmt_rtt as _; // global logger -use embassy_executor::Spawner; -use embassy_futures::select::{Either, select}; -use embassy_futures::yield_now; -use embassy_net::tcp::TcpSocket; -use embassy_net::{Ipv6Address, Ipv6Cidr, Stack, StackResources, StaticConfigV6}; -use embassy_net_adin1110::{ADIN1110, Device, Runner}; -use embassy_stm32::gpio::{Level, Output, Pull, Speed}; -use embassy_stm32::i2c::{self, Config as I2C_Config, I2c}; -use embassy_stm32::mode::Async; -use embassy_stm32::rng::{self, Rng}; -use embassy_stm32::spi::{Config as SPI_Config, Spi}; -use embassy_stm32::time::Hertz; -use embassy_stm32::{bind_interrupts, exti, pac, peripherals}; -use embassy_time::{Delay, Duration, Ticker, Timer}; -use embedded_hal_async::i2c::I2c as I2cBus; -use embedded_hal_bus::spi::ExclusiveDevice; -use embedded_io::Write as bWrite; -use embedded_io_async::Write; -use heapless::Vec; -use panic_probe as _; -use static_cell::StaticCell; - -bind_interrupts!(struct Irqs { - I2C1_EV => i2c::EventInterruptHandler; - I2C1_ER => i2c::ErrorInterruptHandler; - RNG => rng::InterruptHandler; -}); - -// Basic settings -// MAC-address used by the adin1110 -const MAC: [u8; 6] = [0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff]; -// Static IP settings -const IP_ADDRESS: Ipv6Cidr = Ipv6Cidr::new(Ipv6Address::new(0xfd00, 0, 0, 0, 0xc0ff, 0xeef0, 0xcacc, 0x1a99), 64); -// Listen port for the webserver -const HTTP_LISTEN_PORT: u16 = 80; - -pub type SpeSpi = Spi<'static, Async>; -pub type SpeSpiCs = ExclusiveDevice, Delay>; -pub type SpeInt = exti::ExtiInput<'static>; -pub type SpeRst = Output<'static>; -pub type Adin1110T = ADIN1110; -pub type TempSensI2c = I2c<'static, Async, i2c::Master>; - -static TEMP: AtomicI32 = AtomicI32::new(0); - -#[embassy_executor::main] -async fn main(spawner: Spawner) { - defmt::println!("Start main()"); - - let mut config = embassy_stm32::Config::default(); - { - use embassy_stm32::rcc::{ - Hse, HseMode, Hsi48Config, Msirange, Pll, PllDiv, PllMul, PllPreDiv, PllSource, Sysclk, - }; - config.rcc.sys = Sysclk::PLL1_R; - config.rcc.hse = Some(Hse { - freq: Hertz(16_000_000), - mode: HseMode::Oscillator, - }); - config.rcc.pll1 = Some(Pll { - source: PllSource::MSIS, - prediv: PllPreDiv::DIV3, - mul: PllMul::MUL10, - divp: Some(PllDiv::DIV2), - divq: Some(PllDiv::DIV2), - divr: None, - }); - config.rcc.hsi48 = Some(Hsi48Config::default()); - config.rcc.msis = Some(Msirange::RANGE_48MHZ); - } - - let dp = embassy_stm32::init(config); - - let reset_status = pac::RCC.bdcr().read().0; - defmt::println!("bdcr before: 0x{:X}", reset_status); - - defmt::println!("Setup IO pins"); - - // Setup LEDs - // TODO add pca9535 crate - // let _led_uc1_green = Output::new(dp.PC13, Level::Low, Speed::Low); // expander IO1 1 - let mut led_uc1_red = Output::new(dp.PE2, Level::High, Speed::Low); // expander IO1 2 - let led_uc2_green = Output::new(dp.PE6, Level::High, Speed::Low); // expander IO1 3 - // let _led_uc2_red = Output::new(dp.PG15, Level::High, Speed::Low); // expander IO1 4 - - // Setup I2C pins - let _temp_sens_i2c = I2c::new( - dp.I2C1, - dp.PB6, - dp.PB7, - Irqs, - dp.GPDMA1_CH9, - dp.GPDMA1_CH8, - I2C_Config::default(), - ); - - // Setup IO and SPI for the SPE chip - let spe_reset_n = Output::new(dp.PA0, Level::Low, Speed::Low); - let spe_int = exti::ExtiInput::new(dp.PB8, dp.EXTI8, Pull::None); - let spe_spi_cs_n = Output::new(dp.PA15, Level::High, Speed::High); - let spe_spi_sclk = dp.PB3; - let spe_spi_miso = dp.PB4; - let spe_spi_mosi = dp.PB5; - - let mut spi_config = SPI_Config::default(); - spi_config.frequency = Hertz(20_000_000); - - let spe_spi: SpeSpi = Spi::new( - dp.SPI3, - spe_spi_sclk, - spe_spi_mosi, - spe_spi_miso, - dp.GPDMA1_CH13, - dp.GPDMA1_CH12, - spi_config, - ); - let spe_spi = SpeSpiCs::new(spe_spi, spe_spi_cs_n, Delay); - - static STATE: StaticCell> = StaticCell::new(); - let state = STATE.init(embassy_net_adin1110::State::<8, 8>::new()); - - let (device, runner) = embassy_net_adin1110::new(MAC, state, spe_spi, spe_int, spe_reset_n, true, false).await; - - // Start task blink_led - spawner.spawn(unwrap!(heartbeat_led(led_uc2_green))); - // Start ethernet task - spawner.spawn(unwrap!(ethernet_task(runner))); - - let mut rng = Rng::new(dp.RNG, Irqs); - // Generate random seed - let seed = rng.next_u64(); - - let ip_cfg = embassy_net::Config::ipv6_static(StaticConfigV6 { - address: IP_ADDRESS, - gateway: None, - dns_servers: Vec::new(), - }); - - // Init network stack - static RESOURCES: StaticCell> = StaticCell::new(); - let (stack, runner) = embassy_net::new(device, ip_cfg, RESOURCES.init(StackResources::new()), seed); - - // Launch network task - spawner.spawn(unwrap!(net_task(runner))); - - let cfg = wait_for_config(stack).await; - let local_addr = cfg.address.address(); - - // Then we can use it! - let mut rx_buffer = [0; 4096]; - let mut tx_buffer = [0; 4096]; - let mut mb_buf = [0; 4096]; - loop { - let mut socket = TcpSocket::new(stack, &mut rx_buffer, &mut tx_buffer); - socket.set_timeout(Some(Duration::from_secs(1))); - - info!("Listening on http://{}:{}...", local_addr, HTTP_LISTEN_PORT); - if let Err(e) = socket.accept(HTTP_LISTEN_PORT).await { - defmt::error!("accept error: {:?}", e); - continue; - } - - loop { - let _n = match socket.read(&mut mb_buf).await { - Ok(0) => { - defmt::info!("read EOF"); - break; - } - Ok(n) => n, - Err(e) => { - defmt::error!("{:?}", e); - break; - } - }; - led_uc1_red.set_low(); - - let status_line = "HTTP/1.1 200 OK"; - let contents = PAGE; - let length = contents.len(); - - let _ = write!( - &mut mb_buf[..], - "{status_line}\r\nContent-Length: {length}\r\n\r\n{contents}\r\n\0" - ); - let loc = mb_buf.iter().position(|v| *v == b'#').unwrap(); - - let temp = TEMP.load(Ordering::Relaxed); - let cel = temp / 1000; - let mcel = temp % 1000; - - info!("{}.{}", cel, mcel); - - let _ = write!(&mut mb_buf[loc..loc + 7], "{cel}.{mcel}"); - - let n = mb_buf.iter().position(|v| *v == 0).unwrap(); - - if let Err(e) = socket.write_all(&mb_buf[..n]).await { - error!("write error: {:?}", e); - break; - } - - led_uc1_red.set_high(); - } - } -} - -async fn wait_for_config(stack: Stack<'static>) -> embassy_net::StaticConfigV6 { - loop { - if let Some(config) = stack.config_v6() { - return config; - } - yield_now().await; - } -} - -#[embassy_executor::task] -async fn heartbeat_led(mut led: Output<'static>) { - let mut tmr = Ticker::every(Duration::from_hz(3)); - loop { - led.toggle(); - tmr.next().await; - } -} - -// ADT7422 -#[embassy_executor::task] -async fn temp_task(temp_dev_i2c: TempSensI2c, mut led: Output<'static>) -> ! { - let mut tmr = Ticker::every(Duration::from_hz(1)); - let mut temp_sens = ADT7422::new(temp_dev_i2c, 0x48).unwrap(); - - loop { - led.set_low(); - match select(temp_sens.read_temp(), Timer::after_millis(500)).await { - Either::First(i2c_ret) => match i2c_ret { - Ok(value) => { - led.set_high(); - let temp = i32::from(value); - println!("TEMP: {:04x}, {}", temp, temp * 78 / 10); - TEMP.store(temp * 78 / 10, Ordering::Relaxed); - } - Err(e) => defmt::println!("ADT7422: {}", e), - }, - Either::Second(()) => println!("Timeout"), - } - - tmr.next().await; - } -} - -#[embassy_executor::task] -async fn ethernet_task(runner: Runner<'static, SpeSpiCs, SpeInt, SpeRst>) -> ! { - runner.run().await -} - -#[embassy_executor::task] -async fn net_task(mut runner: embassy_net::Runner<'static, Device<'static>>) -> ! { - runner.run().await -} - -// same panicking *behavior* as `panic-probe` but doesn't print a panic message -// this prevents the panic message being printed *twice* when `defmt::panic` is invoked -#[defmt::panic_handler] -fn panic() -> ! { - cortex_m::asm::udf() -} - -#[allow(non_camel_case_types)] -#[repr(C)] -pub enum Registers { - Temp_MSB = 0x00, - Temp_LSB, - Status, - Cfg, - T_HIGH_MSB, - T_HIGH_LSB, - T_LOW_MSB, - T_LOW_LSB, - T_CRIT_MSB, - T_CRIT_LSB, - T_HYST, - ID, - SW_RESET = 0x2F, -} - -pub struct ADT7422<'d, BUS: I2cBus> { - addr: u8, - phantom: PhantomData<&'d ()>, - bus: BUS, -} - -#[derive(Debug, Format, PartialEq, Eq)] -pub enum Error { - I2c(I2cError), - Address, -} - -impl ADT7422<'_, BUS> -where - BUS: I2cBus, - BUS::Error: Format, -{ - pub fn new(bus: BUS, addr: u8) -> Result> { - if !(0x48..=0x4A).contains(&addr) { - return Err(Error::Address); - } - - Ok(Self { - bus, - phantom: PhantomData, - addr, - }) - } - - pub async fn init(&mut self) -> Result<(), Error> { - let mut cfg = 0b000_0000; - // if self.int.is_some() { - // // Set 1 SPS mode - // cfg |= 0b10 << 5; - // } else { - // One shot mode - cfg |= 0b01 << 5; - // } - - self.write_cfg(cfg).await - } - - pub async fn read(&mut self, reg: Registers) -> Result> { - let mut buffer = [0u8; 1]; - self.bus - .write_read(self.addr, &[reg as u8], &mut buffer) - .await - .map_err(Error::I2c)?; - Ok(buffer[0]) - } - - pub async fn write_cfg(&mut self, cfg: u8) -> Result<(), Error> { - let buf = [Registers::Cfg as u8, cfg]; - self.bus.write(self.addr, &buf).await.map_err(Error::I2c) - } - - pub async fn read_temp(&mut self) -> Result> { - let mut buffer = [0u8; 2]; - - // if let Some(int) = &mut self.int { - // // Wait for interrupt - // int.wait_for_low().await.unwrap(); - // } else { - // Start: One shot - let cfg = 0b01 << 5; - self.write_cfg(cfg).await?; - Timer::after_millis(250).await; - self.bus - .write_read(self.addr, &[Registers::Temp_MSB as u8], &mut buffer) - .await - .map_err(Error::I2c)?; - Ok(i16::from_be_bytes(buffer)) - } -} - -// Web page -const PAGE: &str = r#" - - - - - ADIN1110 with Rust - - -

EVAL-ADIN1110EBZ

-
Temp Sensor ADT7422: #00.00 °C
- -"#; diff --git a/examples/stm32u5/.cargo/config.toml b/examples/stm32u5g9/.cargo/config.toml similarity index 100% rename from examples/stm32u5/.cargo/config.toml rename to examples/stm32u5g9/.cargo/config.toml diff --git a/examples/stm32u5/Cargo.toml b/examples/stm32u5g9/Cargo.toml similarity index 75% rename from examples/stm32u5/Cargo.toml rename to examples/stm32u5g9/Cargo.toml index 3fa773a2ef..7a1e624060 100644 --- a/examples/stm32u5/Cargo.toml +++ b/examples/stm32u5g9/Cargo.toml @@ -7,16 +7,12 @@ publish = false [dependencies] # Change stm32u5g9zj to your chip name, if necessary. -embassy-stm32 = { version = "0.4.0", path = "../../embassy-stm32", features = ["defmt", "unstable-pac", "stm32u5g9zj", "time-driver-any", "memory-x", "exti"] } +embassy-stm32 = { version = "0.4.0", path = "../../embassy-stm32", features = ["defmt", "unstable-pac", "stm32u5g9zj", "time-driver-any", "memory-x" ] } embassy-sync = { version = "0.7.2", path = "../../embassy-sync", features = ["defmt"] } embassy-executor = { version = "0.9.0", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "defmt"] } embassy-time = { version = "0.5.0", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } embassy-usb = { version = "0.5.1", path = "../../embassy-usb", features = ["defmt"] } embassy-futures = { version = "0.1.2", path = "../../embassy-futures" } -embassy-net-adin1110 = { version = "0.3.1", path = "../../embassy-net-adin1110" } -embassy-net = { version = "0.7.1", path = "../../embassy-net", features = ["defmt", "udp", "tcp", "proto-ipv6", "medium-ethernet"] } -embedded-io-async = { version = "0.6.1", features = ["defmt-03"] } -embedded-io = { version = "0.6.0", features = ["defmt-03"] } defmt = "1.0.1" defmt-rtt = "1.0.0" @@ -24,13 +20,10 @@ defmt-rtt = "1.0.0" cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } cortex-m-rt = "0.7.0" embedded-hal = "0.2.6" -embedded-hal-async = { version = "1.0" } -embedded-hal-bus = { version = "0.1", features = ["async"] } panic-probe = { version = "1.0.0", features = ["print-defmt"] } heapless = { version = "0.8", default-features = false } embedded-graphics = { version = "0.8.1" } tinybmp = { version = "0.6.0" } -static_cell = "2" micromath = "2.0.0" diff --git a/examples/stm32u5/build.rs b/examples/stm32u5g9/build.rs similarity index 100% rename from examples/stm32u5/build.rs rename to examples/stm32u5g9/build.rs diff --git a/examples/stm32u5/src/bin/adc.rs b/examples/stm32u5g9/src/bin/adc.rs similarity index 100% rename from examples/stm32u5/src/bin/adc.rs rename to examples/stm32u5g9/src/bin/adc.rs diff --git a/examples/stm32u5/src/bin/blinky.rs b/examples/stm32u5g9/src/bin/blinky.rs similarity index 100% rename from examples/stm32u5/src/bin/blinky.rs rename to examples/stm32u5g9/src/bin/blinky.rs diff --git a/examples/stm32u5/src/bin/boot.rs b/examples/stm32u5g9/src/bin/boot.rs similarity index 100% rename from examples/stm32u5/src/bin/boot.rs rename to examples/stm32u5g9/src/bin/boot.rs diff --git a/examples/stm32u5/src/bin/ferris.bmp b/examples/stm32u5g9/src/bin/ferris.bmp similarity index 100% rename from examples/stm32u5/src/bin/ferris.bmp rename to examples/stm32u5g9/src/bin/ferris.bmp diff --git a/examples/stm32u5/src/bin/flash.rs b/examples/stm32u5g9/src/bin/flash.rs similarity index 100% rename from examples/stm32u5/src/bin/flash.rs rename to examples/stm32u5g9/src/bin/flash.rs diff --git a/examples/stm32u5/src/bin/hspi_memory_mapped.rs b/examples/stm32u5g9/src/bin/hspi_memory_mapped.rs similarity index 100% rename from examples/stm32u5/src/bin/hspi_memory_mapped.rs rename to examples/stm32u5g9/src/bin/hspi_memory_mapped.rs diff --git a/examples/stm32u5/src/bin/i2c.rs b/examples/stm32u5g9/src/bin/i2c.rs similarity index 100% rename from examples/stm32u5/src/bin/i2c.rs rename to examples/stm32u5g9/src/bin/i2c.rs diff --git a/examples/stm32u5/src/bin/ltdc.rs b/examples/stm32u5g9/src/bin/ltdc.rs similarity index 100% rename from examples/stm32u5/src/bin/ltdc.rs rename to examples/stm32u5g9/src/bin/ltdc.rs diff --git a/examples/stm32u5/src/bin/rng.rs b/examples/stm32u5g9/src/bin/rng.rs similarity index 100% rename from examples/stm32u5/src/bin/rng.rs rename to examples/stm32u5g9/src/bin/rng.rs diff --git a/examples/stm32u5/src/bin/tsc.rs b/examples/stm32u5g9/src/bin/tsc.rs similarity index 100% rename from examples/stm32u5/src/bin/tsc.rs rename to examples/stm32u5g9/src/bin/tsc.rs diff --git a/examples/stm32u5/src/bin/usb_hs_serial.rs b/examples/stm32u5g9/src/bin/usb_hs_serial.rs similarity index 100% rename from examples/stm32u5/src/bin/usb_hs_serial.rs rename to examples/stm32u5g9/src/bin/usb_hs_serial.rs diff --git a/examples/stm32u5/src/bin/usb_serial.rs b/examples/stm32u5g9/src/bin/usb_serial.rs similarity index 100% rename from examples/stm32u5/src/bin/usb_serial.rs rename to examples/stm32u5g9/src/bin/usb_serial.rs From 61b00609e28e1cfa83d21bbf132b350e3f796ec8 Mon Sep 17 00:00:00 2001 From: Zachary Crockett Date: Sun, 19 Oct 2025 17:15:11 -0700 Subject: [PATCH 03/12] New example dir for stm32u575 I copied from the existing stm32u5 dir and deleted any that had build errors. We can add them again in the future, and I'll check the ones that remain on my board before marking the PR ready for review. I'm focusing only on the spe_adin1110_http_server example. --- examples/stm32u575/.cargo/config.toml | 9 + examples/stm32u575/Cargo.toml | 75 ++++ examples/stm32u575/build.rs | 35 ++ examples/stm32u575/memory.x | 5 + examples/stm32u575/src/bin/blinky.rs | 27 ++ examples/stm32u575/src/bin/boot.rs | 14 + examples/stm32u575/src/bin/flash.rs | 55 +++ examples/stm32u575/src/bin/rng.rs | 25 ++ .../src/bin/spe_adin1110_http_server.rs | 386 ++++++++++++++++++ 9 files changed, 631 insertions(+) create mode 100644 examples/stm32u575/.cargo/config.toml create mode 100644 examples/stm32u575/Cargo.toml create mode 100644 examples/stm32u575/build.rs create mode 100644 examples/stm32u575/memory.x create mode 100644 examples/stm32u575/src/bin/blinky.rs create mode 100644 examples/stm32u575/src/bin/boot.rs create mode 100644 examples/stm32u575/src/bin/flash.rs create mode 100644 examples/stm32u575/src/bin/rng.rs create mode 100644 examples/stm32u575/src/bin/spe_adin1110_http_server.rs diff --git a/examples/stm32u575/.cargo/config.toml b/examples/stm32u575/.cargo/config.toml new file mode 100644 index 0000000000..10447b32af --- /dev/null +++ b/examples/stm32u575/.cargo/config.toml @@ -0,0 +1,9 @@ +[target.'cfg(all(target_arch = "arm", target_os = "none"))'] +# replace STM32U5G9ZJTxQ with your chip as listed in `probe-rs chip list` +runner = "probe-rs run --chip STM32U575CIUxQ" + +[build] +target = "thumbv8m.main-none-eabihf" + +[env] +DEFMT_LOG = "trace" diff --git a/examples/stm32u575/Cargo.toml b/examples/stm32u575/Cargo.toml new file mode 100644 index 0000000000..1676d87203 --- /dev/null +++ b/examples/stm32u575/Cargo.toml @@ -0,0 +1,75 @@ +[package] +edition = "2024" +name = "embassy-stm32u5-examples" +version = "0.1.0" +license = "MIT OR Apache-2.0" +publish = false + +[dependencies] +# Change stm32u5g9zj to your chip name, if necessary. +embassy-stm32 = { version = "0.4.0", path = "../../embassy-stm32", features = [ + "defmt", + "unstable-pac", + "stm32u575ci", + "time-driver-any", + "memory-x", + "exti", +] } +embassy-sync = { version = "0.7.2", path = "../../embassy-sync", features = [ + "defmt", +] } +embassy-executor = { version = "0.9.0", path = "../../embassy-executor", features = [ + "arch-cortex-m", + "executor-thread", + "defmt", +] } +embassy-time = { version = "0.5.0", path = "../../embassy-time", features = [ + "defmt", + "defmt-timestamp-uptime", + "tick-hz-32_768", +] } +embassy-usb = { version = "0.5.1", path = "../../embassy-usb", features = [ + "defmt", +] } +embassy-futures = { version = "0.1.2", path = "../../embassy-futures" } +embassy-net-adin1110 = { version = "0.3.1", path = "../../embassy-net-adin1110" } +embassy-net = { version = "0.7.1", path = "../../embassy-net", features = [ + "defmt", + "udp", + "tcp", + "proto-ipv6", + "medium-ethernet", +] } +embedded-io-async = { version = "0.6.1", features = ["defmt-03"] } +embedded-io = { version = "0.6.0", features = ["defmt-03"] } + +defmt = "1.0.1" +defmt-rtt = "1.0.0" + +cortex-m = { version = "0.7.6", features = [ + "inline-asm", + "critical-section-single-core", +] } +cortex-m-rt = "0.7.0" +embedded-hal = "0.2.6" +embedded-hal-async = { version = "1.0" } +embedded-hal-bus = { version = "0.1", features = ["async"] } +panic-probe = { version = "1.0.0", features = ["print-defmt"] } +heapless = { version = "0.8", default-features = false } +embedded-graphics = { version = "0.8.1" } +tinybmp = { version = "0.6.0" } +static_cell = "2" + +micromath = "2.0.0" + +[features] +## Use secure registers when TrustZone is enabled +trustzone-secure = ["embassy-stm32/trustzone-secure"] + +[profile.release] +debug = 2 + +[package.metadata.embassy] +build = [ + { target = "thumbv8m.main-none-eabihf", artifact-dir = "out/examples/stm32u5" }, +] diff --git a/examples/stm32u575/build.rs b/examples/stm32u575/build.rs new file mode 100644 index 0000000000..30691aa978 --- /dev/null +++ b/examples/stm32u575/build.rs @@ -0,0 +1,35 @@ +//! This build script copies the `memory.x` file from the crate root into +//! a directory where the linker can always find it at build time. +//! For many projects this is optional, as the linker always searches the +//! project root directory -- wherever `Cargo.toml` is. However, if you +//! are using a workspace or have a more complicated build setup, this +//! build script becomes required. Additionally, by requesting that +//! Cargo re-run the build script whenever `memory.x` is changed, +//! updating `memory.x` ensures a rebuild of the application with the +//! new memory settings. + +use std::env; +use std::fs::File; +use std::io::Write; +use std::path::PathBuf; + +fn main() { + // Put `memory.x` in our output directory and ensure it's + // on the linker search path. + let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap()); + File::create(out.join("memory.x")) + .unwrap() + .write_all(include_bytes!("memory.x")) + .unwrap(); + println!("cargo:rustc-link-search={}", out.display()); + + // By default, Cargo will re-run a build script whenever + // any file in the project changes. By specifying `memory.x` + // here, we ensure the build script is only re-run when + // `memory.x` is changed. + println!("cargo:rerun-if-changed=memory.x"); + + println!("cargo:rustc-link-arg-bins=--nmagic"); + println!("cargo:rustc-link-arg-bins=-Tlink.x"); + println!("cargo:rustc-link-arg-bins=-Tdefmt.x"); +} diff --git a/examples/stm32u575/memory.x b/examples/stm32u575/memory.x new file mode 100644 index 0000000000..5c8363bcb6 --- /dev/null +++ b/examples/stm32u575/memory.x @@ -0,0 +1,5 @@ +MEMORY +{ + FLASH : ORIGIN = 0x08000000, LENGTH = 2048K + RAM : ORIGIN = 0x20000000, LENGTH = 786K +} \ No newline at end of file diff --git a/examples/stm32u575/src/bin/blinky.rs b/examples/stm32u575/src/bin/blinky.rs new file mode 100644 index 0000000000..1fdfc7679d --- /dev/null +++ b/examples/stm32u575/src/bin/blinky.rs @@ -0,0 +1,27 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::gpio::{Level, Output, Speed}; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) -> ! { + let p = embassy_stm32::init(Default::default()); + info!("Hello World!"); + + // replace PC13 with the right pin for your board. + let mut led = Output::new(p.PC13, Level::Low, Speed::Medium); + + loop { + defmt::info!("on!"); + led.set_low(); + Timer::after_millis(200).await; + + defmt::info!("off!"); + led.set_high(); + Timer::after_millis(200).await; + } +} diff --git a/examples/stm32u575/src/bin/boot.rs b/examples/stm32u575/src/bin/boot.rs new file mode 100644 index 0000000000..23c7f8b22c --- /dev/null +++ b/examples/stm32u575/src/bin/boot.rs @@ -0,0 +1,14 @@ +#![no_std] +#![no_main] + +use defmt::*; +use {defmt_rtt as _, embassy_stm32 as _, panic_probe as _}; + +#[cortex_m_rt::entry] +fn main() -> ! { + info!("Hello World!"); + + loop { + //defmt::info!("loop!"); + } +} diff --git a/examples/stm32u575/src/bin/flash.rs b/examples/stm32u575/src/bin/flash.rs new file mode 100644 index 0000000000..e4fd6bb9c7 --- /dev/null +++ b/examples/stm32u575/src/bin/flash.rs @@ -0,0 +1,55 @@ +#![no_std] +#![no_main] + +use defmt::{info, unwrap}; +use embassy_executor::Spawner; +use embassy_stm32::flash::Flash; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_stm32::init(Default::default()); + info!("Hello Flash!"); + + const ADDR: u32 = 0x8_0000; // This is the offset into the third region, the absolute address is 4x32K + 128K + 0x8_0000. + + // wait a bit before accessing the flash + Timer::after_millis(300).await; + + let mut f = Flash::new_blocking(p.FLASH).into_blocking_regions().bank1_region; + + info!("Reading..."); + let mut buf = [0u8; 32]; + unwrap!(f.blocking_read(ADDR, &mut buf)); + info!("Read: {=[u8]:x}", buf); + + info!("Erasing..."); + unwrap!(f.blocking_erase(ADDR, ADDR + 256 * 1024)); + + info!("Reading..."); + let mut buf = [0u8; 32]; + unwrap!(f.blocking_read(ADDR, &mut buf)); + info!("Read after erase: {=[u8]:x}", buf); + + info!("Writing..."); + unwrap!(f.blocking_write( + ADDR, + &[ + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, + 30, 31, 32 + ] + )); + + info!("Reading..."); + let mut buf = [0u8; 32]; + unwrap!(f.blocking_read(ADDR, &mut buf)); + info!("Read: {=[u8]:x}", buf); + assert_eq!( + &buf[..], + &[ + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, + 30, 31, 32 + ] + ); +} diff --git a/examples/stm32u575/src/bin/rng.rs b/examples/stm32u575/src/bin/rng.rs new file mode 100644 index 0000000000..3a5bce0970 --- /dev/null +++ b/examples/stm32u575/src/bin/rng.rs @@ -0,0 +1,25 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::rng::Rng; +use embassy_stm32::{bind_interrupts, peripherals, rng}; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + RNG => rng::InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_stm32::init(Default::default()); + + info!("Hello World!"); + + let mut rng = Rng::new(p.RNG, Irqs); + + let mut buf = [0u8; 16]; + unwrap!(rng.async_fill_bytes(&mut buf).await); + info!("random bytes: {:02x}", buf); +} diff --git a/examples/stm32u575/src/bin/spe_adin1110_http_server.rs b/examples/stm32u575/src/bin/spe_adin1110_http_server.rs new file mode 100644 index 0000000000..9fb3951855 --- /dev/null +++ b/examples/stm32u575/src/bin/spe_adin1110_http_server.rs @@ -0,0 +1,386 @@ +#![no_main] +#![no_std] +#![deny(clippy::pedantic)] +#![allow(clippy::doc_markdown)] +#![allow(clippy::missing_errors_doc)] + +// This example works on a Bristlemouth dev kit from Sofar Ocean. +// The webserver shows the actual temperature of the onboard i2c temp sensor. + +use core::marker::PhantomData; +use core::sync::atomic::{AtomicI32, Ordering}; + +use defmt::{Format, error, info, println, unwrap}; +use defmt_rtt as _; // global logger +use embassy_executor::Spawner; +use embassy_futures::select::{Either, select}; +use embassy_futures::yield_now; +use embassy_net::tcp::TcpSocket; +use embassy_net::{Ipv6Address, Ipv6Cidr, Stack, StackResources, StaticConfigV6}; +use embassy_net_adin1110::{ADIN1110, Device, Runner}; +use embassy_stm32::gpio::{Level, Output, Pull, Speed}; +use embassy_stm32::i2c::{self, Config as I2C_Config, I2c}; +use embassy_stm32::mode::Async; +use embassy_stm32::rng::{self, Rng}; +use embassy_stm32::spi::{Config as SPI_Config, Spi}; +use embassy_stm32::time::Hertz; +use embassy_stm32::{bind_interrupts, exti, pac, peripherals}; +use embassy_time::{Delay, Duration, Ticker, Timer}; +use embedded_hal_async::i2c::I2c as I2cBus; +use embedded_hal_bus::spi::ExclusiveDevice; +use embedded_io::Write as bWrite; +use embedded_io_async::Write; +use heapless::Vec; +use panic_probe as _; +use static_cell::StaticCell; + +bind_interrupts!(struct Irqs { + I2C1_EV => i2c::EventInterruptHandler; + I2C1_ER => i2c::ErrorInterruptHandler; + RNG => rng::InterruptHandler; +}); + +// Basic settings +// MAC-address used by the adin1110 +const MAC: [u8; 6] = [0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff]; +// Static IP settings +const IP_ADDRESS: Ipv6Cidr = Ipv6Cidr::new(Ipv6Address::new(0xfd00, 0, 0, 0, 0xc0ff, 0xeef0, 0xcacc, 0x1a99), 64); +// Listen port for the webserver +const HTTP_LISTEN_PORT: u16 = 80; + +pub type SpeSpi = Spi<'static, Async>; +pub type SpeSpiCs = ExclusiveDevice, Delay>; +pub type SpeInt = exti::ExtiInput<'static>; +pub type SpeRst = Output<'static>; +pub type Adin1110T = ADIN1110; +pub type TempSensI2c = I2c<'static, Async, i2c::Master>; + +static TEMP: AtomicI32 = AtomicI32::new(0); + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + defmt::println!("Start main()"); + + let mut config = embassy_stm32::Config::default(); + { + use embassy_stm32::rcc::{ + Hse, HseMode, Hsi48Config, Msirange, Pll, PllDiv, PllMul, PllPreDiv, PllSource, Sysclk, + }; + config.rcc.sys = Sysclk::PLL1_R; + config.rcc.hse = Some(Hse { + freq: Hertz(16_000_000), + mode: HseMode::Oscillator, + }); + config.rcc.pll1 = Some(Pll { + source: PllSource::MSIS, + prediv: PllPreDiv::DIV3, + mul: PllMul::MUL10, + divp: Some(PllDiv::DIV2), + divq: Some(PllDiv::DIV2), + divr: None, + }); + config.rcc.hsi48 = Some(Hsi48Config::default()); + config.rcc.msis = Some(Msirange::RANGE_48MHZ); + } + + let dp = embassy_stm32::init(config); + + let reset_status = pac::RCC.bdcr().read().0; + defmt::println!("bdcr before: 0x{:X}", reset_status); + + defmt::println!("Setup IO pins"); + + // Setup LEDs + // TODO add pca9535 crate + // let _led_uc1_green = Output::new(dp.PC13, Level::Low, Speed::Low); // expander IO1 1 + // let mut led_uc1_red = Output::new(dp.PE2, Level::High, Speed::Low); // expander IO1 2 + // let led_uc2_green = Output::new(dp.PE6, Level::High, Speed::Low); // expander IO1 3 + // let _led_uc2_red = Output::new(dp.PG15, Level::High, Speed::Low); // expander IO1 4 + + // Setup I2C pins + let _temp_sens_i2c = I2c::new( + dp.I2C1, + dp.PB6, + dp.PB7, + Irqs, + dp.GPDMA1_CH9, + dp.GPDMA1_CH8, + I2C_Config::default(), + ); + + // Setup IO and SPI for the SPE chip + let spe_reset_n = Output::new(dp.PA0, Level::Low, Speed::Low); + let spe_int = exti::ExtiInput::new(dp.PB8, dp.EXTI8, Pull::None); + let spe_spi_cs_n = Output::new(dp.PA15, Level::High, Speed::High); + let spe_spi_sclk = dp.PB3; + let spe_spi_miso = dp.PB4; + let spe_spi_mosi = dp.PB5; + + let mut spi_config = SPI_Config::default(); + spi_config.frequency = Hertz(20_000_000); + + let spe_spi: SpeSpi = Spi::new( + dp.SPI3, + spe_spi_sclk, + spe_spi_mosi, + spe_spi_miso, + dp.GPDMA1_CH13, + dp.GPDMA1_CH12, + spi_config, + ); + let spe_spi = SpeSpiCs::new(spe_spi, spe_spi_cs_n, Delay); + + static STATE: StaticCell> = StaticCell::new(); + let state = STATE.init(embassy_net_adin1110::State::<8, 8>::new()); + + let (device, runner) = embassy_net_adin1110::new(MAC, state, spe_spi, spe_int, spe_reset_n, true, false).await; + + // Start task blink_led + // spawner.spawn(unwrap!(heartbeat_led(led_uc2_green))); + // Start ethernet task + spawner.spawn(unwrap!(ethernet_task(runner))); + + let mut rng = Rng::new(dp.RNG, Irqs); + // Generate random seed + let seed = rng.next_u64(); + + let ip_cfg = embassy_net::Config::ipv6_static(StaticConfigV6 { + address: IP_ADDRESS, + gateway: None, + dns_servers: Vec::new(), + }); + + // Init network stack + static RESOURCES: StaticCell> = StaticCell::new(); + let (stack, runner) = embassy_net::new(device, ip_cfg, RESOURCES.init(StackResources::new()), seed); + + // Launch network task + spawner.spawn(unwrap!(net_task(runner))); + + let cfg = wait_for_config(stack).await; + let local_addr = cfg.address.address(); + + // Then we can use it! + let mut rx_buffer = [0; 4096]; + let mut tx_buffer = [0; 4096]; + let mut mb_buf = [0; 4096]; + loop { + let mut socket = TcpSocket::new(stack, &mut rx_buffer, &mut tx_buffer); + socket.set_timeout(Some(Duration::from_secs(1))); + + info!("Listening on http://{}:{}...", local_addr, HTTP_LISTEN_PORT); + if let Err(e) = socket.accept(HTTP_LISTEN_PORT).await { + defmt::error!("accept error: {:?}", e); + continue; + } + + loop { + let _n = match socket.read(&mut mb_buf).await { + Ok(0) => { + defmt::info!("read EOF"); + break; + } + Ok(n) => n, + Err(e) => { + defmt::error!("{:?}", e); + break; + } + }; + // led_uc1_red.set_low(); + + let status_line = "HTTP/1.1 200 OK"; + let contents = PAGE; + let length = contents.len(); + + let _ = write!( + &mut mb_buf[..], + "{status_line}\r\nContent-Length: {length}\r\n\r\n{contents}\r\n\0" + ); + let loc = mb_buf.iter().position(|v| *v == b'#').unwrap(); + + let temp = TEMP.load(Ordering::Relaxed); + let cel = temp / 1000; + let mcel = temp % 1000; + + info!("{}.{}", cel, mcel); + + let _ = write!(&mut mb_buf[loc..loc + 7], "{cel}.{mcel}"); + + let n = mb_buf.iter().position(|v| *v == 0).unwrap(); + + if let Err(e) = socket.write_all(&mb_buf[..n]).await { + error!("write error: {:?}", e); + break; + } + + // led_uc1_red.set_high(); + } + } +} + +async fn wait_for_config(stack: Stack<'static>) -> embassy_net::StaticConfigV6 { + loop { + if let Some(config) = stack.config_v6() { + return config; + } + yield_now().await; + } +} + +#[embassy_executor::task] +async fn heartbeat_led(mut led: Output<'static>) { + let mut tmr = Ticker::every(Duration::from_hz(3)); + loop { + led.toggle(); + tmr.next().await; + } +} + +// ADT7422 +#[embassy_executor::task] +async fn temp_task(temp_dev_i2c: TempSensI2c, mut led: Output<'static>) -> ! { + let mut tmr = Ticker::every(Duration::from_hz(1)); + let mut temp_sens = ADT7422::new(temp_dev_i2c, 0x48).unwrap(); + + loop { + led.set_low(); + match select(temp_sens.read_temp(), Timer::after_millis(500)).await { + Either::First(i2c_ret) => match i2c_ret { + Ok(value) => { + led.set_high(); + let temp = i32::from(value); + println!("TEMP: {:04x}, {}", temp, temp * 78 / 10); + TEMP.store(temp * 78 / 10, Ordering::Relaxed); + } + Err(e) => defmt::println!("ADT7422: {}", e), + }, + Either::Second(()) => println!("Timeout"), + } + + tmr.next().await; + } +} + +#[embassy_executor::task] +async fn ethernet_task(runner: Runner<'static, SpeSpiCs, SpeInt, SpeRst>) -> ! { + runner.run().await +} + +#[embassy_executor::task] +async fn net_task(mut runner: embassy_net::Runner<'static, Device<'static>>) -> ! { + runner.run().await +} + +// same panicking *behavior* as `panic-probe` but doesn't print a panic message +// this prevents the panic message being printed *twice* when `defmt::panic` is invoked +#[defmt::panic_handler] +fn panic() -> ! { + cortex_m::asm::udf() +} + +#[allow(non_camel_case_types)] +#[repr(C)] +pub enum Registers { + Temp_MSB = 0x00, + Temp_LSB, + Status, + Cfg, + T_HIGH_MSB, + T_HIGH_LSB, + T_LOW_MSB, + T_LOW_LSB, + T_CRIT_MSB, + T_CRIT_LSB, + T_HYST, + ID, + SW_RESET = 0x2F, +} + +pub struct ADT7422<'d, BUS: I2cBus> { + addr: u8, + phantom: PhantomData<&'d ()>, + bus: BUS, +} + +#[derive(Debug, Format, PartialEq, Eq)] +pub enum Error { + I2c(I2cError), + Address, +} + +impl ADT7422<'_, BUS> +where + BUS: I2cBus, + BUS::Error: Format, +{ + pub fn new(bus: BUS, addr: u8) -> Result> { + if !(0x48..=0x4A).contains(&addr) { + return Err(Error::Address); + } + + Ok(Self { + bus, + phantom: PhantomData, + addr, + }) + } + + pub async fn init(&mut self) -> Result<(), Error> { + let mut cfg = 0b000_0000; + // if self.int.is_some() { + // // Set 1 SPS mode + // cfg |= 0b10 << 5; + // } else { + // One shot mode + cfg |= 0b01 << 5; + // } + + self.write_cfg(cfg).await + } + + pub async fn read(&mut self, reg: Registers) -> Result> { + let mut buffer = [0u8; 1]; + self.bus + .write_read(self.addr, &[reg as u8], &mut buffer) + .await + .map_err(Error::I2c)?; + Ok(buffer[0]) + } + + pub async fn write_cfg(&mut self, cfg: u8) -> Result<(), Error> { + let buf = [Registers::Cfg as u8, cfg]; + self.bus.write(self.addr, &buf).await.map_err(Error::I2c) + } + + pub async fn read_temp(&mut self) -> Result> { + let mut buffer = [0u8; 2]; + + // if let Some(int) = &mut self.int { + // // Wait for interrupt + // int.wait_for_low().await.unwrap(); + // } else { + // Start: One shot + let cfg = 0b01 << 5; + self.write_cfg(cfg).await?; + Timer::after_millis(250).await; + self.bus + .write_read(self.addr, &[Registers::Temp_MSB as u8], &mut buffer) + .await + .map_err(Error::I2c)?; + Ok(i16::from_be_bytes(buffer)) + } +} + +// Web page +const PAGE: &str = r#" + + + + + ADIN1110 with Rust + + +

EVAL-ADIN1110EBZ

+
Temp Sensor ADT7422: #00.00 °C
+ +"#; From 8dd298c9c2243bde051e5bc7856628325ddfef8a Mon Sep 17 00:00:00 2001 From: Zachary Crockett Date: Sun, 19 Oct 2025 23:41:56 -0700 Subject: [PATCH 04/12] Add PCA9535 IO expander and fix RAM length --- examples/stm32u575/.cargo/config.toml | 2 +- examples/stm32u575/Cargo.toml | 1 + examples/stm32u575/memory.x | 2 +- examples/stm32u575/src/bin/blinky.rs | 34 +++++++++++++++---- .../src/bin/spe_adin1110_http_server.rs | 25 ++++++++------ 5 files changed, 44 insertions(+), 20 deletions(-) diff --git a/examples/stm32u575/.cargo/config.toml b/examples/stm32u575/.cargo/config.toml index 10447b32af..fd5eed3f4f 100644 --- a/examples/stm32u575/.cargo/config.toml +++ b/examples/stm32u575/.cargo/config.toml @@ -1,5 +1,5 @@ [target.'cfg(all(target_arch = "arm", target_os = "none"))'] -# replace STM32U5G9ZJTxQ with your chip as listed in `probe-rs chip list` +# replace STM32U575CIUxQ with your chip as listed in `probe-rs chip list` runner = "probe-rs run --chip STM32U575CIUxQ" [build] diff --git a/examples/stm32u575/Cargo.toml b/examples/stm32u575/Cargo.toml index 1676d87203..80518f9e0f 100644 --- a/examples/stm32u575/Cargo.toml +++ b/examples/stm32u575/Cargo.toml @@ -59,6 +59,7 @@ heapless = { version = "0.8", default-features = false } embedded-graphics = { version = "0.8.1" } tinybmp = { version = "0.6.0" } static_cell = "2" +pca9535 = "2.0.0" micromath = "2.0.0" diff --git a/examples/stm32u575/memory.x b/examples/stm32u575/memory.x index 5c8363bcb6..57c2a8325e 100644 --- a/examples/stm32u575/memory.x +++ b/examples/stm32u575/memory.x @@ -1,5 +1,5 @@ MEMORY { FLASH : ORIGIN = 0x08000000, LENGTH = 2048K - RAM : ORIGIN = 0x20000000, LENGTH = 786K + RAM : ORIGIN = 0x20000000, LENGTH = 768K } \ No newline at end of file diff --git a/examples/stm32u575/src/bin/blinky.rs b/examples/stm32u575/src/bin/blinky.rs index 1fdfc7679d..bef383d6c8 100644 --- a/examples/stm32u575/src/bin/blinky.rs +++ b/examples/stm32u575/src/bin/blinky.rs @@ -2,26 +2,46 @@ #![no_main] use defmt::*; +use defmt_rtt as _; use embassy_executor::Spawner; -use embassy_stm32::gpio::{Level, Output, Speed}; +use embassy_stm32::bind_interrupts; +use embassy_stm32::i2c::{self, Config as I2C_Config, I2c}; +use embassy_stm32::peripherals; use embassy_time::Timer; -use {defmt_rtt as _, panic_probe as _}; +use panic_probe as _; +use pca9535::{GPIOBank, Pca9535Immediate, StandardExpanderInterface}; + +bind_interrupts!(struct Irqs { + I2C1_EV => i2c::EventInterruptHandler; + I2C1_ER => i2c::ErrorInterruptHandler; +}); #[embassy_executor::main] async fn main(_spawner: Spawner) -> ! { - let p = embassy_stm32::init(Default::default()); + let dp = embassy_stm32::init(Default::default()); info!("Hello World!"); - // replace PC13 with the right pin for your board. - let mut led = Output::new(p.PC13, Level::Low, Speed::Medium); + let i2c_bus = I2c::new( + dp.I2C1, + dp.PB6, + dp.PB7, + Irqs, + dp.GPDMA1_CH13, + dp.GPDMA1_CH12, + I2C_Config::default(), + ); + + // Setup LEDs + let mut expander = Pca9535Immediate::new(i2c_bus, 0x21); + expander.pin_into_output(GPIOBank::Bank1, 2).unwrap(); loop { defmt::info!("on!"); - led.set_low(); + expander.pin_set_low(GPIOBank::Bank1, 2).unwrap(); Timer::after_millis(200).await; defmt::info!("off!"); - led.set_high(); + expander.pin_set_high(GPIOBank::Bank1, 2).unwrap(); Timer::after_millis(200).await; } } diff --git a/examples/stm32u575/src/bin/spe_adin1110_http_server.rs b/examples/stm32u575/src/bin/spe_adin1110_http_server.rs index 9fb3951855..15c6969dd4 100644 --- a/examples/stm32u575/src/bin/spe_adin1110_http_server.rs +++ b/examples/stm32u575/src/bin/spe_adin1110_http_server.rs @@ -32,6 +32,7 @@ use embedded_io::Write as bWrite; use embedded_io_async::Write; use heapless::Vec; use panic_probe as _; +use pca9535::{GPIOBank, Pca9535Immediate, StandardExpanderInterface}; use static_cell::StaticCell; bind_interrupts!(struct Irqs { @@ -77,7 +78,7 @@ async fn main(spawner: Spawner) { mul: PllMul::MUL10, divp: Some(PllDiv::DIV2), divq: Some(PllDiv::DIV2), - divr: None, + divr: Some(PllDiv::DIV1), }); config.rcc.hsi48 = Some(Hsi48Config::default()); config.rcc.msis = Some(Msirange::RANGE_48MHZ); @@ -90,15 +91,8 @@ async fn main(spawner: Spawner) { defmt::println!("Setup IO pins"); - // Setup LEDs - // TODO add pca9535 crate - // let _led_uc1_green = Output::new(dp.PC13, Level::Low, Speed::Low); // expander IO1 1 - // let mut led_uc1_red = Output::new(dp.PE2, Level::High, Speed::Low); // expander IO1 2 - // let led_uc2_green = Output::new(dp.PE6, Level::High, Speed::Low); // expander IO1 3 - // let _led_uc2_red = Output::new(dp.PG15, Level::High, Speed::Low); // expander IO1 4 - // Setup I2C pins - let _temp_sens_i2c = I2c::new( + let i2c_bus = I2c::new( dp.I2C1, dp.PB6, dp.PB7, @@ -108,6 +102,15 @@ async fn main(spawner: Spawner) { I2C_Config::default(), ); + // Setup LEDs + let mut expander = Pca9535Immediate::new(i2c_bus, 0x21); + expander.pin_into_output(GPIOBank::Bank1, 2).unwrap(); + + // let _led_uc1_green = Output::new(dp.PC13, Level::Low, Speed::Low); // expander IO1 1 + // let mut led_uc1_red = Output::new(dp.PE2, Level::High, Speed::Low); // expander IO1 2 + // let led_uc2_green = Output::new(dp.PE6, Level::High, Speed::Low); // expander IO1 3 + // let _led_uc2_red = Output::new(dp.PG15, Level::High, Speed::Low); // expander IO1 4 + // Setup IO and SPI for the SPE chip let spe_reset_n = Output::new(dp.PA0, Level::Low, Speed::Low); let spe_int = exti::ExtiInput::new(dp.PB8, dp.EXTI8, Pull::None); @@ -186,7 +189,7 @@ async fn main(spawner: Spawner) { break; } }; - // led_uc1_red.set_low(); + expander.pin_set_low(GPIOBank::Bank1, 2).unwrap(); let status_line = "HTTP/1.1 200 OK"; let contents = PAGE; @@ -213,7 +216,7 @@ async fn main(spawner: Spawner) { break; } - // led_uc1_red.set_high(); + expander.pin_set_high(GPIOBank::Bank1, 2).unwrap(); } } } From 621fb08af1ac2ee021efb0632b93c6eed60f9d94 Mon Sep 17 00:00:00 2001 From: Zachary Crockett Date: Mon, 20 Oct 2025 08:52:50 -0700 Subject: [PATCH 05/12] Fix rustfmt from ci failure --- examples/stm32u575/src/bin/blinky.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/examples/stm32u575/src/bin/blinky.rs b/examples/stm32u575/src/bin/blinky.rs index bef383d6c8..41f1b79b86 100644 --- a/examples/stm32u575/src/bin/blinky.rs +++ b/examples/stm32u575/src/bin/blinky.rs @@ -2,14 +2,12 @@ #![no_main] use defmt::*; -use defmt_rtt as _; use embassy_executor::Spawner; -use embassy_stm32::bind_interrupts; use embassy_stm32::i2c::{self, Config as I2C_Config, I2c}; -use embassy_stm32::peripherals; +use embassy_stm32::{bind_interrupts, peripherals}; use embassy_time::Timer; -use panic_probe as _; use pca9535::{GPIOBank, Pca9535Immediate, StandardExpanderInterface}; +use {defmt_rtt as _, panic_probe as _}; bind_interrupts!(struct Irqs { I2C1_EV => i2c::EventInterruptHandler; From 08ea99a74c581c75c4f60f4bf99c0cc8b025af31 Mon Sep 17 00:00:00 2001 From: Zachary Crockett Date: Mon, 20 Oct 2025 17:26:10 -0700 Subject: [PATCH 06/12] Refactor generic SPI to impl new protocol trait --- embassy-net-adin1110/Cargo.toml | 23 +- embassy-net-adin1110/src/lib.rs | 287 +++--------------- .../src/protocol/generic_spi.rs | 254 ++++++++++++++++ embassy-net-adin1110/src/protocol/mod.rs | 31 ++ 4 files changed, 354 insertions(+), 241 deletions(-) create mode 100644 embassy-net-adin1110/src/protocol/generic_spi.rs create mode 100644 embassy-net-adin1110/src/protocol/mod.rs diff --git a/embassy-net-adin1110/Cargo.toml b/embassy-net-adin1110/Cargo.toml index a5655870f5..3b9bdbedad 100644 --- a/embassy-net-adin1110/Cargo.toml +++ b/embassy-net-adin1110/Cargo.toml @@ -2,8 +2,20 @@ name = "embassy-net-adin1110" version = "0.3.1" description = "embassy-net driver for the ADIN1110 ethernet chip" -keywords = ["embedded", "ADIN1110", "embassy-net", "embedded-hal-async", "ethernet"] -categories = ["embedded", "hardware-support", "no-std", "network-programming", "asynchronous"] +keywords = [ + "embedded", + "ADIN1110", + "embassy-net", + "embedded-hal-async", + "ethernet", +] +categories = [ + "embedded", + "hardware-support", + "no-std", + "network-programming", + "asynchronous", +] license = "MIT OR Apache-2.0" edition = "2024" repository = "https://github.com/embassy-rs/embassy" @@ -22,15 +34,20 @@ embassy-futures = { version = "0.1.2", path = "../embassy-futures" } bitfield = "0.14.0" [dev-dependencies] -embedded-hal-mock = { version = "0.10.0", features = ["embedded-hal-async", "eh1"] } +embedded-hal-mock = { version = "0.10.0", features = [ + "embedded-hal-async", + "eh1", +] } crc = "3.0.1" env_logger = "0.10" critical-section = { version = "1.1.2", features = ["std"] } futures-test = "0.3.28" [features] +default = ["generic-spi"] defmt = ["dep:defmt", "embedded-hal-1/defmt-03"] log = ["dep:log"] +generic-spi = [] [package.metadata.embassy_docs] src_base = "https://github.com/embassy-rs/embassy/blob/embassy-net-adin1110-v$VERSION/embassy-net-adin1110/src/" diff --git a/embassy-net-adin1110/src/lib.rs b/embassy-net-adin1110/src/lib.rs index 90ac242bde..efa115bf89 100644 --- a/embassy-net-adin1110/src/lib.rs +++ b/embassy-net-adin1110/src/lib.rs @@ -14,25 +14,26 @@ mod crc32; mod crc8; mod mdio; mod phy; +mod protocol; mod regs; use ch::driver::LinkState; -use crc8::crc8; pub use crc32::ETH_FCS; use embassy_futures::select::{Either, select}; use embassy_net_driver_channel as ch; use embassy_time::Timer; use embedded_hal_1::digital::OutputPin; use embedded_hal_async::digital::Wait; -use embedded_hal_async::spi::{Error, Operation, SpiDevice}; -use heapless::Vec; +use embedded_hal_async::spi::{Error, SpiDevice}; pub use mdio::MdioBus; pub use phy::Phy10BaseT1x; use phy::{RegsC22, RegsC45}; +pub use protocol::Adin1110Protocol; +#[cfg(feature = "generic-spi")] +pub use protocol::GenericSpi; use regs::{Config0, Config2, SpiRegisters as sr, Status0, Status1}; -use crate::fmt::Bytes; -use crate::regs::{LedCntrl, LedFunc, LedPol, LedPolarity, SpiHeader}; +use crate::regs::{LedCntrl, LedFunc, LedPol, LedPolarity}; /// ADIN1110 intern PHY ID pub const PHYID: u32 = 0x0283_BC91; @@ -111,95 +112,29 @@ impl State { /// ADIN1110 embassy-net driver #[derive(Debug)] -pub struct ADIN1110 { - /// SPI bus - spi: SPI, - /// Enable CRC on SPI transfer. - /// This must match with the hardware pin `SPI_CFG0` were low = CRC enable, high = CRC disabled. - spi_crc: bool, - /// Append FCS by the application of transmit packet, false = FCS is appended by the MAC, true = FCS appended by the application. - append_fcs_on_tx: bool, +pub struct ADIN1110 { + /// Generic or OPEN Alliance TC6 SPI protocol, wraps SPI device + protocol: P, } -impl ADIN1110 { +impl ADIN1110

{ /// Create a new ADIN1110 instance. - pub fn new(spi: SPI, spi_crc: bool, append_fcs_on_tx: bool) -> Self { - Self { - spi, - spi_crc, - append_fcs_on_tx, - } + pub fn new(protocol: P) -> Self { + Self { protocol } } /// Read a SPI register - pub async fn read_reg(&mut self, reg: sr) -> AEResult { - let mut tx_buf = Vec::::new(); - - let mut spi_hdr = SpiHeader(0); - spi_hdr.set_control(true); - spi_hdr.set_addr(reg); - let _ = tx_buf.extend_from_slice(spi_hdr.0.to_be_bytes().as_slice()); - - if self.spi_crc { - // Add CRC for header data - let _ = tx_buf.push(crc8(&tx_buf)); - } - - // Turn around byte, give the chip the time to access/setup the answer data. - let _ = tx_buf.push(TURN_AROUND_BYTE); - - let mut rx_buf = [0; 5]; - - let spi_read_len = if self.spi_crc { rx_buf.len() } else { rx_buf.len() - 1 }; - - let mut spi_op = [Operation::Write(&tx_buf), Operation::Read(&mut rx_buf[0..spi_read_len])]; - - self.spi.transaction(&mut spi_op).await.map_err(AdinError::Spi)?; - - if self.spi_crc { - let crc = crc8(&rx_buf[0..4]); - if crc != rx_buf[4] { - return Err(AdinError::SPI_CRC); - } - } - - let value = u32::from_be_bytes(rx_buf[0..4].try_into().unwrap()); - - trace!("REG Read {} = {:08x} SPI {}", reg, value, Bytes(&tx_buf)); - - Ok(value) + pub async fn read_reg(&mut self, reg: sr) -> AEResult { + self.protocol.read_reg(reg.into()).await } /// Write a SPI register - pub async fn write_reg(&mut self, reg: sr, value: u32) -> AEResult<(), SPI::Error> { - let mut tx_buf = Vec::::new(); - - let mut spi_hdr = SpiHeader(0); - spi_hdr.set_control(true); - spi_hdr.set_write(true); - spi_hdr.set_addr(reg); - let _ = tx_buf.extend_from_slice(spi_hdr.0.to_be_bytes().as_slice()); - - if self.spi_crc { - // Add CRC for header data - let _ = tx_buf.push(crc8(&tx_buf)); - } - - let val = value.to_be_bytes(); - let _ = tx_buf.extend_from_slice(val.as_slice()); - - if self.spi_crc { - // Add CRC for header data - let _ = tx_buf.push(crc8(val.as_slice())); - } - - trace!("REG Write {} = {:08x} SPI {}", reg, value, Bytes(&tx_buf)); - - self.spi.write(&tx_buf).await.map_err(AdinError::Spi) + pub async fn write_reg(&mut self, reg: sr, value: u32) -> AEResult<(), P::SpiError> { + self.protocol.write_reg(reg.into(), value).await } /// helper function for write to `MDIO_ACC` register and wait for ready! - async fn write_mdio_acc_reg(&mut self, mdio_acc_val: u32) -> AEResult { + async fn write_mdio_acc_reg(&mut self, mdio_acc_val: u32) -> AEResult { self.write_reg(sr::MDIO_ACC, mdio_acc_val).await?; // TODO: Add proper timeout! @@ -214,158 +149,19 @@ impl ADIN1110 { } /// Read out fifo ethernet packet memory received via the wire. - pub async fn read_fifo(&mut self, frame: &mut [u8]) -> AEResult { - const HEAD_LEN: usize = SPI_HEADER_LEN + SPI_HEADER_CRC_LEN + SPI_HEADER_TA_LEN; - const TAIL_LEN: usize = FCS_LEN + SPI_SPACE_MULTIPULE; - - let mut tx_buf = Vec::::new(); - - // Size of the frame, also includes the `frame header` and `FCS`. - let fifo_frame_size = self.read_reg(sr::RX_FSIZE).await? as usize; - - if fifo_frame_size < ETH_MIN_LEN + FRAME_HEADER_LEN { - return Err(AdinError::PACKET_TOO_SMALL); - } - - let packet_size = fifo_frame_size - FRAME_HEADER_LEN - FCS_LEN; - - if packet_size > frame.len() { - trace!("MAX: {} WANT: {}", frame.len(), packet_size); - return Err(AdinError::PACKET_TOO_BIG); - } - - let mut spi_hdr = SpiHeader(0); - spi_hdr.set_control(true); - spi_hdr.set_addr(sr::RX); - let _ = tx_buf.extend_from_slice(spi_hdr.0.to_be_bytes().as_slice()); - - if self.spi_crc { - // Add CRC for header data - let _ = tx_buf.push(crc8(&tx_buf)); - } - - // Turn around byte, TODO: Unknown that this is. - let _ = tx_buf.push(TURN_AROUND_BYTE); - - let mut frame_header = [0, 0]; - let mut fcs_and_extra = [0; TAIL_LEN]; - - // Packet read of write to the MAC packet buffer must be a multipul of 4! - let tail_size = (fifo_frame_size & 0x03) + FCS_LEN; - - let mut spi_op = [ - Operation::Write(&tx_buf), - Operation::Read(&mut frame_header), - Operation::Read(&mut frame[0..packet_size]), - Operation::Read(&mut fcs_and_extra[0..tail_size]), - ]; - - self.spi.transaction(&mut spi_op).await.map_err(AdinError::Spi)?; - - // According to register `CONFIG2`, bit 5 `CRC_APPEND` discription: - // "Similarly, on receive, the CRC32 is forwarded with the frame to the host where the host must verify it is correct." - // The application must allways check the FCS. It seems that the MAC/PHY has no option to handle this. - let fcs_calc = ETH_FCS::new(&frame[0..packet_size]); - - if fcs_calc.hton_bytes() == fcs_and_extra[0..4] { - Ok(packet_size) - } else { - Err(AdinError::FCS) - } + pub async fn read_fifo(&mut self, frame: &mut [u8]) -> AEResult { + self.protocol.read_fifo(frame).await } /// Write to fifo ethernet packet memory send over the wire. - pub async fn write_fifo(&mut self, frame: &[u8]) -> AEResult<(), SPI::Error> { - const HEAD_LEN: usize = SPI_HEADER_LEN + SPI_HEADER_CRC_LEN + FRAME_HEADER_LEN; - const TAIL_LEN: usize = ETH_MIN_LEN - FCS_LEN + FCS_LEN + SPI_SPACE_MULTIPULE; - - if frame.len() < (6 + 6 + 2) { - return Err(AdinError::PACKET_TOO_SMALL); - } - if frame.len() > (MAX_BUFF - FRAME_HEADER_LEN) { - return Err(AdinError::PACKET_TOO_BIG); - } - - // SPI HEADER + [OPTIONAL SPI CRC] + FRAME HEADER - let mut head_data = Vec::::new(); - // [OPTIONAL PAD DATA] + FCS + [OPTINAL BYTES MAKE SPI FRAME EVEN] - let mut tail_data = Vec::::new(); - - let mut spi_hdr = SpiHeader(0); - spi_hdr.set_control(true); - spi_hdr.set_write(true); - spi_hdr.set_addr(sr::TX); - - head_data - .extend_from_slice(spi_hdr.0.to_be_bytes().as_slice()) - .map_err(|_e| AdinError::PACKET_TOO_BIG)?; - - if self.spi_crc { - // Add CRC for header data - head_data - .push(crc8(&head_data[0..2])) - .map_err(|_| AdinError::PACKET_TOO_BIG)?; - } - - // Add port number, ADIN1110 its fixed to zero/P1, but for ADIN2111 has two ports. - head_data - .extend_from_slice(u16::from(PORT_ID_BYTE).to_be_bytes().as_slice()) - .map_err(|_e| AdinError::PACKET_TOO_BIG)?; - - // ADIN1110 MAC and PHY don´t accept ethernet packet smaller than 64 bytes. - // So padded the data minus the FCS, FCS is automatilly added to by the MAC. - if frame.len() < ETH_MIN_WITHOUT_FCS_LEN { - let _ = tail_data.resize(ETH_MIN_WITHOUT_FCS_LEN - frame.len(), 0x00); - } - - // Append FCS by the application - if self.append_fcs_on_tx { - let mut frame_fcs = ETH_FCS::new(frame); - - if !tail_data.is_empty() { - frame_fcs = frame_fcs.update(&tail_data); - } - - let _ = tail_data.extend_from_slice(frame_fcs.hton_bytes().as_slice()); - } - - // len = frame_size + optional padding + 2 bytes Frame header - let send_len_orig = frame.len() + tail_data.len() + FRAME_HEADER_LEN; - - let send_len = u32::try_from(send_len_orig).map_err(|_| AdinError::PACKET_TOO_BIG)?; - - // Packet read of write to the MAC packet buffer must be a multipul of 4 bytes! - let pad_len = send_len_orig & 0x03; - if pad_len != 0 { - let spi_pad_len = 4 - pad_len + tail_data.len(); - let _ = tail_data.resize(spi_pad_len, DONT_CARE_BYTE); - } - - self.write_reg(sr::TX_FSIZE, send_len).await?; - - trace!( - "TX: hdr {} [{}] {}-{}-{} SIZE: {}", - head_data.len(), - frame.len(), - Bytes(head_data.as_slice()), - Bytes(frame), - Bytes(tail_data.as_slice()), - send_len, - ); - - let mut transaction = [ - Operation::Write(head_data.as_slice()), - Operation::Write(frame), - Operation::Write(tail_data.as_slice()), - ]; - - self.spi.transaction(&mut transaction).await.map_err(AdinError::Spi) + pub async fn write_fifo(&mut self, frame: &[u8]) -> AEResult<(), P::SpiError> { + self.protocol.write_fifo(frame).await } /// Programs the mac address in the mac filters. /// Also set the boardcast address. /// The chip supports 2 priority queues but current code doesn't support this mode. - pub async fn set_mac_addr(&mut self, mac: &[u8; 6]) -> AEResult<(), SPI::Error> { + pub async fn set_mac_addr(&mut self, mac: &[u8; 6]) -> AEResult<(), P::SpiError> { let mac_high_part = u16::from_be_bytes(mac[0..2].try_into().unwrap()); let mac_low_part = u32::from_be_bytes(mac[2..6].try_into().unwrap()); @@ -388,7 +184,7 @@ impl ADIN1110 { } } -impl mdio::MdioBus for ADIN1110 { +impl mdio::MdioBus for ADIN1110> { type Error = AdinError; /// Read from the PHY Registers as Clause 22. @@ -439,15 +235,19 @@ impl mdio::MdioBus for ADIN1110 { /// Background runner for the ADIN1110. /// /// You must call `.run()` in a background task for the ADIN1110 to operate. -pub struct Runner<'d, SPI, INT, RST> { - mac: ADIN1110, +pub struct Runner<'d, P: Adin1110Protocol, INT, RST> { + mac: ADIN1110

, ch: ch::Runner<'d, MTU>, int: INT, is_link_up: bool, _reset: RST, } -impl<'d, SPI: SpiDevice, INT: Wait, RST: OutputPin> Runner<'d, SPI, INT, RST> { +impl<'d, P: Adin1110Protocol, INT: Wait, RST: OutputPin> Runner<'d, P, INT, RST> +where + ADIN1110

: MdioBus, + as MdioBus>::Error: core::fmt::Debug, +{ /// Run the driver. #[allow(clippy::too_many_lines)] pub async fn run(mut self) -> ! { @@ -594,7 +394,16 @@ impl<'d, SPI: SpiDevice, INT: Wait, RST: OutputPin> Runner<'d, SPI, INT, RST> { } } +#[cfg(feature = "generic-spi")] +impl ADIN1110> { + /// Create driver with Generic SPI protocol (existing behavior) + pub fn new_generic(spi: SPI, crc_enabled: bool, append_fcs_on_tx: bool) -> Self { + let protocol = GenericSpi::new(spi, crc_enabled, append_fcs_on_tx); + Self::new(protocol) + } +} /// Obtain a driver for using the ADIN1110 with [`embassy-net`](crates.io/crates/embassy-net). +#[cfg(feature = "generic-spi")] pub async fn new( mac_addr: [u8; 6], state: &'_ mut State, @@ -603,7 +412,7 @@ pub async fn new (Device<'_>, Runner<'_, SPI, INT, RST>) { +) -> (Device<'_>, Runner<'_, GenericSpi, INT, RST>) { use crate::regs::{IMask0, IMask1}; info!("INIT ADIN1110"); @@ -620,7 +429,7 @@ pub async fn new>, CsPinMock, MockDelay>>, + spe: ADIN1110< + GenericSpi>, CsPinMock, MockDelay>>, + >, spi: Generic>, } @@ -788,9 +601,7 @@ mod tests { let spi = SpiMock::new(expectations); let spi_dev: ExclusiveDevice>, CsPinMock, MockDelay> = ExclusiveDevice::new(spi.clone(), cs, delay); - let spe: ADIN1110< - ExclusiveDevice>, CsPinMock, MockDelay>, - > = ADIN1110::new(spi_dev, spi_crc, append_fcs_on_tx); + let spe = ADIN1110::new_generic(spi_dev, spi_crc, append_fcs_on_tx); Self { spe, spi } } diff --git a/embassy-net-adin1110/src/protocol/generic_spi.rs b/embassy-net-adin1110/src/protocol/generic_spi.rs new file mode 100644 index 0000000000..d1f2015619 --- /dev/null +++ b/embassy-net-adin1110/src/protocol/generic_spi.rs @@ -0,0 +1,254 @@ +use super::Adin1110Protocol; +use crate::crc8::crc8; +use crate::crc32::ETH_FCS; +use crate::fmt::Bytes; +use crate::regs::{SpiHeader, SpiRegisters as sr}; +use crate::{AdinError, DONT_CARE_BYTE, ETH_MIN_WITHOUT_FCS_LEN, FCS_LEN, FRAME_HEADER_LEN}; +use crate::{MAX_BUFF, PORT_ID_BYTE, SPI_HEADER_CRC_LEN, SPI_HEADER_LEN, SPI_HEADER_TA_LEN, SPI_SPACE_MULTIPULE}; +use crate::{ETH_MIN_LEN, TURN_AROUND_BYTE}; +use embedded_hal_async::spi::{Operation, SpiDevice}; +use heapless::Vec; + +/// Generic SPI protocol implementation for ADIN1110 +pub struct GenericSpi { + spi: SPI, + crc_enabled: bool, + append_fcs_on_tx: bool, +} + +impl GenericSpi { + /// Create a new GenericSpi protocol handler + pub fn new(spi: SPI, crc_enabled: bool, append_fcs_on_tx: bool) -> Self { + Self { + spi, + crc_enabled, + append_fcs_on_tx, + } + } +} + +impl Adin1110Protocol for GenericSpi { + type SpiError = SPI::Error; + + async fn read_reg(&mut self, addr: u16) -> Result> { + let mut tx_buf = Vec::::new(); + + let mut spi_hdr = SpiHeader(0); + spi_hdr.set_control(true); + spi_hdr.set_addr(sr::from(addr)); + let _ = tx_buf.extend_from_slice(spi_hdr.0.to_be_bytes().as_slice()); + + if self.crc_enabled { + // Add CRC for header data + let _ = tx_buf.push(crc8(&tx_buf)); + } + + // Turn around byte, give the chip the time to access/setup the answer data. + let _ = tx_buf.push(TURN_AROUND_BYTE); + + let mut rx_buf = [0; 5]; + + let spi_read_len = if self.crc_enabled { + rx_buf.len() + } else { + rx_buf.len() - 1 + }; + + let mut spi_op = [ + Operation::Write(&tx_buf), + Operation::Read(&mut rx_buf[0..spi_read_len]), + ]; + + self.spi.transaction(&mut spi_op).await.map_err(AdinError::Spi)?; + + if self.crc_enabled { + let crc = crc8(&rx_buf[0..4]); + if crc != rx_buf[4] { + return Err(AdinError::SPI_CRC); + } + } + + let value = u32::from_be_bytes(rx_buf[0..4].try_into().unwrap()); + + trace!("REG Read {} = {:08x} SPI {}", addr, value, Bytes(&tx_buf)); + + Ok(value) + } + + async fn write_reg(&mut self, addr: u16, value: u32) -> Result<(), AdinError> { + let mut tx_buf = Vec::::new(); + + let mut spi_hdr = SpiHeader(0); + spi_hdr.set_control(true); + spi_hdr.set_write(true); + spi_hdr.set_addr(sr::from(addr)); + let _ = tx_buf.extend_from_slice(spi_hdr.0.to_be_bytes().as_slice()); + + if self.crc_enabled { + // Add CRC for header data + let _ = tx_buf.push(crc8(&tx_buf)); + } + + let val = value.to_be_bytes(); + let _ = tx_buf.extend_from_slice(val.as_slice()); + + if self.crc_enabled { + // Add CRC for header data + let _ = tx_buf.push(crc8(val.as_slice())); + } + + trace!("REG Write {} = {:08x} SPI {}", addr, value, Bytes(&tx_buf)); + + self.spi.write(&tx_buf).await.map_err(AdinError::Spi) + } + + async fn read_fifo(&mut self, frame: &mut [u8]) -> Result> { + const HEAD_LEN: usize = SPI_HEADER_LEN + SPI_HEADER_CRC_LEN + SPI_HEADER_TA_LEN; + const TAIL_LEN: usize = FCS_LEN + SPI_SPACE_MULTIPULE; + + let mut tx_buf = Vec::::new(); + + // Size of the frame, also includes the `frame header` and `FCS`. + let fifo_frame_size = self.read_reg(sr::RX_FSIZE.into()).await? as usize; + + if fifo_frame_size < ETH_MIN_LEN + FRAME_HEADER_LEN { + return Err(AdinError::PACKET_TOO_SMALL); + } + + let packet_size = fifo_frame_size - FRAME_HEADER_LEN - FCS_LEN; + + if packet_size > frame.len() { + trace!("MAX: {} WANT: {}", frame.len(), packet_size); + return Err(AdinError::PACKET_TOO_BIG); + } + + let mut spi_hdr = SpiHeader(0); + spi_hdr.set_control(true); + spi_hdr.set_addr(sr::RX); + let _ = tx_buf.extend_from_slice(spi_hdr.0.to_be_bytes().as_slice()); + + if self.crc_enabled { + // Add CRC for header data + let _ = tx_buf.push(crc8(&tx_buf)); + } + + // Turn around byte, TODO: Unknown that this is. + let _ = tx_buf.push(TURN_AROUND_BYTE); + + let mut frame_header = [0, 0]; + let mut fcs_and_extra = [0; TAIL_LEN]; + + // Packet read of write to the MAC packet buffer must be a multipul of 4! + let tail_size = (fifo_frame_size & 0x03) + FCS_LEN; + + let mut spi_op = [ + Operation::Write(&tx_buf), + Operation::Read(&mut frame_header), + Operation::Read(&mut frame[0..packet_size]), + Operation::Read(&mut fcs_and_extra[0..tail_size]), + ]; + + self.spi.transaction(&mut spi_op).await.map_err(AdinError::Spi)?; + + // According to register `CONFIG2`, bit 5 `CRC_APPEND` discription: + // "Similarly, on receive, the CRC32 is forwarded with the frame to the host where the host must verify it is correct." + // The application must allways check the FCS. It seems that the MAC/PHY has no option to handle this. + let fcs_calc = ETH_FCS::new(&frame[0..packet_size]); + + if fcs_calc.hton_bytes() == fcs_and_extra[0..4] { + Ok(packet_size) + } else { + Err(AdinError::FCS) + } + } + + async fn write_fifo(&mut self, frame: &[u8]) -> Result<(), AdinError> { + const HEAD_LEN: usize = SPI_HEADER_LEN + SPI_HEADER_CRC_LEN + FRAME_HEADER_LEN; + const TAIL_LEN: usize = ETH_MIN_LEN - FCS_LEN + FCS_LEN + SPI_SPACE_MULTIPULE; + + if frame.len() < (6 + 6 + 2) { + return Err(AdinError::PACKET_TOO_SMALL); + } + if frame.len() > (MAX_BUFF - FRAME_HEADER_LEN) { + return Err(AdinError::PACKET_TOO_BIG); + } + + // SPI HEADER + [OPTIONAL SPI CRC] + FRAME HEADER + let mut head_data = Vec::::new(); + // [OPTIONAL PAD DATA] + FCS + [OPTINAL BYTES MAKE SPI FRAME EVEN] + let mut tail_data = Vec::::new(); + + let mut spi_hdr = SpiHeader(0); + spi_hdr.set_control(true); + spi_hdr.set_write(true); + spi_hdr.set_addr(sr::TX); + + head_data + .extend_from_slice(spi_hdr.0.to_be_bytes().as_slice()) + .map_err(|_e| AdinError::PACKET_TOO_BIG)?; + + if self.crc_enabled { + // Add CRC for header data + head_data + .push(crc8(&head_data[0..2])) + .map_err(|_| AdinError::PACKET_TOO_BIG)?; + } + + // Add port number, ADIN1110 its fixed to zero/P1, but for ADIN2111 has two ports. + head_data + .extend_from_slice(u16::from(PORT_ID_BYTE).to_be_bytes().as_slice()) + .map_err(|_e| AdinError::PACKET_TOO_BIG)?; + + // ADIN1110 MAC and PHY don´t accept ethernet packet smaller than 64 bytes. + // So padded the data minus the FCS, FCS is automatilly added to by the MAC. + if frame.len() < ETH_MIN_WITHOUT_FCS_LEN { + let _ = tail_data.resize(ETH_MIN_WITHOUT_FCS_LEN - frame.len(), 0x00); + } + + // Append FCS by the application + if self.append_fcs_on_tx { + let mut frame_fcs = ETH_FCS::new(frame); + + if !tail_data.is_empty() { + frame_fcs = frame_fcs.update(&tail_data); + } + + let _ = tail_data.extend_from_slice(frame_fcs.hton_bytes().as_slice()); + } + + // len = frame_size + optional padding + 2 bytes Frame header + let send_len_orig = frame.len() + tail_data.len() + FRAME_HEADER_LEN; + + let send_len = u32::try_from(send_len_orig).map_err(|_| AdinError::PACKET_TOO_BIG)?; + + // Packet read of write to the MAC packet buffer must be a multipul of 4 bytes! + let pad_len = send_len_orig & 0x03; + if pad_len != 0 { + let spi_pad_len = 4 - pad_len + tail_data.len(); + let _ = tail_data.resize(spi_pad_len, DONT_CARE_BYTE); + } + + self.write_reg(sr::TX_FSIZE.into(), send_len).await?; + + trace!( + "TX: hdr {} [{}] {}-{}-{} SIZE: {}", + head_data.len(), + frame.len(), + Bytes(head_data.as_slice()), + Bytes(frame), + Bytes(tail_data.as_slice()), + send_len, + ); + + let mut transaction = [ + Operation::Write(head_data.as_slice()), + Operation::Write(frame), + Operation::Write(tail_data.as_slice()), + ]; + + self.spi + .transaction(&mut transaction) + .await + .map_err(AdinError::Spi) + } +} diff --git a/embassy-net-adin1110/src/protocol/mod.rs b/embassy-net-adin1110/src/protocol/mod.rs new file mode 100644 index 0000000000..fa44463aed --- /dev/null +++ b/embassy-net-adin1110/src/protocol/mod.rs @@ -0,0 +1,31 @@ +/// Protocol abstraction for ADIN1110 SPI communication +/// +/// This module defines a trait-based abstraction for different SPI protocols +/// supported by the ADIN1110 chip: +/// - Generic SPI +/// - OPEN Alliance TC6 + +#[cfg(feature = "generic-spi")] +mod generic_spi; +#[cfg(feature = "generic-spi")] +pub use generic_spi::GenericSpi; + +use crate::AdinError; + +/// Protocol abstraction trait for ADIN1110 SPI communication +pub trait Adin1110Protocol { + /// SPI error type + type SpiError: core::fmt::Debug + embedded_hal_async::spi::Error; + + /// Read a register + async fn read_reg(&mut self, addr: u16) -> Result>; + + /// Write a register + async fn write_reg(&mut self, addr: u16, val: u32) -> Result<(), AdinError>; + + /// Read FIFO data into buffer, returns number of bytes read + async fn read_fifo(&mut self, frame: &mut [u8]) -> Result>; + + /// Write frame data to FIFO + async fn write_fifo(&mut self, frame: &[u8]) -> Result<(), AdinError>; +} From dfcb7c9bafc7d32ecd3399624943f5598c8afa82 Mon Sep 17 00:00:00 2001 From: Zachary Crockett Date: Mon, 20 Oct 2025 17:39:13 -0700 Subject: [PATCH 07/12] Add changelog and fix rustfmt, build still failing --- embassy-net-adin1110/CHANGELOG.md | 2 ++ .../src/protocol/generic_spi.rs | 22 ++++++++----------- 2 files changed, 11 insertions(+), 13 deletions(-) diff --git a/embassy-net-adin1110/CHANGELOG.md b/embassy-net-adin1110/CHANGELOG.md index 1804d1313f..6a92798b57 100644 --- a/embassy-net-adin1110/CHANGELOG.md +++ b/embassy-net-adin1110/CHANGELOG.md @@ -8,6 +8,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased - ReleaseDate +- Added OPEN Alliance TC6 SPI protocol support + ## 0.3.1 - 2025-08-26 - First release with changelog. diff --git a/embassy-net-adin1110/src/protocol/generic_spi.rs b/embassy-net-adin1110/src/protocol/generic_spi.rs index d1f2015619..c8c1d0ccfb 100644 --- a/embassy-net-adin1110/src/protocol/generic_spi.rs +++ b/embassy-net-adin1110/src/protocol/generic_spi.rs @@ -1,13 +1,15 @@ +use embedded_hal_async::spi::{Operation, SpiDevice}; +use heapless::Vec; + use super::Adin1110Protocol; use crate::crc8::crc8; use crate::crc32::ETH_FCS; use crate::fmt::Bytes; use crate::regs::{SpiHeader, SpiRegisters as sr}; -use crate::{AdinError, DONT_CARE_BYTE, ETH_MIN_WITHOUT_FCS_LEN, FCS_LEN, FRAME_HEADER_LEN}; -use crate::{MAX_BUFF, PORT_ID_BYTE, SPI_HEADER_CRC_LEN, SPI_HEADER_LEN, SPI_HEADER_TA_LEN, SPI_SPACE_MULTIPULE}; -use crate::{ETH_MIN_LEN, TURN_AROUND_BYTE}; -use embedded_hal_async::spi::{Operation, SpiDevice}; -use heapless::Vec; +use crate::{ + AdinError, DONT_CARE_BYTE, ETH_MIN_LEN, ETH_MIN_WITHOUT_FCS_LEN, FCS_LEN, FRAME_HEADER_LEN, MAX_BUFF, PORT_ID_BYTE, + SPI_HEADER_CRC_LEN, SPI_HEADER_LEN, SPI_HEADER_TA_LEN, SPI_SPACE_MULTIPULE, TURN_AROUND_BYTE, +}; /// Generic SPI protocol implementation for ADIN1110 pub struct GenericSpi { @@ -54,10 +56,7 @@ impl Adin1110Protocol for GenericSpi { rx_buf.len() - 1 }; - let mut spi_op = [ - Operation::Write(&tx_buf), - Operation::Read(&mut rx_buf[0..spi_read_len]), - ]; + let mut spi_op = [Operation::Write(&tx_buf), Operation::Read(&mut rx_buf[0..spi_read_len])]; self.spi.transaction(&mut spi_op).await.map_err(AdinError::Spi)?; @@ -246,9 +245,6 @@ impl Adin1110Protocol for GenericSpi { Operation::Write(tail_data.as_slice()), ]; - self.spi - .transaction(&mut transaction) - .await - .map_err(AdinError::Spi) + self.spi.transaction(&mut transaction).await.map_err(AdinError::Spi) } } From ff4261dcdf0663d6cd47e1cc435a683eee58eba9 Mon Sep 17 00:00:00 2001 From: Zachary Crockett Date: Tue, 21 Oct 2025 12:29:54 -0700 Subject: [PATCH 08/12] Fix stm32l4 example usage of adin1110 --- examples/stm32l4/src/bin/spe_adin1110_http_server.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/stm32l4/src/bin/spe_adin1110_http_server.rs b/examples/stm32l4/src/bin/spe_adin1110_http_server.rs index 8e54938d1b..dc01f62664 100644 --- a/examples/stm32l4/src/bin/spe_adin1110_http_server.rs +++ b/examples/stm32l4/src/bin/spe_adin1110_http_server.rs @@ -23,7 +23,7 @@ use embassy_futures::select::{Either, select}; use embassy_futures::yield_now; use embassy_net::tcp::TcpSocket; use embassy_net::{Ipv4Address, Ipv4Cidr, Stack, StackResources, StaticConfigV4}; -use embassy_net_adin1110::{ADIN1110, Device, Runner}; +use embassy_net_adin1110::{ADIN1110, Device, GenericSpi, Runner}; use embassy_stm32::gpio::{Input, Level, Output, Pull, Speed}; use embassy_stm32::i2c::{self, Config as I2C_Config, I2c}; use embassy_stm32::mode::Async; @@ -58,7 +58,7 @@ pub type SpeSpi = Spi<'static, Async>; pub type SpeSpiCs = ExclusiveDevice, Delay>; pub type SpeInt = exti::ExtiInput<'static>; pub type SpeRst = Output<'static>; -pub type Adin1110T = ADIN1110; +pub type Adin1110T = ADIN1110>; pub type TempSensI2c = I2c<'static, Async, i2c::Master>; static TEMP: AtomicI32 = AtomicI32::new(0); @@ -317,7 +317,7 @@ async fn temp_task(temp_dev_i2c: TempSensI2c, mut led: Output<'static>) -> ! { } #[embassy_executor::task] -async fn ethernet_task(runner: Runner<'static, SpeSpiCs, SpeInt, SpeRst>) -> ! { +async fn ethernet_task(runner: Runner<'static, GenericSpi, SpeInt, SpeRst>) -> ! { runner.run().await } From a328e9fa8906b5a770dd5f6c2664195f1945c870 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 23 Oct 2025 19:33:29 +0000 Subject: [PATCH 09/12] Implement OPEN Alliance TC6 protocol for ADIN1110 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add support for the OPEN Alliance 10BASE-T1x MAC-PHY Serial Interface (TC6) protocol as an alternative to the Generic SPI protocol. Key features: - Chunk-based protocol with 4-byte header/footer + 64-byte payload - Control transactions for register read/write (DNC=0) - Data transactions for Ethernet frames (DNC=1) - Even parity calculation for error detection - Support for both TX and RX data chunks - Configurable via 'tc6' feature flag Implementation includes: - src/protocol/tc6.rs: TC6 protocol implementation - Adin1110Protocol trait implementation for Tc6 - MdioBus trait implementation for PHY register access - new_tc6() constructor for easy instantiation - Updated README with TC6 documentation The TC6 protocol is selected via hardware pins (SPI_CFG0/SPI_CFG1) and complements the existing Generic SPI support. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- embassy-net-adin1110/Cargo.toml | 1 + embassy-net-adin1110/README.md | 6 +- embassy-net-adin1110/src/lib.rs | 60 ++++ embassy-net-adin1110/src/protocol/mod.rs | 5 + embassy-net-adin1110/src/protocol/tc6.rs | 357 +++++++++++++++++++++++ 5 files changed, 426 insertions(+), 3 deletions(-) create mode 100644 embassy-net-adin1110/src/protocol/tc6.rs diff --git a/embassy-net-adin1110/Cargo.toml b/embassy-net-adin1110/Cargo.toml index 3b9bdbedad..4d7bd1f05b 100644 --- a/embassy-net-adin1110/Cargo.toml +++ b/embassy-net-adin1110/Cargo.toml @@ -48,6 +48,7 @@ default = ["generic-spi"] defmt = ["dep:defmt", "embedded-hal-1/defmt-03"] log = ["dep:log"] generic-spi = [] +tc6 = [] [package.metadata.embassy_docs] src_base = "https://github.com/embassy-rs/embassy/blob/embassy-net-adin1110-v$VERSION/embassy-net-adin1110/src/" diff --git a/embassy-net-adin1110/README.md b/embassy-net-adin1110/README.md index 0514274b43..2cba331daa 100644 --- a/embassy-net-adin1110/README.md +++ b/embassy-net-adin1110/README.md @@ -21,10 +21,10 @@ APL can be used in [`intrinsic safety applications/explosion hazardous areas`](h ## Supported SPI modes -`ADIN1110` supports two SPI modes. `Generic` and [`OPEN Alliance 10BASE-T1x MAC-PHY serial interface`](https://opensig.org/wp-content/uploads/2023/12/OPEN_Alliance_10BASET1x_MAC-PHY_Serial_Interface_V1.1.pdf) +`ADIN1110` supports two SPI modes. `Generic` and [`OPEN Alliance 10BASE-T1x MAC-PHY serial interface (TC6)`](https://opensig.org/wp-content/uploads/2023/12/OPEN_Alliance_10BASET1x_MAC-PHY_Serial_Interface_V1.1.pdf) -Both modes support with and without additional CRC. -Currently only `Generic` SPI with or without CRC is supported. +- **Generic SPI**: Traditional SPI protocol with optional CRC (feature flag: `generic-spi`, enabled by default) +- **TC6 Protocol**: OPEN Alliance TC6 chunk-based protocol (feature flag: `tc6`) *NOTE:* SPI Mode is selected by the hardware pins `SPI_CFG0` and `SPI_CFG1`. Software can't detect nor change the mode. diff --git a/embassy-net-adin1110/src/lib.rs b/embassy-net-adin1110/src/lib.rs index efa115bf89..f39be3460d 100644 --- a/embassy-net-adin1110/src/lib.rs +++ b/embassy-net-adin1110/src/lib.rs @@ -31,6 +31,8 @@ use phy::{RegsC22, RegsC45}; pub use protocol::Adin1110Protocol; #[cfg(feature = "generic-spi")] pub use protocol::GenericSpi; +#[cfg(feature = "tc6")] +pub use protocol::Tc6; use regs::{Config0, Config2, SpiRegisters as sr, Status0, Status1}; use crate::regs::{LedCntrl, LedFunc, LedPol, LedPolarity}; @@ -232,6 +234,55 @@ impl mdio::MdioBus for ADIN1110> { } } +#[cfg(feature = "tc6")] +impl mdio::MdioBus for ADIN1110> { + type Error = AdinError; + + /// Read from the PHY Registers as Clause 22. + async fn read_cl22(&mut self, phy_id: u8, reg: u8) -> Result { + let mdio_acc_val: u32 = + (0x1 << 28) | u32::from(phy_id & 0x1F) << 21 | u32::from(reg & 0x1F) << 16 | (0x3 << 26); + + // Result is in the lower half of the answer. + #[allow(clippy::cast_possible_truncation)] + self.write_mdio_acc_reg(mdio_acc_val).await.map(|val| val as u16) + } + + /// Read from the PHY Registers as Clause 45. + async fn read_cl45(&mut self, phy_id: u8, regc45: (u8, u16)) -> Result { + let mdio_acc_val = u32::from(phy_id & 0x1F) << 21 | u32::from(regc45.0 & 0x1F) << 16 | u32::from(regc45.1); + + self.write_mdio_acc_reg(mdio_acc_val).await?; + + let mdio_acc_val = u32::from(phy_id & 0x1F) << 21 | u32::from(regc45.0 & 0x1F) << 16 | (0x03 << 26); + + // Result is in the lower half of the answer. + #[allow(clippy::cast_possible_truncation)] + self.write_mdio_acc_reg(mdio_acc_val).await.map(|val| val as u16) + } + + /// Write to the PHY Registers as Clause 22. + async fn write_cl22(&mut self, phy_id: u8, reg: u8, val: u16) -> Result<(), Self::Error> { + let mdio_acc_val: u32 = + (0x1 << 28) | u32::from(phy_id & 0x1F) << 21 | u32::from(reg & 0x1F) << 16 | (0x1 << 26) | u32::from(val); + + self.write_mdio_acc_reg(mdio_acc_val).await.map(|_| ()) + } + + /// Write to the PHY Registers as Clause 45. + async fn write_cl45(&mut self, phy_id: u8, regc45: (u8, u16), value: u16) -> AEResult<(), SPI::Error> { + let phy_id = u32::from(phy_id & 0x1F) << 21; + let dev_addr = u32::from(regc45.0 & 0x1F) << 16; + let reg = u32::from(regc45.1); + + let mdio_acc_val: u32 = phy_id | dev_addr | reg; + self.write_mdio_acc_reg(mdio_acc_val).await?; + + let mdio_acc_val: u32 = phy_id | dev_addr | (0x01 << 26) | u32::from(value); + self.write_mdio_acc_reg(mdio_acc_val).await.map(|_| ()) + } +} + /// Background runner for the ADIN1110. /// /// You must call `.run()` in a background task for the ADIN1110 to operate. @@ -402,6 +453,15 @@ impl ADIN1110> { Self::new(protocol) } } + +#[cfg(feature = "tc6")] +impl ADIN1110> { + /// Create driver with OPEN Alliance TC6 SPI protocol + pub fn new_tc6(spi: SPI) -> Self { + let protocol = Tc6::new(spi); + Self::new(protocol) + } +} /// Obtain a driver for using the ADIN1110 with [`embassy-net`](crates.io/crates/embassy-net). #[cfg(feature = "generic-spi")] pub async fn new( diff --git a/embassy-net-adin1110/src/protocol/mod.rs b/embassy-net-adin1110/src/protocol/mod.rs index fa44463aed..af139508fa 100644 --- a/embassy-net-adin1110/src/protocol/mod.rs +++ b/embassy-net-adin1110/src/protocol/mod.rs @@ -10,6 +10,11 @@ mod generic_spi; #[cfg(feature = "generic-spi")] pub use generic_spi::GenericSpi; +#[cfg(feature = "tc6")] +mod tc6; +#[cfg(feature = "tc6")] +pub use tc6::Tc6; + use crate::AdinError; /// Protocol abstraction trait for ADIN1110 SPI communication diff --git a/embassy-net-adin1110/src/protocol/tc6.rs b/embassy-net-adin1110/src/protocol/tc6.rs new file mode 100644 index 0000000000..737e318d37 --- /dev/null +++ b/embassy-net-adin1110/src/protocol/tc6.rs @@ -0,0 +1,357 @@ +use embedded_hal_async::spi::{Operation, SpiDevice}; +use heapless::Vec; + +use super::Adin1110Protocol; +use crate::regs::SpiRegisters as sr; +use crate::{AdinError, ETH_MIN_LEN, FCS_LEN, FRAME_HEADER_LEN, MAX_BUFF, PORT_ID_BYTE}; + +/// TC6 (OPEN Alliance 10BASE-T1x MAC-PHY Serial Interface) protocol implementation +/// +/// TC6 uses a chunk-based protocol where each chunk consists of: +/// - 4 bytes of overhead (header for TX, footer for RX) +/// - 64 bytes of payload +/// +/// For control transactions (register access): DNC bit = 0 +/// For data transactions (Ethernet frames): DNC bit = 1 +pub struct Tc6 { + spi: SPI, +} + +/// TC6 chunk payload size (64 bytes as per OPEN Alliance spec) +const TC6_CHUNK_PAYLOAD_SIZE: usize = 64; + +/// TC6 header/footer size (4 bytes) +const TC6_HDR_SIZE: usize = 4; + +/// Total chunk size including header and payload +const TC6_CHUNK_SIZE: usize = TC6_HDR_SIZE + TC6_CHUNK_PAYLOAD_SIZE; + +/// TC6 Control Command Header +/// +/// Bit 31: DNC (Data-Not-Control) = 0 for control commands +/// Bit 30: WNR (Write-Not-Read) = 1 for write, 0 for read +/// Bit 29: AID (Address Increment Disable) +/// Bit 28: MMS (Memory Map Selector) +/// Bits 27-16: ADDR (Register Address) +/// Bits 15-8: LEN (Length in DWORDs) +/// Bits 7-1: Reserved +/// Bit 0: P (Parity bit - even parity over bits 31:1) +#[derive(Debug, Clone, Copy)] +struct Tc6ControlHeader(u32); + +impl Tc6ControlHeader { + /// Create a new control header for register read + fn new_read(addr: u16) -> Self { + let mut val = 0u32; + // DNC = 0 (control command) + // WNR = 0 (read) + // ADDR + val |= u32::from(addr) << 16; + // LEN = 1 (one DWORD) + val |= 1u32 << 8; + + // Calculate even parity over bits 31:1 + let parity = (val >> 1).count_ones() & 1; + val |= parity; + + Self(val) + } + + /// Create a new control header for register write + fn new_write(addr: u16) -> Self { + let mut val = 0u32; + // DNC = 0 (control command) + // WNR = 1 (write) + val |= 1u32 << 30; + // ADDR + val |= u32::from(addr) << 16; + // LEN = 1 (one DWORD) + val |= 1u32 << 8; + + // Calculate even parity over bits 31:1 + let parity = (val >> 1).count_ones() & 1; + val |= parity; + + Self(val) + } + + fn to_bytes(self) -> [u8; 4] { + self.0.to_be_bytes() + } +} + +/// TC6 Data Chunk Header +/// +/// Bit 31: DNC (Data-Not-Control) = 1 for data chunks +/// Bit 30: SEQ (Sequence bit for even/odd indication) +/// Bit 29: NORX (No Receive - prevents MAC-PHY from sending RX data) +/// Bits 28-24: Reserved +/// Bits 23-16: DV (Data Valid - number of valid bytes in payload, 0 = no data) +/// Bits 15-11: SV (Start Valid - byte offset to frame start) +/// Bits 10-6: SWO (Start Word Offset) +/// Bits 5-1: EV (End Valid - byte offset after frame end) +/// Bit 0: EBO (End Byte Offset) +/// Bit 0: P (Parity bit - even parity over bits 31:1) +#[derive(Debug, Clone, Copy)] +struct Tc6DataHeader(u32); + +impl Tc6DataHeader { + /// Create a new data chunk header + fn new(dv: u8, sv: u8, ev: u8) -> Self { + let mut val = 0u32; + // DNC = 1 (data chunk) + val |= 1u32 << 31; + // SEQ = 0 (we'll toggle this as needed) + // NORX = 0 (we want to receive data) + // DV (Data Valid bytes) + val |= u32::from(dv) << 16; + // SV (Start Valid) + val |= u32::from(sv) << 11; + // EV (End Valid) + val |= u32::from(ev) << 1; + + // Calculate even parity over bits 31:1 + let parity = (val >> 1).count_ones() & 1; + val |= parity; + + Self(val) + } + + fn to_bytes(self) -> [u8; 4] { + self.0.to_be_bytes() + } +} + +/// TC6 Data Footer (received from device) +/// +/// Similar structure to header but received at end of RX data chunk +#[derive(Debug, Clone, Copy)] +struct Tc6DataFooter(u32); + +impl Tc6DataFooter { + fn from_bytes(bytes: [u8; 4]) -> Self { + Self(u32::from_be_bytes(bytes)) + } + + /// Extract number of valid data bytes in the received chunk + fn data_valid(&self) -> u8 { + ((self.0 >> 16) & 0xFF) as u8 + } + + /// Extract start valid offset + fn start_valid(&self) -> u8 { + ((self.0 >> 11) & 0x1F) as u8 + } + + /// Extract end valid offset + fn end_valid(&self) -> u8 { + ((self.0 >> 1) & 0x1F) as u8 + } + + /// Check parity + fn check_parity(&self) -> bool { + let parity = (self.0 >> 1).count_ones() & 1; + let expected_parity = self.0 & 1; + parity == expected_parity + } +} + +impl Tc6 { + /// Create a new TC6 protocol handler + pub fn new(spi: SPI) -> Self { + Self { spi } + } +} + +impl Adin1110Protocol for Tc6 { + type SpiError = SPI::Error; + + async fn read_reg(&mut self, addr: u16) -> Result> { + // Create control command header for read + let header = Tc6ControlHeader::new_read(addr); + let header_bytes = header.to_bytes(); + + // Prepare buffers + let mut rx_buf = [0u8; 4]; + + // Perform SPI transaction: write header, read response + let mut ops = [Operation::Write(&header_bytes), Operation::Read(&mut rx_buf)]; + + self.spi.transaction(&mut ops).await.map_err(AdinError::Spi)?; + + // Parse response + let value = u32::from_be_bytes(rx_buf); + + trace!("TC6 REG Read {} = {:08x}", addr, value); + + Ok(value) + } + + async fn write_reg(&mut self, addr: u16, value: u32) -> Result<(), AdinError> { + // Create control command header for write + let header = Tc6ControlHeader::new_write(addr); + let header_bytes = header.to_bytes(); + let value_bytes = value.to_be_bytes(); + + // Prepare write buffer + let mut write_buf = [0u8; 8]; + write_buf[0..4].copy_from_slice(&header_bytes); + write_buf[4..8].copy_from_slice(&value_bytes); + + trace!("TC6 REG Write {} = {:08x}", addr, value); + + self.spi.write(&write_buf).await.map_err(AdinError::Spi) + } + + async fn read_fifo(&mut self, frame: &mut [u8]) -> Result> { + // Read the frame size from register + let fifo_frame_size = self.read_reg(sr::RX_FSIZE.into()).await? as usize; + + if fifo_frame_size < ETH_MIN_LEN + FRAME_HEADER_LEN { + return Err(AdinError::PACKET_TOO_SMALL); + } + + let packet_size = fifo_frame_size - FRAME_HEADER_LEN - FCS_LEN; + + if packet_size > frame.len() { + trace!("MAX: {} WANT: {}", frame.len(), packet_size); + return Err(AdinError::PACKET_TOO_BIG); + } + + // TC6 uses chunks: we need to read data in 64-byte chunks + // For now, implement a basic version that reads the frame header + data + + // Read frame header (2 bytes) + let mut frame_header = [0u8; 2]; + let mut bytes_read = 0; + + // Create data chunk header for reading + let data_header = Tc6DataHeader::new(0, 0, 0); // Request to read data + let header_bytes = data_header.to_bytes(); + + // Read frame header first + let mut chunk_buf = [0u8; TC6_CHUNK_SIZE]; + let mut ops = [Operation::Write(&header_bytes), Operation::Read(&mut chunk_buf)]; + + self.spi.transaction(&mut ops).await.map_err(AdinError::Spi)?; + + // Extract footer (last 4 bytes of chunk) + let footer_bytes: [u8; 4] = chunk_buf[TC6_CHUNK_PAYLOAD_SIZE..TC6_CHUNK_SIZE].try_into().unwrap(); + let footer = Tc6DataFooter::from_bytes(footer_bytes); + + if !footer.check_parity() { + return Err(AdinError::SPI_CRC); + } + + // Extract frame header from payload + frame_header.copy_from_slice(&chunk_buf[0..2]); + + // Copy data to frame buffer + let chunk_data_len = footer.data_valid() as usize; + let to_copy = core::cmp::min(chunk_data_len.saturating_sub(2), packet_size); + + if to_copy > 0 { + frame[0..to_copy].copy_from_slice(&chunk_buf[2..2 + to_copy]); + bytes_read += to_copy; + } + + // Read remaining chunks if needed + while bytes_read < packet_size { + let mut ops = [Operation::Write(&header_bytes), Operation::Read(&mut chunk_buf)]; + + self.spi.transaction(&mut ops).await.map_err(AdinError::Spi)?; + + let footer_bytes: [u8; 4] = chunk_buf[TC6_CHUNK_PAYLOAD_SIZE..TC6_CHUNK_SIZE].try_into().unwrap(); + let footer = Tc6DataFooter::from_bytes(footer_bytes); + + if !footer.check_parity() { + return Err(AdinError::SPI_CRC); + } + + let chunk_data_len = footer.data_valid() as usize; + let to_copy = core::cmp::min(chunk_data_len, packet_size - bytes_read); + + frame[bytes_read..bytes_read + to_copy].copy_from_slice(&chunk_buf[0..to_copy]); + bytes_read += to_copy; + + if chunk_data_len < TC6_CHUNK_PAYLOAD_SIZE { + break; // Last chunk + } + } + + // TODO: Verify FCS + Ok(packet_size) + } + + async fn write_fifo(&mut self, frame: &[u8]) -> Result<(), AdinError> { + if frame.len() < (6 + 6 + 2) { + return Err(AdinError::PACKET_TOO_SMALL); + } + if frame.len() > (MAX_BUFF - FRAME_HEADER_LEN) { + return Err(AdinError::PACKET_TOO_BIG); + } + + // Calculate total size including frame header and FCS + let total_size = frame.len() + FRAME_HEADER_LEN; + let send_len = u32::try_from(total_size).map_err(|_| AdinError::PACKET_TOO_BIG)?; + + // Write TX frame size + self.write_reg(sr::TX_FSIZE.into(), send_len).await?; + + // Prepare to send data in chunks + let mut offset = 0; + + // First chunk includes frame header (2 bytes) + data + let mut chunk_payload = [0u8; TC6_CHUNK_PAYLOAD_SIZE]; + + // Add frame header (port ID) + let port_header = u16::from(PORT_ID_BYTE).to_be_bytes(); + chunk_payload[0..2].copy_from_slice(&port_header); + + // Add frame data + let first_chunk_data_len = core::cmp::min(frame.len(), TC6_CHUNK_PAYLOAD_SIZE - 2); + chunk_payload[2..2 + first_chunk_data_len].copy_from_slice(&frame[0..first_chunk_data_len]); + offset += first_chunk_data_len; + + // Calculate DV (data valid bytes in this chunk) + let dv = (2 + first_chunk_data_len) as u8; + let data_header = Tc6DataHeader::new(dv, 0, 0); + let header_bytes = data_header.to_bytes(); + + // Send first chunk + let mut write_buf = [0u8; TC6_CHUNK_SIZE]; + write_buf[0..4].copy_from_slice(&header_bytes); + write_buf[4..4 + TC6_CHUNK_PAYLOAD_SIZE].copy_from_slice(&chunk_payload); + + self.spi.write(&write_buf).await.map_err(AdinError::Spi)?; + + // Send remaining chunks + while offset < frame.len() { + let remaining = frame.len() - offset; + let chunk_len = core::cmp::min(remaining, TC6_CHUNK_PAYLOAD_SIZE); + + let mut chunk_payload = [0u8; TC6_CHUNK_PAYLOAD_SIZE]; + chunk_payload[0..chunk_len].copy_from_slice(&frame[offset..offset + chunk_len]); + + let dv = chunk_len as u8; + let data_header = Tc6DataHeader::new(dv, 0, 0); + let header_bytes = data_header.to_bytes(); + + let mut write_buf = [0u8; TC6_CHUNK_SIZE]; + write_buf[0..4].copy_from_slice(&header_bytes); + write_buf[4..4 + TC6_CHUNK_PAYLOAD_SIZE].copy_from_slice(&chunk_payload); + + self.spi.write(&write_buf).await.map_err(AdinError::Spi)?; + + offset += chunk_len; + } + + trace!( + "TC6 TX: {} bytes in {} chunks", + frame.len(), + (frame.len() + TC6_CHUNK_PAYLOAD_SIZE - 1) / TC6_CHUNK_PAYLOAD_SIZE + ); + + Ok(()) + } +} From f779f0605a8c09adabe436bd8401b70432d5ec91 Mon Sep 17 00:00:00 2001 From: Zachary Crockett Date: Thu, 30 Oct 2025 17:07:16 -0700 Subject: [PATCH 10/12] Suggestions from clippy --- embassy-net-adin1110/README.md | 2 +- embassy-net-adin1110/src/fmt.rs | 8 +- embassy-net-adin1110/src/lib.rs | 230 +++++++++--------- .../src/protocol/generic_spi.rs | 2 +- embassy-net-adin1110/src/protocol/mod.rs | 12 +- embassy-net-adin1110/src/protocol/tc6.rs | 1 - 6 files changed, 126 insertions(+), 129 deletions(-) diff --git a/embassy-net-adin1110/README.md b/embassy-net-adin1110/README.md index 2cba331daa..8fab9c0f62 100644 --- a/embassy-net-adin1110/README.md +++ b/embassy-net-adin1110/README.md @@ -17,7 +17,7 @@ In the industry SPE is also called [`APL (Advanced Physical Layer)`](https://www APL can be used in [`intrinsic safety applications/explosion hazardous areas`](https://en.wikipedia.org/wiki/Electrical_equipment_in_hazardous_areas) which has its own name and standard called [`2-WISE (2-wire intrinsically safe ethernet) IEC TS 60079-47:2021`](https://webstore.iec.ch/publication/64292). -`10 BASE-T1L` and `ADIN1110` are designed to support intrinsic safety applications. The power supply energy is fixed and PDoL is not supported. +`10BASE-T1L` and `ADIN1110` are designed to support intrinsic safety applications. The power supply energy is fixed and `PoDL` is not supported. ## Supported SPI modes diff --git a/embassy-net-adin1110/src/fmt.rs b/embassy-net-adin1110/src/fmt.rs index 8ca61bc39a..83937caa35 100644 --- a/embassy-net-adin1110/src/fmt.rs +++ b/embassy-net-adin1110/src/fmt.rs @@ -244,26 +244,26 @@ impl Try for Result { pub(crate) struct Bytes<'a>(pub &'a [u8]); -impl<'a> Debug for Bytes<'a> { +impl Debug for Bytes<'_> { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { write!(f, "{:#02x?}", self.0) } } -impl<'a> Display for Bytes<'a> { +impl Display for Bytes<'_> { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { write!(f, "{:#02x?}", self.0) } } -impl<'a> LowerHex for Bytes<'a> { +impl LowerHex for Bytes<'_> { 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> { +impl defmt::Format for Bytes<'_> { fn format(&self, fmt: defmt::Formatter) { defmt::write!(fmt, "{:02x}", self.0) } diff --git a/embassy-net-adin1110/src/lib.rs b/embassy-net-adin1110/src/lib.rs index f39be3460d..9168e89a28 100644 --- a/embassy-net-adin1110/src/lib.rs +++ b/embassy-net-adin1110/src/lib.rs @@ -294,7 +294,7 @@ pub struct Runner<'d, P: Adin1110Protocol, INT, RST> { _reset: RST, } -impl<'d, P: Adin1110Protocol, INT: Wait, RST: OutputPin> Runner<'d, P, INT, RST> +impl Runner<'_, P, INT, RST> where ADIN1110

: MdioBus, as MdioBus>::Error: core::fmt::Debug, @@ -302,143 +302,141 @@ where /// Run the driver. #[allow(clippy::too_many_lines)] pub async fn run(mut self) -> ! { + let (state_chan, mut rx_chan, mut tx_chan) = self.ch.split(); + loop { - let (state_chan, mut rx_chan, mut tx_chan) = self.ch.split(); - - loop { - debug!("Waiting for interrupts"); - match select(self.int.wait_for_low(), tx_chan.tx_buf()).await { - Either::First(_) => { - let mut status1_clr = Status1(0); - let mut status1 = Status1(self.mac.read_reg(sr::STATUS1).await.unwrap()); - - while status1.p1_rx_rdy() { - debug!("alloc RX packet buffer"); - match select(rx_chan.rx_buf(), tx_chan.tx_buf()).await { - // Handle frames that needs to transmit from the wire. - // Note: rx_chan.rx_buf() channel don´t accept new request - // when the tx_chan is full. So these will be handled - // automaticly. - Either::First(frame) => match self.mac.read_fifo(frame).await { - Ok(n) => { - rx_chan.rx_done(n); + debug!("Waiting for interrupts"); + match select(self.int.wait_for_low(), tx_chan.tx_buf()).await { + Either::First(_) => { + let mut status1_clr = Status1(0); + let mut status1 = Status1(self.mac.read_reg(sr::STATUS1).await.unwrap()); + + while status1.p1_rx_rdy() { + debug!("alloc RX packet buffer"); + match select(rx_chan.rx_buf(), tx_chan.tx_buf()).await { + // Handle frames that needs to transmit from the wire. + // Note: rx_chan.rx_buf() channel don´t accept new request + // when the tx_chan is full. So these will be handled + // automaticly. + Either::First(frame) => match self.mac.read_fifo(frame).await { + Ok(n) => { + rx_chan.rx_done(n); + } + Err(e) => match e { + AdinError::PACKET_TOO_BIG => { + error!("RX Packet too big, DROP"); + self.mac.write_reg(sr::FIFO_CLR, 1).await.unwrap(); + } + AdinError::PACKET_TOO_SMALL => { + error!("RX Packet too small, DROP"); + self.mac.write_reg(sr::FIFO_CLR, 1).await.unwrap(); + } + AdinError::Spi(e) => { + error!("RX Spi error {}", e.kind()); + } + e => { + error!("RX Error {:?}", e); } - Err(e) => match e { - AdinError::PACKET_TOO_BIG => { - error!("RX Packet too big, DROP"); - self.mac.write_reg(sr::FIFO_CLR, 1).await.unwrap(); - } - AdinError::PACKET_TOO_SMALL => { - error!("RX Packet too small, DROP"); - self.mac.write_reg(sr::FIFO_CLR, 1).await.unwrap(); - } - AdinError::Spi(e) => { - error!("RX Spi error {}", e.kind()); - } - e => { - error!("RX Error {:?}", e); - } - }, }, - Either::Second(frame) => { - // Handle frames that needs to transmit to the wire. - self.mac.write_fifo(frame).await.unwrap(); - tx_chan.tx_done(); - } + }, + Either::Second(frame) => { + // Handle frames that needs to transmit to the wire. + self.mac.write_fifo(frame).await.unwrap(); + tx_chan.tx_done(); } - status1 = Status1(self.mac.read_reg(sr::STATUS1).await.unwrap()); - } - - let status0 = Status0(self.mac.read_reg(sr::STATUS0).await.unwrap()); - if status1.0 & !0x1b != 0 { - error!("SPE CHIP STATUS 0:{:08x} 1:{:08x}", status0.0, status1.0); - } - - if status1.tx_rdy() { - status1_clr.set_tx_rdy(true); - trace!("TX_DONE"); - } - - if status1.link_change() { - let link = status1.p1_link_status(); - self.is_link_up = link; - - if link { - let link_status = self - .mac - .read_cl45(MDIO_PHY_ADDR, RegsC45::DA7::AN_STATUS_EXTRA.into()) - .await - .unwrap(); - - let volt = if link_status & (0b11 << 5) == (0b11 << 5) { - "2.4" - } else { - "1.0" - }; - - let mse = self - .mac - .read_cl45(MDIO_PHY_ADDR, RegsC45::DA1::MSE_VAL.into()) - .await - .unwrap(); - - info!("LINK Changed: Link Up, Volt: {} V p-p, MSE: {:0004}", volt, mse); - } else { - info!("LINK Changed: Link Down"); - } - - state_chan.set_link_state(if link { LinkState::Up } else { LinkState::Down }); - status1_clr.set_link_change(true); } + status1 = Status1(self.mac.read_reg(sr::STATUS1).await.unwrap()); + } - if status1.tx_ecc_err() { - error!("SPI TX_ECC_ERR error, CLEAR TX FIFO"); - self.mac.write_reg(sr::FIFO_CLR, 2).await.unwrap(); - status1_clr.set_tx_ecc_err(true); - } + let status0 = Status0(self.mac.read_reg(sr::STATUS0).await.unwrap()); + if status1.0 & !0x1b != 0 { + error!("SPE CHIP STATUS 0:{:08x} 1:{:08x}", status0.0, status1.0); + } - if status1.rx_ecc_err() { - error!("SPI RX_ECC_ERR error"); - status1_clr.set_rx_ecc_err(true); - } + if status1.tx_rdy() { + status1_clr.set_tx_rdy(true); + trace!("TX_DONE"); + } - if status1.spi_err() { - error!("SPI SPI_ERR CRC error"); - status1_clr.set_spi_err(true); - } + if status1.link_change() { + let link = status1.p1_link_status(); + self.is_link_up = link; - if status0.phyint() { - let crsm_irq_st = self + if link { + let link_status = self .mac - .read_cl45(MDIO_PHY_ADDR, RegsC45::DA1E::CRSM_IRQ_STATUS.into()) + .read_cl45(MDIO_PHY_ADDR, RegsC45::DA7::AN_STATUS_EXTRA.into()) .await .unwrap(); - let phy_irq_st = self + let volt = if link_status & (0b11 << 5) == (0b11 << 5) { + "2.4" + } else { + "1.0" + }; + + let mse = self .mac - .read_cl45(MDIO_PHY_ADDR, RegsC45::DA1F::PHY_SYBSYS_IRQ_STATUS.into()) + .read_cl45(MDIO_PHY_ADDR, RegsC45::DA1::MSE_VAL.into()) .await .unwrap(); - warn!( - "SPE CHIP PHY CRSM_IRQ_STATUS {:04x} PHY_SUBSYS_IRQ_STATUS {:04x}", - crsm_irq_st, phy_irq_st - ); + info!("LINK Changed: Link Up, Volt: {} V p-p, MSE: {:0004}", volt, mse); + } else { + info!("LINK Changed: Link Down"); } - if status0.txfcse() { - error!("Ethernet Frame FCS and calc FCS don't match!"); - } + state_chan.set_link_state(if link { LinkState::Up } else { LinkState::Down }); + status1_clr.set_link_change(true); + } - // Clear status0 - self.mac.write_reg(sr::STATUS0, 0xFFF).await.unwrap(); - self.mac.write_reg(sr::STATUS1, status1_clr.0).await.unwrap(); + if status1.tx_ecc_err() { + error!("SPI TX_ECC_ERR error, CLEAR TX FIFO"); + self.mac.write_reg(sr::FIFO_CLR, 2).await.unwrap(); + status1_clr.set_tx_ecc_err(true); } - Either::Second(packet) => { - // Handle frames that needs to transmit to the wire. - self.mac.write_fifo(packet).await.unwrap(); - tx_chan.tx_done(); + + if status1.rx_ecc_err() { + error!("SPI RX_ECC_ERR error"); + status1_clr.set_rx_ecc_err(true); } + + if status1.spi_err() { + error!("SPI SPI_ERR CRC error"); + status1_clr.set_spi_err(true); + } + + if status0.phyint() { + let crsm_irq_st = self + .mac + .read_cl45(MDIO_PHY_ADDR, RegsC45::DA1E::CRSM_IRQ_STATUS.into()) + .await + .unwrap(); + + let phy_irq_st = self + .mac + .read_cl45(MDIO_PHY_ADDR, RegsC45::DA1F::PHY_SYBSYS_IRQ_STATUS.into()) + .await + .unwrap(); + + warn!( + "SPE CHIP PHY CRSM_IRQ_STATUS {:04x} PHY_SUBSYS_IRQ_STATUS {:04x}", + crsm_irq_st, phy_irq_st + ); + } + + if status0.txfcse() { + error!("Ethernet Frame FCS and calc FCS don't match!"); + } + + // Clear status0 + self.mac.write_reg(sr::STATUS0, 0xFFF).await.unwrap(); + self.mac.write_reg(sr::STATUS1, status1_clr.0).await.unwrap(); + } + Either::Second(packet) => { + // Handle frames that needs to transmit to the wire. + self.mac.write_fifo(packet).await.unwrap(); + tx_chan.tx_done(); } } } diff --git a/embassy-net-adin1110/src/protocol/generic_spi.rs b/embassy-net-adin1110/src/protocol/generic_spi.rs index c8c1d0ccfb..27fc4c093d 100644 --- a/embassy-net-adin1110/src/protocol/generic_spi.rs +++ b/embassy-net-adin1110/src/protocol/generic_spi.rs @@ -19,7 +19,7 @@ pub struct GenericSpi { } impl GenericSpi { - /// Create a new GenericSpi protocol handler + /// Create a new `GenericSpi` protocol handler pub fn new(spi: SPI, crc_enabled: bool, append_fcs_on_tx: bool) -> Self { Self { spi, diff --git a/embassy-net-adin1110/src/protocol/mod.rs b/embassy-net-adin1110/src/protocol/mod.rs index af139508fa..7346e35a01 100644 --- a/embassy-net-adin1110/src/protocol/mod.rs +++ b/embassy-net-adin1110/src/protocol/mod.rs @@ -1,9 +1,9 @@ -/// Protocol abstraction for ADIN1110 SPI communication -/// -/// This module defines a trait-based abstraction for different SPI protocols -/// supported by the ADIN1110 chip: -/// - Generic SPI -/// - OPEN Alliance TC6 +//! Protocol abstraction for ADIN1110 SPI communication +//! +//! This module defines a trait-based abstraction for different SPI protocols +//! supported by the ADIN1110 chip: +//! - Generic SPI +//! - OPEN Alliance TC6 #[cfg(feature = "generic-spi")] mod generic_spi; diff --git a/embassy-net-adin1110/src/protocol/tc6.rs b/embassy-net-adin1110/src/protocol/tc6.rs index 737e318d37..23c467aa54 100644 --- a/embassy-net-adin1110/src/protocol/tc6.rs +++ b/embassy-net-adin1110/src/protocol/tc6.rs @@ -1,5 +1,4 @@ use embedded_hal_async::spi::{Operation, SpiDevice}; -use heapless::Vec; use super::Adin1110Protocol; use crate::regs::SpiRegisters as sr; From ba0115975d42245f96b6bc5bdb875f5453770e44 Mon Sep 17 00:00:00 2001 From: Zachary Crockett Date: Thu, 30 Oct 2025 21:15:19 -0700 Subject: [PATCH 11/12] Driver and example successfully build --- embassy-net-adin1110/src/lib.rs | 147 ++++++++++++++++-- .../src/protocol/generic_spi.rs | 16 +- embassy-net-adin1110/src/protocol/tc6.rs | 10 -- embassy-net-adin1110/src/regs.rs | 4 + examples/stm32u575/Cargo.toml | 4 +- .../src/bin/spe_adin1110_http_server.rs | 8 +- 6 files changed, 161 insertions(+), 28 deletions(-) diff --git a/embassy-net-adin1110/src/lib.rs b/embassy-net-adin1110/src/lib.rs index 9168e89a28..cbb021b61e 100644 --- a/embassy-net-adin1110/src/lib.rs +++ b/embassy-net-adin1110/src/lib.rs @@ -11,7 +11,10 @@ mod fmt; mod crc32; + +#[cfg(feature = "generic-spi")] mod crc8; + mod mdio; mod phy; mod protocol; @@ -78,19 +81,8 @@ const TURN_AROUND_BYTE: u8 = 0x00; const ETH_MIN_LEN: usize = 64; /// Ethernet `Frame Check Sequence` length const FCS_LEN: usize = 4; -/// Packet minimal frame/packet length without `Frame Check Sequence` length -const ETH_MIN_WITHOUT_FCS_LEN: usize = ETH_MIN_LEN - FCS_LEN; - -/// SPI Header, contains SPI action and register id. -const SPI_HEADER_LEN: usize = 2; -/// SPI Header CRC length -const SPI_HEADER_CRC_LEN: usize = 1; -/// SPI Header Turn Around length -const SPI_HEADER_TA_LEN: usize = 1; /// Frame Header length const FRAME_HEADER_LEN: usize = 2; -/// Space for last bytes to create multipule 4 bytes on the end of a FIFO read/write. -const SPI_SPACE_MULTIPULE: usize = 3; /// P1 = 0x00, P2 = 0x01 const PORT_ID_BYTE: u8 = 0x00; @@ -186,6 +178,7 @@ impl ADIN1110

{ } } +#[cfg(feature = "generic-spi")] impl mdio::MdioBus for ADIN1110> { type Error = AdinError; @@ -460,6 +453,7 @@ impl ADIN1110> { Self::new(protocol) } } + /// Obtain a driver for using the ADIN1110 with [`embassy-net`](crates.io/crates/embassy-net). #[cfg(feature = "generic-spi")] pub async fn new( @@ -592,6 +586,137 @@ pub async fn new( + mac_addr: [u8; 6], + state: &'_ mut State, + spi_dev: SPI, + int: INT, + mut reset: RST, + append_fcs_on_tx: bool, +) -> (Device<'_>, Runner<'_, Tc6, INT, RST>) { + use crate::regs::{IMask0, IMask1}; + + info!("INIT ADIN1110"); + + // Reset sequence + reset.set_low().unwrap(); + + // Wait t1: 20-43mS + Timer::after_millis(30).await; + + reset.set_high().unwrap(); + + // Wait t3: 50mS + Timer::after_millis(50).await; + + // Create device + let mut mac = ADIN1110::new_tc6(spi_dev); + + // Check PHYID + let id = mac.read_reg(sr::PHYID).await.unwrap(); + assert_eq!(id, PHYID); + + debug!("SPE: CHIP MAC/ID: {:08x}", id); + + #[cfg(any(feature = "defmt", feature = "log"))] + { + let adin_phy = Phy10BaseT1x::default(); + let phy_id = adin_phy.get_id(&mut mac).await.unwrap(); + debug!("SPE: CHIP: PHY ID: {:08x}", phy_id); + } + + let mi_control = mac.read_cl22(MDIO_PHY_ADDR, RegsC22::CONTROL as u8).await.unwrap(); + debug!("SPE CHIP PHY MI_CONTROL {:04x}", mi_control); + if mi_control & 0x0800 != 0 { + let val = mi_control & !0x0800; + debug!("SPE CHIP PHY MI_CONTROL Disable PowerDown"); + mac.write_cl22(MDIO_PHY_ADDR, RegsC22::CONTROL as u8, val) + .await + .unwrap(); + } + + // Config0 + let mut config0 = Config0(0x0000_0006); + config0.set_txfcsve(append_fcs_on_tx); + mac.write_reg(sr::CONFIG0, config0.0).await.unwrap(); + + // Config2 + let mut config2 = Config2(0x0000_0800); + // crc_append must be disable if tx_fcs_validation_enable is true! + config2.set_crc_append(!append_fcs_on_tx); + mac.write_reg(sr::CONFIG2, config2.0).await.unwrap(); + + // Pin Mux Config 1 + let led_val = (0b11 << 6) | (0b11 << 4); // | (0b00 << 1); + mac.write_cl45(MDIO_PHY_ADDR, RegsC45::DA1E::DIGIO_PINMUX.into(), led_val) + .await + .unwrap(); + + let mut led_pol = LedPolarity(0); + led_pol.set_led1_polarity(LedPol::ActiveLow); + led_pol.set_led0_polarity(LedPol::ActiveLow); + + // Led Polarity Regisgere Active Low + mac.write_cl45(MDIO_PHY_ADDR, RegsC45::DA1E::LED_POLARITY.into(), led_pol.0) + .await + .unwrap(); + + // Led Both On + let mut led_cntr = LedCntrl(0x0); + + // LED1: Yellow + led_cntr.set_led1_en(true); + led_cntr.set_led1_function(LedFunc::TxLevel2P4); + // LED0: Green + led_cntr.set_led0_en(true); + led_cntr.set_led0_function(LedFunc::LinkupTxRxActicity); + + mac.write_cl45(MDIO_PHY_ADDR, RegsC45::DA1E::LED_CNTRL.into(), led_cntr.0) + .await + .unwrap(); + + // Set ADIN1110 Interrupts, RX_READY and LINK_CHANGE + // Enable interrupts LINK_CHANGE, TX_RDY, RX_RDY(P1), SPI_ERR + // Have to clear the mask the enable it. + let mut imask0_val = IMask0(0x0000_1FBF); + imask0_val.set_txfcsem(false); + imask0_val.set_phyintm(false); + imask0_val.set_txboem(false); + imask0_val.set_rxboem(false); + imask0_val.set_txpem(false); + + mac.write_reg(sr::IMASK0, imask0_val.0).await.unwrap(); + + // Set ADIN1110 Interrupts, RX_READY and LINK_CHANGE + // Enable interrupts LINK_CHANGE, TX_RDY, RX_RDY(P1), SPI_ERR + // Have to clear the mask the enable it. + let mut imask1_val = IMask1(0x43FA_1F1A); + imask1_val.set_link_change_mask(false); + imask1_val.set_p1_rx_rdy_mask(false); + imask1_val.set_spi_err_mask(false); + imask1_val.set_tx_ecc_err_mask(false); + imask1_val.set_rx_ecc_err_mask(false); + + mac.write_reg(sr::IMASK1, imask1_val.0).await.unwrap(); + + // Program mac address but also sets mac filters. + mac.set_mac_addr(&mac_addr).await.unwrap(); + + let (runner, device) = ch::new(&mut state.ch_state, ch::driver::HardwareAddress::Ethernet(mac_addr)); + ( + device, + Runner { + ch: runner, + mac, + int, + is_link_up: false, + _reset: reset, + }, + ) +} + #[allow(clippy::similar_names)] #[cfg(test)] mod tests { diff --git a/embassy-net-adin1110/src/protocol/generic_spi.rs b/embassy-net-adin1110/src/protocol/generic_spi.rs index 27fc4c093d..1d88b68939 100644 --- a/embassy-net-adin1110/src/protocol/generic_spi.rs +++ b/embassy-net-adin1110/src/protocol/generic_spi.rs @@ -7,10 +7,22 @@ use crate::crc32::ETH_FCS; use crate::fmt::Bytes; use crate::regs::{SpiHeader, SpiRegisters as sr}; use crate::{ - AdinError, DONT_CARE_BYTE, ETH_MIN_LEN, ETH_MIN_WITHOUT_FCS_LEN, FCS_LEN, FRAME_HEADER_LEN, MAX_BUFF, PORT_ID_BYTE, - SPI_HEADER_CRC_LEN, SPI_HEADER_LEN, SPI_HEADER_TA_LEN, SPI_SPACE_MULTIPULE, TURN_AROUND_BYTE, + AdinError, DONT_CARE_BYTE, ETH_MIN_LEN, FCS_LEN, FRAME_HEADER_LEN, MAX_BUFF, PORT_ID_BYTE, TURN_AROUND_BYTE, }; +/// Packet minimal frame/packet length without `Frame Check Sequence` length +const ETH_MIN_WITHOUT_FCS_LEN: usize = ETH_MIN_LEN - FCS_LEN; + +/// SPI Header, contains SPI action and register id. +const SPI_HEADER_LEN: usize = 2; +/// SPI Header CRC length +const SPI_HEADER_CRC_LEN: usize = 1; +/// SPI Header Turn Around length +const SPI_HEADER_TA_LEN: usize = 1; + +/// Space for last bytes to create multipule 4 bytes on the end of a FIFO read/write. +const SPI_SPACE_MULTIPULE: usize = 3; + /// Generic SPI protocol implementation for ADIN1110 pub struct GenericSpi { spi: SPI, diff --git a/embassy-net-adin1110/src/protocol/tc6.rs b/embassy-net-adin1110/src/protocol/tc6.rs index 23c467aa54..ea244023be 100644 --- a/embassy-net-adin1110/src/protocol/tc6.rs +++ b/embassy-net-adin1110/src/protocol/tc6.rs @@ -137,16 +137,6 @@ impl Tc6DataFooter { ((self.0 >> 16) & 0xFF) as u8 } - /// Extract start valid offset - fn start_valid(&self) -> u8 { - ((self.0 >> 11) & 0x1F) as u8 - } - - /// Extract end valid offset - fn end_valid(&self) -> u8 { - ((self.0 >> 1) & 0x1F) as u8 - } - /// Check parity fn check_parity(&self) -> bool { let parity = (self.0 >> 1).count_ones() & 1; diff --git a/embassy-net-adin1110/src/regs.rs b/embassy-net-adin1110/src/regs.rs index 8780c2b9d5..e15b079a29 100644 --- a/embassy-net-adin1110/src/regs.rs +++ b/embassy-net-adin1110/src/regs.rs @@ -399,10 +399,14 @@ impl LedPolarity { } /// SPI Header +#[cfg(feature = "generic-spi")] #[derive(Copy, Clone, PartialEq, Eq, Hash)] pub struct SpiHeader(pub u16); + +#[cfg(feature = "generic-spi")] bitfield_bitrange! {struct SpiHeader(u16)} +#[cfg(feature = "generic-spi")] impl SpiHeader { bitfield_fields! { u16; diff --git a/examples/stm32u575/Cargo.toml b/examples/stm32u575/Cargo.toml index 80518f9e0f..4b0102cf04 100644 --- a/examples/stm32u575/Cargo.toml +++ b/examples/stm32u575/Cargo.toml @@ -32,7 +32,9 @@ embassy-usb = { version = "0.5.1", path = "../../embassy-usb", features = [ "defmt", ] } embassy-futures = { version = "0.1.2", path = "../../embassy-futures" } -embassy-net-adin1110 = { version = "0.3.1", path = "../../embassy-net-adin1110" } +embassy-net-adin1110 = { version = "0.3.1", path = "../../embassy-net-adin1110", default-features = false, features = [ + "tc6", +] } embassy-net = { version = "0.7.1", path = "../../embassy-net", features = [ "defmt", "udp", diff --git a/examples/stm32u575/src/bin/spe_adin1110_http_server.rs b/examples/stm32u575/src/bin/spe_adin1110_http_server.rs index 15c6969dd4..bbfbfa65fe 100644 --- a/examples/stm32u575/src/bin/spe_adin1110_http_server.rs +++ b/examples/stm32u575/src/bin/spe_adin1110_http_server.rs @@ -17,7 +17,7 @@ use embassy_futures::select::{Either, select}; use embassy_futures::yield_now; use embassy_net::tcp::TcpSocket; use embassy_net::{Ipv6Address, Ipv6Cidr, Stack, StackResources, StaticConfigV6}; -use embassy_net_adin1110::{ADIN1110, Device, Runner}; +use embassy_net_adin1110::{ADIN1110, Device, Runner, Tc6}; use embassy_stm32::gpio::{Level, Output, Pull, Speed}; use embassy_stm32::i2c::{self, Config as I2C_Config, I2c}; use embassy_stm32::mode::Async; @@ -53,7 +53,7 @@ pub type SpeSpi = Spi<'static, Async>; pub type SpeSpiCs = ExclusiveDevice, Delay>; pub type SpeInt = exti::ExtiInput<'static>; pub type SpeRst = Output<'static>; -pub type Adin1110T = ADIN1110; +pub type Adin1110T = ADIN1110>; pub type TempSensI2c = I2c<'static, Async, i2c::Master>; static TEMP: AtomicI32 = AtomicI32::new(0); @@ -136,7 +136,7 @@ async fn main(spawner: Spawner) { static STATE: StaticCell> = StaticCell::new(); let state = STATE.init(embassy_net_adin1110::State::<8, 8>::new()); - let (device, runner) = embassy_net_adin1110::new(MAC, state, spe_spi, spe_int, spe_reset_n, true, false).await; + let (device, runner) = embassy_net_adin1110::new_tc6(MAC, state, spe_spi, spe_int, spe_reset_n, false).await; // Start task blink_led // spawner.spawn(unwrap!(heartbeat_led(led_uc2_green))); @@ -265,7 +265,7 @@ async fn temp_task(temp_dev_i2c: TempSensI2c, mut led: Output<'static>) -> ! { } #[embassy_executor::task] -async fn ethernet_task(runner: Runner<'static, SpeSpiCs, SpeInt, SpeRst>) -> ! { +async fn ethernet_task(runner: Runner<'static, Tc6, SpeInt, SpeRst>) -> ! { runner.run().await } From d4731bc10a386e9fff9fe049e524463f1f1eef49 Mon Sep 17 00:00:00 2001 From: Zachary Crockett Date: Sun, 23 Nov 2025 22:29:11 -0800 Subject: [PATCH 12/12] Bringing up the Bristlemouth dev kit - Power on the load switch - Fix clocks - Fix parity calculation - Handle different ADIN model numbers in PHY ID - Check echoed header in OA TC6 SPI responses - Fill out some register bitfields from the datasheet - Some clippy suggestions - Working toward correct ADIN configs --- embassy-net-adin1110/src/lib.rs | 84 ++++++++++--------- embassy-net-adin1110/src/protocol/tc6.rs | 35 ++++---- embassy-net-adin1110/src/regs.rs | 28 +++++++ examples/stm32u575/Cargo.toml | 3 +- .../src/bin/spe_adin1110_http_server.rs | 15 ++-- 5 files changed, 104 insertions(+), 61 deletions(-) diff --git a/embassy-net-adin1110/src/lib.rs b/embassy-net-adin1110/src/lib.rs index cbb021b61e..7e34e96ec2 100644 --- a/embassy-net-adin1110/src/lib.rs +++ b/embassy-net-adin1110/src/lib.rs @@ -22,6 +22,8 @@ mod regs; use ch::driver::LinkState; pub use crc32::ETH_FCS; +#[cfg(feature = "defmt")] +use defmt::Format; use embassy_futures::select::{Either, select}; use embassy_net_driver_channel as ch; use embassy_time::Timer; @@ -41,7 +43,9 @@ use regs::{Config0, Config2, SpiRegisters as sr, Status0, Status1}; use crate::regs::{LedCntrl, LedFunc, LedPol, LedPolarity}; /// ADIN1110 intern PHY ID -pub const PHYID: u32 = 0x0283_BC91; +pub const PHYID_ADIN1110: u32 = 0x0283_BC91; +/// ADIN2111 intern PHY ID +pub const PHYID_ADIN2111: u32 = 0x0283_BCA1; /// Error values ADIN1110 #[derive(Debug)] @@ -54,12 +58,14 @@ pub enum AdinError { FCS, /// SPI Header CRC error SPI_CRC, - /// Received or sended ethernet packet is too big + /// Received or sent ethernet packet is too big PACKET_TOO_BIG, - /// Received or sended ethernet packet is too small + /// Received or sent ethernet packet is too small PACKET_TOO_SMALL, /// MDIO transaction timeout MDIO_ACC_TIMEOUT, + /// The echoed TC6 header does not match what was sent + SPI_TC6_HEADER_MISMATCH, } /// Type alias `Result` type with `AdinError` as error type. @@ -103,6 +109,11 @@ impl State { } } } +impl Default for State { + fn default() -> Self { + Self::new() + } +} /// ADIN1110 embassy-net driver #[derive(Debug)] @@ -291,6 +302,7 @@ impl Runner<'_, P, INT, RST> where ADIN1110

: MdioBus, as MdioBus>::Error: core::fmt::Debug, + P::SpiError: Format, { /// Run the driver. #[allow(clippy::too_many_lines)] @@ -316,12 +328,13 @@ where rx_chan.rx_done(n); } Err(e) => match e { - AdinError::PACKET_TOO_BIG => { - error!("RX Packet too big, DROP"); - self.mac.write_reg(sr::FIFO_CLR, 1).await.unwrap(); - } - AdinError::PACKET_TOO_SMALL => { - error!("RX Packet too small, DROP"); + AdinError::PACKET_TOO_BIG | AdinError::PACKET_TOO_SMALL => { + let size = if matches!(e, AdinError::PACKET_TOO_BIG) { + "big" + } else { + "small" + }; + error!("RX Packet too {}, DROP", size); self.mac.write_reg(sr::FIFO_CLR, 1).await.unwrap(); } AdinError::Spi(e) => { @@ -616,7 +629,7 @@ pub async fn new_tc6 100 { + break; + } + } - mac.write_cl45(MDIO_PHY_ADDR, RegsC45::DA1E::LED_CNTRL.into(), led_cntr.0) - .await - .unwrap(); + // Bristlemouth bm_core initializes each PHY here... possible TODO // Set ADIN1110 Interrupts, RX_READY and LINK_CHANGE // Enable interrupts LINK_CHANGE, TX_RDY, RX_RDY(P1), SPI_ERR @@ -704,6 +709,9 @@ pub async fn new_tc6> 1).count_ones() & 1; + // Calculate odd parity over bits 31:1 + let parity = (val | 1u32).count_ones() & 1; val |= parity; Self(val) @@ -67,8 +67,8 @@ impl Tc6ControlHeader { // LEN = 1 (one DWORD) val |= 1u32 << 8; - // Calculate even parity over bits 31:1 - let parity = (val >> 1).count_ones() & 1; + // Calculate odd parity over bits 31:1 + let parity = (val | 1u32).count_ones() & 1; val |= parity; Self(val) @@ -90,7 +90,7 @@ impl Tc6ControlHeader { /// Bits 10-6: SWO (Start Word Offset) /// Bits 5-1: EV (End Valid - byte offset after frame end) /// Bit 0: EBO (End Byte Offset) -/// Bit 0: P (Parity bit - even parity over bits 31:1) +/// Bit 0: P (Parity bit - odd parity over bits 31:1) #[derive(Debug, Clone, Copy)] struct Tc6DataHeader(u32); @@ -109,8 +109,8 @@ impl Tc6DataHeader { // EV (End Valid) val |= u32::from(ev) << 1; - // Calculate even parity over bits 31:1 - let parity = (val >> 1).count_ones() & 1; + // Calculate odd parity over bits 31:1 + let parity = (val | 1u32).count_ones() & 1; val |= parity; Self(val) @@ -139,7 +139,7 @@ impl Tc6DataFooter { /// Check parity fn check_parity(&self) -> bool { - let parity = (self.0 >> 1).count_ones() & 1; + let parity = (self.0 | 1u32).count_ones() & 1; let expected_parity = self.0 & 1; parity == expected_parity } @@ -161,15 +161,22 @@ impl Adin1110Protocol for Tc6 { let header_bytes = header.to_bytes(); // Prepare buffers - let mut rx_buf = [0u8; 4]; + let mut rx_buf = [0u8; 8]; // Perform SPI transaction: write header, read response let mut ops = [Operation::Write(&header_bytes), Operation::Read(&mut rx_buf)]; self.spi.transaction(&mut ops).await.map_err(AdinError::Spi)?; - // Parse response - let value = u32::from_be_bytes(rx_buf); + // Verify echoed header matches sent header + let echoed_header = u32::from_be_bytes([rx_buf[0], rx_buf[1], rx_buf[2], rx_buf[3]]); + let sent_header = u32::from_be_bytes(header_bytes); + if echoed_header != sent_header { + return Err(AdinError::SPI_TC6_HEADER_MISMATCH); + } + + // Extract register value from bytes 4-7 + let value = u32::from_be_bytes([rx_buf[4], rx_buf[5], rx_buf[6], rx_buf[7]]); trace!("TC6 REG Read {} = {:08x}", addr, value); diff --git a/embassy-net-adin1110/src/regs.rs b/embassy-net-adin1110/src/regs.rs index e15b079a29..d8a3650e35 100644 --- a/embassy-net-adin1110/src/regs.rs +++ b/embassy-net-adin1110/src/regs.rs @@ -121,6 +121,22 @@ bitfield! { pub struct Status1(u32); impl Debug; u32; + /// CRC Mismatch on a Tx Frame on Port 2 + pub p2_txfcse, set_p2_txfcse : 24; + /// Rx MAC Interframe Gap Error + pub p2_rx_ifg_err, set_p2_rx_ifg_err : 23; + /// Transmit Time Stamp Capture Available C + pub p2_ttscac, set_p2_ttscac : 22; + /// Transmit Time Stamp Capture Available B + pub p2_ttscab, set_p2_ttscab : 21; + /// Transmit Time Stamp Capture Available A + pub p2_ttscaa, set_p2_ttscaa : 20; + /// PHY Interrupt for Port 2 + pub p2_phyint, set_p2_phyint : 19; + /// Port 2 Rx Ready High Priority + pub p2_rx_rdy_hi, set_p2_rx_rdy_hi : 18; + /// Port 2 Rx FIFO Contains Data + pub p2_rx_rdy, set_p2_rx_rdy : 17; /// ECC Error on Reading the Frame Size from a Tx FIFO pub tx_ecc_err, set_tx_ecc_err: 12; /// ECC Error on Reading the Frame Size from an Rx FIFO @@ -177,6 +193,18 @@ bitfield! { pub struct Config2(u32); impl Debug; u32; + /// Admit Frames with IFG Errors on Port 2 + pub p2_rcv_ifg_err_frm, set_p2_rcv_ifg_err_frm : 16; + /// Rx Ports Read Order for the OPEN Alliance Data Protocol + pub rx_rd_order, set_rx_rd_order : 15; + /// Forward Frames from Port 2 Not Matching a MAC Address to Port 1 + pub p2_fwd_unk2p1, set_p2_fwd_unk2p1 : 14; + /// Forward Frames from Port 1 Not Matching a MAC Address to Port 2 + pub p1_fwd_unk2p2, set_p1_fwd_unk2p2 : 13; + /// Forward Frames Not Matching Any MAC Address to the Host + pub p2_fwd_unk2host, set_p2_fwd_unk2host : 12; + /// Enable Cut Through from Port to Port + pub port_cut_thru_en, set_port_cut_thru_en : 11; /// Assert TX_RDY When the Tx FIFO is Empty pub tx_rdy_on_empty, set_tx_rdy_on_empty : 8; /// Determines If the SFD is Detected in the PHY or MAC diff --git a/examples/stm32u575/Cargo.toml b/examples/stm32u575/Cargo.toml index 4b0102cf04..8d06201a7a 100644 --- a/examples/stm32u575/Cargo.toml +++ b/examples/stm32u575/Cargo.toml @@ -34,6 +34,7 @@ embassy-usb = { version = "0.5.1", path = "../../embassy-usb", features = [ embassy-futures = { version = "0.1.2", path = "../../embassy-futures" } embassy-net-adin1110 = { version = "0.3.1", path = "../../embassy-net-adin1110", default-features = false, features = [ "tc6", + "defmt", ] } embassy-net = { version = "0.7.1", path = "../../embassy-net", features = [ "defmt", @@ -55,7 +56,7 @@ cortex-m = { version = "0.7.6", features = [ cortex-m-rt = "0.7.0" embedded-hal = "0.2.6" embedded-hal-async = { version = "1.0" } -embedded-hal-bus = { version = "0.1", features = ["async"] } +embedded-hal-bus = { version = "0.1", features = ["async", "defmt-03"] } panic-probe = { version = "1.0.0", features = ["print-defmt"] } heapless = { version = "0.8", default-features = false } embedded-graphics = { version = "0.8.1" } diff --git a/examples/stm32u575/src/bin/spe_adin1110_http_server.rs b/examples/stm32u575/src/bin/spe_adin1110_http_server.rs index bbfbfa65fe..e1223dcc3f 100644 --- a/examples/stm32u575/src/bin/spe_adin1110_http_server.rs +++ b/examples/stm32u575/src/bin/spe_adin1110_http_server.rs @@ -64,14 +64,8 @@ async fn main(spawner: Spawner) { let mut config = embassy_stm32::Config::default(); { - use embassy_stm32::rcc::{ - Hse, HseMode, Hsi48Config, Msirange, Pll, PllDiv, PllMul, PllPreDiv, PllSource, Sysclk, - }; + use embassy_stm32::rcc::{Msirange, Pll, PllDiv, PllMul, PllPreDiv, PllSource, Sysclk}; config.rcc.sys = Sysclk::PLL1_R; - config.rcc.hse = Some(Hse { - freq: Hertz(16_000_000), - mode: HseMode::Oscillator, - }); config.rcc.pll1 = Some(Pll { source: PllSource::MSIS, prediv: PllPreDiv::DIV3, @@ -80,7 +74,6 @@ async fn main(spawner: Spawner) { divq: Some(PllDiv::DIV2), divr: Some(PllDiv::DIV1), }); - config.rcc.hsi48 = Some(Hsi48Config::default()); config.rcc.msis = Some(Msirange::RANGE_48MHZ); } @@ -136,6 +129,12 @@ async fn main(spawner: Spawner) { static STATE: StaticCell> = StaticCell::new(); let state = STATE.init(embassy_net_adin1110::State::<8, 8>::new()); + // On the Bristlemouth dev kit PH1 controls load switches to the ADIN2111 + let mut adin2111_en = Output::new(dp.PH1, Level::Low, Speed::Low); + adin2111_en.set_high(); + // From page 22 of the ADIN2111 datasheet + Timer::after_millis(90).await; + let (device, runner) = embassy_net_adin1110::new_tc6(MAC, state, spe_spi, spe_int, spe_reset_n, false).await; // Start task blink_led