diff --git a/embassy-embedded-hal/Cargo.toml b/embassy-embedded-hal/Cargo.toml index 8600b56443..3690963b75 100644 --- a/embassy-embedded-hal/Cargo.toml +++ b/embassy-embedded-hal/Cargo.toml @@ -38,6 +38,7 @@ embedded-hal-02 = { package = "embedded-hal", version = "0.2.6", features = [ ] } embedded-hal-1 = { package = "embedded-hal", version = "1.0" } embedded-hal-async = { version = "1.0" } +embedded-hal-bus = { version = "0.3.0" } embedded-storage = "0.3.1" embedded-storage-async = { version = "0.4.1" } nb = "1.0.0" diff --git a/embassy-embedded-hal/src/lib.rs b/embassy-embedded-hal/src/lib.rs index 1db4fe012a..b8f569335d 100644 --- a/embassy-embedded-hal/src/lib.rs +++ b/embassy-embedded-hal/src/lib.rs @@ -5,6 +5,7 @@ pub mod adapter; pub mod flash; +pub mod qspi; pub mod shared_bus; /// Set the configuration of a peripheral driver. diff --git a/embassy-embedded-hal/src/qspi/device_error.rs b/embassy-embedded-hal/src/qspi/device_error.rs new file mode 100644 index 0000000000..c38ceb1655 --- /dev/null +++ b/embassy-embedded-hal/src/qspi/device_error.rs @@ -0,0 +1,38 @@ +//! Module for QSPI device error +use core::fmt::{self, Debug, Display, Formatter}; +use embedded_hal_async::spi::{Error, ErrorKind}; + +/// Error type for [`ExclusiveDevice`] operations. +#[derive(Copy, Clone, Eq, PartialEq, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum DeviceError { + /// An inner QSPI bus operation failed. + Qspi(BUS), + /// Asserting or deasserting CS failed. + Cs(CS), +} + +impl Display for DeviceError { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + match self { + Self::Qspi(bus) => write!(f, "SPI bus error: {}", bus), + Self::Cs(cs) => write!(f, "SPI CS error: {}", cs), + } + } +} + +impl core::error::Error for DeviceError {} + +impl Error for DeviceError +where + BUS: Error + Debug, + CS: Debug, +{ + #[inline] + fn kind(&self) -> ErrorKind { + match self { + Self::Qspi(e) => e.kind(), + Self::Cs(_) => ErrorKind::ChipSelectFault, + } + } +} diff --git a/embassy-embedded-hal/src/qspi/exclusive.rs b/embassy-embedded-hal/src/qspi/exclusive.rs new file mode 100644 index 0000000000..5dbf53b78e --- /dev/null +++ b/embassy-embedded-hal/src/qspi/exclusive.rs @@ -0,0 +1,135 @@ +//! QSPI bus sharing mechanisms. + +use super::traits::{Operation, QspiBus, QspiDevice}; +use embedded_hal_1::digital::OutputPin; +use embedded_hal_async::delay::DelayNs; +use embedded_hal_async::spi::ErrorType; + +use super::device_error::DeviceError; + +/// Dummy [`DelayNs`](embedded_hal::delay::DelayNs) implementation that panics on use. +#[derive(Copy, Clone, Eq, PartialEq, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct NoDelay; + +/// [`QspiDevice`] implementation with exclusive access to the bus (not shared). +/// +/// This is the most straightforward way of obtaining an [`QspiDevice`] from an [`QspiBus`], +/// ideal for when no sharing is required (only one SPI device is present on the bus). +pub struct ExclusiveDevice { + bus: BUS, + cs: CS, + delay: D, +} + +impl ExclusiveDevice { + /// Create a new [`ExclusiveDevice`]. + /// + /// This sets the `cs` pin high, and returns an error if that fails. It is recommended + /// to set the pin high the moment it's configured as an output, to avoid glitches. + #[inline] + pub fn new(bus: BUS, mut cs: CS, delay: D) -> Result + where + CS: OutputPin, + { + cs.set_high()?; + Ok(Self { bus, cs, delay }) + } + + /// Returns a reference to the underlying bus object. + #[inline] + pub fn bus(&self) -> &BUS { + &self.bus + } + + /// Returns a mutable reference to the underlying bus object. + #[inline] + pub fn bus_mut(&mut self) -> &mut BUS { + &mut self.bus + } +} + +impl ExclusiveDevice { + /// Create a new [`ExclusiveDevice`] without support for in-transaction delays. + /// + /// This sets the `cs` pin high, and returns an error if that fails. It is recommended + /// to set the pin high the moment it's configured as an output, to avoid glitches. + /// + /// **Warning**: The returned instance *technically* doesn't comply with the `QspiDevice` + /// contract, which mandates delay support. It is relatively rare for drivers to use + /// in-transaction delays, so you might still want to use this method because it's more practical. + /// + /// Note that a future version of the driver might start using delays, causing your + /// code to panic. This wouldn't be considered a breaking change from the driver side, because + /// drivers are allowed to assume `QspiDevice` implementations comply with the contract. + /// If you feel this risk outweighs the convenience of having `cargo` automatically upgrade + /// the driver crate, you might want to pin the driver's version. + /// + /// # Panics + /// + /// The returned device will panic if you try to execute a transaction + /// that contains any operations of type [`Operation::DelayNs`]. + #[inline] + pub fn new_no_delay(bus: BUS, mut cs: CS) -> Result + where + CS: OutputPin, + { + cs.set_high()?; + Ok(Self { + bus, + cs, + delay: NoDelay, + }) + } +} + +impl ErrorType for ExclusiveDevice +where + BUS: ErrorType, + CS: OutputPin, +{ + type Error = DeviceError; +} + +impl QspiDevice for ExclusiveDevice +where + BUS: QspiBus, + CS: OutputPin, + D: DelayNs, +{ + #[inline] + async fn transaction(&mut self, operations: &mut [Operation<'_, Word>]) -> Result<(), Self::Error> { + self.cs.set_low().map_err(DeviceError::Cs)?; + + let op_res = 'ops: { + for op in operations { + let res = match op { + Operation::Read(buf) => self.bus.read(buf).await, + Operation::Write(buf) => self.bus.write(buf).await, + Operation::WriteSingleLine(buf) => self.bus.write_single_line(buf).await, + Operation::DelayNs(ns) => match self.bus.flush().await { + Err(e) => Err(e), + Ok(()) => { + self.delay.delay_ns(*ns).await; + Ok(()) + } + }, + }; + if let Err(e) = res { + break 'ops Err(e); + } + } + Ok(()) + }; + + // On failure, it's important to still flush and deassert CS. + let flush_res = self.bus.flush().await; + let cs_res = self.cs.set_high(); + + op_res.map_err(DeviceError::Qspi)?; + flush_res.map_err(DeviceError::Qspi)?; + cs_res.map_err(DeviceError::Cs)?; + + Ok(()) + } +} diff --git a/embassy-embedded-hal/src/qspi/mod.rs b/embassy-embedded-hal/src/qspi/mod.rs new file mode 100644 index 0000000000..f6432240d2 --- /dev/null +++ b/embassy-embedded-hal/src/qspi/mod.rs @@ -0,0 +1,4 @@ +//! Utilities for Quad SPI +pub mod device_error; +pub mod exclusive; +pub mod traits; diff --git a/embassy-embedded-hal/src/qspi/traits.rs b/embassy-embedded-hal/src/qspi/traits.rs new file mode 100644 index 0000000000..7f351b3484 --- /dev/null +++ b/embassy-embedded-hal/src/qspi/traits.rs @@ -0,0 +1,385 @@ +//! Blocking QSPI master mode traits. +//! +//! # Bus vs Device +//! +//! QSPI allows sharing a single bus between many QSPI devices. The SCK, MOSI and MISO lines are +//! wired in parallel to all the devices, and each device gets a dedicated chip-select (CS) line from the MCU, like this: +//! +//! CS is usually active-low. When CS is high (not asserted), QSPI devices ignore all incoming data, and +//! don't drive MISO. When CS is low (asserted), the device is active: reacts to incoming data on MOSI and +//! drives MISO with the response data. By asserting one CS or another, the MCU can choose to which +//! QSPI device it "talks" to on the (possibly shared) bus. +//! +//! This bus sharing is common when having multiple QSPI devices in the same board, since it uses fewer MCU +//! pins (`n+3` instead of `4*n`), and fewer MCU QSPI peripherals (`1` instead of `n`). +//! +//! However, it poses a challenge when building portable drivers for QSPI devices. The driver needs to +//! be able to talk to its device on the bus, while not interfering with other drivers talking to other +//! devices. +//! +//! To solve this, `embedded-hal` has two kinds of QSPI traits: **QSPI bus** and **QSPI device**. +//! +//! ## Bus +//! +//! The [`QspiBus`] trait represents **exclusive ownership** over the whole QSPI bus. This is usually the entire +//! QSPI MCU peripheral, plus the SCK, MOSI and MISO pins. +//! +//! Owning an instance of an QSPI bus guarantees exclusive access, this is, we have the guarantee no other +//! piece of code will try to use the bus while we own it. +//! +//! ## Device +//! +//! The [`QspiDevice`] trait represents **ownership over a single QSPI device selected by a CS pin** in a (possibly shared) bus. This is typically: +//! +//! - Exclusive ownership of the **CS pin**. +//! - Access to the **underlying QSPI bus**. If shared, it'll be behind some kind of lock/mutex. +//! +//! An [`QspiDevice`] allows initiating [transactions](QspiDevice::transaction) against the target device on the bus. A transaction +//! consists of asserting CS, then doing one or more operations, then deasserting CS. For the entire duration of the transaction, the [`QspiDevice`] +//! implementation will ensure no other transaction can be opened on the same bus. This is the key that allows correct sharing of the bus. +//! +//! # For driver authors +//! +//! When implementing a driver, it's crucial to pick the right trait, to ensure correct operation +//! with maximum interoperability. Here are some guidelines depending on the device you're implementing a driver for: +//! +//! If your device **has a CS pin**, use [`QspiDevice`]. Do not manually +//! manage the CS pin, the [`QspiDevice`] implementation will do it for you. +//! By using [`QspiDevice`], your driver will cooperate nicely with other drivers for other devices in the same shared QSPI bus. +//! +//! ``` +//! # use embedded_hal::spi::{QspiBus, QspiDevice, Operation}; +//! pub struct MyDriver { +//! spi: QSPI, +//! } +//! +//! impl MyDriver +//! where +//! QSPI: QspiDevice, +//! { +//! pub fn new(spi: QSPI) -> Self { +//! Self { spi } +//! } +//! +//! pub fn read_foo(&mut self) -> Result<[u8; 2], MyError> { +//! let mut buf = [0; 2]; +//! +//! // `transaction` asserts and deasserts CS for us. No need to do it manually! +//! self.spi.transaction(&mut [ +//! Operation::Write(&[0x90]), +//! Operation::Read(&mut buf), +//! ]).map_err(MyError::Qspi)?; +//! +//! Ok(buf) +//! } +//! } +//! +//! #[derive(Copy, Clone, Debug)] +//! enum MyError { +//! Qspi(QSPI), +//! // Add other errors for your driver here. +//! } +//! ``` +//! +//! If your device **does not have a CS pin**, use [`QspiBus`]. This will ensure +//! your driver has exclusive access to the bus, so no other drivers can interfere. It's not possible to safely share +//! a bus without CS pins. By requiring [`QspiBus`] you disallow sharing, ensuring correct operation. +//! +//! ``` +//! # use embedded_hal::spi::QspiBus; +//! pub struct MyDriver { +//! spi: QSPI, +//! } +//! +//! impl MyDriver +//! where +//! QSPI: QspiBus, +//! { +//! pub fn new(spi: QSPI) -> Self { +//! Self { spi } +//! } +//! +//! pub fn read_foo(&mut self) -> Result<[u8; 2], MyError> { +//! let mut buf = [0; 2]; +//! self.spi.write(&[0x90]).map_err(MyError::Qspi)?; +//! self.spi.read(&mut buf).map_err(MyError::Qspi)?; +//! Ok(buf) +//! } +//! } +//! +//! #[derive(Copy, Clone, Debug)] +//! enum MyError { +//! Qspi(QSPI), +//! // Add other errors for your driver here. +//! } +//! ``` +//! +//! If you're (ab)using QSPI to **implement other protocols** by bitbanging (WS2812B, onewire, generating arbitrary waveforms...), use [`QspiBus`]. +//! QSPI bus sharing doesn't make sense at all in this case. By requiring [`QspiBus`] you disallow sharing, ensuring correct operation. +//! +//! # For HAL authors +//! +//! HALs **must** implement [`QspiBus`]. Users can combine the bus together with the CS pin (which should +//! implement [`OutputPin`](crate::digital::OutputPin)) using HAL-independent [`QspiDevice`] implementations such as the ones in [`embedded-hal-bus`](https://crates.io/crates/embedded-hal-bus). +//! +//! HALs may additionally implement [`QspiDevice`] to **take advantage of hardware CS management**, which may provide some performance +//! benefits. (There's no point in a HAL implementing [`QspiDevice`] if the CS management is software-only, this task is better left to +//! the HAL-independent implementations). +//! +//! HALs **must not** add infrastructure for sharing at the [`QspiBus`] level. User code owning a [`QspiBus`] must have the guarantee +//! of exclusive access. +//! +//! # Flushing +//! +//! To improve performance, [`QspiBus`] implementations are allowed to return before the operation is finished, i.e. when the bus is still not +//! idle. This allows pipelining QSPI operations with CPU work. +//! +//! When calling another method when a previous operation is still in progress, implementations can either wait for the previous operation +//! to finish, or enqueue the new one, but they must not return a "busy" error. Users must be able to do multiple method calls in a row +//! and have them executed "as if" they were done sequentially, without having to check for "busy" errors. +//! +//! When using a [`QspiBus`], call [`flush`](QspiBus::flush) to wait for operations to actually finish. Examples of situations +//! where this is needed are: +//! - To synchronize QSPI activity and GPIO activity, for example before deasserting a CS pin. +//! - Before deinitializing the hardware QSPI peripheral. +//! +//! When using a [`QspiDevice`], you can still call [`flush`](QspiBus::flush) on the bus within a transaction. +//! It's very rarely needed, because [`transaction`](QspiDevice::transaction) already flushes for you +//! before deasserting CS. For example, you may need it to synchronize with GPIOs other than CS, such as DCX pins +//! sometimes found in QSPI displays. +//! +//! For example, for [`write`](QspiBus::write) operations, it is common for hardware QSPI peripherals to have a small +//! FIFO buffer, usually 1-4 bytes. Software writes data to the FIFO, and the peripheral sends it on MOSI at its own pace, +//! at the specified QSPI frequency. It is allowed for an implementation of [`write`](QspiBus::write) to return as soon +//! as all the data has been written to the FIFO, before it is actually sent. Calling [`flush`](QspiBus::flush) would +//! wait until all the bits have actually been sent, the FIFO is empty, and the bus is idle. +//! +//! This still applies to other operations such as [`read`](QspiBus::read). It is less obvious +//! why, because these methods can't return before receiving all the read data. However it's still technically possible +//! for them to return before the bus is idle. For example, assuming QSPI mode 0, the last bit is sampled on the first (rising) edge +//! of SCK, at which point a method could return, but the second (falling) SCK edge still has to happen before the bus is idle. +//! +//! # CS-to-clock delays +//! +//! Many chips require a minimum delay between asserting CS and the first SCK edge, and the last SCK edge and deasserting CS. +//! Drivers should *NOT* use [`Operation::DelayNs`] for this, they should instead document that the user should configure the +//! delays when creating the `QspiDevice` instance, same as they have to configure the QSPI frequency and mode. This has a few advantages: +//! +//! - Allows implementations that use hardware-managed CS to program the delay in hardware +//! - Allows the end user more flexibility. For example, they can choose to not configure any delay if their MCU is slow +//! enough to "naturally" do the delay (very common if the delay is in the order of nanoseconds). + +use core::fmt::Debug; +pub use embedded_hal_async::spi::Error; +pub use embedded_hal_async::spi::ErrorType; + +#[cfg(feature = "defmt")] +use defmt; + +/// Clock polarity. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Polarity { + /// Clock signal low when idle. + IdleLow, + /// Clock signal high when idle. + IdleHigh, +} + +/// Clock phase. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Phase { + /// Data in "captured" on the first clock transition. + CaptureOnFirstTransition, + /// Data in "captured" on the second clock transition. + CaptureOnSecondTransition, +} + +/// QSPI mode. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct Mode { + /// Clock polarity. + pub polarity: Polarity, + /// Clock phase. + pub phase: Phase, +} + +/// Helper for CPOL = 0, CPHA = 0. +pub const MODE_0: Mode = Mode { + polarity: Polarity::IdleLow, + phase: Phase::CaptureOnFirstTransition, +}; + +/// Helper for CPOL = 0, CPHA = 1. +pub const MODE_1: Mode = Mode { + polarity: Polarity::IdleLow, + phase: Phase::CaptureOnSecondTransition, +}; + +/// Helper for CPOL = 1, CPHA = 0. +pub const MODE_2: Mode = Mode { + polarity: Polarity::IdleHigh, + phase: Phase::CaptureOnFirstTransition, +}; + +/// Helper for CPOL = 1, CPHA = 1. +pub const MODE_3: Mode = Mode { + polarity: Polarity::IdleHigh, + phase: Phase::CaptureOnSecondTransition, +}; + +/// QSPI transaction operation. +/// +/// This allows composition of QSPI operations into a single bus transaction. +#[derive(Debug, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Operation<'a, Word: 'static> { + /// Read data into the provided buffer. + /// + /// Equivalent to [`QspiBus::read`]. + Read(&'a mut [Word]), + /// Write data from the provided buffer. + /// + /// Equivalent to [`QspiBus::write`]. + Write(&'a [Word]), + /// Write data from the provided buffer using a single line. + /// + /// Useful for instruction phases + WriteSingleLine(&'a [Word]), + /// Delay for at least the specified number of nanoseconds. + DelayNs(u32), +} + +/// QSPI device trait. +/// +/// `QspiDevice` represents ownership over a single QSPI device on a (possibly shared) bus, selected +/// with a CS (Chip Select) pin. +/// +/// See the [module-level documentation](self) for important usage information. +pub trait QspiDevice: ErrorType { + /// Perform a transaction against the device. + /// + /// - Locks the bus + /// - Asserts the CS (Chip Select) pin. + /// - Performs all the operations. + /// - [Flushes](QspiBus::flush) the bus. + /// - Deasserts the CS pin. + /// - Unlocks the bus. + /// + /// The locking mechanism is implementation-defined. The only requirement is it must prevent two + /// transactions from executing concurrently against the same bus. Examples of implementations are: + /// critical sections, blocking mutexes, returning an error or panicking if the bus is already busy. + /// + /// On bus errors the implementation should try to deassert CS. + /// If an error occurs while deasserting CS the bus error should take priority as the return value. + async fn transaction(&mut self, operations: &mut [Operation<'_, Word>]) -> Result<(), Self::Error>; + + /// Do a read within a transaction. + /// + /// This is a convenience method equivalent to `device.transaction(&mut [Operation::Read(buf)])`. + /// + /// See also: [`QspiDevice::transaction`], [`QspiBus::read`] + #[inline] + async fn read(&mut self, buf: &mut [Word]) -> Result<(), Self::Error> { + self.transaction(&mut [Operation::Read(buf)]).await + } + + /// Do a write within a transaction. + /// + /// This is a convenience method equivalent to `device.transaction(&mut [Operation::Write(buf)])`. + /// + /// See also: [`QspiDevice::transaction`], [`QspiBus::write`] + #[inline] + async fn write(&mut self, buf: &[Word]) -> Result<(), Self::Error> { + self.transaction(&mut [Operation::Write(buf)]).await + } + + /// Do a write using a single line within a transaction. + /// + /// This is a convenience method equivalent to `device.transaction(&mut [Operation::WriteSingleLine(buf)])`. + /// + /// See also: [`QspiDevice::transaction`], [`QspiBus::write`] + #[inline] + async fn write_single_line(&mut self, buf: &[Word]) -> Result<(), Self::Error> { + self.transaction(&mut [Operation::WriteSingleLine(buf)]).await + } +} + +impl + ?Sized> QspiDevice for &mut T { + #[inline] + async fn transaction(&mut self, operations: &mut [Operation<'_, Word>]) -> Result<(), Self::Error> { + T::transaction(self, operations).await + } + + #[inline] + async fn read(&mut self, buf: &mut [Word]) -> Result<(), Self::Error> { + T::read(self, buf).await + } + + #[inline] + async fn write(&mut self, buf: &[Word]) -> Result<(), Self::Error> { + T::write(self, buf).await + } + + #[inline] + async fn write_single_line(&mut self, buf: &[Word]) -> Result<(), Self::Error> { + T::write_single_line(self, buf).await + } +} + +/// QSPI bus. +/// +/// `QspiBus` represents **exclusive ownership** over the whole QSPI bus, with SCK, MOSI and MISO pins. +/// +/// See the [module-level documentation](self) for important information on QSPI Bus vs Device traits. +pub trait QspiBus: ErrorType { + /// Read `words` from the slave. + /// + /// The word value sent on MOSI during reading is implementation-defined, + /// typically `0x00`, `0xFF`, or configurable. + /// + /// Implementations are allowed to return before the operation is + /// complete. See the [module-level documentation](self) for details. + async fn read(&mut self, words: &mut [Word]) -> Result<(), Self::Error>; + + /// Write `words` to the slave, ignoring all the incoming words. + /// + /// Implementations are allowed to return before the operation is + /// complete. See the [module-level documentation](self) for details. + async fn write(&mut self, words: &[Word]) -> Result<(), Self::Error>; + + /// Write `words` to the slave using a single line, ignoring all the incoming words. + /// + /// Implementations are allowed to return before the operation is + /// complete. See the [module-level documentation](self) for details. + async fn write_single_line(&mut self, words: &[Word]) -> Result<(), Self::Error>; + + /// Wait until all operations have completed and the bus is idle. + /// + /// See the [module-level documentation](self) for important usage information. + async fn flush(&mut self) -> Result<(), Self::Error>; +} + +impl + ?Sized, Word: Copy + 'static> QspiBus for &mut T { + #[inline] + async fn read(&mut self, words: &mut [Word]) -> Result<(), Self::Error> { + T::read(self, words).await + } + + #[inline] + async fn write(&mut self, words: &[Word]) -> Result<(), Self::Error> { + T::write(self, words).await + } + + #[inline] + async fn write_single_line(&mut self, words: &[Word]) -> Result<(), Self::Error> { + T::write_single_line(self, words).await + } + + #[inline] + async fn flush(&mut self) -> Result<(), Self::Error> { + T::flush(self).await + } +} diff --git a/embassy-net-wiznet/Cargo.toml b/embassy-net-wiznet/Cargo.toml index c12c53677e..84f7708392 100644 --- a/embassy-net-wiznet/Cargo.toml +++ b/embassy-net-wiznet/Cargo.toml @@ -12,13 +12,14 @@ documentation = "https://docs.embassy.dev/embassy-net-wiznet" [dependencies] embedded-hal = { version = "1.0" } embedded-hal-async = { version = "1.0" } +embassy-embedded-hal = { version = "0.6.0", path = "../embassy-embedded-hal" } embassy-net-driver-channel = { version = "0.4.0", path = "../embassy-net-driver-channel" } embassy-time = { version = "0.5.1", path = "../embassy-time" } embassy-futures = { version = "0.1.2", path = "../embassy-futures" } defmt = { version = "1.0.1", optional = true } [features] -defmt = ["dep:defmt", "embassy-net-driver-channel/defmt"] +defmt = ["dep:defmt", "embassy-net-driver-channel/defmt", "embassy-embedded-hal/defmt"] [package.metadata.embassy_docs] src_base = "https://github.com/embassy-rs/embassy/blob/embassy-net-wiznet-v$VERSION/embassy-net-wiznet/src/" diff --git a/embassy-net-wiznet/README.md b/embassy-net-wiznet/README.md index 2429599b0d..b428d83472 100644 --- a/embassy-net-wiznet/README.md +++ b/embassy-net-wiznet/README.md @@ -9,7 +9,7 @@ See [`examples`](https://github.com/embassy-rs/embassy/tree/main/examples/rp) di - W5500 - W5100S - W6100 -- W6300 (Single SPI only) +- W6300 (Single SPI and QSPI) ## Interoperability diff --git a/embassy-net-wiznet/src/chip/mod.rs b/embassy-net-wiznet/src/chip/mod.rs index 5521386441..0ada6e2c8b 100644 --- a/embassy-net-wiznet/src/chip/mod.rs +++ b/embassy-net-wiznet/src/chip/mod.rs @@ -9,9 +9,10 @@ mod w6100; pub use w6100::W6100; mod w6300; -use embedded_hal_async::spi::SpiDevice; pub use w6300::W6300; +use crate::wiznet_spi_interface::WiznetSpiBus; + pub(crate) trait SealedChip { type Address; @@ -47,9 +48,13 @@ pub(crate) trait SealedChip { fn rx_addr(addr: u16) -> Self::Address; fn tx_addr(addr: u16) -> Self::Address; - async fn bus_read(spi: &mut SPI, address: Self::Address, data: &mut [u8]) + async fn bus_read( + spi: &mut SPI, + address: Self::Address, + data: &mut [u8], + ) -> Result<(), SPI::Error>; + async fn bus_write(spi: &mut SPI, address: Self::Address, data: &[u8]) -> Result<(), SPI::Error>; - async fn bus_write(spi: &mut SPI, address: Self::Address, data: &[u8]) -> Result<(), SPI::Error>; } /// Trait for Wiznet chips. diff --git a/embassy-net-wiznet/src/chip/w5100s.rs b/embassy-net-wiznet/src/chip/w5100s.rs index 1eef2369eb..59bb74448e 100644 --- a/embassy-net-wiznet/src/chip/w5100s.rs +++ b/embassy-net-wiznet/src/chip/w5100s.rs @@ -1,4 +1,4 @@ -use embedded_hal_async::spi::{Operation, SpiDevice}; +use crate::wiznet_spi_interface::{WiznetSpiBus, WiznetSpiOperation}; const SOCKET_BASE: u16 = 0x400; const TX_BASE: u16 = 0x4000; @@ -44,22 +44,26 @@ impl super::SealedChip for W5100S { TX_BASE + addr } - async fn bus_read( + async fn bus_read( spi: &mut SPI, address: Self::Address, data: &mut [u8], ) -> Result<(), SPI::Error> { - spi.transaction(&mut [ - Operation::Write(&[0x0F, (address >> 8) as u8, address as u8]), - Operation::Read(data), + spi.transaction([ + WiznetSpiOperation::Write(&[0x0F, (address >> 8) as u8, address as u8]), + WiznetSpiOperation::Read(data), ]) .await } - async fn bus_write(spi: &mut SPI, address: Self::Address, data: &[u8]) -> Result<(), SPI::Error> { - spi.transaction(&mut [ - Operation::Write(&[0xF0, (address >> 8) as u8, address as u8]), - Operation::Write(data), + async fn bus_write( + spi: &mut SPI, + address: Self::Address, + data: &[u8], + ) -> Result<(), SPI::Error> { + spi.transaction([ + WiznetSpiOperation::Write(&[0xF0, (address >> 8) as u8, address as u8]), + WiznetSpiOperation::Write(data), ]) .await } diff --git a/embassy-net-wiznet/src/chip/w5500.rs b/embassy-net-wiznet/src/chip/w5500.rs index 19284c4302..356db83b59 100644 --- a/embassy-net-wiznet/src/chip/w5500.rs +++ b/embassy-net-wiznet/src/chip/w5500.rs @@ -1,4 +1,4 @@ -use embedded_hal_async::spi::{Operation, SpiDevice}; +use crate::wiznet_spi_interface::{WiznetSpiBus, WiznetSpiOperation}; #[repr(u8)] pub enum RegisterBlock { @@ -48,21 +48,27 @@ impl super::SealedChip for W5500 { (RegisterBlock::TxBuf, addr) } - async fn bus_read( + async fn bus_read( spi: &mut SPI, address: Self::Address, data: &mut [u8], ) -> Result<(), SPI::Error> { let addr = address.1.to_be_bytes(); let header = [addr[0], addr[1], (address.0 as u8) << 3]; - let operations = &mut [Operation::Write(&header), Operation::TransferInPlace(data)]; + + let operations = [WiznetSpiOperation::Write(&header), WiznetSpiOperation::Read(data)]; spi.transaction(operations).await } - async fn bus_write(spi: &mut SPI, address: Self::Address, data: &[u8]) -> Result<(), SPI::Error> { + async fn bus_write( + spi: &mut SPI, + address: Self::Address, + data: &[u8], + ) -> Result<(), SPI::Error> { let addr = address.1.to_be_bytes(); let header = [addr[0], addr[1], (address.0 as u8) << 3 | 0b0000_0100]; - let operations = &mut [Operation::Write(&header), Operation::Write(data)]; + + let operations = [WiznetSpiOperation::Write(&header), WiznetSpiOperation::Write(data)]; spi.transaction(operations).await } } diff --git a/embassy-net-wiznet/src/chip/w6100.rs b/embassy-net-wiznet/src/chip/w6100.rs index 740b0edaf3..78a53a2f82 100644 --- a/embassy-net-wiznet/src/chip/w6100.rs +++ b/embassy-net-wiznet/src/chip/w6100.rs @@ -1,4 +1,4 @@ -use embedded_hal_async::spi::{Operation, SpiDevice}; +use crate::wiznet_spi_interface::{WiznetSpiBus, WiznetSpiOperation}; #[repr(u8)] pub enum RegisterBlock { @@ -54,29 +54,35 @@ impl super::SealedChip for W6100 { (RegisterBlock::TxBuf, addr) } - async fn bus_read( + async fn bus_read( spi: &mut SPI, address: Self::Address, data: &mut [u8], ) -> Result<(), SPI::Error> { let address_phase = address.1.to_be_bytes(); let control_phase = [(address.0 as u8) << 3]; - let operations = &mut [ - Operation::Write(&address_phase), - Operation::Write(&control_phase), - Operation::TransferInPlace(data), + + let operations = [ + WiznetSpiOperation::Write(&address_phase), + WiznetSpiOperation::Write(&control_phase), + WiznetSpiOperation::Read(data), ]; spi.transaction(operations).await } - async fn bus_write(spi: &mut SPI, address: Self::Address, data: &[u8]) -> Result<(), SPI::Error> { + async fn bus_write( + spi: &mut SPI, + address: Self::Address, + data: &[u8], + ) -> Result<(), SPI::Error> { let address_phase = address.1.to_be_bytes(); let control_phase = [(address.0 as u8) << 3 | 0b0000_0100]; let data_phase = data; - let operations = &mut [ - Operation::Write(&address_phase[..]), - Operation::Write(&control_phase), - Operation::Write(&data_phase), + + let operations = [ + WiznetSpiOperation::Write(&address_phase[..]), + WiznetSpiOperation::Write(&control_phase), + WiznetSpiOperation::Write(&data_phase), ]; spi.transaction(operations).await } diff --git a/embassy-net-wiznet/src/chip/w6300.rs b/embassy-net-wiznet/src/chip/w6300.rs index 221a95ab98..a76cb180ac 100644 --- a/embassy-net-wiznet/src/chip/w6300.rs +++ b/embassy-net-wiznet/src/chip/w6300.rs @@ -1,4 +1,4 @@ -use embedded_hal_async::spi::{Operation, SpiDevice}; +use crate::wiznet_spi_interface::{SpiType, WiznetSpiBus, WiznetSpiOperation}; #[repr(u8)] pub enum RegisterBlock { @@ -8,6 +8,17 @@ pub enum RegisterBlock { RxBuf = 0x03, } +/// Return bits to be ORed with instruction byte to set SPI mode corresponding to given SPI type +const fn spi_mode_instruction_bits(spi_type: &SpiType) -> u8 { + // page 75 of datasheet + // https://docs.wiznet.io/pdf-viewer?file=%2Fassets%2Ffiles%2F20251204_W6300_DS_V101E-4f4cd2e75de8d76f51a741f6a492ea01.pdf + match spi_type { + SpiType::Single => 0b0000_0000, + SpiType::Dual => 0b0100_0000, + SpiType::Quad => 0b1000_0000, + } +} + /// Wiznet W6300 chip. pub enum W6300 {} @@ -55,7 +66,7 @@ impl super::SealedChip for W6300 { // Note: Bit 7 is MAC filter. On the W5500 this is normally turned ON however the W6300 will not successfully retrieve an IP address with this enabled. Disabling for now and will have live with the extra noise. const SOCKET_MODE_VALUE: u8 = 0b0000_0111; - const BUF_SIZE: u16 = 0x1000; + const BUF_SIZE: u16 = 0x8000; const AUTO_WRAP: bool = true; fn rx_addr(addr: u16) -> Self::Address { @@ -66,34 +77,42 @@ impl super::SealedChip for W6300 { (RegisterBlock::TxBuf, addr) } - async fn bus_read( + async fn bus_read( spi: &mut SPI, address: Self::Address, data: &mut [u8], ) -> Result<(), SPI::Error> { - let instruction_phase = [address.0 as u8]; - let address_phase = address.1.to_be_bytes(); - let dummy_phase = [0u8]; - let operations = &mut [ - Operation::Write(&instruction_phase), - Operation::Write(&address_phase), - Operation::Write(&dummy_phase), - Operation::TransferInPlace(data), + let spi_mode_bits: u8 = spi_mode_instruction_bits(&SPI::SPI_TYPE); + let instruction_phase = [(address.0 as u8) | spi_mode_bits]; + let address_offset = address.1.to_be_bytes(); + // Combine address offset with dummy for performance + let address_and_dummy = [address_offset[0], address_offset[1], 0u8]; + + let operations = [ + WiznetSpiOperation::WriteSingleLine(&instruction_phase), + WiznetSpiOperation::Write(&address_and_dummy), + WiznetSpiOperation::Read(data), ]; spi.transaction(operations).await } - async fn bus_write(spi: &mut SPI, address: Self::Address, data: &[u8]) -> Result<(), SPI::Error> { - // Set the Write Access Bit - let instruction_phase = [(address.0 as u8) | 0b0010_0000]; - let address_phase = address.1.to_be_bytes(); - let dummy_phase = [0u8]; + async fn bus_write( + spi: &mut SPI, + address: Self::Address, + data: &[u8], + ) -> Result<(), SPI::Error> { + const WRITE_ACCESS_BIT: u8 = 0b0010_0000; + let spi_mode_bits: u8 = spi_mode_instruction_bits(&SPI::SPI_TYPE); + // Set the SPI Mode and Write Access bits + let instruction_phase = [(address.0 as u8) | spi_mode_bits | WRITE_ACCESS_BIT]; + let address_offset = address.1.to_be_bytes(); + // Combine address offset with dummy for performance + let address_and_dummy = [address_offset[0], address_offset[1], 0u8]; - let operations = &mut [ - Operation::Write(&instruction_phase), - Operation::Write(&address_phase), - Operation::Write(&dummy_phase), - Operation::Write(data), + let operations = [ + WiznetSpiOperation::WriteSingleLine(&instruction_phase), + WiznetSpiOperation::Write(&address_and_dummy), + WiznetSpiOperation::Write(data), ]; spi.transaction(operations).await } diff --git a/embassy-net-wiznet/src/device.rs b/embassy-net-wiznet/src/device.rs index 8391f497f8..ba2813006a 100644 --- a/embassy-net-wiznet/src/device.rs +++ b/embassy-net-wiznet/src/device.rs @@ -1,6 +1,6 @@ use core::marker::PhantomData; -use embedded_hal_async::spi::SpiDevice; +use crate::wiznet_spi_interface::WiznetSpiBus; use crate::chip::Chip; @@ -19,15 +19,15 @@ enum Interrupt { /// Wiznet chip in MACRAW mode #[derive(Debug)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub(crate) struct WiznetDevice { - spi: SPI, +pub(crate) struct WiznetDevice { + spi: WizInterface, _phantom: PhantomData, } /// Error type when initializing a new Wiznet device pub enum InitError { - /// Error occurred when sending or receiving SPI data - SpiError(SE), + /// Error occurred when sending or receiving WizInterface data + InterfaceError(SE), /// The chip returned a version that isn't expected or supported InvalidChipVersion { /// The version that is supported @@ -39,7 +39,7 @@ pub enum InitError { impl From for InitError { fn from(e: SE) -> Self { - InitError::SpiError(e) + InitError::InterfaceError(e) } } @@ -49,7 +49,7 @@ where { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { match self { - InitError::SpiError(e) => write!(f, "SpiError({:?})", e), + InitError::InterfaceError(e) => write!(f, "SpiError({:?})", e), InitError::InvalidChipVersion { expected, actual } => { write!(f, "InvalidChipVersion {{ expected: {}, actual: {} }}", expected, actual) } @@ -64,7 +64,7 @@ where { fn format(&self, f: defmt::Formatter) { match self { - InitError::SpiError(e) => defmt::write!(f, "SpiError({})", e), + InitError::InterfaceError(e) => defmt::write!(f, "SpiError({})", e), InitError::InvalidChipVersion { expected, actual } => { defmt::write!(f, "InvalidChipVersion {{ expected: {}, actual: {} }}", expected, actual) } @@ -72,9 +72,9 @@ where } } -impl WiznetDevice { +impl WiznetDevice { /// Create and initialize the driver - pub async fn new(spi: SPI, mac_addr: [u8; 6]) -> Result> { + pub async fn new(spi: WizInterface, mac_addr: [u8; 6]) -> Result> { let mut this = Self { spi, _phantom: PhantomData, @@ -114,47 +114,47 @@ impl WiznetDevice { Ok(this) } - async fn bus_read(&mut self, address: C::Address, data: &mut [u8]) -> Result<(), SPI::Error> { + async fn bus_read(&mut self, address: C::Address, data: &mut [u8]) -> Result<(), WizInterface::Error> { C::bus_read(&mut self.spi, address, data).await } - async fn bus_write(&mut self, address: C::Address, data: &[u8]) -> Result<(), SPI::Error> { + async fn bus_write(&mut self, address: C::Address, data: &[u8]) -> Result<(), WizInterface::Error> { C::bus_write(&mut self.spi, address, data).await } - async fn reset_interrupt(&mut self, code: Interrupt) -> Result<(), SPI::Error> { + async fn reset_interrupt(&mut self, code: Interrupt) -> Result<(), WizInterface::Error> { let data = [code as u8]; self.bus_write(C::SOCKET_INTR_CLR, &data).await } - async fn get_tx_write_ptr(&mut self) -> Result { + async fn get_tx_write_ptr(&mut self) -> Result { let mut data = [0u8; 2]; self.bus_read(C::SOCKET_TX_DATA_WRITE_PTR, &mut data).await?; Ok(u16::from_be_bytes(data)) } - async fn set_tx_write_ptr(&mut self, ptr: u16) -> Result<(), SPI::Error> { + async fn set_tx_write_ptr(&mut self, ptr: u16) -> Result<(), WizInterface::Error> { let data = ptr.to_be_bytes(); self.bus_write(C::SOCKET_TX_DATA_WRITE_PTR, &data).await } - async fn get_rx_read_ptr(&mut self) -> Result { + async fn get_rx_read_ptr(&mut self) -> Result { let mut data = [0u8; 2]; self.bus_read(C::SOCKET_RX_DATA_READ_PTR, &mut data).await?; Ok(u16::from_be_bytes(data)) } - async fn set_rx_read_ptr(&mut self, ptr: u16) -> Result<(), SPI::Error> { + async fn set_rx_read_ptr(&mut self, ptr: u16) -> Result<(), WizInterface::Error> { let data = ptr.to_be_bytes(); self.bus_write(C::SOCKET_RX_DATA_READ_PTR, &data).await } - async fn command(&mut self, command: Command) -> Result<(), SPI::Error> { + async fn command(&mut self, command: Command) -> Result<(), WizInterface::Error> { let data = [command as u8]; self.bus_write(C::SOCKET_COMMAND, &data).await } - async fn get_rx_size(&mut self) -> Result { + async fn get_rx_size(&mut self) -> Result { loop { // Wait until two sequential reads are equal let mut res0 = [0u8; 2]; @@ -167,14 +167,14 @@ impl WiznetDevice { } } - async fn get_tx_free_size(&mut self) -> Result { + async fn get_tx_free_size(&mut self) -> Result { let mut data = [0; 2]; self.bus_read(C::SOCKET_TX_FREE_SIZE, &mut data).await?; Ok(u16::from_be_bytes(data)) } /// Read bytes from the RX buffer. - async fn read_bytes(&mut self, read_ptr: &mut u16, buffer: &mut [u8]) -> Result<(), SPI::Error> { + async fn read_bytes(&mut self, read_ptr: &mut u16, buffer: &mut [u8]) -> Result<(), WizInterface::Error> { if C::AUTO_WRAP { self.bus_read(C::rx_addr(*read_ptr), buffer).await?; } else { @@ -194,7 +194,7 @@ impl WiznetDevice { } /// Read an ethernet frame from the device. Returns the number of bytes read. - pub async fn read_frame(&mut self, frame: &mut [u8]) -> Result { + pub async fn read_frame(&mut self, frame: &mut [u8]) -> Result { let rx_size = self.get_rx_size().await? as usize; if rx_size == 0 { return Ok(0); @@ -239,7 +239,7 @@ impl WiznetDevice { } /// Write an ethernet frame to the device. Returns number of bytes written - pub async fn write_frame(&mut self, frame: &[u8]) -> Result { + pub async fn write_frame(&mut self, frame: &[u8]) -> Result { while self.get_tx_free_size().await? < frame.len() as u16 {} let write_ptr = self.get_tx_write_ptr().await?; diff --git a/embassy-net-wiznet/src/lib.rs b/embassy-net-wiznet/src/lib.rs index 776e531741..07c3cd6ecc 100644 --- a/embassy-net-wiznet/src/lib.rs +++ b/embassy-net-wiznet/src/lib.rs @@ -9,13 +9,15 @@ mod fmt; pub mod chip; mod device; +pub mod wiznet_spi_interface; + use embassy_futures::select::{Either3, select3}; use embassy_net_driver_channel as ch; use embassy_net_driver_channel::driver::LinkState; use embassy_time::{Duration, Ticker, Timer}; use embedded_hal::digital::OutputPin; use embedded_hal_async::digital::Wait; -use embedded_hal_async::spi::SpiDevice; +use wiznet_spi_interface::WiznetSpiBus; use crate::chip::Chip; pub use crate::device::InitError; @@ -53,15 +55,15 @@ impl State { /// Background runner for the driver. /// /// You must call `.run()` in a background task for the driver to operate. -pub struct Runner<'d, C: Chip, SPI: SpiDevice, INT: Wait, RST: OutputPin> { - mac: WiznetDevice, +pub struct Runner<'d, C: Chip, WizInterface: WiznetSpiBus, INT: Wait, RST: OutputPin> { + mac: WiznetDevice, ch: ch::Runner<'d, MTU>, int: INT, _reset: RST, } /// You must call this in a background task for the driver to operate. -impl<'d, C: Chip, SPI: SpiDevice, INT: Wait, RST: OutputPin> Runner<'d, C, SPI, INT, RST> { +impl<'d, C: Chip, WizInterface: WiznetSpiBus, INT: Wait, RST: OutputPin> Runner<'d, C, WizInterface, INT, RST> { /// Run the driver. pub async fn run(mut self) -> ! { let (state_chan, mut rx_chan, mut tx_chan) = self.ch.split(); @@ -103,13 +105,21 @@ impl<'d, C: Chip, SPI: SpiDevice, INT: Wait, RST: OutputPin> Runner<'d, C, SPI, /// This returns two structs: /// - a `Device` that you must pass to the `embassy-net` stack. /// - a `Runner`. You must call `.run()` on it in a background task. -pub async fn new<'a, const N_RX: usize, const N_TX: usize, C: Chip, SPI: SpiDevice, INT: Wait, RST: OutputPin>( +pub async fn new< + 'a, + const N_RX: usize, + const N_TX: usize, + C: Chip, + WizInterface: WiznetSpiBus, + INT: Wait, + RST: OutputPin, +>( mac_addr: [u8; 6], state: &'a mut State, - spi_dev: SPI, + spi_dev: WizInterface, int: INT, mut reset: RST, -) -> Result<(Device<'a>, Runner<'a, C, SPI, INT, RST>), InitError> { +) -> Result<(Device<'a>, Runner<'a, C, WizInterface, INT, RST>), InitError> { // Reset the chip. reset.set_low().ok(); // Ensure the reset is registered. diff --git a/embassy-net-wiznet/src/wiznet_spi_interface/mod.rs b/embassy-net-wiznet/src/wiznet_spi_interface/mod.rs new file mode 100644 index 0000000000..67e7a1c467 --- /dev/null +++ b/embassy-net-wiznet/src/wiznet_spi_interface/mod.rs @@ -0,0 +1,51 @@ +//! Trait for different types of SPI and blanket implementations + +mod qspi; +mod spi; + +pub use qspi::WiznetQspiBus; + +use embedded_hal::spi::ErrorType; + +/// Number of lines used by SPI +#[derive(Debug)] +pub enum SpiType { + /// Regular, full-duplex SPI with 2 data lines + Single, + /// Dual SPI - half-duplex using 2 bi-directional data lines + Dual, + /// Quad SPI - half-duplex using 4 bi-directional data lines + Quad, +} + +/// Wiznet SPI operations to build transactions with +#[derive(Debug, PartialEq, Eq)] +pub enum WiznetSpiOperation<'a> { + /// Read data into the provided buffer. + Read(&'a mut [u8]), + /// Write data from the provided buffer, discarding possible read data. + Write(&'a [u8]), + /// Write data from the provided buffer using a single line. + /// + /// Useful for instruction phases + WriteSingleLine(&'a [u8]), +} + +/// Interface for communicating with Wiznet chip with various types of SPI +pub trait WiznetSpiBus: ErrorType { + /// Type of SPI implemented by the type + const SPI_TYPE: SpiType; + + /// Perform a transaction against the device. + /// + /// - Locks the bus + /// - Asserts the CS (Chip Select) pin. + /// - Performs all the operations. + /// - [Flushes](SpiBus::flush) the bus. + /// - Deasserts the CS pin. + /// - Unlocks the bus. + async fn transaction<'a, const N: usize>( + &mut self, + operations: [WiznetSpiOperation<'a>; N], + ) -> Result<(), Self::Error>; +} diff --git a/embassy-net-wiznet/src/wiznet_spi_interface/qspi.rs b/embassy-net-wiznet/src/wiznet_spi_interface/qspi.rs new file mode 100644 index 0000000000..92d43292eb --- /dev/null +++ b/embassy-net-wiznet/src/wiznet_spi_interface/qspi.rs @@ -0,0 +1,33 @@ +use embassy_embedded_hal::qspi; +use embedded_hal_async::spi::ErrorType; + +use crate::wiznet_spi_interface::{SpiType, WiznetSpiBus, WiznetSpiOperation}; + +impl<'a> From> for qspi::traits::Operation<'a, u8> { + fn from(value: WiznetSpiOperation<'a>) -> Self { + match value { + WiznetSpiOperation::Read(data) => qspi::traits::Operation::Read(data), + WiznetSpiOperation::Write(data) => qspi::traits::Operation::Write(data), + WiznetSpiOperation::WriteSingleLine(data) => qspi::traits::Operation::WriteSingleLine(data), + } + } +} + +/// Wrapper to use QSPI with Wiznet device +pub struct WiznetQspiBus(pub T); + +impl ErrorType for WiznetQspiBus { + type Error = T::Error; +} + +impl WiznetSpiBus for WiznetQspiBus { + const SPI_TYPE: SpiType = SpiType::Quad; + + async fn transaction<'a, const N: usize>( + &mut self, + operations: [WiznetSpiOperation<'a>; N], + ) -> Result<(), T::Error> { + let mut ops = operations.map(|op| op.into()); + self.0.transaction(&mut ops).await + } +} diff --git a/embassy-net-wiznet/src/wiznet_spi_interface/spi.rs b/embassy-net-wiznet/src/wiznet_spi_interface/spi.rs new file mode 100644 index 0000000000..0a892585db --- /dev/null +++ b/embassy-net-wiznet/src/wiznet_spi_interface/spi.rs @@ -0,0 +1,24 @@ +use embedded_hal_async::spi; + +use crate::wiznet_spi_interface::{SpiType, WiznetSpiBus, WiznetSpiOperation}; + +impl<'a> From> for spi::Operation<'a, u8> { + fn from(value: WiznetSpiOperation<'a>) -> Self { + match value { + WiznetSpiOperation::Read(data) => spi::Operation::Read(data), + WiznetSpiOperation::Write(data) | WiznetSpiOperation::WriteSingleLine(data) => spi::Operation::Write(data), + } + } +} + +impl WiznetSpiBus for SPI { + const SPI_TYPE: SpiType = SpiType::Single; + + async fn transaction<'a, const N: usize>( + &mut self, + operations: [WiznetSpiOperation<'a>; N], + ) -> Result<(), Self::Error> { + let mut ops = operations.map(|op| op.into()); + self.transaction(&mut ops).await + } +} diff --git a/embassy-rp/src/pio_programs/mod.rs b/embassy-rp/src/pio_programs/mod.rs index 74a3714c77..bc00b12d2a 100644 --- a/embassy-rp/src/pio_programs/mod.rs +++ b/embassy-rp/src/pio_programs/mod.rs @@ -6,6 +6,7 @@ pub mod hd44780; pub mod i2s; pub mod onewire; pub mod pwm; +pub mod qspi; pub mod rotary_encoder; pub mod spi; pub mod stepper; diff --git a/embassy-rp/src/pio_programs/qspi.rs b/embassy-rp/src/pio_programs/qspi.rs new file mode 100644 index 0000000000..de12c9b9ee --- /dev/null +++ b/embassy-rp/src/pio_programs/qspi.rs @@ -0,0 +1,486 @@ +//! PIO backed QSPI drivers + +use core::marker::PhantomData; + +use embassy_hal_internal::Peri; +use embedded_hal_02::spi::{Phase, Polarity}; +use fixed::traits::ToFixed; +use fixed::types::extra::U8; + +use crate::clocks::clk_sys_freq; +use crate::gpio::{Level, SlewRate}; +use crate::pio::{Common, Direction, Instance, Irq, LoadedProgram, Pin, PioPin, ShiftDirection, StateMachine}; +use crate::spi::{Async, Blocking, Config, Mode}; +use crate::{dma, interrupt}; + +/// This struct represents a set of QSPI program loaded into pio instruction memory. +struct PioQspiProgram<'d, PIO: Instance> { + prg: LoadedProgram<'d, PIO>, + read_jmp: u16, + write_jmp: u16, + write_single_line_jmp: u16, + phase: Phase, +} + +impl<'d, PIO: Instance> PioQspiProgram<'d, PIO> { + /// Load the qspi program into the given pio + pub fn new(common: &mut Common<'d, PIO>, phase: Phase) -> Self { + // These PIO programs are adapted from the datasheet (3.6.1 in + // RP2040 datasheet, 11.6.1 in RP2350 datasheet) + + // Pin assignments: + // - SCK is side-set pin 0 + // - QD0 is pin 0 + // - QD1 is pin 1 + // - QD2 is pin 2 + // - QD3 is pin 3 + // + // Auto-push and auto-pull must be enabled, and the serial frame size is set by + // configuring the push/pull threshold. Shift left/right is fine, but you must + // justify the data yourself. This is done most conveniently for frame sizes of + // 8 or 16 bits by using the narrow store replication and narrow load byte + // picking behavior of RP2040's IO fabric. + + match phase { + Phase::CaptureOnFirstTransition => { + // Clock phase = 0: data is captured on the leading edge of each SCK pulse, and + // transitions on the trailing edge, or some time before the first leading edge. + + // TODO: might need to make read hang after completing to prevent clocking and + // reading rubbish + let prg = pio::pio_asm!( + r#" + ; Use 1 bit for side-set for SCK + .side_set 1 + ; Set irq to indicate ready for new operation + irq set 0 rel side 0 + + public read_entry: + ; Set all data pins to input + set pindirs 0b0000 side 0 + + read_n_nibbles: + ; Read (num_nibbles_to_read - 1) from output shift register + out x, 32 side 0 + + read_nibble: + nop side 0 [1] + in pins, 4 side 1 + jmp x-- read_nibble side 1 + + ; Set irq to indicate ready for new operation + irq set 0 rel side 0 + jmp read_n_nibbles side 0 + + public write_entry: + ; Set all data pins to output + set pindirs 0b1111 side 0 + + write_n_nibbles: + ; Read (num_nibbles_to_write - 1) from output shift register + out x, 32 side 0 + + write_nibble: + ; Side set proceeds even if instruction stalls, so we stall with SCK low + out pins, 4 side 0 [1] + nop side 1 + jmp x-- write_nibble side 1 + + ; Set irq to indicate ready for new operation + irq set 0 rel side 0 + jmp write_n_nibbles side 0 + + public write_single_line_entry: + ; Set QD0 pin to output + set pindirs 0b0001 side 0 + + write_single_line_n_nibbles: + ; Read (num_bits_to_write - 1) from output shift register + out x, 32 side 0 + + write_bit: + ; Side set proceeds even if instruction stalls, so we stall with SCK low + out pins, 1 side 0 [1] + nop side 1 + jmp x-- write_bit side 1 + + ; Set irq to indicate ready for new operation + irq set 0 rel side 0 + jmp write_single_line_n_nibbles side 0 + "# + ); + + let read_jmp = pio::InstructionOperands::JMP { + address: prg.public_defines.read_entry as u8, + condition: pio::JmpCondition::Always, + } + .encode(); + let write_jmp = pio::InstructionOperands::JMP { + address: prg.public_defines.write_entry as u8, + condition: pio::JmpCondition::Always, + } + .encode(); + let write_single_line_jmp = pio::InstructionOperands::JMP { + address: prg.public_defines.write_single_line_entry as u8, + condition: pio::JmpCondition::Always, + } + .encode(); + + Self { + prg: common.load_program(&prg.program), + read_jmp, + write_jmp, + write_single_line_jmp, + phase, + } + } + Phase::CaptureOnSecondTransition => { + todo!() + } + } + } +} + +/// PIO QSPI errors. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[non_exhaustive] +pub enum Error { + // No errors for now +} + +/// PIO based QSPI driver. +/// Unlike other PIO programs, the PIO QSPI driver owns and holds a reference to +/// the PIO memory it uses. This is so that it can be reconfigured at runtime if +/// desired. +pub struct Qspi<'d, PIO: Instance, const SM: usize, M: Mode> { + sm: StateMachine<'d, PIO, SM>, + pio_irq: Irq<'d, PIO, SM>, + cfg: crate::pio::Config<'d, PIO>, + program: Option>, + clk_pin: Pin<'d, PIO>, + tx_dma: Option>, + rx_dma: Option>, + phantom: PhantomData, +} + +impl<'d, PIO: Instance, const SM: usize, M: Mode> Qspi<'d, PIO, SM, M> { + #[allow(clippy::too_many_arguments)] + fn new_inner( + pio: &mut Common<'d, PIO>, + mut sm: StateMachine<'d, PIO, SM>, + pio_irq: Irq<'d, PIO, SM>, + clk_pin: Peri<'d, impl PioPin>, + qd0_pin: Peri<'d, impl PioPin>, + qd1_pin: Peri<'d, impl PioPin>, + qd2_pin: Peri<'d, impl PioPin>, + qd3_pin: Peri<'d, impl PioPin>, + tx_dma: Option>, + rx_dma: Option>, + config: Config, + ) -> Self { + let program = PioQspiProgram::new(pio, config.phase); + + let mut clk_pin = pio.make_pio_pin(clk_pin); + let mut qd0_pin = pio.make_pio_pin(qd0_pin); + let mut qd1_pin = pio.make_pio_pin(qd1_pin); + let mut qd2_pin = pio.make_pio_pin(qd2_pin); + let mut qd3_pin = pio.make_pio_pin(qd3_pin); + + if let Polarity::IdleHigh = config.polarity { + clk_pin.set_output_inversion(true); + } else { + clk_pin.set_output_inversion(false); + } + + clk_pin.set_slew_rate(SlewRate::Fast); + + for pin in [&mut qd0_pin, &mut qd1_pin, &mut qd2_pin, &mut qd3_pin] { + pin.set_input_sync_bypass(true); + // pin.set_pull(Pull::Down); + // pin.set_schmitt(true); + } + + let mut cfg = crate::pio::Config::default(); + + cfg.use_program(&program.prg, &[&clk_pin]); + cfg.set_in_pins(&[&qd0_pin, &qd1_pin, &qd2_pin, &qd3_pin]); + cfg.set_out_pins(&[&qd0_pin, &qd1_pin, &qd2_pin, &qd3_pin]); + cfg.set_set_pins(&[&qd0_pin, &qd1_pin, &qd2_pin, &qd3_pin]); + + cfg.shift_in.auto_fill = true; + cfg.shift_in.direction = ShiftDirection::Left; + cfg.shift_in.threshold = 8; + + cfg.shift_out.auto_fill = true; + cfg.shift_out.direction = ShiftDirection::Left; + cfg.shift_out.threshold = 8; + + cfg.clock_divider = calculate_clock_divider(config.frequency); + let bytes = cfg.clock_divider.to_le_bytes(); + defmt::info!("clock divider: {:?}", bytes); + + sm.set_config(&cfg); + + sm.set_pins(Level::Low, &[&clk_pin, &qd0_pin, &qd1_pin, &qd2_pin, &qd3_pin]); + sm.set_pin_dirs(Direction::Out, &[&clk_pin]); + + sm.set_enable(true); + + Self { + sm, + pio_irq, + program: Some(program), + cfg, + clk_pin, + tx_dma, + rx_dma, + phantom: PhantomData, + } + } + + /// Block execution until QSPI is done. + pub fn flush(&mut self) -> Result<(), Error> { + // Wait for all words in the FIFO to have been pulled by the SM + while !self.sm.tx().empty() {} + + // Wait for last value to be written out to the wire + while !self.sm.tx().stalled() {} + + Ok(()) + } + + /// Wait for QSPI ready for new operation + pub async fn async_flush(&mut self) { + // IRQ fires when ready for a new operation + self.pio_irq.wait().await; + } + + /// Set QSPI frequency. + pub fn set_frequency(&mut self, freq: u32) { + self.sm.set_enable(false); + + let divider = calculate_clock_divider(freq); + + // save into the config for later but dont use sm.set_config() since + // that operation is relatively more expensive than just setting the + // clock divider + self.cfg.clock_divider = divider; + self.sm.set_clock_divider(divider); + + self.sm.set_enable(true); + } + + /// Set QSPI config. + /// + /// This operation will panic if the PIO program needs to be reloaded and + /// there is insufficient room. This is unlikely since the programs for each + /// phase only differ in size by a single instruction. + pub fn set_config(&mut self, pio: &mut Common<'d, PIO>, config: &Config) { + self.flush(); + self.sm.set_enable(false); + + self.cfg.clock_divider = calculate_clock_divider(config.frequency); + + if let Polarity::IdleHigh = config.polarity { + self.clk_pin.set_output_inversion(true); + } else { + self.clk_pin.set_output_inversion(false); + } + + if self.program.as_ref().unwrap().phase != config.phase { + let old_program = self.program.take().unwrap(); + + // SAFETY: the state machine is disabled while this happens + unsafe { + pio.free_instr(old_program.prg.used_memory); + }; + + let new_program = PioQspiProgram::new(pio, config.phase); + + self.cfg.use_program(&new_program.prg, &[&self.clk_pin]); + self.program = Some(new_program); + } + + self.sm.set_config(&self.cfg); + self.sm.restart(); + + self.sm.set_enable(true); + } +} + +fn calculate_clock_divider(frequency_hz: u32) -> fixed::FixedU32 { + // we multiply by 4 since each clock period is equal to 4 instructions + + let sys_freq = clk_sys_freq().to_fixed::>(); + let target_freq = (frequency_hz * 4).to_fixed::>(); + (sys_freq / target_freq).to_fixed() +} + +impl<'d, PIO: Instance, const SM: usize> Qspi<'d, PIO, SM, Blocking> { + /// Create an QSPI driver in blocking mode. + pub fn new_blocking( + pio: &mut Common<'d, PIO>, + sm: StateMachine<'d, PIO, SM>, + pio_irq: Irq<'d, PIO, SM>, + clk: Peri<'d, impl PioPin>, + qd0: Peri<'d, impl PioPin>, + qd1: Peri<'d, impl PioPin>, + qd2: Peri<'d, impl PioPin>, + qd3: Peri<'d, impl PioPin>, + config: Config, + ) -> Self { + Self::new_inner(pio, sm, pio_irq, clk, qd0, qd1, qd2, qd3, None, None, config) + } +} + +impl<'d, PIO: Instance, const SM: usize> Qspi<'d, PIO, SM, Async> { + /// Create an QSPI driver in async mode supporting DMA operations. + #[allow(clippy::too_many_arguments)] + pub fn new( + pio: &mut Common<'d, PIO>, + sm: StateMachine<'d, PIO, SM>, + pio_irq: Irq<'d, PIO, SM>, + clk: Peri<'d, impl PioPin>, + qd0: Peri<'d, impl PioPin>, + qd1: Peri<'d, impl PioPin>, + qd2: Peri<'d, impl PioPin>, + qd3: Peri<'d, impl PioPin>, + tx_dma: Peri<'d, TxDma>, + rx_dma: Peri<'d, RxDma>, + irq: impl interrupt::typelevel::Binding> + + interrupt::typelevel::Binding> + + 'd, + config: Config, + ) -> Self { + let tx_dma_ch = dma::Channel::new(tx_dma, irq); + let rx_dma_ch = dma::Channel::new(rx_dma, irq); + Self::new_inner( + pio, + sm, + pio_irq, + clk, + qd0, + qd1, + qd2, + qd3, + Some(tx_dma_ch), + Some(rx_dma_ch), + config, + ) + } + + /// Read data from QSPI using DMA. + pub async fn read(&mut self, buffer: &mut [u8]) -> Result<(), Error> { + self.async_flush().await; + + // jump to read + unsafe { + self.sm.exec_instr(self.program.as_ref().unwrap().read_jmp); + } + + let (rx, tx) = self.sm.rx_tx(); + + let num_nibbles: u32 = 2 * (buffer.len() as u32); + tx.wait_push(num_nibbles - 1).await; + + let mut rx_ch = self.rx_dma.as_mut().unwrap().reborrow(); + let rx_transfer = rx.dma_pull(&mut rx_ch, buffer, false); + + rx_transfer.await; + defmt::debug!("read: {}", &buffer); + + Ok(()) + } + + /// Write data to QSPI using DMA. + pub async fn write(&mut self, buffer: &[u8]) -> Result<(), Error> { + self.async_flush().await; + + // jump to write + unsafe { + self.sm.exec_instr(self.program.as_ref().unwrap().write_jmp); + } + + let tx = self.sm.tx(); + + let num_nibbles: u32 = 2 * (buffer.len() as u32); + tx.wait_push(num_nibbles - 1).await; + + let mut tx_ch = self.tx_dma.as_mut().unwrap().reborrow(); + let tx_transfer = tx.dma_push(&mut tx_ch, buffer, false); + + tx_transfer.await; + defmt::debug!("wrote: {}", &buffer); + + Ok(()) + } + + /// Write data using a single line to QSPI using DMA. + pub async fn write_single_line(&mut self, buffer: &[u8]) -> Result<(), Error> { + self.async_flush().await; + + // jump to write + unsafe { + self.sm.exec_instr(self.program.as_ref().unwrap().write_single_line_jmp); + } + + let tx = self.sm.tx(); + + let num_bits: u32 = 8 * (buffer.len() as u32); + tx.wait_push(num_bits - 1).await; + + let mut tx_ch = self.tx_dma.as_mut().unwrap().reborrow(); + let tx_transfer = tx.dma_push(&mut tx_ch, buffer, false); + + tx_transfer.await; + defmt::debug!("wrote single line: {}", &buffer); + + Ok(()) + } +} + +// HAL traits: + +impl embassy_embedded_hal::qspi::traits::Error for Error { + fn kind(&self) -> embedded_hal_1::spi::ErrorKind { + match *self {} + } +} + +impl<'d, PIO: Instance, const SM: usize, M: Mode> embassy_embedded_hal::qspi::traits::ErrorType + for Qspi<'d, PIO, SM, M> +{ + type Error = Error; +} + +impl<'d, PIO: Instance, const SM: usize> embassy_embedded_hal::qspi::traits::QspiBus for Qspi<'d, PIO, SM, Async> { + async fn flush(&mut self) -> Result<(), Self::Error> { + // wait for the ready IRQ to fire + self.async_flush().await; + // set the ready IRQ back + const RESET_IRQ: u16 = pio::InstructionOperands::IRQ { + clear: false, + wait: false, + index: 0, + index_mode: pio::IrqIndexMode::REL, + } + .encode(); + unsafe { + self.sm.exec_instr(RESET_IRQ); + } + Ok(()) + } + + async fn read(&mut self, words: &mut [u8]) -> Result<(), Self::Error> { + self.read(words).await + } + + async fn write(&mut self, words: &[u8]) -> Result<(), Self::Error> { + self.write(words).await + } + + async fn write_single_line(&mut self, words: &[u8]) -> Result<(), Self::Error> { + self.write_single_line(words).await + } +} diff --git a/examples/rp235x/src/bin/ethernet_w6300_qspi_udp.rs b/examples/rp235x/src/bin/ethernet_w6300_qspi_udp.rs new file mode 100644 index 0000000000..0930899c0a --- /dev/null +++ b/examples/rp235x/src/bin/ethernet_w6300_qspi_udp.rs @@ -0,0 +1,151 @@ +//! This example implements a UDP server listening on port 1234 and echoing back the data. +//! +//! Example written for the [`WIZnet W6300-EVB-Pico2`](https://wiznet.io/products/evaluation-boards/w6300-evb-pico2) board. + +#![no_std] +#![no_main] + +use defmt::*; +use embassy_embedded_hal::qspi::exclusive::ExclusiveDevice; +use embassy_executor::Spawner; +use embassy_futures::yield_now; +use embassy_net::udp::{PacketMetadata, UdpSocket}; +use embassy_net::{Stack, StackResources}; +use embassy_net_wiznet::chip::W6300; +use embassy_net_wiznet::wiznet_spi_interface::WiznetQspiBus; +use embassy_net_wiznet::*; +use embassy_rp::clocks::RoscRng; +use embassy_rp::gpio::{Input, Level, Output, Pull}; +use embassy_rp::peripherals::{DMA_CH0, DMA_CH1, PIO0}; +use embassy_rp::pio::{InterruptHandler, Pio}; +use embassy_rp::pio_programs::qspi::Qspi; +use embassy_rp::spi::{Async, Config as SpiConfig}; +use embassy_rp::{bind_interrupts, dma}; +use embassy_time::Delay; +use static_cell::StaticCell; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + PIO0_IRQ_0 => InterruptHandler; + DMA_IRQ_0 => dma::InterruptHandler, dma::InterruptHandler; +}); + +#[embassy_executor::task] +async fn ethernet_task( + runner: Runner< + 'static, + W6300, + WiznetQspiBus, Output<'static>, Delay>>, + Input<'static>, + Output<'static>, + >, +) -> ! { + runner.run().await +} + +#[embassy_executor::task] +async fn net_task(mut runner: embassy_net::Runner<'static, Device<'static>>) -> ! { + runner.run().await +} + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + let rp_peripherals = embassy_rp::init(Default::default()); + + let mut rng = RoscRng; + let mut spi_cfg = SpiConfig::default(); + spi_cfg.frequency = 25_000_000; + + let Pio { + mut common, sm0, irq0, .. + } = Pio::new(rp_peripherals.PIO0, Irqs); + + let clk = rp_peripherals.PIN_17; + let qspi_0 = rp_peripherals.PIN_18; + let qspi_1 = rp_peripherals.PIN_19; + let qspi_2 = rp_peripherals.PIN_20; + let qspi_3 = rp_peripherals.PIN_21; + + let qspi = Qspi::new( + &mut common, + sm0, + irq0, + clk, + qspi_0, + qspi_1, + qspi_2, + qspi_3, + rp_peripherals.DMA_CH0, + rp_peripherals.DMA_CH1, + Irqs, + spi_cfg, + ); + + let cs = Output::new(rp_peripherals.PIN_16, Level::High); + let w6300_int = Input::new(rp_peripherals.PIN_15, Pull::Up); + let w6300_reset = Output::new(rp_peripherals.PIN_22, Level::High); + + let mac_addr = [0x02, 0x00, 0x00, 0x00, 0x00, 0x00]; + + static STATE: StaticCell> = StaticCell::new(); + let state = STATE.init(State::<8, 8>::new()); + + let (device, runner) = embassy_net_wiznet::new( + mac_addr, + state, + WiznetQspiBus(ExclusiveDevice::new(qspi, cs, Delay).unwrap()), + w6300_int, + w6300_reset, + ) + .await + .unwrap(); + spawner.spawn(unwrap!(ethernet_task(runner))); + + // Generate random seed + let seed = rng.next_u64(); + + // Init network stack + static RESOURCES: StaticCell> = StaticCell::new(); + let (stack, runner) = embassy_net::new( + device, + embassy_net::Config::dhcpv4(Default::default()), + RESOURCES.init(StackResources::new()), + seed, + ); + + // Launch network task + spawner.spawn(unwrap!(net_task(runner))); + + info!("Waiting for DHCP..."); + let cfg = wait_for_config(stack).await; + let local_addr = cfg.address.address(); + info!("IP address: {:?}", local_addr); + + // Then we can use it! + let mut rx_buffer = [0; 4096]; + let mut tx_buffer = [0; 4096]; + let mut rx_meta = [PacketMetadata::EMPTY; 16]; + let mut tx_meta = [PacketMetadata::EMPTY; 16]; + let mut buf = [0; 4096]; + loop { + let mut socket = UdpSocket::new(stack, &mut rx_meta, &mut rx_buffer, &mut tx_meta, &mut tx_buffer); + socket.bind(1234).unwrap(); + + loop { + let (n, ep) = socket.recv_from(&mut buf).await.unwrap(); + if let Ok(s) = core::str::from_utf8(&buf[..n]) { + info!("rxd from {}: {}", ep, s); + } + socket.send_to(&buf[..n], ep).await.unwrap(); + } + } +} + +async fn wait_for_config(stack: Stack<'static>) -> embassy_net::StaticConfigV4 { + loop { + if let Some(config) = stack.config_v4() { + return config.clone(); + } + yield_now().await; + } +}