Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Restructure and implement embedded-nal UDP traits #26

Merged
merged 58 commits into from
Mar 23, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
58 commits
Select commit Hold shift + click to select a range
03e30ef
Began skeleton of new UninitializedW5500 struct and Bus trait/impls
jonahbron Aug 5, 2019
a43f86d
Added bus model and InactiveW5500 state
jonahbron Aug 7, 2019
16e813e
Implemented frame transfer for four-wire bus
jonahbron Aug 8, 2019
aa0c69b
Fixed some masking issues with FourWireBus, added implementation for …
jonahbron Aug 8, 2019
d47de54
Added state-machine diagram SVG
jonahbron Aug 8, 2019
3177bad
Restored chip mode settings/common type structs
jonahbron Aug 8, 2019
ce36644
Added module containing new register address representations, added c…
jonahbron Aug 8, 2019
7dd4c04
Renamed Settings to Mode since it only applies to network mode byte
jonahbron Aug 9, 2019
fd9e861
Added Network trait that keeps track of network settings and has DHCP…
jonahbron Aug 9, 2019
d2fb6b9
fmt
jonahbron Aug 9, 2019
e74f7f4
Fixed bug in bitmasking
jonahbron Aug 9, 2019
b30e4d0
Stubbed UdpSocket struct, added Socket structs
jonahbron Aug 14, 2019
e8c8e3c
Laid out concept for UdpSocket init
jonahbron Aug 14, 2019
bd78b82
Comment for checking socket at run-time
jonahbron Aug 14, 2019
715cdea
Changed UdpSocket to store a socket reference
jonahbron Aug 14, 2019
b82bb92
Added inactive UP socket state, added run-time socket ownership check…
jonahbron Sep 4, 2019
65a9552
Moved register addresses into register modules, fixed borrow error
jonahbron Sep 5, 2019
36df284
Finished UDP socket init
jonahbron Sep 6, 2019
95ca1be
Ran formatting
jonahbron Sep 6, 2019
d0f5792
Added Packet struct and beginnings of packet reading
jonahbron Sep 10, 2019
423d2f6
Fleshed out packet receipt
jonahbron Sep 11, 2019
332ab92
Moved more functionality into socket
jonahbron Sep 11, 2019
63890a5
Moved packet to incoming_packet, stubbed outgoing_packet
jonahbron Sep 11, 2019
b9f9166
Fleshed out packet sending
jonahbron Sep 14, 2019
b6a52cb
Removed socket ownership checking, giving up on that effort for now
jonahbron Sep 19, 2019
adc7005
Fixed representation of enum to be u8, fixed FourWire bus to set CS p…
jonahbron Oct 24, 2019
4ff4bc9
Added register_dump function to show the contents of a socket register
jonahd-g Jan 20, 2020
3dbb2d4
Made dump_register suppress any errors
jonahd-g Jan 20, 2020
074e01e
Removed nb from areas where it's not necessary
jonahbron Jul 27, 2020
41cd42e
added all
jonahbron Nov 6, 2020
8aa5656
Added timeout interrupt bit, fixed several bugs with Socket code, add…
jonahbron Nov 8, 2020
b1e83e3
Added TODOs for safer sending
jonahbron Nov 8, 2020
f546ff2
Re-wrote socket implementations to be more compatible with embedded-nal
jonahbron Nov 18, 2020
3cad9ca
Renamed W5500 -> Device, added an Interface struct that should implem…
jonahbron Nov 20, 2020
1d533b2
Added a way to consume the Interface object, and easy single-function…
jonahbron Nov 20, 2020
79b1f52
Ran cargo fmt
jonahbron Nov 20, 2020
6caeeae
Updated dependencies
jonahbron Nov 20, 2020
79dc9a4
Add method to read PHY configuration.
newAM Aug 12, 2020
cc4db22
Changed to depend on upstream version of embedded-nal after PR merge
jonahbron Nov 22, 2020
1cac758
Added self to authors list, fixed Clippy lint errors
jonahbron Nov 22, 2020
dcfa655
Renamed Network mod to Host, removed debugging function, communted ou…
jonahbron Nov 24, 2020
839367b
Different clippy lint allowed
jonahbron Nov 24, 2020
2b59081
Removed implementation description from README, updated example to wo…
jonahbron Nov 24, 2020
2f26a50
Implemented From trait for InitializeError
jonahbron Nov 25, 2020
90604fc
Implemented From trait for busses
jonahbron Nov 25, 2020
259da58
Recovered MAC address docs from previous driver code
jonahbron Dec 1, 2020
d3ca4a3
Removed unnecessary extern crate statements
jonahbron Dec 1, 2020
da2bcd4
Updated to released version of embedded-nal
jonahbron Dec 2, 2020
9a05719
Updated UDP impl to be compatible with embedded-nal 0.2.0
jonahbron Dec 2, 2020
d844a11
Updated to latest embedded-nal
jonahbron Jan 19, 2021
d6574b8
Removed refcell container from Interface
jonahbron Jan 20, 2021
5509732
Updated embedded-nal and removed (now-redundent) Interface struct
jonahbron Feb 16, 2021
ca8268a
Removed the active/inactive concept, and changed bus to use blocking …
jonahbron Feb 16, 2021
a3e0911
Minor typo fixes, clean-up of the public interface
jonahbron Feb 16, 2021
daa1c00
Minor typo fixes, clean-up of the public interface
jonahbron Feb 16, 2021
b49f41c
fmt
jonahbron Feb 16, 2021
dfa252f
Short-term net fix
jonahbron Feb 26, 2021
52f0d06
Updated dependencies, fixed port closing after receive
jonahbron Mar 23, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/rust.yml
Original file line number Diff line number Diff line change
Expand Up @@ -65,4 +65,4 @@ jobs:
- uses: actions-rs/cargo@v1
with:
command: clippy
args: -- -D warnings
args: -- -D warnings -A clippy::unknown-clippy-lints
6 changes: 4 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[package]
name = "w5500"
version = "0.3.0"
authors = ["Michael Watzko <michael@watzko.de>"]
authors = ["Michael Watzko <michael@watzko.de>", "Jonah Dahlquist <hi@jonah.name>"]
repository = "https://github.com/kellerkindt/w5500.git"
description = "W5500 IoT Controller implementation. Currently UDP sending and receiving is working. WIP"
keywords = ["embedded", "w5500", "iot", "arm", "embedded-hal-driver"]
Expand All @@ -13,4 +13,6 @@ edition = "2018"
[dependencies]
byteorder = { version = "1.3.4", default-features = false }
embedded-hal = "0.2.4"
nb = "0.1.2"
embedded-nal = "0.4.0"
bit_field = "0.10.1"
nb = "1.0.0"
58 changes: 10 additions & 48 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,25 +19,9 @@ like this one. Any microcontroller that implements the
[`spi::FullDuplex<u8>`](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<u8>`](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
Below is a basic example of sending UDP packets to a remote host. 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
Expand All @@ -47,34 +31,16 @@ of the SPI implementation. It must be set up to work as the W5500 chip requires

```rust
let mut spi = ...; // SPI interface to use
let mut cs_w5500 : OutputPin = ...; // chip select

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,
).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 cs : OutputPin = ...; // chip select

