diff --git a/CHANGELOG.md b/CHANGELOG.md index ec2bb13..010d5dd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Add `defmt` features for enabling `defmt::Format` to most structs and errors by [@elpiel](https://github.com/elpiel) ([#39](https://github.com/kellerkindt/w5500/issues/39)) - Fixed an issue where internal function names were conflicting with trait names by [@ryan-summers](https://github.com/ryan-summers) ([#36](https://github.com/kellerkindt/w5500/issues/36)) +- Add `RetryTime` and `RetryCount` common register methods to `Device` and `UninitializedDevice` by [@elpiel](https://github.com/elpiel) ([#54][PR54]) - Add `Udp::get_port` and `Udp::set_port` by [@elpiel](https://github.com/elpiel) ([#57](https://github.com/kellerkindt/w5500/pull/57)) ### Fixed diff --git a/Cargo.toml b/Cargo.toml index 7b7c523..6652d97 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,14 +10,17 @@ license = "MIT OR Apache-2.0" readme = "README.md" edition = "2018" +[features] +no-chip-version-assertion = [] + [dependencies] byteorder = { version = "1.3.4", default-features = false } -embedded-hal = "0.2.4" +embedded-hal = "0.2" embedded-nal = "0.6.0" -bit_field = "0.10.1" +bit_field = "0.10" derive-try-from-primitive = "1" nb = "1.0.0" defmt = { version = "0.3", optional = true } -[features] -no-chip-version-assertion = [] +[dev-dependencies] +embedded-hal-mock = "0.9" \ No newline at end of file diff --git a/src/bus/four_wire.rs b/src/bus/four_wire.rs index f33f5d5..4640191 100644 --- a/src/bus/four_wire.rs +++ b/src/bus/four_wire.rs @@ -29,10 +29,13 @@ impl + Write, ChipSelect: OutputPin> FourWire + Write, ChipSelect: OutputPin> Bus for FourWire { type Error = FourWireError<>::Error, >::Error, ChipSelect::Error>; + fn read_frame(&mut self, block: u8, address: u16, data: &mut [u8]) -> Result<(), Self::Error> { let address_phase = address.to_be_bytes(); let control_phase = block << 3; let data_phase = data; + + // set Chip select to Low, i.e. prepare to receive data self.cs.set_low().map_err(FourWireError::ChipSelectError)?; let result = (|| { self.spi @@ -44,29 +47,39 @@ impl + Write, ChipSelect: OutputPin> Bus for FourWire Result<(), Self::Error> { let address_phase = address.to_be_bytes(); let control_phase = block << 3 | WRITE_MODE_MASK; let data_phase = data; + + // set Chip select to Low, i.e. prepare to transmit self.cs.set_low().map_err(FourWireError::ChipSelectError)?; - let result = (|| { - self.spi - .write(&address_phase) - .and_then(|_| self.spi.write(&[control_phase])) - .and_then(|_| self.spi.write(data_phase)) - .map_err(FourWireError::WriteError)?; - Ok(()) - })(); + let result = self + .spi + .write(&address_phase) + .and_then(|_| self.spi.write(&[control_phase])) + .and_then(|_| self.spi.write(data_phase)) + .map_err(FourWireError::WriteError); + + // set Chip select to High, i.e. we've finished transmitting self.cs.set_high().map_err(FourWireError::ChipSelectError)?; + + // then return the result of the transmission result } } // Must use map_err, ambiguity prevents From from being implemented #[repr(u8)] +#[derive(Clone)] pub enum FourWireError { TransferError(TransferError), WriteError(WriteError), @@ -91,3 +104,90 @@ impl fmt::Debug // TODO Improved error rendering could be done with specialization. // https://github.com/rust-lang/rust/issues/31844 + +#[cfg(test)] +mod test { + use embedded_hal::digital::v2::OutputPin; + use embedded_hal_mock::{ + pin::{Mock as PinMock, State as PinState, Transaction as PinTransaction}, + spi::{Mock as SpiMock, Transaction as SpiTransaction}, + }; + + use crate::{ + bus::{four_wire::WRITE_MODE_MASK, Bus}, + register, + }; + + use super::FourWire; + + #[test] + fn test_read_frame() { + let mut cs_pin = PinMock::new(&[ + // we begin with pin HIGH + PinTransaction::set(PinState::High), + // When reading + PinTransaction::set(PinState::Low), + // When finished reading + PinTransaction::set(PinState::High), + ]); + + // initiate the pin to high. + cs_pin.set_high().expect("Should set pin to high"); + + let mut actual_version = [0_u8; 1]; + let mut expected_version = 5; + + let expectations = [ + SpiTransaction::write(register::common::VERSION.to_be_bytes().to_vec()), + SpiTransaction::write(vec![register::COMMON << 3]), + SpiTransaction::transfer(actual_version.to_vec(), vec![expected_version]), + ]; + + let mock_spi = SpiMock::new(&expectations); + + let mut four_wire = FourWire::new(mock_spi, cs_pin); + + four_wire.read_frame( + register::COMMON, + register::common::VERSION, + &mut actual_version, + ); + + assert_eq!(expected_version, actual_version[0]); + } + + #[test] + fn test_write_frame() { + let mut cs_pin = PinMock::new(&[ + // we begin with pin HIGH + PinTransaction::set(PinState::High), + // When reading + PinTransaction::set(PinState::Low), + // When finished reading + PinTransaction::set(PinState::High), + ]); + + // initiate the pin to high. + cs_pin.set_high().expect("Should set pin to high"); + + let socket_0_reg = 0x01_u8; + let socket_1_reg = 0x05_u8; + let source_port = 49849_u16; + + let expectations = [ + SpiTransaction::write(register::socketn::SOURCE_PORT.to_be_bytes().to_vec()), + SpiTransaction::write(vec![socket_1_reg << 3 | WRITE_MODE_MASK]), + SpiTransaction::write(source_port.to_be_bytes().to_vec()), + ]; + + let mock_spi = SpiMock::new(&expectations); + + let mut four_wire = FourWire::new(mock_spi, cs_pin); + + four_wire.write_frame( + socket_1_reg, + register::socketn::SOURCE_PORT, + &source_port.to_be_bytes(), + ); + } +} diff --git a/src/device.rs b/src/device.rs index bf41a7c..74ec66d 100644 --- a/src/device.rs +++ b/src/device.rs @@ -6,8 +6,13 @@ use crate::host::Host; use crate::net::Ipv4Addr; use crate::socket::Socket; use crate::uninitialized_device::UninitializedDevice; -use crate::{register, MacAddress}; +use crate::{ + register::{self, common::RetryTime}, + MacAddress, Mode, +}; +#[derive(Debug, Clone)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum ResetError { SocketsNotReleased, Other(E), @@ -21,7 +26,7 @@ impl From for ResetError { #[derive(Debug)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub(crate) struct DeviceState { +pub struct DeviceState { host: HostImpl, sockets: [u8; 1], } @@ -42,6 +47,10 @@ impl Device { } } + pub fn get_state(&self) -> &DeviceState { + &self.state + } + pub fn reset(mut self) -> Result, ResetError> { if self.state.sockets != [0b11111111] { Err(ResetError::SocketsNotReleased) @@ -52,11 +61,8 @@ impl Device { } fn clear_mode(&mut self) -> Result<(), SpiBus::Error> { - // reset bit - let mode = [0b10000000]; - self.bus - .write_frame(register::COMMON, register::common::MODE, &mode)?; - Ok(()) + // Set RST common register of the w5500 + self.as_mut().reset_device() } #[inline] @@ -89,6 +95,47 @@ impl Device { self.as_mut().version() } + /// Get the currently set Retry Time-value Register. + /// + /// RTR (Retry Time-value Register) [R/W] [0x0019 – 0x001A] [0x07D0] + #[inline] + pub fn current_retry_timeout(&mut self) -> Result { + self.as_mut().current_retry_timeout() + } + + /// Set a new value for the Retry Time-value Register. + /// + /// RTR (Retry Time-value Register) [R/W] [0x0019 – 0x001A] [0x07D0] + /// + /// # Example + /// + /// ``` + /// use w5500::register::common::RetryTime; + /// + /// let default = RetryTime::from_millis(200); + /// assert_eq!(RetryTime::default(), default); + /// + /// // E.g. 4000 (register) = 400ms + /// let four_hundred_ms = RetryTime::from_millis(400); + /// assert_eq!(four_hundred_ms.to_u16(), 4000); + /// ``` + #[inline] + pub fn set_retry_timeout(&mut self, retry_time_value: RetryTime) -> Result<(), SpiBus::Error> { + self.as_mut().set_retry_timeout(retry_time_value) + } + + /// Get the current Retry Count Register value. + #[inline] + pub fn current_retry_count(&mut self) -> Result { + self.as_mut().current_retry_count() + } + + /// Set a new value for the Retry Count register. + #[inline] + pub fn set_retry_count(&mut self, retry_count: u8) -> Result<(), SpiBus::Error> { + self.as_mut().set_retry_count(retry_count) + } + #[inline] pub(crate) fn as_mut(&mut self) -> DeviceRefMut<'_, BusRef<'_, SpiBus>, HostImpl> { DeviceRefMut { @@ -181,10 +228,116 @@ impl DeviceRefMut<'_, SpiBus, HostImpl> { Ok(phy[0].into()) } - pub fn version(&mut self) -> Result { - let mut version = [0u8]; + #[inline] + pub fn reset_device(&mut self) -> Result<(), SpiBus::Error> { + // Set RST common register of the w5500 + let mode = [0b10000000]; self.bus - .read_frame(register::COMMON, register::common::VERSION, &mut version)?; - Ok(version[0]) + .write_frame(register::COMMON, register::common::MODE, &mode) + } + + #[inline] + pub fn set_mode(&mut self, mode_options: Mode) -> Result<(), SpiBus::Error> { + self.bus.write_frame( + register::COMMON, + register::common::MODE, + &mode_options.to_register(), + ) + } + + #[inline] + pub fn version(&mut self) -> Result { + let mut version_register = [0_u8]; + self.bus.read_frame( + register::COMMON, + register::common::VERSION, + &mut version_register, + )?; + + Ok(version_register[0]) + } + + /// RTR (Retry Time-value Register) [R/W] [0x0019 – 0x001A] [0x07D0] + /// + /// # Example + /// + /// ``` + /// use w5500::register::common::RetryTime; + /// + /// let default = RetryTime::from_millis(200); + /// assert_eq!(RetryTime::default(), default); + /// + /// // E.g. 4000 (register) = 400ms + /// let four_hundred_ms = RetryTime::from_millis(400); + /// assert_eq!(four_hundred_ms.to_u16(), 4000); + /// ``` + #[inline] + pub fn set_retry_timeout(&mut self, retry_time_value: RetryTime) -> Result<(), SpiBus::Error> { + self.bus.write_frame( + register::COMMON, + register::common::RETRY_TIME, + &retry_time_value.to_register(), + )?; + + Ok(()) + } + + /// RTR (Retry Time-value Register) [R/W] [0x0019 – 0x001A] [0x07D0] + /// + /// E.g. 4000 = 400ms + #[inline] + pub fn current_retry_timeout(&mut self) -> Result { + let mut retry_time_register: [u8; 2] = [0, 0]; + self.bus.read_frame( + register::COMMON, + register::common::RETRY_TIME, + &mut retry_time_register, + )?; + + Ok(RetryTime::from_register(retry_time_register)) + } + + /// Set a new value for the Retry Count register. + /// + /// RCR (Retry Count Register) [R/W] [0x001B] [0x08] + /// + /// For more details check out the rest of the datasheet documentation on the Retry count. + /// + /// From datasheet: + /// + /// RCR configures the number of time of retransmission. When retransmission occurs + /// as many as ‘RCR+1’, Timeout interrupt is issued (Sn_IR[TIMEOUT] = ‘1’). + /// + /// The timeout of W5500 can be configurable with RTR and RCR. W5500 has two kind + /// timeout such as Address Resolution Protocol (ARP) and TCP retransmission. + /// + /// E.g. In case of errors it will retry for 7 times: + /// `RCR = 0x0007` + pub fn set_retry_count(&mut self, retry_count: u8) -> Result<(), SpiBus::Error> { + self.bus.write_frame( + register::COMMON, + register::common::RETRY_COUNT, + &[retry_count], + )?; + + Ok(()) + } + + /// Get the current Retry Count value + /// + /// RCR (Retry Count Register) [R/W] [0x001B] [0x08] + /// + /// E.g. In case of errors it will retry for 7 times: + /// `RCR = 0x0007` + #[inline] + pub fn current_retry_count(&mut self) -> Result { + let mut retry_count_register: [u8; 1] = [0]; + self.bus.read_frame( + register::COMMON, + register::common::RETRY_COUNT, + &mut retry_count_register, + )?; + + Ok(retry_count_register[0]) } } diff --git a/src/lib.rs b/src/lib.rs index 475ba9e..124ff6d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,4 @@ -#![no_std] +#![cfg_attr(not(test), no_std)] #![allow(unused)] #![deny(rustdoc::broken_intra_doc_links)] #![doc = include_str!("../README.md")] @@ -15,50 +15,66 @@ pub mod tcp; pub mod udp; mod uninitialized_device; -pub use device::{Device, DeviceRefMut, InactiveDevice}; -pub use host::{Dhcp, Host, HostConfig, Manual}; -pub use net::MacAddress; -pub use uninitialized_device::{InitializeError, UninitializedDevice}; +#[doc(inline)] +pub use self::{ + device::{Device, DeviceRefMut, InactiveDevice}, + host::{Dhcp, Host, HostConfig, Manual}, + net::MacAddress, + uninitialized_device::{InitializeError, UninitializedDevice}, +}; // TODO add better docs to all public items, add unit tests. /// Settings for wake on LAN. Allows the W5500 to optionally emit an interrupt upon receiving a packet #[repr(u8)] -#[derive(Copy, Clone, Debug, PartialOrd, PartialEq)] +#[derive(Copy, Clone, Debug, PartialOrd, PartialEq, Eq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum OnWakeOnLan { InvokeInterrupt = 0b00100000, Ignore = 0b00000000, } +/// Ping Block Mode +/// /// Settings for ping. Allows the W5500 to respond to or ignore network ping requests #[repr(u8)] -#[derive(Copy, Clone, Debug, PartialOrd, PartialEq)] +#[derive(Copy, Clone, Debug, PartialOrd, PartialEq, Eq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum OnPingRequest { + /// 0: Disable Ping block Respond = 0b00000000, + /// 1 : Enable Ping block + /// + /// If the bit is ‘1’, it blocks the response to a ping request. Ignore = 0b00010000, } /// Use [ConnectionType::PPoE] when talking /// to an ADSL modem. Otherwise use [ConnectionType::Ethernet] #[repr(u8)] -#[derive(Copy, Clone, Debug, PartialOrd, PartialEq)] +#[derive(Copy, Clone, Debug, PartialOrd, PartialEq, Eq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum ConnectionType { PPoE = 0b00001000, Ethernet = 0b00000000, } -#[derive(Copy, Clone, Debug, PartialOrd, PartialEq)] +/// Force ARP +/// +#[derive(Copy, Clone, Debug, PartialOrd, PartialEq, Eq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] #[repr(u8)] pub enum ArpResponses { + /// 0 : Disable Force ARP mode Cache = 0b00000000, + /// 1 : Enable Force ARP mode + /// + /// In Force ARP mode, It forces on sending ARP Request whenever data is + /// sent. DropAfterUse = 0b00000010, } -#[derive(Copy, Clone, Debug, PartialEq)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct Mode { pub on_wake_on_lan: OnWakeOnLan, @@ -67,6 +83,22 @@ pub struct Mode { pub arp_responses: ArpResponses, } +impl Mode { + pub fn to_register(self) -> [u8; 1] { + [self.to_u8()] + } + + pub fn to_u8(self) -> u8 { + let mut register = 0; + register |= self.on_wake_on_lan as u8; + register |= self.on_ping_request as u8; + register |= self.connection_type as u8; + register |= self.arp_responses as u8; + + register + } +} + impl Default for Mode { fn default() -> Self { Self { @@ -77,3 +109,43 @@ impl Default for Mode { } } } + +#[cfg(test)] +mod test { + use crate::Mode; + + #[test] + fn test_mode_register() { + let ping_respond_and_force_arp = Mode { + // Bit: 7 Reset (RST) should be 0 + // Bit: 6 reserved + // Bit: 5 should be 0 - Disable WOL mode + on_wake_on_lan: crate::OnWakeOnLan::Ignore, + // Bit: 4 should be 0 - Disable Ping Block Mode + on_ping_request: crate::OnPingRequest::Respond, + // Bit: 3 should be 0 - PPoE disabled + connection_type: crate::ConnectionType::Ethernet, + // Bit: 2 reserved + // Bit: 1 should be 0 - Disabled Force ARP + arp_responses: crate::ArpResponses::Cache, + // Bit: 0 reserved + }; + assert_eq!(0b0000_0000, ping_respond_and_force_arp.to_u8()); + + let all_enabled = Mode { + // Bit: 7 Reset (RST) should be 0 + // Bit: 6 reserved + // Bit: 5 should be 1 - Enable WOL mode + on_wake_on_lan: crate::OnWakeOnLan::InvokeInterrupt, + // Bit: 4 should be 0 - Disable Ping Block Mode + on_ping_request: crate::OnPingRequest::Respond, + // Bit: 3 should be 1 - PPoE enable + connection_type: crate::ConnectionType::PPoE, + // Bit: 2 reserved + // Bit: 1 should be 1 - Enable Force ARP + arp_responses: crate::ArpResponses::DropAfterUse, + // Bit: 0 reserved + }; + assert_eq!(0b0010_1010, all_enabled.to_u8()); + } +} diff --git a/src/register.rs b/src/register.rs index d1bfef2..3a1acfa 100644 --- a/src/register.rs +++ b/src/register.rs @@ -6,13 +6,84 @@ pub mod common { use bit_field::BitArray; pub const MODE: u16 = 0x0; + + /// Register: GAR (Gateway IP Address Register) [R/W] [0x0001 – 0x0004] [0x00] pub const GATEWAY: u16 = 0x01; + + /// Register: SUBR (Subnet Mask Register) [R/W] [0x0005 – 0x0008] [0x00] pub const SUBNET_MASK: u16 = 0x05; + + /// Register: SHAR (Source Hardware Address Register) [R/W] [0x0009 – 0x000E] [0x00] pub const MAC: u16 = 0x09; + + /// Register: SIPR (Source IP Address Register) [R/W] [0x000F – 0x0012] [0x00] pub const IP: u16 = 0x0F; + + /// Register: INTLEVEL (Interrupt Low Level Timer Register) [R/W] [0x0013 – 0x0014] [0x0000] + pub const INTERRUPT_TIMER: u16 = 0x13; + + /// Register: RTR (Retry Time-value Register) [R/W] [0x0019 – 0x001A] [0x07D0] + pub const RETRY_TIME: u16 = 0x19; + + /// Register: RCR (Retry Count Register) [R/W] [0x001B] [0x08] + pub const RETRY_COUNT: u16 = 0x1B; + pub const PHY_CONFIG: u16 = 0x2E; pub const VERSION: u16 = 0x39; + /// A Retry Time-value + /// + /// RTR (Retry Time-value Register) [R/W] [0x0019 – 0x001A] [0x07D0] + /// + /// From datasheet: + /// + /// RTR configures the retransmission timeout period. The unit of timeout period is + /// 100us and the default of RTR is ‘0x07D0’ or ‘2000’. And so the default timeout period + /// is 200ms(100us X 2000). + /// During the time configured by RTR, W5500 waits for the peer response to the packet + /// that is transmitted by Sn_CR(CONNECT, DISCON, CLOSE, SEND, SEND_MAC, SEND_KEEP + /// command). If the peer does not respond within the RTR time, W5500 retransmits the + /// packet or issues timeout. + /// + /// + /// > Ex) When timeout-period is set as 400ms, RTR = (400ms / 1ms) X 10 = 4000(0x0FA0) + #[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub struct RetryTime(pub(crate) u16); + + impl RetryTime { + #[inline] + pub fn to_u16(&self) -> u16 { + self.0 + } + + #[inline] + pub fn to_register(&self) -> [u8; 2] { + self.0.to_be_bytes() + } + + #[inline] + pub fn from_register(register: [u8; 2]) -> Self { + Self(u16::from_be_bytes(register)) + } + + #[inline] + pub fn from_millis(milliseconds: u16) -> Self { + Self(milliseconds * 10) + } + + #[inline] + pub fn to_millis(&self) -> u16 { + self.0 / 10 + } + } + + impl Default for RetryTime { + fn default() -> Self { + Self::from_millis(200) + } + } + #[derive(Default, Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)] #[repr(u8)] pub enum PhyOperationMode { @@ -156,7 +227,10 @@ pub const SOCKET7_BUFFER_RX: u8 = 0b000_11111; pub mod socketn { use derive_try_from_primitive::TryFromPrimitive; + /// The Protocol mode pub const MODE: u16 = 0x00; + + /// The protocol modes that can be used with the `w5500` #[repr(u8)] pub enum Protocol { Closed = 0b00, @@ -164,33 +238,100 @@ pub mod socketn { Udp = 0b10, MacRaw = 0b100, } + + /// Socket n Command Register + /// + /// `Sn_CR` pub const COMMAND: u16 = 0x01; + + /// Socket n Commands + /// + /// `Sn_CR` register #[repr(u8)] pub enum Command { Open = 0x01, + /// [Datasheet page 46](https://docs.wiznet.io/img/products/w5500/W5500_ds_v110e.pdf): + /// + /// > This is valid only in TCP mode (Sn_MR(P3:P0) = Sn_MR_TCP). In this + /// > mode, Socket n operates as a ‘TCP server’ and waits for connection- + /// > request (SYN packet) from any ‘TCP client Listen = 0x02, Connect = 0x04, Discon = 0x08, Close = 0x10, Send = 0x20, + + /// [Datasheet page 48](https://docs.wiznet.io/img/products/w5500/W5500_ds_v110e.pdf): + /// + /// > RECV completes the processing of the received data in Socket n RX + /// > Buffer by using a RX read pointer register (Sn_RX_RD). + /// > For more details, refer to Socket n RX Received Size Register + /// > (Sn_RX_RSR), Socket n RX Write Pointer Register (Sn_RX_WR), and + /// > Socket n RX Read Pointer Register (Sn_RX_RD). Receive = 0x40, } pub const INTERRUPT: u16 = 0x02; + /// | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | + /// | Reserved | Reserved | Reserved | SEND_OK | TIMEOUT | RECV | DISCON | CON | + /// + /// | Bit | Symbol | Description | + /// | 7~5 | Reserved | Reserved | + /// | 4 | SENDOK | Sn_IR(SENDOK) | Interrupt Mask | + /// | 3 | TIMEOUT | Sn_IR(TIMEOUT) | Interrupt Mask | + /// | 2 | RECV | Sn_IR(RECV) | Interrupt Mask | + /// | 1 | DISCON | Sn_IR(DISCON) | Interrupt Mask | + /// | 0 | CON | Sn_IR(CON) | Interrupt Mask | #[repr(u8)] pub enum Interrupt { All = 0b11111111u8, - SendOk = 0b010000u8, - Timeout = 0b01000u8, - Receive = 0b00100u8, + SendOk = 0b10000u8, + Timeout = 0b1000u8, + + /// Receive data + /// + /// bit 2, symbol `RECV`, `Sn_IR(RECV) Interrupt Mask` + Receive = 0b100u8, + + /// Disconnect + /// + /// bit 1, symbol `DISCON`, `Sn_IR(DISCON) Interrupt Mask` + Disconnect = 0b10u8, + + /// Connect + /// + /// bit 0, symbol `CON`, `Sn_IR(CON) Interrupt Mask` + Connect = 0b1u8, } pub const STATUS: u16 = 0x03; + + /// Socket status register + /// + /// `W5500 Datasheet Version 1.1.0` page 49: + /// + /// > Sn_SR (Socket n Status Register) [R] [0x0003] [0x00] + /// + /// - 0x18 SOCK_FIN_WAIT + /// - 0x1A SOCK_CLOSING + /// - 0X1B SOCK_TIME_WAIT + /// > These indicate Socket n is closing. + /// > These are shown in disconnect-process such as active-close + /// > and passive-close. + /// > When Disconnect-process is successfully completed, or + /// > when timeout occurs, these change to SOCK_CLOSED. + /// + #[derive(TryFromPrimitive, Debug, Copy, Clone, PartialEq, Eq)] #[repr(u8)] - #[derive(TryFromPrimitive)] pub enum Status { Closed = 0x00, Init = 0x13, + + /// [Datasheet page 49](https://docs.wiznet.io/img/products/w5500/W5500_ds_v110e.pdf): + /// + /// > This indicates Socket n is operating as ‘TCP server’ mode and + /// > waiting for connection-request (SYN packet) from a peer + /// > (‘TCP client’). Listen = 0x14, Established = 0x17, CloseWait = 0x1c, @@ -206,6 +347,19 @@ pub mod socketn { LastAck = 0x1d, } + #[cfg(feature = "defmt")] + impl defmt::Format for Status { + fn format(&self, fmt: defmt::Formatter) { + // Format as hexadecimal. + defmt::write!( + fmt, + "Status::{} ({=u8:#x})", + defmt::Debug2Format(self), + *self as u8 + ); + } + } + pub const SOURCE_PORT: u16 = 0x04; pub const DESTINATION_IP: u16 = 0x0C; @@ -214,17 +368,87 @@ pub mod socketn { pub const RXBUF_SIZE: u16 = 0x1E; + /// Socket n TX Buffer Size Register + /// + /// `Sn_TXBUF_SIZE` + /// + /// From datasheet: + /// + /// > .. can be configured with 1,2,4,8, and 16 Kbytes. + /// > + /// > Although Socket n TX Buffer Block size is initially configured to 2Kbytes, user can + /// > be re-configure its size using Sn_TXBUF_SIZE. The total sum of Sn_TXBUF_SIZE + /// > cannot be exceed 16Kbytes. When exceeded, the data transmission error is + /// > occurred. pub const TXBUF_SIZE: u16 = 0x1F; + /// TX Free Size Register + /// + /// `Sn_TX_FSR` + /// + /// Socket n TX Free Size + /// + /// offset (register) + /// 0x0020 (Sn_TX_FSR0) + /// 0x0021 (Sn_TX_FSR1) pub const TX_FREE_SIZE: u16 = 0x20; + /// Socket n TX Read Pointer + /// + /// offset (register) + /// 0x0022 (Sn_TX_RD0) + /// 0x0023 (Sn_TX_RD1) pub const TX_DATA_READ_POINTER: u16 = 0x22; + /// Socket n TX Write Pointer + /// + /// offset (register) + /// 0x0024 (Sn_TX_WR0) + /// 0x0025 (Sn_TX_WR1) + /// + /// [Datasheet page 54](https://docs.wiznet.io/img/products/w5500/W5500_ds_v110e.pdf): + /// + /// > Sn_TX_WR (Socket n TX Write Pointer Register) [R/W] [0x0024-0x0025] [0x0000] + /// > + /// > Sn_TX_WR is initialized by OPEN command. However, if Sn_MR(P[3:0]) is TCP + /// > mode(‘0001’), it is re-initialized while connecting with TCP. + /// > It should be read or to be updated like as follows. + /// > 1. Read the starting address for saving the transmitting data. + /// > 2. Save the transmitting data from the starting address of Socket n TX + /// > buffer. + /// > 3. After saving the transmitting data, update Sn_TX_WR to the + /// > increased value as many as transmitting data size. If the increment value + /// > exceeds the maximum value 0xFFFF(greater than 0x10000 and the carry + /// > bit occurs), then the carry bit is ignored and will automatically update + /// > with the lower 16bits value. + /// > 4. Transmit the saved data in Socket n TX Buffer by using SEND/SEND + /// command pub const TX_DATA_WRITE_POINTER: u16 = 0x24; + /// Socket n Received Size Register + /// + /// `Sn_RX_RSR` pub const RECEIVED_SIZE: u16 = 0x26; pub const RX_DATA_READ_POINTER: u16 = 0x28; + /// Socket n Interrupt Mask + /// + /// offset (register) + /// 0x002C (Sn_IMR) pub const INTERRUPT_MASK: u16 = 0x2C; + + #[cfg(test)] + mod tests { + use core::convert::TryFrom; + + use super::Status; + + #[test] + fn test_status_from_byte() { + let udp = 0x22_u8; + let status = Status::try_from(udp).expect("Should parse to Status"); + assert_eq!(status, Status::Udp); + } + } } diff --git a/src/socket.rs b/src/socket.rs index 2a9d794..98dc8a1 100644 --- a/src/socket.rs +++ b/src/socket.rs @@ -12,6 +12,7 @@ pub struct Socket { } impl Socket { + /// 8 sockets available on the w5500 pub fn new(index: u8) -> Self { /* * Socket 0 is at address 0x01 @@ -75,6 +76,7 @@ impl Socket { ) -> Result { let mut data = [0u8]; bus.read_frame(self.register(), socketn::INTERRUPT, &mut data)?; + Ok(data[0] & code as u8 != 0) } @@ -179,9 +181,11 @@ impl Socket { Ok(()) } + /// Get the received bytes size of the socket's RX buffer. + /// + /// Section 4.2 of datasheet, Sn_TX_FSR address docs indicate that read must be repeated until two sequential reads are stable pub fn get_receive_size(&self, bus: &mut SpiBus) -> Result { loop { - // Section 4.2 of datasheet, Sn_TX_FSR address docs indicate that read must be repeated until two sequential reads are stable let mut sample_0 = [0u8; 2]; bus.read_frame(self.register(), socketn::RECEIVED_SIZE, &mut sample_0)?; let mut sample_1 = [0u8; 2]; @@ -192,9 +196,94 @@ impl Socket { } } + /// Get the free TX buffer size still available for this socket. + /// + /// It's cleared once we `SEND` the buffer over the socket. pub fn get_tx_free_size(&self, bus: &mut SpiBus) -> Result { let mut data = [0; 2]; bus.read_frame(self.register(), socketn::TX_FREE_SIZE, &mut data)?; Ok(u16::from_be_bytes(data)) } } + +#[cfg(test)] +mod test { + use crate::register::*; + + use super::*; + + #[test] + fn test_socket_registers() { + // Socket 0 + { + let socket_0 = Socket::new(0); + + assert_eq!(socket_0.register, SOCKET0); + assert_eq!(socket_0.tx_buffer, SOCKET0_BUFFER_TX); + assert_eq!(socket_0.rx_buffer, SOCKET0_BUFFER_RX); + } + + // Socket 1 + { + let socket_1 = Socket::new(1); + + assert_eq!(socket_1.register, SOCKET1); + assert_eq!(socket_1.tx_buffer, SOCKET1_BUFFER_TX); + assert_eq!(socket_1.rx_buffer, SOCKET1_BUFFER_RX); + } + + // Socket 2 + { + let socket_2 = Socket::new(2); + + assert_eq!(socket_2.register, SOCKET2); + assert_eq!(socket_2.tx_buffer, SOCKET2_BUFFER_TX); + assert_eq!(socket_2.rx_buffer, SOCKET2_BUFFER_RX); + } + + // Socket 3 + { + let socket_3 = Socket::new(3); + + assert_eq!(socket_3.register, SOCKET3); + assert_eq!(socket_3.tx_buffer, SOCKET3_BUFFER_TX); + assert_eq!(socket_3.rx_buffer, SOCKET3_BUFFER_RX); + } + + // Socket 4 + { + let socket_4 = Socket::new(4); + + assert_eq!(socket_4.register, SOCKET4); + assert_eq!(socket_4.tx_buffer, SOCKET4_BUFFER_TX); + assert_eq!(socket_4.rx_buffer, SOCKET4_BUFFER_RX); + } + + // Socket 5 + { + let socket_5 = Socket::new(5); + + assert_eq!(socket_5.register, SOCKET5); + assert_eq!(socket_5.tx_buffer, SOCKET5_BUFFER_TX); + assert_eq!(socket_5.rx_buffer, SOCKET5_BUFFER_RX); + } + + // Socket 6 + { + let socket_6 = Socket::new(6); + + assert_eq!(socket_6.register, SOCKET6); + assert_eq!(socket_6.tx_buffer, SOCKET6_BUFFER_TX); + assert_eq!(socket_6.rx_buffer, SOCKET6_BUFFER_RX); + } + + // Socket 7 + { + let socket_7 = Socket::new(7); + + assert_eq!(socket_7.register, SOCKET7); + assert_eq!(socket_7.tx_buffer, SOCKET7_BUFFER_TX); + assert_eq!(socket_7.rx_buffer, SOCKET7_BUFFER_RX); + } + } +} diff --git a/src/uninitialized_device.rs b/src/uninitialized_device.rs index ae230a3..e6a7c2b 100644 --- a/src/uninitialized_device.rs +++ b/src/uninitialized_device.rs @@ -6,8 +6,10 @@ use crate::bus::{Bus, FourWire, ThreeWire}; use crate::device::Device; use crate::host::{Dhcp, Host, Manual}; use crate::raw_device::RawDevice; -use crate::register; -use crate::{MacAddress, Mode}; +use crate::{ + register::{self, common::RetryTime}, + MacAddress, Mode, +}; #[derive(Debug)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] @@ -58,6 +60,9 @@ impl UninitializedDevice { self.initialize_with_host(host, mode_options) } + /// The gateway overrides the passed `ip` ([`Ip4Addr`]) to end with `.1`. + /// + /// E.g. `let ip = "192.168.0.201".parse::()` will become a device with a gateway `192.168.0.1`. pub fn initialize_manual( self, mac: MacAddress, @@ -92,9 +97,7 @@ impl UninitializedDevice { self.assert_chip_version(0x4)?; // RESET - let mode = [0b10000000]; - self.bus - .write_frame(register::COMMON, register::common::MODE, &mode)?; + self.reset()?; self.set_mode(mode_options)?; host.refresh(&mut self.bus)?; @@ -115,31 +118,131 @@ impl UninitializedDevice { RawDevice::new(self.bus) } + /// Reset the device + #[inline] + pub fn reset(&mut self) -> Result<(), SpiBus::Error> { + // Set RST common register of the w5500 + let mode = [0b10000000]; + self.bus + .write_frame(register::COMMON, register::common::MODE, &mode) + } + #[inline] + pub fn set_mode(&mut self, mode_options: Mode) -> Result<(), SpiBus::Error> { + self.bus.write_frame( + register::COMMON, + register::common::MODE, + &mode_options.to_register(), + ) + } + + #[inline] + pub fn version(&mut self) -> Result { + let mut version_register = [0_u8]; + self.bus.read_frame( + register::COMMON, + register::common::VERSION, + &mut version_register, + )?; + + Ok(version_register[0]) + } + + /// RTR (Retry Time-value Register) [R/W] [0x0019 – 0x001A] [0x07D0] + /// + /// # Example + /// + /// ``` + /// use w5500::register::common::RetryTime; + /// + /// let default = RetryTime::from_millis(200); + /// assert_eq!(RetryTime::default(), default); + /// + /// // E.g. 4000 (register) = 400ms + /// let four_hundred_ms = RetryTime::from_millis(400); + /// assert_eq!(four_hundred_ms.to_u16(), 4000); + /// ``` + #[inline] + pub fn set_retry_timeout(&mut self, retry_time_value: RetryTime) -> Result<(), SpiBus::Error> { + self.bus.write_frame( + register::COMMON, + register::common::RETRY_TIME, + &retry_time_value.to_register(), + )?; + + Ok(()) + } + + /// RTR (Retry Time-value Register) [R/W] [0x0019 – 0x001A] [0x07D0] + /// + /// E.g. 4000 = 400ms + #[inline] + pub fn current_retry_timeout(&mut self) -> Result { + let mut retry_time_register: [u8; 2] = [0, 0]; + self.bus.read_frame( + register::COMMON, + register::common::RETRY_TIME, + &mut retry_time_register, + )?; + + Ok(RetryTime::from_register(retry_time_register)) + } + + /// Set a new value for the Retry Count register. + /// + /// RCR (Retry Count Register) [R/W] [0x001B] [0x08] + /// + /// For more details check out the rest of the datasheet documentation on the Retry count. + /// + /// From datasheet: + /// + /// RCR configures the number of time of retransmission. When retransmission occurs + /// as many as ‘RCR+1’, Timeout interrupt is issued (Sn_IR[TIMEOUT] = ‘1’). + /// + /// The timeout of W5500 can be configurable with RTR and RCR. W5500 has two kind + /// timeout such as Address Resolution Protocol (ARP) and TCP retransmission. + /// + /// E.g. In case of errors it will retry for 7 times: + /// `RCR = 0x0007` + pub fn set_retry_count(&mut self, retry_count: u8) -> Result<(), SpiBus::Error> { + self.bus.write_frame( + register::COMMON, + register::common::RETRY_COUNT, + &[retry_count], + )?; + + Ok(()) + } + + /// Get the current Retry Count value + /// RCR (Retry Count Register) [R/W] [0x001B] [0x08] + /// + /// E.g. In case of errors it will retry for 7 times: + /// `RCR = 0x0007` + #[inline] + pub fn current_retry_count(&mut self) -> Result { + let mut retry_count_register: [u8; 1] = [0]; + self.bus.read_frame( + register::COMMON, + register::common::RETRY_COUNT, + &mut retry_count_register, + )?; + + Ok(retry_count_register[0]) + } + #[cfg(not(feature = "no-chip-version-assertion"))] fn assert_chip_version( &mut self, expected_version: u8, ) -> Result<(), InitializeError> { - let mut version = [0]; - self.bus - .read_frame(register::COMMON, register::common::VERSION, &mut version)?; - if version[0] != expected_version { + let version = self.version()?; + + if version != expected_version { Err(InitializeError::ChipNotConnected) } else { Ok(()) } } - - fn set_mode(&mut self, mode_options: Mode) -> Result<(), SpiBus::Error> { - let mut mode = [0]; - mode[0] |= mode_options.on_wake_on_lan as u8; - mode[0] |= mode_options.on_ping_request as u8; - mode[0] |= mode_options.connection_type as u8; - mode[0] |= mode_options.arp_responses as u8; - self.bus - .write_frame(register::COMMON, register::common::MODE, &mode)?; - Ok(()) - } } impl + Write, ChipSelect: OutputPin>