From b9aa4f9ca1dbd4e64d137418c766aa7911e7251f Mon Sep 17 00:00:00 2001 From: Jonah Dahlquist Date: Fri, 2 Aug 2019 10:15:26 -0500 Subject: [PATCH] Improved documentation in README and doc comments in library --- README.md | 91 +++++++++++++++++++++++++++++++++++++----------------- src/lib.rs | 63 ++++++++++++++++++++++++++++++++----- 2 files changed, 119 insertions(+), 35 deletions(-) diff --git a/README.md b/README.md index 67cf663..779b751 100644 --- a/README.md +++ b/README.md @@ -1,47 +1,82 @@ -# Example usage +# W5500 Driver -Below some really basic usage how I am ca using it: +This crate is a driver for the WIZnet W5500 chips. The W5500 chip is a hardwired TCP/IP embedded Ethernet controller +that enables easier internet connection for embedded systems using SPI (Serial Peripheral Interface). It is one of the +more popular platforms for Ethernet modules on Arduino platforms. + +## Embedded-HAL + +Embedded-HAL is a standard set of traits meant to permit communication between MCU implementations and hardware drivers +like this one. Any microcontroller that implements the +[`spi::FullDuplex`](https://docs.rs/embedded-hal/0.2.3/embedded_hal/spi/trait.FullDuplex.html) interface can use +this driver. + +## Implementation + +This driver is built in several layers of structs. + +The lowest level (and the first a program would instantiate) is the `W5500` struct. It contains a reference to the +chip-select [pin](https://docs.rs/embedded-hal/0.2.3/embedded_hal/digital/v2/trait.OutputPin.html). + +The next layer is the `ActiveW5500` struct. It contains a reference to a `W5500` instance, and an implementation of +the [`spi::FullDuplex`](https://docs.rs/embedded-hal/0.2.3/embedded_hal/spi/trait.FullDuplex.html) trait. It has +the ability to actually communicate with the chip. It has general methods for reading/writing to the chip, and +higher-level functions that can set up specific configuration, like the MAC address, etc. + +The last layer is the network protocol. Currently that is only `Udp`. `Udp` is a tuple struct made up of an +`ActiveW5500` and a `Socket`. This last layer can be used to send and receive UDP packets over the network via the +`receive` and `blocking_send` methods. + +# Example Usage + +Below is a basic example of listening for UDP packets and replying. An important thing to confirm is the configuration +of the SPI implementation. It must be set up to work as the W5500 chip requires. That configuration is as follows: + +* Data Order: Most significant bit first +* Clock Polarity: Idle low +* Clock Phase: Sample leading edge +* Clock speed: 33MHz maximum ```rust let mut spi = ...; // SPI interface to use let mut cs_w5500 : OutputPin = ...; // chip select - let mut w5500: Option = W5500::with_initialisation( + let mut w5500 = W5500::with_initialisation( &mut cs_w5500, // borrowed for whole W5500 lifetime &mut spi, // borrowed for call to `with_initialisation` only OnWakeOnLan::Ignore, OnPingRequest::Respond, ConnectionType::Ethernet, ArpResponses::Cache, - ) - .ok(); + ).unwrap(); - if let Some(ref mut w5500) = w5500 { - let mut w5500: ActiveW5500<_> = w5500.activate(&mut spi).unwrap(); - // using a 'locally administered' MAC address - active.set_mac(MacAddress::new(0x02, 0x01, 0x02, 0x03, 0x04, 0x05)).unwrap(); - active.set_ip(IpAddress::new(192, 168, 0, 222)).unwrap(); - active.set_subnet(IpAddress::new(255, 255, 255, 0)).unwrap(); - active.set_gateway(IpAddress::new(192, 168, 0, 1)).unwrap(); - } + let mut active = w5500.activate(&mut spi).unwrap(); + // using a 'locally administered' MAC address + active.set_mac(MacAddress::new(0x02, 0x01, 0x02, 0x03, 0x04, 0x05)).unwrap(); + active.set_ip(IpAddress::new(192, 168, 0, 222)).unwrap(); + active.set_subnet(IpAddress::new(255, 255, 255, 0)).unwrap(); + active.set_gateway(IpAddress::new(192, 168, 0, 1)).unwrap(); - let mut udp_server_socket: Option = w5500.as_mut().and_then(|w5500| { - let mut w5500: ActiveW5500<_> = w5500.activate(&mut spi).ok()?; - let socket0: UninitializedSocket = w5500.take_socket(Socket::Socket0)?; - (&mut w5500, socket0).try_into_udp_server_socket(1234).ok() - }); + let socket0: UninitializedSocket = w5500.take_socket(Socket::Socket0).unwrap(); + let udp_server_socket = (&mut w5500, socket0).try_into_udp_server_socket(1234).unwrap(); let mut buffer = [0u8; 256]; - if let (Some(ref mut w5500), Some(ref socket)) = ( - w5500.as_mut().and_then(w5500.activate(&mut spi).ok()), - udp_server_socket, - ) { - if let Ok(Some((ip, port, len))) = (w5500, socket).receive(&mut buffer[..]) { - let (request_buffer, response_buffer) = buffer.split_mut_at(len); - - // ... fill the response_buffer with some data ... - - (w5500, socket).blocking_send(ip, port, response_buffer[..response_len]).unwrap(); + let response = [104, 101, 108, 108, 111, 10];// "hello" as ASCII + loop { + if let Ok(Some((ip, port, len))) = udp_server_socket.receive(&mut buffer[..]) { + udp_server_socket.blocking_send(ip, port, response[..]).unwrap(); } } ``` + +## To To + +In no particular order, things to do to improve this driver. + +* Add support for TCP +* Add support for DHCP +* Method to return socket back to the pool +* Make reset safe by requiring that all sockets be returned to the pool first +* Support a 3-wire SPI bus +* Sane defaults for IP/Gateway/Subnet +* Improve documentation diff --git a/src/lib.rs b/src/lib.rs index 2d300f2..b8f297b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -21,12 +21,14 @@ const FIXED_DATA_LENGTH_1_BYTE: u8 = 0b_01; const FIXED_DATA_LENGTH_2_BYTES: u8 = 0b_10; const FIXED_DATA_LENGTH_4_BYTES: u8 = 0b_11; +/// IP Address struct. Represents an IP address as a u8 array of length 4. Can be instantiated with `IpAddress::new` #[derive(Copy, Clone, PartialOrd, PartialEq, Default, Debug)] pub struct IpAddress { pub address: [u8; 4], } impl IpAddress { + /// Instantiate a new IP address with u8s for each address fragment pub fn new(a0: u8, a1: u8, a2: u8, a3: u8) -> IpAddress { IpAddress { address: [a0, a1, a2, a3], @@ -35,6 +37,7 @@ impl IpAddress { } impl ::core::fmt::Display for IpAddress { + /// String formatter for IP addresses, useful for debugging output fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { write!( f, @@ -44,12 +47,14 @@ impl ::core::fmt::Display for IpAddress { } } +/// MAC address struct. Represents a MAC address as a u8 array of length 6. Can be instantiated with `MacAddress::new` #[derive(Copy, Clone, PartialOrd, PartialEq, Default, Debug)] pub struct MacAddress { pub address: [u8; 6], } impl MacAddress { + /// Instantiate a new MAC address with u8s for each address fragment pub fn new(a0: u8, a1: u8, a2: u8, a3: u8, a4: u8, a5: u8) -> MacAddress { MacAddress { address: [a0, a1, a2, a3, a4, a5], @@ -58,6 +63,7 @@ impl MacAddress { } impl ::core::fmt::Display for MacAddress { + /// String formatter for MAC addresses, useful for debugging output fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { write!( f, @@ -72,18 +78,22 @@ impl ::core::fmt::Display for MacAddress { } } +/// Error enum that represents the union between SPI hardware errors and digital IO pin errors. Returned as an Error +/// type by many W5500 that talk to the chip #[derive(Copy, Clone, Debug)] pub enum TransferError { SpiError(SpiError), ChipSelectError(ChipSelectError), } +/// Settings for wake on LAN. Allows the W5500 to optionally emit an interrupt upon receiving a packet #[derive(Copy, Clone, PartialOrd, PartialEq)] pub enum OnWakeOnLan { InvokeInterrupt, Ignore, } +/// Settings for ping. Allows the W5500 to respond to or ignore network ping requests #[derive(Copy, Clone, PartialOrd, PartialEq)] pub enum OnPingRequest { Respond, @@ -104,9 +114,14 @@ pub enum ArpResponses { DropAfterUse, } +/// Represents a socket that has not yet been initialized for a particular protocol pub struct UninitializedSocket(Socket); + +/// Represents a socket that has been initialized to use the UDP protocol pub struct UdpSocket(Socket); +/// The first level of instantiating communication with the W5500. It can not communicate by itself, but calling +/// `activate` will return an `ActiveW5500` which can. pub struct W5500<'a, ChipSelect: OutputPin> { chip_select: &'a mut ChipSelect, sockets: u8, // each bit represents whether the corresponding socket is available for take @@ -122,6 +137,7 @@ impl<'b, 'a: 'b, ChipSelectError, ChipSelect: OutputPin } } + /// Primary method for instantiating. Briefly activates the W5500, and sets it up with the specified configuration pub fn with_initialisation<'c, Spi: FullDuplex>( chip_select: &'a mut ChipSelect, spi: &'c mut Spi, @@ -141,6 +157,7 @@ impl<'b, 'a: 'b, ChipSelectError, ChipSelect: OutputPin Ok(w5500) } + /// Returns the requested socket if it is not already used. pub fn take_socket(&mut self, socket: Socket) -> Option { let mask = (0x01 << socket.number()); if self.sockets & mask == mask { @@ -151,6 +168,7 @@ impl<'b, 'a: 'b, ChipSelectError, ChipSelect: OutputPin } } + /// Creates a new `ActiveW5500` with the provided `FullDuplex` implementation pub fn activate<'c, Spi: FullDuplex>( &'b mut self, spi: &'c mut Spi, @@ -160,6 +178,7 @@ impl<'b, 'a: 'b, ChipSelectError, ChipSelect: OutputPin } } +/// Struct that can communicate with the W5500 chip, configuring it and reading/writing to the registers on a low level pub struct ActiveW5500<'a, 'b: 'a, 'c, ChipSelect: OutputPin, Spi: FullDuplex>( &'a mut W5500<'b, ChipSelect>, &'c mut Spi, @@ -172,10 +191,12 @@ impl< Spi: FullDuplex, > ActiveW5500<'_, '_, '_, ChipSelect, Spi> { + /// Returns the requested socket if it is not already used pub fn take_socket(&mut self, socket: Socket) -> Option { self.0.take_socket(socket) } + /// Set up basic configuration of the W5500 chip pub fn update_operation_mode( &mut self, wol: OnWakeOnLan, @@ -204,6 +225,7 @@ impl< self.write_to(Register::CommonRegister(0x00_00_u16), &[value]) } + /// Sets the IP address of the network gateway (router) pub fn set_gateway( &mut self, gateway: IpAddress, @@ -211,6 +233,7 @@ impl< self.write_to(Register::CommonRegister(0x00_01_u16), &gateway.address) } + /// Sets the subnet on the network pub fn set_subnet( &mut self, subnet: IpAddress, @@ -218,6 +241,7 @@ impl< self.write_to(Register::CommonRegister(0x00_05_u16), &subnet.address) } + /// Sets the MAC address of the W5500 device on the network pub fn set_mac( &mut self, mac: MacAddress, @@ -225,6 +249,7 @@ impl< self.write_to(Register::CommonRegister(0x00_09_u16), &mac.address) } + /// Sets the IP address of the W5500 device on network. Must be within the range permitted by the gateway pub fn set_ip( &mut self, ip: IpAddress, @@ -232,6 +257,7 @@ impl< self.write_to(Register::CommonRegister(0x00_0F_u16), &ip.address) } + /// Reads 4 bytesfrom any register location and returns the value as an IP address pub fn read_ip( &mut self, register: Register, @@ -255,6 +281,7 @@ impl< Ok(()) } + /// TODO document fn is_interrupt_set( &mut self, socket: Socket, @@ -265,6 +292,7 @@ impl< Ok(state[0] & interrupt as u8 != 0) } + /// TODO document pub fn reset_interrupt( &mut self, socket: Socket, @@ -273,6 +301,7 @@ impl< self.write_to(socket.at(SocketRegister::Interrupt), &[interrupt as u8]) } + /// Reads one byte from any register address as a u8 fn read_u8( &mut self, register: Register, @@ -282,6 +311,7 @@ impl< Ok(buffer[0]) } + /// Reads two bytes from any register address and returns as a u16 fn read_u16( &mut self, register: Register, @@ -291,6 +321,7 @@ impl< Ok(BigEndian::read_u16(&buffer)) } + /// Reads enough bytes from any register address to fill the `target` u8 slice fn read_from( &mut self, register: Register, @@ -316,6 +347,7 @@ impl< result.map_err(|error| TransferError::SpiError(error)) } + /// Reads enough bytes over SPI to fill the `target` u8 slice fn read_bytes(&mut self, bytes: &mut [u8]) -> Result<(), SpiError> { for byte in bytes { *byte = self.read()?; @@ -323,11 +355,13 @@ impl< Ok(()) } + /// Reads a single byte over SPI by writing a zero and reading the response fn read(&mut self) -> Result { block!(self.1.send(0x00))?; block!(self.1.read()) } + /// Write a single u8 byte to any register address fn write_u8( &mut self, register: Register, @@ -336,6 +370,7 @@ impl< self.write_to(register, &[value]) } + /// Write a u16 as two bytes o any register address fn write_u16( &mut self, register: Register, @@ -346,6 +381,7 @@ impl< self.write_to(register, &data) } + /// Write a slice of u8 bytes to any register address fn write_to( &mut self, register: Register, @@ -371,6 +407,7 @@ impl< result.map_err(|error| TransferError::SpiError(error)) } + /// Write a slice of u8 bytes over SPI fn write_bytes(&mut self, bytes: &[u8]) -> Result<(), SpiError> { for b in bytes { self.write(*b)?; @@ -378,16 +415,19 @@ impl< Ok(()) } + /// Write a single byte over SPI fn write(&mut self, byte: u8) -> Result<(), SpiError> { block!(self.1.send(byte))?; block!(self.1.read())?; Ok(()) } + /// Begin a SPI frame by setting the CS signal to low fn chip_select(&mut self) -> Result<(), ChipSelectError> { self.0.chip_select.set_low() } + /// End a SPI frame by setting the CS signal to high fn chip_deselect(&mut self) -> Result<(), ChipSelectError> { self.0.chip_select.set_high() } @@ -405,6 +445,7 @@ impl> IntoUdpSocket Result { let socket = (self.1).0; (|| { @@ -425,6 +466,7 @@ impl> IntoUdpSocket> Udp { type Error = TransferError; + /// Returns a UDP packet if one is available. Will return `None` if no UDP packets are in the socket's buffer fn receive( &mut self, destination: &mut [u8], @@ -481,9 +524,6 @@ impl> Udp &mut destination[..data_length], )?; - // self.read_from(socket.register_at(0x00_0C), &mut ip.address)?; - // self.read_u16(socket.register_at(0x00_10))?; - // reset w5500.write_u16( socket.at(SocketRegister::RxReadPointer), @@ -500,6 +540,7 @@ impl> Udp } } + /// Sends a UDP packet to the specified IP and port, and blocks until it is sent fn blocking_send( &mut self, host: &IpAddress, @@ -564,10 +605,6 @@ impl> Udp } } // restore listen state - /* - self.network - .listen_udp(self.spi, SOCKET_UDP, SOCKET_UDP_PORT) - */ w5500.write_to( socket.at(SocketRegister::Mode), &[ @@ -579,6 +616,7 @@ impl> Udp } } +/// Offset addresses in each socket register #[repr(u8)] #[derive(Copy, Clone, PartialEq, Debug)] pub enum SocketRegister { @@ -609,6 +647,7 @@ pub enum SocketRegister { // Reserved 0x0030 - 0xFFFF } +/// Interrupt state bits #[repr(u8)] #[derive(Copy, Clone, PartialEq, Debug)] pub enum Interrupt { @@ -619,6 +658,7 @@ pub enum Interrupt { Connected = 1, // 1 << 0 } +/// Register protocol mode bits #[repr(u8)] #[derive(Copy, Clone, PartialEq, Debug)] pub enum Protocol { @@ -627,6 +667,7 @@ pub enum Protocol { MACRAW = 0b0100, } +/// Bits for socket commands #[repr(u8)] #[derive(Copy, Clone, PartialEq, Debug)] pub enum SocketCommand { @@ -641,6 +682,7 @@ pub enum SocketCommand { Recv = 0x40, } +/// Identifiers for each socket on the W5500 #[derive(Copy, Clone, PartialEq, PartialOrd, Debug)] pub enum Socket { Socket0, @@ -654,6 +696,7 @@ pub enum Socket { } impl Socket { + /// Gets the number of any given socket pub fn number(self) -> usize { match self { Socket::Socket0 => 0, @@ -667,6 +710,7 @@ impl Socket { } } + /// Returns the register address for a socket instance's TX fn tx_register_at(self, address: u16) -> Register { match self { Socket::Socket0 => Register::Socket0TxBuffer(address), @@ -680,6 +724,7 @@ impl Socket { } } + /// Returns the register address for a socket instance's RX fn rx_register_at(self, address: u16) -> Register { match self { Socket::Socket0 => Register::Socket0RxBuffer(address), @@ -693,6 +738,7 @@ impl Socket { } } + /// Returns the register address for a socket instance's register fn register_at(self, address: u16) -> Register { match self { Socket::Socket0 => Register::Socket0Register(address), @@ -711,6 +757,7 @@ impl Socket { } } +/// Chip register names #[derive(Copy, Clone, PartialEq, Debug)] pub enum Register { CommonRegister(u16), @@ -749,6 +796,7 @@ pub enum Register { } impl Register { + /// Gets the control bits to identify any given register fn control_byte(self) -> u8 { #[allow(clippy::inconsistent_digit_grouping)] match self { @@ -788,6 +836,7 @@ impl Register { } } + /// Returns the associated address as a u16 fn address(self) -> u16 { match self { Register::CommonRegister(address) => address,