Expand description

Implement embedded_hal traits for Spi structs

As noted in the spi module documentation, the embedded-hal trait implementations vary by both Size and Capability. Each implementation is optimized to take advantage of all information known at compile-time, so it is importatnt to carefully read the documentation in this module.

Variations by Size

Remember that SAMx5x chips operate in 32-bit extension mode and use the hardware LENGTH counter to set the number of bytes in each transaction. The transaction Length is usually tracked at compile-time using type-level integers from the typenum crate, but it can also be tracked at run-time when using a DynLength.

The transaction Lengths can be sub-divided into three groups:

  • Lengths of 1-4 bytes can be completed in a single read/write of the DATA register. These Lengths are marked as AtomicSizes.
  • Lengths GreaterThan4 are known at compile-time but cannot be completed atomically.
  • A DynLength can be any length, but the value is only known at run-time.

In general, transaction lengths with an AtomicSize implement embedded HAL traits with the corresponding Word type. For example, Spi structs using a transaction Length of 2 bytes implement FullDuplex<u16>. These lengths implement both the blocking and non-blocking traits from embedded HAL. The non-blocking traits are found in the spi and serial modules, while the blocking traits are found in the blocking module.

Transaction lengths GreaterThan4 cannot be completed in a single read or write of the DATA register, so these lengths do NOT implement the non-blocking traits from the embedded HAL spi and serial modules. Instead, they only implement traits from the blocking module. These traits are implemented for u8 types, e.g. blocking::spi::Transfer<u8>, and operate on [u8] slices. The length of the slice is checked to ensure it matches the transaction Length.

Because a DynLength is not guaranteed to be an AtomicSize, the corresponding Spi structs only implement the blocking traits as well.

For a non-blocking alternative that can be used to transfer arbitrary-length slices, you could use either DMA or the spi_future module.

Variations by Capability

The implementations in this module also seek to optimize as much as possible based on the Capability of the Spi struct. They follow a few general rules:

  • Tx structs can never receive data, so their corresponding trait implementations never read the DATA register and can never return an Error::Overflow.
  • Rx structs in a MasterMode must initiate all transactions, so their implementations of non-blocking traits must track the state of on-going transactions.
  • Duplex structs must always read as many bytes as they send, even when implementing Write-only traits, to ensure they never introduce an Error::Overflow.

Notes on individual embedded HAL traits

spi::FullDuplex

spi::FullDuplex is only implemented for structs with Duplex Capability. Although the embedded HAL documentation assumes a MasterMode, this module also implements it for the Slave OpMode.

serial::Read

serial::Read is only implemented for structs with Rx Capability. When in a MasterMode, it initiates and tracks the state of the on-going transactions. But this is not required when acting as a Slave.

serial::Write

serial::Write is only implemented for structs with Tx Capability. These implementations never read the DATA register and ignore all instances of Error::Overflow.

blocking::serial::Write

This trait uses the blocking::serial::write::Default implementation for implementers of serial::Write.

blocking::spi traits

These traits are implemented following all of the rules outlined above for the different Size and Capability options.