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/Cargo.toml b/embassy-net-adin1110/Cargo.toml index a5655870f5..4d7bd1f05b 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,21 @@ 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 = [] +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..8fab9c0f62 100644 --- a/embassy-net-adin1110/README.md +++ b/embassy-net-adin1110/README.md @@ -17,14 +17,14 @@ 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 -`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/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 90ac242bde..cbb021b61e 100644 --- a/embassy-net-adin1110/src/lib.rs +++ b/embassy-net-adin1110/src/lib.rs @@ -11,28 +11,34 @@ mod fmt; mod crc32; + +#[cfg(feature = "generic-spi")] 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; +#[cfg(feature = "tc6")] +pub use protocol::Tc6; 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; @@ -75,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; @@ -111,95 +106,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 +143,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 +178,57 @@ impl ADIN1110 { } } -impl mdio::MdioBus for ADIN1110 { +#[cfg(feature = "generic-spi")] +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(|_| ()) + } +} + +#[cfg(feature = "tc6")] +impl mdio::MdioBus for ADIN1110> { type Error = AdinError; /// Read from the PHY Registers as Clause 22. @@ -439,162 +279,183 @@ 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 Runner<'_, 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) -> ! { + 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(); - } - } - 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"); + }, + Either::Second(frame) => { + // Handle frames that needs to transmit to the wire. + self.mac.write_fifo(frame).await.unwrap(); + tx_chan.tx_done(); } - - 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); + } + + 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); + } - // Clear status0 - self.mac.write_reg(sr::STATUS0, 0xFFF).await.unwrap(); - self.mac.write_reg(sr::STATUS1, status1_clr.0).await.unwrap(); + if status1.rx_ecc_err() { + error!("SPI RX_ECC_ERR error"); + status1_clr.set_rx_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.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(); } } } } } +#[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) + } +} + +#[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( mac_addr: [u8; 6], state: &'_ mut State, @@ -603,7 +464,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 +481,7 @@ 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 @@ -736,6 +728,8 @@ mod tests { use embedded_hal_mock::common::Generic; use embedded_hal_mock::eh1::spi::{Mock as SpiMock, Transaction as SpiTransaction}; + use crate::crc8::crc8; + #[derive(Debug, Default)] struct CsPinMock { pub high: u32, @@ -777,7 +771,9 @@ mod tests { } struct TestHarnass { - spe: ADIN1110>, CsPinMock, MockDelay>>, + spe: ADIN1110< + GenericSpi>, CsPinMock, MockDelay>>, + >, spi: Generic>, } @@ -788,9 +784,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..1d88b68939 --- /dev/null +++ b/embassy-net-adin1110/src/protocol/generic_spi.rs @@ -0,0 +1,262 @@ +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_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, + 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..7346e35a01 --- /dev/null +++ b/embassy-net-adin1110/src/protocol/mod.rs @@ -0,0 +1,36 @@ +//! 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; + +#[cfg(feature = "tc6")] +mod tc6; +#[cfg(feature = "tc6")] +pub use tc6::Tc6; + +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>; +} diff --git a/embassy-net-adin1110/src/protocol/tc6.rs b/embassy-net-adin1110/src/protocol/tc6.rs new file mode 100644 index 0000000000..ea244023be --- /dev/null +++ b/embassy-net-adin1110/src/protocol/tc6.rs @@ -0,0 +1,346 @@ +use embedded_hal_async::spi::{Operation, SpiDevice}; + +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 + } + + /// 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(()) + } +} 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/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 } diff --git a/examples/stm32u575/.cargo/config.toml b/examples/stm32u575/.cargo/config.toml new file mode 100644 index 0000000000..fd5eed3f4f --- /dev/null +++ b/examples/stm32u575/.cargo/config.toml @@ -0,0 +1,9 @@ +[target.'cfg(all(target_arch = "arm", target_os = "none"))'] +# replace STM32U575CIUxQ 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..4b0102cf04 --- /dev/null +++ b/examples/stm32u575/Cargo.toml @@ -0,0 +1,78 @@ +[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", default-features = false, features = [ + "tc6", +] } +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" +pca9535 = "2.0.0" + +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..57c2a8325e --- /dev/null +++ b/examples/stm32u575/memory.x @@ -0,0 +1,5 @@ +MEMORY +{ + FLASH : ORIGIN = 0x08000000, LENGTH = 2048K + 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 new file mode 100644 index 0000000000..41f1b79b86 --- /dev/null +++ b/examples/stm32u575/src/bin/blinky.rs @@ -0,0 +1,45 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::i2c::{self, Config as I2C_Config, I2c}; +use embassy_stm32::{bind_interrupts, peripherals}; +use embassy_time::Timer; +use pca9535::{GPIOBank, Pca9535Immediate, StandardExpanderInterface}; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + I2C1_EV => i2c::EventInterruptHandler; + I2C1_ER => i2c::ErrorInterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) -> ! { + let dp = embassy_stm32::init(Default::default()); + info!("Hello World!"); + + 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!"); + expander.pin_set_low(GPIOBank::Bank1, 2).unwrap(); + Timer::after_millis(200).await; + + defmt::info!("off!"); + expander.pin_set_high(GPIOBank::Bank1, 2).unwrap(); + Timer::after_millis(200).await; + } +} diff --git a/examples/stm32u5/src/bin/boot.rs b/examples/stm32u575/src/bin/boot.rs similarity index 100% rename from examples/stm32u5/src/bin/boot.rs rename to examples/stm32u575/src/bin/boot.rs diff --git a/examples/stm32u5/src/bin/flash.rs b/examples/stm32u575/src/bin/flash.rs similarity index 100% rename from examples/stm32u5/src/bin/flash.rs rename to examples/stm32u575/src/bin/flash.rs diff --git a/examples/stm32u5/src/bin/rng.rs b/examples/stm32u575/src/bin/rng.rs similarity index 100% rename from examples/stm32u5/src/bin/rng.rs rename to examples/stm32u575/src/bin/rng.rs 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..bbfbfa65fe --- /dev/null +++ b/examples/stm32u575/src/bin/spe_adin1110_http_server.rs @@ -0,0 +1,389 @@ +#![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, Tc6}; +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 pca9535::{GPIOBank, Pca9535Immediate, StandardExpanderInterface}; +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: Some(PllDiv::DIV1), + }); + 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 I2C pins + let i2c_bus = I2c::new( + dp.I2C1, + dp.PB6, + dp.PB7, + Irqs, + dp.GPDMA1_CH9, + dp.GPDMA1_CH8, + 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); + 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_tc6(MAC, state, spe_spi, spe_int, spe_reset_n, 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; + } + }; + expander.pin_set_low(GPIOBank::Bank1, 2).unwrap(); + + 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; + } + + expander.pin_set_high(GPIOBank::Bank1, 2).unwrap(); + } + } +} + +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, Tc6, 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 100% rename from examples/stm32u5/Cargo.toml rename to examples/stm32u5g9/Cargo.toml 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/stm32u5g9/src/bin/boot.rs b/examples/stm32u5g9/src/bin/boot.rs new file mode 100644 index 0000000000..23c7f8b22c --- /dev/null +++ b/examples/stm32u5g9/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/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/stm32u5g9/src/bin/flash.rs b/examples/stm32u5g9/src/bin/flash.rs new file mode 100644 index 0000000000..e4fd6bb9c7 --- /dev/null +++ b/examples/stm32u5g9/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/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/stm32u5g9/src/bin/rng.rs b/examples/stm32u5g9/src/bin/rng.rs new file mode 100644 index 0000000000..3a5bce0970 --- /dev/null +++ b/examples/stm32u5g9/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/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