let socket0: UninitializedSocket = active.take_socket(Socket::Socket0).unwrap();
let udp_server_socket = (&mut active, socket0).try_into_udp_server_socket(1234).unwrap();

let mut buffer = [0u8; 256];
let response = [104, 101, 108, 108, 111, 10];// "hello" as ASCII
loop {
if let Ok(Some((ip, port, len))) = (&mut active, udp_server_socket).receive(&mut buffer[..]) {
(&mut active, udp_server_socket).blocking_send(ip, port, response[..]).unwrap();
}
}
let device = UninitializedDevice::new(FourWire::new(spi, cs));
let device = device.initialize_manual(MacAddress::new(0, 1, 2, 3, 4, 5), Ipv4Addr::new(192, 168, 86, 79), Mode::default()).unwrap();
let socket = interface.socket();
socket.connect(
SocketAddr::new(IpAddr::V4(Ipv4Addr::new(192, 168, 86, 38)), 8000),
).unwrap();
block!(interface.send(&mut socket, &[104, 101, 108, 108, 111, 10]));
interface.close(socket);
```

## Todo
Expand All @@ -83,7 +49,3 @@ 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
1 change: 1 addition & 0 deletions driver_state_machine_diagram.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
87 changes: 87 additions & 0 deletions src/bus/four_wire.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
#![allow(clippy::inconsistent_digit_grouping, clippy::unusual_byte_groupings)]

use core::fmt;
use embedded_hal::blocking::spi::{Transfer, Write};
use embedded_hal::digital::v2::OutputPin;

use crate::bus::Bus;

const WRITE_MODE_MASK: u8 = 0b00000_1_00;

// TODO This name is not ideal, should be renamed to VDM
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What stands VDM for?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Variable length Data Mode, as defined in the data sheet.

image

pub struct FourWire<Spi: Transfer<u8> + Write<u8>, ChipSelect: OutputPin> {
cs: ChipSelect,
spi: Spi,
}

impl<Spi: Transfer<u8> + Write<u8>, ChipSelect: OutputPin> FourWire<Spi, ChipSelect> {
pub fn new(spi: Spi, cs: ChipSelect) -> Self {
Self { spi, cs }
}

pub fn release(self) -> (Spi, ChipSelect) {
(self.spi, self.cs)
}
}

impl<Spi: Transfer<u8> + Write<u8>, ChipSelect: OutputPin> Bus for FourWire<Spi, ChipSelect> {
type Error =
FourWireError<<Spi as Transfer<u8>>::Error, <Spi as Write<u8>>::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;
self.cs.set_low().map_err(FourWireError::ChipSelectError)?;
self.spi
.write(&address_phase)
.and_then(|_| self.spi.write(&[control_phase]))
.map_err(FourWireError::WriteError)?;
self.spi
.transfer(data_phase)
.map_err(FourWireError::TransferError)?;
self.cs.set_high().map_err(FourWireError::ChipSelectError)?;

Ok(())
}
fn write_frame(&mut self, block: u8, address: u16, data: &[u8]) -> Result<(), Self::Error> {
let address_phase = address.to_be_bytes();
let control_phase = block << 3 | WRITE_MODE_MASK;
let data_phase = data;
self.cs.set_low().map_err(FourWireError::ChipSelectError)?;
self.spi
.write(&address_phase)
.and_then(|_| self.spi.write(&[control_phase]))
.and_then(|_| self.spi.write(data_phase))
.map_err(FourWireError::WriteError)?;
self.cs.set_high().map_err(FourWireError::ChipSelectError)?;

Ok(())
}
}

// Must use map_err, ambiguity prevents From from being implemented
#[repr(u8)]
pub enum FourWireError<TransferError, WriteError, ChipSelectError> {
TransferError(TransferError),
WriteError(WriteError),
ChipSelectError(ChipSelectError),
}

impl<TransferError, WriteError, ChipSelectError> fmt::Debug
for FourWireError<TransferError, WriteError, ChipSelectError>
{
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"FourWireError::{}",
match self {
Self::TransferError(_) => "TransferError",
Self::WriteError(_) => "WriteError",
Self::ChipSelectError(_) => "ChipSelectError",
}
)
}
}

// TODO Improved error rendering could be done with specialization.
// https://github.com/rust-lang/rust/issues/31844
15 changes: 15 additions & 0 deletions src/bus/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
use core::fmt::Debug;

mod four_wire;
mod three_wire;

pub use self::four_wire::FourWire;
pub use self::three_wire::ThreeWire;
Comment on lines +6 to +7
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How to access FourWire/ThreeWire Error when not public?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure what you mean, bus is public.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This wasn't public before. Otherwise one could not work with the FourWireError type?


pub trait Bus {
type Error: Debug;

fn read_frame(&mut self, block: u8, address: u16, data: &mut [u8]) -> Result<(), Self::Error>;

fn write_frame(&mut self, block: u8, address: u16, data: &[u8]) -> Result<(), Self::Error>;
}
128 changes: 128 additions & 0 deletions src/bus/three_wire.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
#![allow(clippy::inconsistent_digit_grouping, clippy::unusual_byte_groupings)]

use core::fmt;
use embedded_hal::blocking::spi::{Transfer, Write};

use crate::bus::Bus;

const WRITE_MODE_MASK: u8 = 0b00000_1_0;

const FIXED_DATA_LENGTH_MODE_1: u8 = 0b000000_01;
const FIXED_DATA_LENGTH_MODE_2: u8 = 0b000000_10;
const FIXED_DATA_LENGTH_MODE_4: u8 = 0b000000_11;

// TODO This name is not ideal, should be renamed to FDM
pub struct ThreeWire<Spi: Transfer<u8> + Write<u8>> {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What stands FDM for?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed length Data Mode (see screenshot of datasheet above).

spi: Spi,
}

impl<Spi: Transfer<u8> + Write<u8>> ThreeWire<Spi> {
pub fn new(spi: Spi) -> Self {
Self { spi }
}

pub fn release(self) -> Spi {
self.spi
}
}

impl<Spi: Transfer<u8> + Write<u8>> Bus for ThreeWire<Spi> {
type Error = ThreeWireError<<Spi as Transfer<u8>>::Error, <Spi as Write<u8>>::Error>;

/// Transfers a frame with an arbitrary data length in FDM
///
/// This is done by passing chunks of fixed length 4, 2, or 1. For example if a frame looks like this:
///
/// (address 23) 0xF0 0xAB 0x83 0xB2 0x44 0x2C 0xAA
///
/// This will be sent as separate frames in the chunks
///
/// (address 23) 0xF0 0xAB 0x83 0xB2
/// (address 27) 44 2C
/// (address 29) AA
Comment on lines +32 to +42
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the advantage of this behavior?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These two modes are for when you either have or do not have a CS pin. You can connect with only three wires (MISO, MOSI, CLK), but you have to use FDM.

fn read_frame(
&mut self,
block: u8,
mut address: u16,
data: &mut [u8],
) -> Result<(), Self::Error> {
let mut control_phase = block << 3;

let mut data_phase = &mut data[..];
let mut last_length_written: u16;
while !data_phase.is_empty() {
if data_phase.len() >= 4 {
control_phase |= FIXED_DATA_LENGTH_MODE_4;
last_length_written = 4;
} else if data_phase.len() >= 2 {
control_phase |= FIXED_DATA_LENGTH_MODE_2;
last_length_written = 2;
} else {
control_phase |= FIXED_DATA_LENGTH_MODE_1;
last_length_written = 1;
}

let address_phase = address.to_be_bytes();
self.spi
.write(&address_phase)
.and_then(|_| self.spi.write(&[control_phase]))
.map_err(ThreeWireError::WriteError)?;
self.spi
.transfer(&mut data_phase[..last_length_written as usize])
.map_err(ThreeWireError::TransferError)?;

address += last_length_written;
data_phase = &mut data_phase[last_length_written as usize..];
}
Ok(())
}

fn write_frame(&mut self, block: u8, mut address: u16, data: &[u8]) -> Result<(), Self::Error> {
let mut control_phase = block << 3 | WRITE_MODE_MASK;

let mut data_phase = &data[..];
let mut last_length_written: u16;
while !data_phase.is_empty() {
if data_phase.len() >= 4 {
control_phase |= FIXED_DATA_LENGTH_MODE_4;
last_length_written = 4;
} else if data_phase.len() >= 2 {
control_phase |= FIXED_DATA_LENGTH_MODE_2;
last_length_written = 2;
} else {
control_phase |= FIXED_DATA_LENGTH_MODE_1;
last_length_written = 1;
}

let address_phase = address.to_be_bytes();
self.spi
.write(&address_phase)
.and_then(|_| self.spi.write(&[control_phase]))
.and_then(|_| self.spi.write(&data_phase[..last_length_written as usize]))
.map_err(ThreeWireError::WriteError)?;

address += last_length_written;
data_phase = &data_phase[last_length_written as usize..];
}
Ok(())
}
}

// Must use map_err, ambiguity prevents From from being implemented
pub enum ThreeWireError<TransferError, WriteError> {
TransferError(TransferError),
WriteError(WriteError),
}

impl<TransferError, WriteError> fmt::Debug for ThreeWireError<TransferError, WriteError> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"ThreeWireError::{}",
match self {
Self::TransferError(_) => "TransferError",
Self::WriteError(_) => "WriteError",
}
)
}
}
Loading