1582 lines
46 KiB
Rust
1582 lines
46 KiB
Rust
//! # Serial Peripheral Interface
|
|
//!
|
|
//! There are multiple ways to use SPI, depending on your needs. Regardless of
|
|
//! which way you choose, you must first create an SPI instance with
|
|
//! [`Spi::new`].
|
|
//!
|
|
//! ```rust
|
|
//! let io = IO::new(peripherals.GPIO, peripherals.IO_MUX);
|
|
//! let sclk = io.pins.gpio12;
|
|
//! let miso = io.pins.gpio11;
|
|
//! let mosi = io.pins.gpio13;
|
|
//! let cs = io.pins.gpio10;
|
|
//!
|
|
//! let mut spi = hal::spi::Spi::new(
|
|
//! peripherals.SPI2,
|
|
//! sclk,
|
|
//! mosi,
|
|
//! miso,
|
|
//! cs,
|
|
//! 100u32.kHz(),
|
|
//! SpiMode::Mode0,
|
|
//! &mut peripheral_clock_control,
|
|
//! &mut clocks,
|
|
//! );
|
|
//! ```
|
|
//!
|
|
//! ## Exclusive access to the SPI bus
|
|
//!
|
|
//! If all you want to do is to communicate to a single device, and you initiate
|
|
//! transactions yourself, there are a number of ways to achieve this:
|
|
//!
|
|
//! - Use the [`FullDuplex`](embedded_hal::spi::FullDuplex) trait to read/write
|
|
//! single bytes at a time,
|
|
//! - Use the [`SpiBus`](embedded_hal_1::spi::SpiBus) trait (requires the "eh1"
|
|
//! feature) and its associated functions to initiate transactions with
|
|
//! simultaneous reads and writes, or
|
|
//! - Use the [`SpiBusWrite`](embedded_hal_1::spi::SpiBusWrite) and
|
|
//! [`SpiBusRead`](embedded_hal_1::spi::SpiBusRead) traits (requires the "eh1"
|
|
//! feature) and their associated functions to read or write mutiple bytes at
|
|
//! a time.
|
|
//!
|
|
//!
|
|
//! ## Shared SPI access
|
|
//!
|
|
//! If you have multiple devices on the same SPI bus that each have their own CS
|
|
//! line, you may want to have a look at the [`SpiBusController`] and
|
|
//! [`SpiBusDevice`] implemented here. These give exclusive access to the
|
|
//! underlying SPI bus by means of a Mutex. This ensures that device
|
|
//! transactions do not interfere with each other.
|
|
|
|
use fugit::HertzU32;
|
|
|
|
#[cfg(any(esp32c3))]
|
|
use crate::dma::gdma::{DmaError, Rx, Tx};
|
|
use crate::{
|
|
clock::Clocks,
|
|
pac::spi2::RegisterBlock,
|
|
system::PeripheralClockControl,
|
|
types::{InputSignal, OutputSignal},
|
|
InputPin,
|
|
OutputPin,
|
|
};
|
|
|
|
/// The size of the FIFO buffer for SPI
|
|
#[cfg(not(esp32s2))]
|
|
const FIFO_SIZE: usize = 64;
|
|
#[cfg(esp32s2)]
|
|
const FIFO_SIZE: usize = 72;
|
|
/// Padding byte for empty write transfers
|
|
const EMPTY_WRITE_PAD: u8 = 0x00u8;
|
|
|
|
#[allow(unused)]
|
|
const MAX_DMA_SIZE: usize = 32736;
|
|
|
|
#[derive(Debug, Clone, Copy)]
|
|
pub enum Error {
|
|
#[cfg(esp32c3)]
|
|
DmaError(DmaError),
|
|
MaxDmaTransferSizeExceeded,
|
|
Unknown,
|
|
}
|
|
|
|
#[cfg(esp32c3)]
|
|
impl From<DmaError> for Error {
|
|
fn from(value: DmaError) -> Self {
|
|
Error::DmaError(value)
|
|
}
|
|
}
|
|
|
|
#[cfg(feature = "eh1")]
|
|
impl embedded_hal_1::spi::Error for Error {
|
|
fn kind(&self) -> embedded_hal_1::spi::ErrorKind {
|
|
embedded_hal_1::spi::ErrorKind::Other
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, Copy)]
|
|
pub enum SpiMode {
|
|
Mode0,
|
|
Mode1,
|
|
Mode2,
|
|
Mode3,
|
|
}
|
|
|
|
pub struct Spi<T> {
|
|
spi: T,
|
|
}
|
|
|
|
impl<T> Spi<T>
|
|
where
|
|
T: Instance,
|
|
{
|
|
/// Constructs an SPI instance in 8bit dataframe mode.
|
|
pub fn new<SCK: OutputPin, MOSI: OutputPin, MISO: InputPin, CS: OutputPin>(
|
|
spi: T,
|
|
mut sck: SCK,
|
|
mut mosi: MOSI,
|
|
mut miso: MISO,
|
|
mut cs: CS,
|
|
frequency: HertzU32,
|
|
mode: SpiMode,
|
|
peripheral_clock_control: &mut PeripheralClockControl,
|
|
clocks: &Clocks,
|
|
) -> Self {
|
|
sck.set_to_push_pull_output()
|
|
.connect_peripheral_to_output(spi.sclk_signal());
|
|
|
|
mosi.set_to_push_pull_output()
|
|
.connect_peripheral_to_output(spi.mosi_signal());
|
|
|
|
miso.set_to_input()
|
|
.connect_input_to_peripheral(spi.miso_signal());
|
|
|
|
cs.set_to_push_pull_output()
|
|
.connect_peripheral_to_output(spi.cs_signal());
|
|
|
|
Self::new_internal(spi, frequency, mode, peripheral_clock_control, clocks)
|
|
}
|
|
|
|
/// Constructs an SPI instance in 8bit dataframe mode without CS pin.
|
|
pub fn new_no_cs<SCK: OutputPin, MOSI: OutputPin, MISO: InputPin>(
|
|
spi: T,
|
|
mut sck: SCK,
|
|
mut mosi: MOSI,
|
|
mut miso: MISO,
|
|
frequency: HertzU32,
|
|
mode: SpiMode,
|
|
peripheral_clock_control: &mut PeripheralClockControl,
|
|
clocks: &Clocks,
|
|
) -> Self {
|
|
sck.set_to_push_pull_output()
|
|
.connect_peripheral_to_output(spi.sclk_signal());
|
|
|
|
mosi.set_to_push_pull_output()
|
|
.connect_peripheral_to_output(spi.mosi_signal());
|
|
|
|
miso.set_to_input()
|
|
.connect_input_to_peripheral(spi.miso_signal());
|
|
|
|
Self::new_internal(spi, frequency, mode, peripheral_clock_control, clocks)
|
|
}
|
|
|
|
/// Constructs an SPI instance in 8bit dataframe mode without CS and MISO
|
|
/// pin.
|
|
pub fn new_no_cs_no_miso<SCK: OutputPin, MOSI: OutputPin>(
|
|
spi: T,
|
|
mut sck: SCK,
|
|
mut mosi: MOSI,
|
|
frequency: HertzU32,
|
|
mode: SpiMode,
|
|
peripheral_clock_control: &mut PeripheralClockControl,
|
|
clocks: &Clocks,
|
|
) -> Self {
|
|
sck.set_to_push_pull_output()
|
|
.connect_peripheral_to_output(spi.sclk_signal());
|
|
|
|
mosi.set_to_push_pull_output()
|
|
.connect_peripheral_to_output(spi.mosi_signal());
|
|
|
|
Self::new_internal(spi, frequency, mode, peripheral_clock_control, clocks)
|
|
}
|
|
|
|
/// Constructs an SPI instance in 8bit dataframe mode with only MOSI
|
|
/// connected. This might be useful for (ab)using SPI to implement
|
|
/// other protocols by bitbanging (WS2812B, onewire, generating arbitrary
|
|
/// waveforms…)
|
|
pub fn new_mosi_only<MOSI: OutputPin>(
|
|
spi: T,
|
|
mut mosi: MOSI,
|
|
frequency: HertzU32,
|
|
mode: SpiMode,
|
|
peripheral_clock_control: &mut PeripheralClockControl,
|
|
clocks: &Clocks,
|
|
) -> Self {
|
|
mosi.set_to_push_pull_output()
|
|
.connect_peripheral_to_output(spi.mosi_signal());
|
|
|
|
Self::new_internal(spi, frequency, mode, peripheral_clock_control, clocks)
|
|
}
|
|
|
|
pub(crate) fn new_internal(
|
|
spi: T,
|
|
frequency: HertzU32,
|
|
mode: SpiMode,
|
|
peripheral_clock_control: &mut PeripheralClockControl,
|
|
clocks: &Clocks,
|
|
) -> Self {
|
|
spi.enable_peripheral(peripheral_clock_control);
|
|
|
|
let mut spi = Self { spi };
|
|
spi.spi.setup(frequency, clocks);
|
|
spi.spi.init();
|
|
spi.spi.set_data_mode(mode);
|
|
|
|
spi
|
|
}
|
|
|
|
/// Return the raw interface to the underlying peripheral instance
|
|
pub fn free(self) -> T {
|
|
self.spi
|
|
}
|
|
}
|
|
|
|
impl<T> embedded_hal::spi::FullDuplex<u8> for Spi<T>
|
|
where
|
|
T: Instance,
|
|
{
|
|
type Error = Error;
|
|
|
|
fn read(&mut self) -> nb::Result<u8, Self::Error> {
|
|
self.spi.read_byte()
|
|
}
|
|
|
|
fn send(&mut self, word: u8) -> nb::Result<(), Self::Error> {
|
|
self.spi.write_byte(word)
|
|
}
|
|
}
|
|
|
|
impl<T> embedded_hal::blocking::spi::Transfer<u8> for Spi<T>
|
|
where
|
|
T: Instance,
|
|
{
|
|
type Error = Error;
|
|
|
|
fn transfer<'w>(&mut self, words: &'w mut [u8]) -> Result<&'w [u8], Self::Error> {
|
|
self.spi.transfer(words)
|
|
}
|
|
}
|
|
|
|
impl<T> embedded_hal::blocking::spi::Write<u8> for Spi<T>
|
|
where
|
|
T: Instance,
|
|
{
|
|
type Error = Error;
|
|
|
|
fn write(&mut self, words: &[u8]) -> Result<(), Self::Error> {
|
|
self.spi.write_bytes(words)?;
|
|
self.spi.flush()?;
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
#[cfg(any(esp32c3))]
|
|
pub mod dma {
|
|
use core::mem;
|
|
|
|
use embedded_dma::{ReadBuffer, WriteBuffer};
|
|
|
|
use super::{Instance, InstanceDma, Spi, MAX_DMA_SIZE};
|
|
use crate::dma::gdma::{DmaTransfer, DmaTransferRxTx, Rx, Tx};
|
|
|
|
impl<T> Spi<T>
|
|
where
|
|
T: Instance,
|
|
{
|
|
/// Make this SPI instance into a DMA capable instance.
|
|
/// Pass the Rx and Tx half of the DMA channel.
|
|
pub fn with_tx_rx_dma<RX, TX>(self, tx: TX, rx: RX) -> SpiDma<T, TX, RX>
|
|
where
|
|
T: InstanceDma<TX, RX>,
|
|
TX: Tx,
|
|
RX: Rx,
|
|
{
|
|
SpiDma {
|
|
spi: self.spi,
|
|
tx,
|
|
rx,
|
|
}
|
|
}
|
|
}
|
|
|
|
/// An in-progress DMA transfer
|
|
pub struct SpiDmaTransferRxTx<T, TX, RX, RBUFFER, TBUFFER>
|
|
where
|
|
T: InstanceDma<TX, RX>,
|
|
TX: Tx,
|
|
RX: Rx,
|
|
{
|
|
spi_dma: SpiDma<T, TX, RX>,
|
|
rbuffer: RBUFFER,
|
|
tbuffer: TBUFFER,
|
|
}
|
|
|
|
impl<T, TX, RX, RXBUF, TXBUF> DmaTransferRxTx<RXBUF, TXBUF, SpiDma<T, TX, RX>>
|
|
for SpiDmaTransferRxTx<T, TX, RX, RXBUF, TXBUF>
|
|
where
|
|
T: InstanceDma<TX, RX>,
|
|
TX: Tx,
|
|
RX: Rx,
|
|
{
|
|
/// Wait for the DMA transfer to complete and return the buffers and the
|
|
/// SPI instance.
|
|
fn wait(mut self) -> (RXBUF, TXBUF, SpiDma<T, TX, RX>) {
|
|
self.spi_dma.spi.flush().ok(); // waiting for the DMA transfer is not enough
|
|
|
|
// `DmaTransfer` needs to have a `Drop` implementation, because we accept
|
|
// managed buffers that can free their memory on drop. Because of that
|
|
// we can't move out of the `DmaTransfer`'s fields, so we use `ptr::read`
|
|
// and `mem::forget`.
|
|
//
|
|
// NOTE(unsafe) There is no panic branch between getting the resources
|
|
// and forgetting `self`.
|
|
unsafe {
|
|
let rbuffer = core::ptr::read(&self.rbuffer);
|
|
let tbuffer = core::ptr::read(&self.tbuffer);
|
|
let payload = core::ptr::read(&self.spi_dma);
|
|
mem::forget(self);
|
|
(rbuffer, tbuffer, payload)
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<T, TX, RX, RXBUF, TXBUF> Drop for SpiDmaTransferRxTx<T, TX, RX, RXBUF, TXBUF>
|
|
where
|
|
T: InstanceDma<TX, RX>,
|
|
TX: Tx,
|
|
RX: Rx,
|
|
{
|
|
fn drop(&mut self) {
|
|
self.spi_dma.spi.flush().ok();
|
|
}
|
|
}
|
|
|
|
/// An in-progress DMA transfer.
|
|
pub struct SpiDmaTransfer<T, TX, RX, BUFFER>
|
|
where
|
|
T: InstanceDma<TX, RX>,
|
|
TX: Tx,
|
|
RX: Rx,
|
|
{
|
|
spi_dma: SpiDma<T, TX, RX>,
|
|
buffer: BUFFER,
|
|
}
|
|
|
|
impl<T, TX, RX, BUFFER> DmaTransfer<BUFFER, SpiDma<T, TX, RX>> for SpiDmaTransfer<T, TX, RX, BUFFER>
|
|
where
|
|
T: InstanceDma<TX, RX>,
|
|
TX: Tx,
|
|
RX: Rx,
|
|
{
|
|
/// Wait for the DMA transfer to complete and return the buffers and the
|
|
/// SPI instance.
|
|
fn wait(mut self) -> (BUFFER, SpiDma<T, TX, RX>) {
|
|
self.spi_dma.spi.flush().ok(); // waiting for the DMA transfer is not enough
|
|
|
|
// `DmaTransfer` needs to have a `Drop` implementation, because we accept
|
|
// managed buffers that can free their memory on drop. Because of that
|
|
// we can't move out of the `DmaTransfer`'s fields, so we use `ptr::read`
|
|
// and `mem::forget`.
|
|
//
|
|
// NOTE(unsafe) There is no panic branch between getting the resources
|
|
// and forgetting `self`.
|
|
unsafe {
|
|
let buffer = core::ptr::read(&self.buffer);
|
|
let payload = core::ptr::read(&self.spi_dma);
|
|
mem::forget(self);
|
|
(buffer, payload)
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<T, TX, RX, BUFFER> Drop for SpiDmaTransfer<T, TX, RX, BUFFER>
|
|
where
|
|
T: InstanceDma<TX, RX>,
|
|
TX: Tx,
|
|
RX: Rx,
|
|
{
|
|
fn drop(&mut self) {
|
|
self.spi_dma.spi.flush().ok();
|
|
}
|
|
}
|
|
|
|
/// A DMA capable SPI instance.
|
|
pub struct SpiDma<T, TX, RX> {
|
|
spi: T,
|
|
tx: TX,
|
|
rx: RX,
|
|
}
|
|
|
|
impl<T, TX, RX> SpiDma<T, TX, RX>
|
|
where
|
|
T: InstanceDma<TX, RX>,
|
|
TX: Tx,
|
|
RX: Rx,
|
|
{
|
|
/// Return the raw interface to the underlying peripheral instance
|
|
pub fn free(self) -> T {
|
|
self.spi
|
|
}
|
|
|
|
/// Perform a DMA write.
|
|
///
|
|
/// This will return a [SpiDmaTransfer] owning the buffer(s) and the SPI
|
|
/// instance. The maximum amount of data to be sent is 32736
|
|
/// bytes.
|
|
pub fn dma_write<TXBUF>(
|
|
mut self,
|
|
words: TXBUF,
|
|
) -> Result<SpiDmaTransfer<T, TX, RX, TXBUF>, super::Error>
|
|
where
|
|
TXBUF: ReadBuffer<Word = u8>,
|
|
{
|
|
let (ptr, len) = unsafe { words.read_buffer() };
|
|
|
|
if len > MAX_DMA_SIZE {
|
|
return Err(super::Error::MaxDmaTransferSizeExceeded);
|
|
}
|
|
|
|
self.spi.start_write_bytes_dma(ptr, len, &mut self.tx)?;
|
|
Ok(SpiDmaTransfer {
|
|
spi_dma: self,
|
|
buffer: words,
|
|
})
|
|
}
|
|
|
|
/// Perform a DMA read.
|
|
///
|
|
/// This will return a [SpiDmaTransfer] owning the buffer(s) and the SPI
|
|
/// instance. The maximum amount of data to be received is 32736
|
|
/// bytes.
|
|
pub fn dma_read<RXBUF>(
|
|
mut self,
|
|
mut words: RXBUF,
|
|
) -> Result<SpiDmaTransfer<T, TX, RX, RXBUF>, super::Error>
|
|
where
|
|
RXBUF: WriteBuffer<Word = u8>,
|
|
{
|
|
let (ptr, len) = unsafe { words.write_buffer() };
|
|
|
|
if len > MAX_DMA_SIZE {
|
|
return Err(super::Error::MaxDmaTransferSizeExceeded);
|
|
}
|
|
|
|
self.spi.start_read_bytes_dma(ptr, len, &mut self.rx)?;
|
|
Ok(SpiDmaTransfer {
|
|
spi_dma: self,
|
|
buffer: words,
|
|
})
|
|
}
|
|
|
|
/// Perform a DMA transfer.
|
|
///
|
|
/// This will return a [SpiDmaTransfer] owning the buffer(s) and the SPI
|
|
/// instance. The maximum amount of data to be sent/received is
|
|
/// 32736 bytes.
|
|
pub fn dma_transfer<TXBUF, RXBUF>(
|
|
mut self,
|
|
words: TXBUF,
|
|
mut read_buffer: RXBUF,
|
|
) -> Result<SpiDmaTransferRxTx<T, TX, RX, RXBUF, TXBUF>, super::Error>
|
|
where
|
|
TXBUF: ReadBuffer<Word = u8>,
|
|
RXBUF: WriteBuffer<Word = u8>,
|
|
{
|
|
let (write_ptr, write_len) = unsafe { words.read_buffer() };
|
|
let (read_ptr, read_len) = unsafe { read_buffer.write_buffer() };
|
|
|
|
if write_len > MAX_DMA_SIZE || read_len > MAX_DMA_SIZE {
|
|
return Err(super::Error::MaxDmaTransferSizeExceeded);
|
|
}
|
|
|
|
self.spi.start_transfer_dma(
|
|
write_ptr,
|
|
write_len,
|
|
read_ptr,
|
|
read_len,
|
|
&mut self.tx,
|
|
&mut self.rx,
|
|
)?;
|
|
Ok(SpiDmaTransferRxTx {
|
|
spi_dma: self,
|
|
rbuffer: read_buffer,
|
|
tbuffer: words,
|
|
})
|
|
}
|
|
}
|
|
|
|
impl<T, TX, RX> embedded_hal::blocking::spi::Transfer<u8> for SpiDma<T, TX, RX>
|
|
where
|
|
T: InstanceDma<TX, RX>,
|
|
TX: Tx,
|
|
RX: Rx,
|
|
{
|
|
type Error = super::Error;
|
|
|
|
fn transfer<'w>(&mut self, words: &'w mut [u8]) -> Result<&'w [u8], Self::Error> {
|
|
self.spi
|
|
.transfer_in_place_dma(words, &mut self.tx, &mut self.rx)
|
|
}
|
|
}
|
|
|
|
impl<T, TX, RX> embedded_hal::blocking::spi::Write<u8> for SpiDma<T, TX, RX>
|
|
where
|
|
T: InstanceDma<TX, RX>,
|
|
TX: Tx,
|
|
RX: Rx,
|
|
{
|
|
type Error = super::Error;
|
|
|
|
fn write(&mut self, words: &[u8]) -> Result<(), Self::Error> {
|
|
self.spi.write_bytes_dma(words, &mut self.tx)?;
|
|
self.spi.flush()?;
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
#[cfg(feature = "eh1")]
|
|
mod ehal1 {
|
|
use embedded_hal_1::spi::{SpiBus, SpiBusFlush, SpiBusRead, SpiBusWrite};
|
|
|
|
use super::{super::InstanceDma, SpiDma};
|
|
use crate::dma::gdma::{Rx, Tx};
|
|
|
|
impl<T, TX, RX> embedded_hal_1::spi::ErrorType for SpiDma<T, TX, RX>
|
|
where
|
|
T: InstanceDma<TX, RX>,
|
|
TX: Tx,
|
|
RX: Rx,
|
|
{
|
|
type Error = super::super::Error;
|
|
}
|
|
|
|
impl<T, TX, RX> SpiBusWrite for SpiDma<T, TX, RX>
|
|
where
|
|
T: InstanceDma<TX, RX>,
|
|
TX: Tx,
|
|
RX: Rx,
|
|
{
|
|
/// See also: [`write_bytes`].
|
|
fn write(&mut self, words: &[u8]) -> Result<(), Self::Error> {
|
|
self.spi.write_bytes_dma(words, &mut self.tx)?;
|
|
self.flush()
|
|
}
|
|
}
|
|
|
|
impl<T, TX, RX> SpiBusRead for SpiDma<T, TX, RX>
|
|
where
|
|
T: InstanceDma<TX, RX>,
|
|
TX: Tx,
|
|
RX: Rx,
|
|
{
|
|
fn read(&mut self, words: &mut [u8]) -> Result<(), Self::Error> {
|
|
self.spi
|
|
.transfer_dma(&[], words, &mut self.tx, &mut self.rx)?;
|
|
self.flush()
|
|
}
|
|
}
|
|
|
|
impl<T, TX, RX> SpiBus for SpiDma<T, TX, RX>
|
|
where
|
|
T: InstanceDma<TX, RX>,
|
|
TX: Tx,
|
|
RX: Rx,
|
|
{
|
|
/// Write out data from `write`, read response into `read`.
|
|
///
|
|
/// `read` and `write` are allowed to have different lengths. If
|
|
/// `write` is longer, all other bytes received are
|
|
/// discarded. If `read` is longer, [`EMPTY_WRITE_PAD`]
|
|
/// is written out as necessary until enough bytes have
|
|
/// been read. Reading and writing happens
|
|
/// simultaneously.
|
|
fn transfer(&mut self, read: &mut [u8], write: &[u8]) -> Result<(), Self::Error> {
|
|
self.spi
|
|
.transfer_dma(write, read, &mut self.tx, &mut self.rx)?;
|
|
self.flush()
|
|
}
|
|
|
|
/// Transfer data in place.
|
|
///
|
|
/// Writes data from `words` out on the bus and stores the reply
|
|
/// into `words`. A convenient wrapper around
|
|
/// [`write`](SpiBusWrite::write), [`flush`](SpiBusFlush::flush) and
|
|
/// [`read`](SpiBusRead::read).
|
|
fn transfer_in_place(&mut self, words: &mut [u8]) -> Result<(), Self::Error> {
|
|
self.spi
|
|
.transfer_in_place_dma(words, &mut self.tx, &mut self.rx)?;
|
|
self.flush()
|
|
}
|
|
}
|
|
|
|
impl<T, TX, RX> SpiBusFlush for SpiDma<T, TX, RX>
|
|
where
|
|
T: InstanceDma<TX, RX>,
|
|
TX: Tx,
|
|
RX: Rx,
|
|
{
|
|
fn flush(&mut self) -> Result<(), Self::Error> {
|
|
self.spi.flush()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(feature = "eh1")]
|
|
pub use ehal1::*;
|
|
|
|
#[cfg(feature = "eh1")]
|
|
mod ehal1 {
|
|
use core::cell::RefCell;
|
|
|
|
use embedded_hal_1::spi::{
|
|
self,
|
|
ErrorType,
|
|
SpiBus,
|
|
SpiBusFlush,
|
|
SpiBusRead,
|
|
SpiBusWrite,
|
|
SpiDevice,
|
|
};
|
|
use embedded_hal_nb::spi::FullDuplex;
|
|
|
|
use super::*;
|
|
use crate::OutputPin;
|
|
|
|
impl<T> embedded_hal_1::spi::ErrorType for Spi<T> {
|
|
type Error = super::Error;
|
|
}
|
|
|
|
impl<T> FullDuplex for Spi<T>
|
|
where
|
|
T: Instance,
|
|
{
|
|
fn read(&mut self) -> nb::Result<u8, Self::Error> {
|
|
self.spi.read_byte()
|
|
}
|
|
|
|
fn write(&mut self, word: u8) -> nb::Result<(), Self::Error> {
|
|
self.spi.write_byte(word)
|
|
}
|
|
}
|
|
|
|
impl<T> SpiBusWrite for Spi<T>
|
|
where
|
|
T: Instance,
|
|
{
|
|
/// See also: [`write_bytes`].
|
|
fn write(&mut self, words: &[u8]) -> Result<(), Self::Error> {
|
|
self.spi.write_bytes(words)
|
|
}
|
|
}
|
|
|
|
impl<T> SpiBusRead for Spi<T>
|
|
where
|
|
T: Instance,
|
|
{
|
|
/// See also: [`read_bytes`].
|
|
fn read(&mut self, words: &mut [u8]) -> Result<(), Self::Error> {
|
|
self.spi.read_bytes(words)
|
|
}
|
|
}
|
|
|
|
impl<T> SpiBus for Spi<T>
|
|
where
|
|
T: Instance,
|
|
{
|
|
/// Write out data from `write`, read response into `read`.
|
|
///
|
|
/// `read` and `write` are allowed to have different lengths. If `write`
|
|
/// is longer, all other bytes received are discarded. If `read`
|
|
/// is longer, [`EMPTY_WRITE_PAD`] is written out as necessary
|
|
/// until enough bytes have been read. Reading and writing happens
|
|
/// simultaneously.
|
|
fn transfer(&mut self, read: &mut [u8], write: &[u8]) -> Result<(), Self::Error> {
|
|
// Optimizations
|
|
if read.len() == 0 {
|
|
SpiBusWrite::write(self, write)?;
|
|
} else if write.len() == 0 {
|
|
SpiBusRead::read(self, read)?;
|
|
}
|
|
|
|
let mut write_from = 0;
|
|
let mut read_from = 0;
|
|
|
|
loop {
|
|
// How many bytes we write in this chunk
|
|
let write_inc = core::cmp::min(FIFO_SIZE, write.len() - write_from);
|
|
let write_to = write_from + write_inc;
|
|
// How many bytes we read in this chunk
|
|
let read_inc = core::cmp::min(FIFO_SIZE, read.len() - read_from);
|
|
let read_to = read_from + read_inc;
|
|
|
|
if (write_inc == 0) && (read_inc == 0) {
|
|
break;
|
|
}
|
|
|
|
if write_to < read_to {
|
|
// Read more than we write, must pad writing part with zeros
|
|
let mut empty = [EMPTY_WRITE_PAD; FIFO_SIZE];
|
|
empty[0..write_inc].copy_from_slice(&write[write_from..write_to]);
|
|
SpiBusWrite::write(self, &empty)?;
|
|
} else {
|
|
SpiBusWrite::write(self, &write[write_from..write_to])?;
|
|
}
|
|
|
|
SpiBusFlush::flush(self)?;
|
|
|
|
if read_inc > 0 {
|
|
self.spi
|
|
.read_bytes_from_fifo(&mut read[read_from..read_to])?;
|
|
}
|
|
|
|
write_from = write_to;
|
|
read_from = read_to;
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
/// Transfer data in place.
|
|
///
|
|
/// Writes data from `words` out on the bus and stores the reply into
|
|
/// `words`. A convenient wrapper around
|
|
/// [`write`](SpiBusWrite::write), [`flush`](SpiBusFlush::flush) and
|
|
/// [`read`](SpiBusRead::read).
|
|
fn transfer_in_place(&mut self, words: &mut [u8]) -> Result<(), Self::Error> {
|
|
// Since we have the traits so neatly implemented above, use them!
|
|
for chunk in words.chunks_mut(FIFO_SIZE) {
|
|
SpiBusWrite::write(self, chunk)?;
|
|
SpiBusFlush::flush(self)?;
|
|
self.spi.read_bytes_from_fifo(chunk)?;
|
|
}
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
impl<T> SpiBusFlush for Spi<T>
|
|
where
|
|
T: Instance,
|
|
{
|
|
fn flush(&mut self) -> Result<(), Self::Error> {
|
|
self.spi.flush()
|
|
}
|
|
}
|
|
|
|
/// SPI bus controller.
|
|
///
|
|
/// Has exclusive access to an SPI bus, which is managed via a `Mutex`. Used
|
|
/// as basis for the [`SpiBusDevice`] implementation. Note that the
|
|
/// wrapped [`RefCell`] is used solely to achieve interior mutability.
|
|
pub struct SpiBusController<I: Instance> {
|
|
lock: critical_section::Mutex<RefCell<Spi<I>>>,
|
|
}
|
|
|
|
impl<I: Instance> SpiBusController<I> {
|
|
/// Create a new controller from an SPI bus instance.
|
|
///
|
|
/// Takes ownership of the SPI bus in the process. Afterwards, the SPI
|
|
/// bus can only be accessed via instances of [`SpiBusDevice`].
|
|
pub fn from_spi(bus: Spi<I>) -> Self {
|
|
SpiBusController {
|
|
lock: critical_section::Mutex::new(RefCell::new(bus)),
|
|
}
|
|
}
|
|
|
|
pub fn add_device<'a, CS: OutputPin>(&'a self, cs: CS) -> SpiBusDevice<'a, I, CS> {
|
|
SpiBusDevice::new(self, cs)
|
|
}
|
|
}
|
|
|
|
impl<I: Instance> ErrorType for SpiBusController<I> {
|
|
type Error = spi::ErrorKind;
|
|
}
|
|
|
|
/// An SPI device on a shared SPI bus.
|
|
///
|
|
/// Provides device specific access on a shared SPI bus. Enables attaching
|
|
/// multiple SPI devices to the same bus, each with its own CS line, and
|
|
/// performing safe transfers on them.
|
|
pub struct SpiBusDevice<'a, I, CS>
|
|
where
|
|
I: Instance,
|
|
CS: OutputPin,
|
|
{
|
|
bus: &'a SpiBusController<I>,
|
|
cs: CS,
|
|
}
|
|
|
|
impl<'a, I, CS> SpiBusDevice<'a, I, CS>
|
|
where
|
|
I: Instance,
|
|
CS: OutputPin,
|
|
{
|
|
pub fn new(bus: &'a SpiBusController<I>, mut cs: CS) -> Self {
|
|
cs.set_to_push_pull_output().set_output_high(true);
|
|
SpiBusDevice { bus, cs }
|
|
}
|
|
}
|
|
|
|
impl<'a, I, CS> ErrorType for SpiBusDevice<'a, I, CS>
|
|
where
|
|
I: Instance,
|
|
CS: OutputPin,
|
|
{
|
|
type Error = spi::ErrorKind;
|
|
}
|
|
|
|
impl<I, CS> SpiDevice for SpiBusDevice<'_, I, CS>
|
|
where
|
|
I: Instance,
|
|
CS: OutputPin + crate::gpio::OutputPin,
|
|
{
|
|
type Bus = Spi<I>;
|
|
|
|
fn transaction<R>(
|
|
&mut self,
|
|
f: impl FnOnce(&mut Self::Bus) -> Result<R, <Self::Bus as ErrorType>::Error>,
|
|
) -> Result<R, Self::Error> {
|
|
critical_section::with(|cs| {
|
|
let mut bus = self.bus.lock.borrow_ref_mut(cs);
|
|
|
|
self.cs.connect_peripheral_to_output(bus.spi.cs_signal());
|
|
|
|
// We postpone handling these errors until AFTER we raised CS again, so the bus
|
|
// is free (Or we die trying if CS errors).
|
|
let f_res = f(&mut bus);
|
|
let flush_res = bus.flush();
|
|
|
|
self.cs.disconnect_peripheral_from_output();
|
|
|
|
let f_res = f_res.map_err(|_| spi::ErrorKind::Other)?;
|
|
flush_res.map_err(|_| spi::ErrorKind::Other)?;
|
|
|
|
Ok(f_res)
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(any(esp32c3))]
|
|
pub trait InstanceDma<TX, RX>: Instance
|
|
where
|
|
TX: Tx,
|
|
RX: Rx,
|
|
{
|
|
fn transfer_in_place_dma<'w>(
|
|
&mut self,
|
|
words: &'w mut [u8],
|
|
tx: &mut TX,
|
|
rx: &mut RX,
|
|
) -> Result<&'w [u8], Error> {
|
|
for chunk in words.chunks_mut(MAX_DMA_SIZE) {
|
|
self.start_transfer_dma(
|
|
chunk.as_ptr(),
|
|
chunk.len(),
|
|
chunk.as_mut_ptr(),
|
|
chunk.len(),
|
|
tx,
|
|
rx,
|
|
)?;
|
|
|
|
while !tx.is_done() && !rx.is_done() {}
|
|
self.flush().unwrap();
|
|
}
|
|
|
|
return Ok(words);
|
|
}
|
|
|
|
fn transfer_dma<'w>(
|
|
&mut self,
|
|
write_buffer: &'w [u8],
|
|
read_buffer: &'w mut [u8],
|
|
tx: &mut TX,
|
|
rx: &mut RX,
|
|
) -> Result<&'w [u8], Error> {
|
|
let mut idx = 0;
|
|
loop {
|
|
let write_idx = isize::min(idx, write_buffer.len() as isize);
|
|
let write_len = usize::min(write_buffer.len() - idx as usize, MAX_DMA_SIZE);
|
|
|
|
let read_idx = isize::min(idx, read_buffer.len() as isize);
|
|
let read_len = usize::min(read_buffer.len() - idx as usize, MAX_DMA_SIZE);
|
|
|
|
self.start_transfer_dma(
|
|
unsafe { write_buffer.as_ptr().offset(write_idx) },
|
|
write_len,
|
|
unsafe { read_buffer.as_mut_ptr().offset(read_idx) },
|
|
read_len,
|
|
tx,
|
|
rx,
|
|
)?;
|
|
|
|
while !tx.is_done() && !rx.is_done() {}
|
|
self.flush().unwrap();
|
|
|
|
idx += MAX_DMA_SIZE as isize;
|
|
if idx >= write_buffer.len() as isize && idx >= read_buffer.len() as isize {
|
|
break;
|
|
}
|
|
}
|
|
|
|
return Ok(read_buffer);
|
|
}
|
|
|
|
fn start_transfer_dma<'w>(
|
|
&mut self,
|
|
write_buffer_ptr: *const u8,
|
|
write_buffer_len: usize,
|
|
read_buffer_ptr: *mut u8,
|
|
read_buffer_len: usize,
|
|
tx: &mut TX,
|
|
rx: &mut RX,
|
|
) -> Result<(), Error> {
|
|
let reg_block = self.register_block();
|
|
self.configure_datalen(usize::max(read_buffer_len, write_buffer_len) as u32 * 8);
|
|
|
|
tx.is_done();
|
|
rx.is_done();
|
|
|
|
reg_block.dma_conf.modify(|_, w| w.dma_tx_ena().set_bit());
|
|
reg_block.dma_conf.modify(|_, w| w.dma_rx_ena().set_bit());
|
|
self.update();
|
|
|
|
tx.prepare_transfer(
|
|
crate::dma::gdma::DmaPeripheral::Spi2,
|
|
write_buffer_ptr,
|
|
write_buffer_len,
|
|
)?;
|
|
rx.prepare_transfer(
|
|
crate::dma::gdma::DmaPeripheral::Spi2,
|
|
read_buffer_ptr,
|
|
read_buffer_len,
|
|
)?;
|
|
|
|
reg_block.dma_int_clr.write(|w| {
|
|
w.dma_infifo_full_err_int_clr()
|
|
.set_bit()
|
|
.dma_outfifo_empty_err_int_clr()
|
|
.set_bit()
|
|
.trans_done_int_clr()
|
|
.set_bit()
|
|
.mst_rx_afifo_wfull_err_int_clr()
|
|
.set_bit()
|
|
.mst_tx_afifo_rempty_err_int_clr()
|
|
.set_bit()
|
|
});
|
|
|
|
reg_block.cmd.modify(|_, w| w.usr().set_bit());
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn write_bytes_dma<'w>(&mut self, words: &'w [u8], tx: &mut TX) -> Result<&'w [u8], Error> {
|
|
for chunk in words.chunks(MAX_DMA_SIZE) {
|
|
self.start_write_bytes_dma(chunk.as_ptr(), chunk.len(), tx)?;
|
|
|
|
while !tx.is_done() {}
|
|
self.flush().unwrap(); // seems "is_done" doesn't work as intended?
|
|
}
|
|
|
|
return Ok(words);
|
|
}
|
|
|
|
fn start_write_bytes_dma<'w>(
|
|
&mut self,
|
|
ptr: *const u8,
|
|
len: usize,
|
|
tx: &mut TX,
|
|
) -> Result<(), Error> {
|
|
let reg_block = self.register_block();
|
|
self.configure_datalen(len as u32 * 8);
|
|
|
|
reg_block.dma_conf.modify(|_, w| w.dma_tx_ena().set_bit());
|
|
reg_block.dma_conf.modify(|_, w| w.dma_rx_ena().set_bit());
|
|
self.update();
|
|
|
|
tx.prepare_transfer(crate::dma::gdma::DmaPeripheral::Spi2, ptr, len)?;
|
|
|
|
reg_block.dma_int_clr.write(|w| {
|
|
w.dma_infifo_full_err_int_clr()
|
|
.set_bit()
|
|
.dma_outfifo_empty_err_int_clr()
|
|
.set_bit()
|
|
.trans_done_int_clr()
|
|
.set_bit()
|
|
.mst_rx_afifo_wfull_err_int_clr()
|
|
.set_bit()
|
|
.mst_tx_afifo_rempty_err_int_clr()
|
|
.set_bit()
|
|
});
|
|
|
|
reg_block.cmd.modify(|_, w| w.usr().set_bit());
|
|
|
|
return Ok(());
|
|
}
|
|
|
|
fn start_read_bytes_dma<'w>(
|
|
&mut self,
|
|
ptr: *mut u8,
|
|
len: usize,
|
|
rx: &mut RX,
|
|
) -> Result<(), Error> {
|
|
let reg_block = self.register_block();
|
|
self.configure_datalen(len as u32 * 8);
|
|
|
|
reg_block.dma_conf.modify(|_, w| w.dma_tx_ena().set_bit());
|
|
reg_block.dma_conf.modify(|_, w| w.dma_rx_ena().set_bit());
|
|
self.update();
|
|
|
|
rx.prepare_transfer(crate::dma::gdma::DmaPeripheral::Spi2, ptr, len)?;
|
|
|
|
reg_block.dma_int_clr.write(|w| {
|
|
w.dma_infifo_full_err_int_clr()
|
|
.set_bit()
|
|
.dma_outfifo_empty_err_int_clr()
|
|
.set_bit()
|
|
.trans_done_int_clr()
|
|
.set_bit()
|
|
.mst_rx_afifo_wfull_err_int_clr()
|
|
.set_bit()
|
|
.mst_tx_afifo_rempty_err_int_clr()
|
|
.set_bit()
|
|
});
|
|
|
|
reg_block.cmd.modify(|_, w| w.usr().set_bit());
|
|
|
|
return Ok(());
|
|
}
|
|
}
|
|
|
|
pub trait Instance {
|
|
fn register_block(&self) -> &RegisterBlock;
|
|
|
|
fn sclk_signal(&self) -> OutputSignal;
|
|
|
|
fn mosi_signal(&self) -> OutputSignal;
|
|
|
|
fn miso_signal(&self) -> InputSignal;
|
|
|
|
fn cs_signal(&self) -> OutputSignal;
|
|
|
|
fn enable_peripheral(&self, peripheral_clock_control: &mut PeripheralClockControl);
|
|
|
|
fn init(&mut self) {
|
|
let reg_block = self.register_block();
|
|
reg_block.user.modify(|_, w| {
|
|
w.usr_miso_highpart()
|
|
.clear_bit()
|
|
.usr_miso_highpart()
|
|
.clear_bit()
|
|
.doutdin()
|
|
.set_bit()
|
|
.usr_miso()
|
|
.set_bit()
|
|
.usr_mosi()
|
|
.set_bit()
|
|
.cs_hold()
|
|
.set_bit()
|
|
.usr_dummy_idle()
|
|
.set_bit()
|
|
.usr_addr()
|
|
.clear_bit()
|
|
.usr_command()
|
|
.clear_bit()
|
|
});
|
|
|
|
#[cfg(not(any(esp32, esp32s2)))]
|
|
reg_block.clk_gate.modify(|_, w| {
|
|
w.clk_en()
|
|
.set_bit()
|
|
.mst_clk_active()
|
|
.set_bit()
|
|
.mst_clk_sel()
|
|
.set_bit()
|
|
});
|
|
|
|
reg_block.ctrl.write(|w| unsafe { w.bits(0) });
|
|
|
|
#[cfg(not(esp32))]
|
|
reg_block.misc.write(|w| unsafe { w.bits(0) });
|
|
|
|
reg_block.slave.write(|w| unsafe { w.bits(0) });
|
|
}
|
|
|
|
// taken from https://github.com/apache/incubator-nuttx/blob/8267a7618629838231256edfa666e44b5313348e/arch/risc-v/src/esp32c3/esp32c3_spi.c#L496
|
|
fn setup(&mut self, frequency: HertzU32, clocks: &Clocks) {
|
|
// FIXME: this might not be always true
|
|
let apb_clk_freq: HertzU32 = HertzU32::Hz(clocks.apb_clock.to_Hz());
|
|
|
|
let reg_val: u32;
|
|
let duty_cycle = 128;
|
|
|
|
// In HW, n, h and l fields range from 1 to 64, pre ranges from 1 to 8K.
|
|
// The value written to register is one lower than the used value.
|
|
|
|
if frequency > ((apb_clk_freq / 4) * 3) {
|
|
// Using APB frequency directly will give us the best result here.
|
|
reg_val = 1 << 31;
|
|
} else {
|
|
/* For best duty cycle resolution, we want n to be as close to 32 as
|
|
* possible, but we also need a pre/n combo that gets us as close as
|
|
* possible to the intended frequency. To do this, we bruteforce n and
|
|
* calculate the best pre to go along with that. If there's a choice
|
|
* between pre/n combos that give the same result, use the one with the
|
|
* higher n.
|
|
*/
|
|
|
|
let mut pre: i32;
|
|
let mut bestn: i32 = -1;
|
|
let mut bestpre: i32 = -1;
|
|
let mut besterr: i32 = 0;
|
|
let mut errval: i32;
|
|
|
|
/* Start at n = 2. We need to be able to set h/l so we have at least
|
|
* one high and one low pulse.
|
|
*/
|
|
|
|
for n in 2..64 {
|
|
/* Effectively, this does:
|
|
* pre = round((APB_CLK_FREQ / n) / frequency)
|
|
*/
|
|
|
|
pre = ((apb_clk_freq.raw() as i32 / n) + (frequency.raw() as i32 / 2))
|
|
/ frequency.raw() as i32;
|
|
|
|
if pre <= 0 {
|
|
pre = 1;
|
|
}
|
|
|
|
if pre > 16 {
|
|
pre = 16;
|
|
}
|
|
|
|
errval = (apb_clk_freq.raw() as i32 / (pre as i32 * n as i32)
|
|
- frequency.raw() as i32)
|
|
.abs();
|
|
if bestn == -1 || errval <= besterr {
|
|
besterr = errval;
|
|
bestn = n as i32;
|
|
bestpre = pre as i32;
|
|
}
|
|
}
|
|
|
|
let n: i32 = bestn;
|
|
pre = bestpre as i32;
|
|
let l: i32 = n;
|
|
|
|
/* Effectively, this does:
|
|
* h = round((duty_cycle * n) / 256)
|
|
*/
|
|
|
|
let mut h: i32 = (duty_cycle * n + 127) / 256;
|
|
if h <= 0 {
|
|
h = 1;
|
|
}
|
|
|
|
reg_val = (l as u32 - 1)
|
|
| ((h as u32 - 1) << 6)
|
|
| ((n as u32 - 1) << 12)
|
|
| ((pre as u32 - 1) << 18);
|
|
}
|
|
|
|
self.register_block()
|
|
.clock
|
|
.write(|w| unsafe { w.bits(reg_val) });
|
|
}
|
|
|
|
#[cfg(not(esp32))]
|
|
fn set_data_mode(&mut self, data_mode: SpiMode) -> &mut Self {
|
|
let reg_block = self.register_block();
|
|
|
|
match data_mode {
|
|
SpiMode::Mode0 => {
|
|
reg_block.misc.modify(|_, w| w.ck_idle_edge().clear_bit());
|
|
reg_block.user.modify(|_, w| w.ck_out_edge().clear_bit());
|
|
}
|
|
SpiMode::Mode1 => {
|
|
reg_block.misc.modify(|_, w| w.ck_idle_edge().clear_bit());
|
|
reg_block.user.modify(|_, w| w.ck_out_edge().set_bit());
|
|
}
|
|
SpiMode::Mode2 => {
|
|
reg_block.misc.modify(|_, w| w.ck_idle_edge().set_bit());
|
|
reg_block.user.modify(|_, w| w.ck_out_edge().set_bit());
|
|
}
|
|
SpiMode::Mode3 => {
|
|
reg_block.misc.modify(|_, w| w.ck_idle_edge().set_bit());
|
|
reg_block.user.modify(|_, w| w.ck_out_edge().clear_bit());
|
|
}
|
|
}
|
|
self
|
|
}
|
|
|
|
#[cfg(esp32)]
|
|
fn set_data_mode(&mut self, data_mode: SpiMode) -> &mut Self {
|
|
let reg_block = self.register_block();
|
|
|
|
match data_mode {
|
|
SpiMode::Mode0 => {
|
|
reg_block.pin.modify(|_, w| w.ck_idle_edge().clear_bit());
|
|
reg_block.user.modify(|_, w| w.ck_out_edge().clear_bit());
|
|
}
|
|
SpiMode::Mode1 => {
|
|
reg_block.pin.modify(|_, w| w.ck_idle_edge().clear_bit());
|
|
reg_block.user.modify(|_, w| w.ck_out_edge().set_bit());
|
|
}
|
|
SpiMode::Mode2 => {
|
|
reg_block.pin.modify(|_, w| w.ck_idle_edge().set_bit());
|
|
reg_block.user.modify(|_, w| w.ck_out_edge().set_bit());
|
|
}
|
|
SpiMode::Mode3 => {
|
|
reg_block.pin.modify(|_, w| w.ck_idle_edge().set_bit());
|
|
reg_block.user.modify(|_, w| w.ck_out_edge().clear_bit());
|
|
}
|
|
}
|
|
self
|
|
}
|
|
|
|
fn read_byte(&mut self) -> nb::Result<u8, Error> {
|
|
let reg_block = self.register_block();
|
|
|
|
if reg_block.cmd.read().usr().bit_is_set() {
|
|
return Err(nb::Error::WouldBlock);
|
|
}
|
|
|
|
Ok(u32::try_into(reg_block.w0.read().bits()).unwrap_or_default())
|
|
}
|
|
|
|
fn write_byte(&mut self, word: u8) -> nb::Result<(), Error> {
|
|
let reg_block = self.register_block();
|
|
|
|
if reg_block.cmd.read().usr().bit_is_set() {
|
|
return Err(nb::Error::WouldBlock);
|
|
}
|
|
|
|
self.configure_datalen(8);
|
|
|
|
reg_block.w0.write(|w| unsafe { w.bits(word.into()) });
|
|
|
|
self.update();
|
|
|
|
reg_block.cmd.modify(|_, w| w.usr().set_bit());
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Write bytes to SPI.
|
|
///
|
|
/// Copies the content of `words` in chunks of 64 bytes into the SPI
|
|
/// transmission FIFO. If `words` is longer than 64 bytes, multiple
|
|
/// sequential transfers are performed. This function will return before
|
|
/// all bytes of the last chunk to transmit have been sent to the wire. If
|
|
/// you must ensure that the whole messages was written correctly, use
|
|
/// [`flush`].
|
|
// FIXME: See below.
|
|
fn write_bytes(&mut self, words: &[u8]) -> Result<(), Error> {
|
|
let reg_block = self.register_block();
|
|
let num_chunks = words.len() / FIFO_SIZE;
|
|
|
|
// The fifo has a limited fixed size, so the data must be chunked and then
|
|
// transmitted
|
|
for (i, chunk) in words.chunks(FIFO_SIZE).enumerate() {
|
|
self.configure_datalen(chunk.len() as u32 * 8);
|
|
|
|
let fifo_ptr = reg_block.w0.as_ptr();
|
|
unsafe {
|
|
// It seems that `copy_nonoverlapping` is significantly faster than regular
|
|
// `copy`, by about 20%... ?
|
|
core::ptr::copy_nonoverlapping::<u32>(
|
|
chunk.as_ptr() as *const u32,
|
|
fifo_ptr as *mut u32,
|
|
// FIXME: Using any other transfer length **does not work**. I don't understand
|
|
// why.
|
|
FIFO_SIZE / 4,
|
|
);
|
|
}
|
|
|
|
self.update();
|
|
|
|
reg_block.cmd.modify(|_, w| w.usr().set_bit());
|
|
|
|
// Wait for all chunks to complete except the last one.
|
|
// The function is allowed to return before the bus is idle.
|
|
// see [embedded-hal flushing](https://docs.rs/embedded-hal/1.0.0-alpha.8/embedded_hal/spi/blocking/index.html#flushing)
|
|
//
|
|
// THIS IS NOT TRUE FOR EH 0.2.X! MAKE SURE TO FLUSH IN EH 0.2.X TRAIT
|
|
// IMPLEMENTATIONS!
|
|
if i < num_chunks {
|
|
while reg_block.cmd.read().usr().bit_is_set() {
|
|
// wait
|
|
}
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
/// Read bytes from SPI.
|
|
///
|
|
/// Sends out a stuffing byte for every byte to read. This function doesn't
|
|
/// perform flushing. If you want to read the response to something you
|
|
/// have written before, consider using [`transfer`] instead.
|
|
fn read_bytes(&mut self, words: &mut [u8]) -> Result<(), Error> {
|
|
let empty_array = [EMPTY_WRITE_PAD; FIFO_SIZE];
|
|
|
|
for chunk in words.chunks_mut(FIFO_SIZE) {
|
|
self.write_bytes(&empty_array[0..chunk.len()])?;
|
|
self.flush()?;
|
|
self.read_bytes_from_fifo(chunk)?;
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
/// Read received bytes from SPI FIFO.
|
|
///
|
|
/// Copies the contents of the SPI receive FIFO into `words`. This function
|
|
/// doesn't perform flushing. If you want to read the response to
|
|
/// something you have written before, consider using [`transfer`]
|
|
/// instead.
|
|
// FIXME: Using something like `core::slice::from_raw_parts` and
|
|
// `copy_from_slice` on the receive registers works only for the esp32 and
|
|
// esp32c3 varaints. The reason for this is unknown.
|
|
fn read_bytes_from_fifo(&mut self, words: &mut [u8]) -> Result<(), Error> {
|
|
let reg_block = self.register_block();
|
|
|
|
for chunk in words.chunks_mut(FIFO_SIZE) {
|
|
self.configure_datalen(chunk.len() as u32 * 8);
|
|
|
|
let mut fifo_ptr = reg_block.w0.as_ptr();
|
|
for index in (0..chunk.len()).step_by(4) {
|
|
let reg_val = unsafe { *fifo_ptr };
|
|
let bytes = reg_val.to_le_bytes();
|
|
|
|
let len = usize::min(chunk.len(), index + 4) - index;
|
|
chunk[index..(index + len)].clone_from_slice(&bytes[0..len]);
|
|
|
|
unsafe {
|
|
fifo_ptr = fifo_ptr.offset(1);
|
|
};
|
|
}
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
// Check if the bus is busy and if it is wait for it to be idle
|
|
fn flush(&mut self) -> Result<(), Error> {
|
|
let reg_block = self.register_block();
|
|
|
|
while reg_block.cmd.read().usr().bit_is_set() {
|
|
// wait for bus to be clear
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
fn transfer<'w>(&mut self, words: &'w mut [u8]) -> Result<&'w [u8], Error> {
|
|
for chunk in words.chunks_mut(FIFO_SIZE) {
|
|
self.write_bytes(chunk)?;
|
|
self.flush()?;
|
|
self.read_bytes_from_fifo(chunk)?;
|
|
}
|
|
|
|
Ok(words)
|
|
}
|
|
|
|
#[cfg(not(any(esp32, esp32s2)))]
|
|
fn update(&self) {
|
|
let reg_block = self.register_block();
|
|
|
|
reg_block.cmd.modify(|_, w| w.update().set_bit());
|
|
|
|
while reg_block.cmd.read().update().bit_is_set() {
|
|
// wait
|
|
}
|
|
}
|
|
|
|
#[cfg(any(esp32, esp32s2))]
|
|
fn update(&self) {
|
|
// not need/available on ESP32/ESP32S2
|
|
}
|
|
|
|
fn configure_datalen(&self, len: u32) {
|
|
let reg_block = self.register_block();
|
|
|
|
#[cfg(any(esp32c2, esp32c3, esp32s3))]
|
|
reg_block
|
|
.ms_dlen
|
|
.write(|w| unsafe { w.ms_data_bitlen().bits(len - 1) });
|
|
|
|
#[cfg(not(any(esp32c2, esp32c3, esp32s3)))]
|
|
{
|
|
reg_block
|
|
.mosi_dlen
|
|
.write(|w| unsafe { w.usr_mosi_dbitlen().bits(len - 1) });
|
|
|
|
reg_block
|
|
.miso_dlen
|
|
.write(|w| unsafe { w.usr_miso_dbitlen().bits(len - 1) });
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(any(esp32c2, esp32c3))]
|
|
impl Instance for crate::pac::SPI2 {
|
|
#[inline(always)]
|
|
fn register_block(&self) -> &RegisterBlock {
|
|
self
|
|
}
|
|
|
|
#[inline(always)]
|
|
fn sclk_signal(&self) -> OutputSignal {
|
|
OutputSignal::FSPICLK_MUX
|
|
}
|
|
|
|
#[inline(always)]
|
|
fn mosi_signal(&self) -> OutputSignal {
|
|
OutputSignal::FSPID
|
|
}
|
|
|
|
#[inline(always)]
|
|
fn miso_signal(&self) -> InputSignal {
|
|
InputSignal::FSPIQ
|
|
}
|
|
|
|
#[inline(always)]
|
|
fn cs_signal(&self) -> OutputSignal {
|
|
OutputSignal::FSPICS0
|
|
}
|
|
|
|
#[inline(always)]
|
|
fn enable_peripheral(&self, peripheral_clock_control: &mut PeripheralClockControl) {
|
|
peripheral_clock_control.enable(crate::system::Peripheral::Spi2);
|
|
}
|
|
}
|
|
|
|
#[cfg(any(esp32c3))]
|
|
impl<TX, RX> InstanceDma<TX, RX> for crate::pac::SPI2
|
|
where
|
|
TX: Tx,
|
|
RX: Rx,
|
|
{
|
|
}
|
|
|
|
#[cfg(any(esp32))]
|
|
impl Instance for crate::pac::SPI2 {
|
|
#[inline(always)]
|
|
fn register_block(&self) -> &RegisterBlock {
|
|
self
|
|
}
|
|
|
|
#[inline(always)]
|
|
fn sclk_signal(&self) -> OutputSignal {
|
|
OutputSignal::HSPICLK
|
|
}
|
|
|
|
#[inline(always)]
|
|
fn mosi_signal(&self) -> OutputSignal {
|
|
OutputSignal::HSPID
|
|
}
|
|
|
|
#[inline(always)]
|
|
fn miso_signal(&self) -> InputSignal {
|
|
InputSignal::HSPIQ
|
|
}
|
|
|
|
#[inline(always)]
|
|
fn cs_signal(&self) -> OutputSignal {
|
|
OutputSignal::HSPICS0
|
|
}
|
|
|
|
#[inline(always)]
|
|
fn enable_peripheral(&self, peripheral_clock_control: &mut PeripheralClockControl) {
|
|
peripheral_clock_control.enable(crate::system::Peripheral::Spi2);
|
|
}
|
|
}
|
|
|
|
#[cfg(any(esp32))]
|
|
impl Instance for crate::pac::SPI3 {
|
|
#[inline(always)]
|
|
fn register_block(&self) -> &RegisterBlock {
|
|
self
|
|
}
|
|
|
|
#[inline(always)]
|
|
fn sclk_signal(&self) -> OutputSignal {
|
|
OutputSignal::VSPICLK
|
|
}
|
|
|
|
#[inline(always)]
|
|
fn mosi_signal(&self) -> OutputSignal {
|
|
OutputSignal::VSPID
|
|
}
|
|
|
|
#[inline(always)]
|
|
fn miso_signal(&self) -> InputSignal {
|
|
InputSignal::VSPIQ
|
|
}
|
|
|
|
#[inline(always)]
|
|
fn cs_signal(&self) -> OutputSignal {
|
|
OutputSignal::VSPICS0
|
|
}
|
|
|
|
#[inline(always)]
|
|
fn enable_peripheral(&self, peripheral_clock_control: &mut PeripheralClockControl) {
|
|
peripheral_clock_control.enable(crate::system::Peripheral::Spi3)
|
|
}
|
|
}
|
|
|
|
#[cfg(any(esp32s2, esp32s3))]
|
|
impl Instance for crate::pac::SPI2 {
|
|
#[inline(always)]
|
|
fn register_block(&self) -> &RegisterBlock {
|
|
self
|
|
}
|
|
|
|
#[inline(always)]
|
|
fn sclk_signal(&self) -> OutputSignal {
|
|
OutputSignal::FSPICLK
|
|
}
|
|
|
|
#[inline(always)]
|
|
fn mosi_signal(&self) -> OutputSignal {
|
|
OutputSignal::FSPID
|
|
}
|
|
|
|
#[inline(always)]
|
|
fn miso_signal(&self) -> InputSignal {
|
|
InputSignal::FSPIQ
|
|
}
|
|
|
|
#[inline(always)]
|
|
fn cs_signal(&self) -> OutputSignal {
|
|
OutputSignal::FSPICS0
|
|
}
|
|
|
|
#[inline(always)]
|
|
fn enable_peripheral(&self, peripheral_clock_control: &mut PeripheralClockControl) {
|
|
peripheral_clock_control.enable(crate::system::Peripheral::Spi2)
|
|
}
|
|
}
|
|
|
|
#[cfg(any(esp32s2, esp32s3))]
|
|
impl Instance for crate::pac::SPI3 {
|
|
#[inline(always)]
|
|
fn register_block(&self) -> &RegisterBlock {
|
|
self
|
|
}
|
|
|
|
#[inline(always)]
|
|
fn sclk_signal(&self) -> OutputSignal {
|
|
OutputSignal::SPI3_CLK
|
|
}
|
|
|
|
#[inline(always)]
|
|
fn mosi_signal(&self) -> OutputSignal {
|
|
OutputSignal::SPI3_D
|
|
}
|
|
|
|
#[inline(always)]
|
|
fn miso_signal(&self) -> InputSignal {
|
|
InputSignal::SPI3_Q
|
|
}
|
|
|
|
#[inline(always)]
|
|
fn cs_signal(&self) -> OutputSignal {
|
|
OutputSignal::SPI3_CS0
|
|
}
|
|
|
|
#[inline(always)]
|
|
fn enable_peripheral(&self, peripheral_clock_control: &mut PeripheralClockControl) {
|
|
peripheral_clock_control.enable(crate::system::Peripheral::Spi3)
|
|
}
|
|
}
|