Lines
85.71 %
Functions
100 %
#![cfg_attr(docsrs, procmacros::doc_replace(
"dma_channel" => {
cfg(spi_slave_dma_engine = "SPI_DMA") => "DMA_SPI2",
cfg(spi_slave_dma_engine = "AHB_GDMA") => "DMA_CH0",
cfg(spi_slave_dma_engine = "AXI_GDMA") => "DMA_AXI_CH0",
},
))]
//! # Serial Peripheral Interface - Slave Mode
//!
//! ## Overview
//! In this mode, the SPI acts as slave and transfers data with its master when
//! its CS is asserted.
//! ## Configuration
//! The SPI slave driver allows using full-duplex and can only be used with DMA.
#![cfg_attr(
spi_slave_supports_dma,
doc = r#"
## Examples
### SPI Slave with DMA
```rust, no_run
# {before_snippet}
# use esp_hal::dma_rx_buffer;
# use esp_hal::dma_tx_buffer;
# use esp_hal::spi::Mode;
# use esp_hal::spi::slave::Spi;
let sclk = peripherals.GPIO0;
let miso = peripherals.GPIO1;
let mosi = peripherals.GPIO2;
let cs = peripherals.GPIO3;
let dma_rx_buf = dma_rx_buffer!(32000).unwrap();
let dma_tx_buf = dma_tx_buffer!(32000).unwrap();
let mut spi = Spi::new(peripherals.SPI2, Mode::_0)
.with_sck(sclk)
.with_mosi(mosi)
.with_miso(miso)
.with_cs(cs)
.with_dma(peripherals.__dma_channel__);
let transfer = spi.transfer(50, dma_rx_buf, 50, dma_tx_buf)?;
transfer.wait();
# {after_snippet}
```
"#
)]
//! ## Implementation State
//! This driver is currently **unstable**.
//! There are several options for working with the SPI peripheral in slave mode,
//! but the code currently only supports:
//! - Single transfers (not segmented transfers)
//! - Full duplex, single bit (not dual or quad SPI)
//! - DMA mode (not CPU mode).
#![cfg_attr(esp32, doc = "- ESP32 only supports SPI mode 1 and 3.\n\n")]
//! It also does not support blocking operations, as the actual
//! transfer is controlled by the SPI master; if these are necessary,
//! then the `SpiDmaTransfer` object can be `wait()`ed on or polled for
//! `is_done()`.
//! See [tracking issue](https://github.com/esp-rs/esp-hal/issues/469) for more information.
use core::marker::PhantomData;
use super::Mode;
use crate::{
Blocking,
DriverMode,
gpio::{
InputSignal,
NoPin,
OutputConfig,
OutputSignal,
interconnect::{PeripheralInput, PeripheralOutput},
pac::spi2::RegisterBlock,
system::PeripheralGuard,
};
/// SPI peripheral driver.
///
/// See the [module-level documentation][self] for more details.
#[instability::unstable]
pub struct Spi<'d, Dm: DriverMode> {
spi: AnySpi<'d>,
#[allow(dead_code)]
data_mode: Mode,
_mode: PhantomData<Dm>,
_guard: PeripheralGuard,
}
impl<'d> Spi<'d, Blocking> {
/// Creates a new SPI instance in 8-bit data-frame mode.
pub fn new(spi: impl Instance + 'd, mode: Mode) -> Spi<'d, Blocking> {
let guard = PeripheralGuard::new(spi.info().peripheral);
let this = Spi {
spi: spi.degrade(),
data_mode: mode,
_mode: PhantomData,
_guard: guard,
this.spi.info().init();
this.spi.info().set_data_mode(mode, false);
this.with_mosi(NoPin)
.with_miso(NoPin)
.with_sck(NoPin)
.with_cs(NoPin)
fn connect_input_pin(&self, pin: impl PeripheralInput<'d>, signal: InputSignal) {
let pin = pin.into();
pin.set_input_enable(true);
signal.connect_to(&pin);
/// Assigns the SCK (Serial Clock) pin for the SPI instance.
pub fn with_sck(self, sclk: impl PeripheralInput<'d>) -> Self {
self.connect_input_pin(sclk, self.spi.info().sclk);
self
/// Assigns the MOSI (Master Out Slave In) pin for the SPI instance.
pub fn with_mosi(self, mosi: impl PeripheralInput<'d>) -> Self {
self.connect_input_pin(mosi, self.spi.info().mosi);
/// Assigns the MISO (Master In Slave Out) pin for the SPI instance.
pub fn with_miso(self, miso: impl PeripheralOutput<'d>) -> Self {
let miso = miso.into();
miso.apply_output_config(&OutputConfig::default());
miso.set_output_enable(true);
self.spi.info().miso.connect_to(&miso);
/// Assigns the CS (Chip Select) pin for the SPI instance.
pub fn with_cs(self, cs: impl PeripheralInput<'d>) -> Self {
self.connect_input_pin(cs, self.spi.info().cs);
/// DMA (Direct Memory Access) functionality (Slave).
#[cfg(spi_slave_supports_dma)]
pub mod dma {
use core::mem::ManuallyDrop;
use enumset::enum_set;
use super::*;
#[cfg(not(spi_slave_dma_engine = "SPI_DMA"))]
use crate::RegisterToggle;
dma::{Channel, DmaRxBuffer, DmaRxInterrupt, DmaTxBuffer, EmptyBuf},
rtc_cntl::WakeLock,
spi::Error,
const MAX_DMA_SIZE: usize = 32768 - 32;
/// Configures the SPI peripheral with the provided DMA channel and
/// descriptors.
#[cfg_attr(esp32, doc = "\n\n**Note**: ESP32 only supports Mode 1 and 3.")]
pub fn with_dma(
self,
channel: impl SpiSlaveDmaChannel<'d, AnySpi<'d>>,
) -> dma::SpiDma<'d, Blocking> {
self.spi.info().set_data_mode(self.data_mode, true);
dma::SpiDma::new(self.spi, channel.into())
/// A structure representing a DMA transfer for SPI.
pub struct SpiDma<'d, Dm>
where
Dm: DriverMode,
{
pub(crate) spi: AnySpi<'d>,
pub(crate) channel: Channel<Dm, SpiSlaveErased<'d>>,
impl<Dm> core::fmt::Debug for SpiDma<'_, Dm>
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("SpiDma").finish()
impl<'d> SpiDma<'d, Blocking> {
pub(super) fn new(spi: AnySpi<'d>, channel: SpiSlaveErased<'d>) -> Self {
let channel = Channel::new(channel);
channel.runtime_ensure_compatible(spi.dma_peripheral());
Self {
spi,
channel,
impl<'d, Dm> SpiDma<'d, Dm>
fn driver(&self) -> DmaDriver {
DmaDriver {
info: self.spi.info(),
dma_peripheral: self.spi.dma_peripheral(),
/// Registers a buffer for a DMA write.
/// Returns a [`SpiDmaTransfer`] that can be used to wait for the transfer to complete.
/// The maximum amount of data to be sent is 32736 bytes.
/// The write is driven by the SPI master's sclk signal and cs line.
pub fn write<TX>(
mut self,
bytes_to_write: usize,
mut buffer: TX,
) -> Result<SpiDmaTransfer<'d, Dm, TX>, (Error, Self, TX)>
TX: DmaTxBuffer,
if bytes_to_write > MAX_DMA_SIZE {
return Err((Error::MaxDmaTransferSizeExceeded, self, buffer));
let result = unsafe {
self.driver().start_transfer_dma(
0,
bytes_to_write,
&mut EmptyBuf,
&mut buffer,
&mut self.channel,
)
if let Err(err) = result {
return Err((err, self, buffer));
Ok(SpiDmaTransfer::new(self, buffer, false, true))
/// Registers a buffer for a DMA read.
/// The maximum amount of data to be received is 32736 bytes.
/// The read is driven by the SPI master's sclk signal and cs line.
pub fn read<RX>(
bytes_to_read: usize,
mut buffer: RX,
) -> Result<SpiDmaTransfer<'d, Dm, RX>, (Error, Self, RX)>
RX: DmaRxBuffer,
if bytes_to_read > MAX_DMA_SIZE {
bytes_to_read,
Ok(SpiDmaTransfer::new(self, buffer, true, false))
/// Registers buffers for a DMA transfer.
/// The maximum amount of data to be sent/received is 32736 bytes.
/// The data transfer is driven by the SPI master's sclk signal and cs
/// line.
#[allow(clippy::type_complexity)]
pub fn transfer<RX, TX>(
mut rx_buffer: RX,
mut tx_buffer: TX,
) -> Result<SpiDmaTransfer<'d, Dm, (RX, TX)>, (Error, Self, RX, TX)>
if bytes_to_read > MAX_DMA_SIZE || bytes_to_write > MAX_DMA_SIZE {
return Err((
Error::MaxDmaTransferSizeExceeded,
rx_buffer,
tx_buffer,
));
&mut rx_buffer,
&mut tx_buffer,
return Err((err, self, rx_buffer, tx_buffer));
Ok(SpiDmaTransfer::new(
(rx_buffer, tx_buffer),
true,
))
/// Holds references to the SPI instance, DMA buffers, and transfer status.
pub struct SpiDmaTransfer<'d, Dm, Buf>
spi_dma: ManuallyDrop<SpiDma<'d, Dm>>,
dma_buf: ManuallyDrop<Buf>,
has_rx: bool,
has_tx: bool,
_wake_lock: WakeLock,
impl<'d, Dm, Buf> SpiDmaTransfer<'d, Dm, Buf>
fn new(spi_dma: SpiDma<'d, Dm>, dma_buf: Buf, has_rx: bool, has_tx: bool) -> Self {
spi_dma: ManuallyDrop::new(spi_dma),
dma_buf: ManuallyDrop::new(dma_buf),
has_rx,
has_tx,
_wake_lock: WakeLock::new(),
/// Returns whether the transfer is complete.
/// Both RX and TX operations are done, and the SPI instance is no longer
/// busy.
pub fn is_done(&self) -> bool {
if self.has_rx {
let done_int =
enum_set!(DmaRxInterrupt::SuccessfulEof | DmaRxInterrupt::DescriptorEmpty);
if self
.spi_dma
.channel
.rx
.pending_in_interrupts()
.is_disjoint(done_int)
return false;
!self.spi_dma.spi.info().is_bus_busy()
/// Waits for the DMA transfer to complete.
/// Blocks until the transfer is finished and returns the
/// `SpiDma` instance and the associated buffer.
pub fn wait(mut self) -> (SpiDma<'d, Dm>, Buf) {
while !self.is_done() {
// Wait for the SPI to become idle
if self.has_tx {
// In case DMA TX buffer is bigger than what the SPI consumes, stop the DMA.
if !self.spi_dma.channel.tx.is_done() {
self.spi_dma.channel.tx.stop_transfer();
let retval = unsafe {
(
ManuallyDrop::take(&mut self.spi_dma),
ManuallyDrop::take(&mut self.dma_buf),
core::mem::forget(self);
retval
impl<Dm, Buf> Drop for SpiDmaTransfer<'_, Dm, Buf>
fn drop(&mut self) {
unsafe {
ManuallyDrop::drop(&mut self.spi_dma);
ManuallyDrop::drop(&mut self.dma_buf);
struct DmaDriver {
info: &'static Info,
dma_peripheral: crate::dma::DmaPeripheral,
impl DmaDriver {
fn regs(&self) -> &RegisterBlock {
self.info.regs()
unsafe fn start_transfer_dma<Dm: DriverMode>(
&self,
read_buffer_len: usize,
write_buffer_len: usize,
rx_buffer: &mut impl DmaRxBuffer,
tx_buffer: &mut impl DmaTxBuffer,
channel: &mut Channel<Dm, SpiSlaveErased<'_>>,
) -> Result<(), Error> {
self.enable_dma();
self.info.reset_spi();
if read_buffer_len > 0 {
channel
.prepare_transfer(self.dma_peripheral, rx_buffer)?;
if write_buffer_len > 0 {
.tx
.prepare_transfer(self.dma_peripheral, tx_buffer)?;
#[cfg(esp32)]
self.info
.prepare_length_and_lines(read_buffer_len, write_buffer_len);
#[cfg(not(esp32))]
self.regs()
.dma_conf()
.modify(|_, w| w.dma_slv_seg_trans_en().clear_bit());
self.clear_dma_interrupts();
self.info.setup_for_flush();
self.reset_dma_rx_fifo();
channel.rx.start_transfer()?;
self.reset_dma_tx_fifo();
channel.tx.start_transfer()?;
// The SPI master owns SCLK and CS. Arm the peripheral only after
// both GDMA directions can service the first edge.
self.regs().cmd().modify(|_, w| w.usr().set_bit());
Ok(())
fn reset_dma_rx_fifo(&self) {
.toggle(|w, bit| w.rx_afifo_rst().bit(bit));
fn reset_dma_tx_fifo(&self) {
.toggle(|w, bit| w.dma_afifo_rst().bit(bit));
fn enable_dma(&self) {
cfg_select! {
spi_slave_dma_engine = "SPI_DMA" => {
self.regs().dma_conf().toggle(|w, bit| {
w.in_rst().bit(bit);
w.out_rst().bit(bit);
w.ahbm_fifo_rst().bit(bit);
#[cfg(esp32s2)]
w.dma_infifo_full_clr().bit(bit);
w.ahbm_rst().bit(bit)
});
_ => {
self.regs().dma_conf().modify(|_, w| {
w.dma_tx_ena().set_bit();
w.dma_rx_ena().set_bit();
w.rx_eof_en().clear_bit()
fn clear_dma_interrupts(&self) {
self.regs().dma_int_clr().write(|w| {
w.inlink_dscr_empty().clear_bit_by_one();
w.outlink_dscr_error().clear_bit_by_one();
w.inlink_dscr_error().clear_bit_by_one();
w.in_done().clear_bit_by_one();
w.in_err_eof().clear_bit_by_one();
w.in_suc_eof().clear_bit_by_one();
w.out_done().clear_bit_by_one();
w.out_eof().clear_bit_by_one();
w.out_total_eof().clear_bit_by_one()
w.dma_infifo_full_err().clear_bit_by_one();
w.dma_outfifo_empty_err().clear_bit_by_one();
w.trans_done().clear_bit_by_one();
w.mst_rx_afifo_wfull_err().clear_bit_by_one();
w.mst_tx_afifo_rempty_err().clear_bit_by_one()
/// A marker for DMA-capable SPI peripheral instances.
#[doc(hidden)]
#[allow(private_bounds)]
pub trait InstanceDma: Instance {}
#[cfg(soc_has_spi2)]
impl InstanceDma for crate::peripherals::SPI2<'_> {}
#[cfg(soc_has_spi3)]
impl InstanceDma for crate::peripherals::SPI3<'_> {}
impl InstanceDma for AnySpi<'_> {}
with_spi_slave_dma_engine! {
($engine:tt, $any_channel:ident) => {
/// DMA channel trait for SPI slave peripherals.
#[diagnostic::on_unimplemented(
message = "The DMA channel cannot be used with this SPI peripheral",
label = "This DMA channel",
note = "Use a channel that matches the SPI instance."
pub trait SpiSlaveDmaChannel<'d, S>: crate::private::Sealed + Into<crate::dma::$any_channel<'d>> {}
crate::macros::impl_dma_channel_trait! {
$engine,
any_peri = AnySpi<'d>,
peris = for_each_spi_slave,
($peri:path, $ch:path) => {
impl<'d> SpiSlaveDmaChannel<'d, $peri> for $ch {}
type SpiSlaveErased<'d> = crate::dma::$any_channel<'d>;
/// A peripheral singleton compatible with the SPI slave driver.
pub trait Instance: crate::private::Sealed + any::Degrade {
/// Returns the peripheral data describing this SPI instance.
fn info(&self) -> &'static Info;
/// Peripheral data describing a particular SPI instance.
#[non_exhaustive]
pub struct Info {
/// Pointer to the register block for this SPI instance.
/// Used with [`Self::register_block`] to access the register block.
pub register_block: *const RegisterBlock,
/// System peripheral marker.
pub peripheral: crate::system::Peripheral,
/// SCLK signal.
pub sclk: InputSignal,
/// MOSI signal.
pub mosi: InputSignal,
/// MISO signal.
pub miso: OutputSignal,
/// Chip select signal.
pub cs: InputSignal,
impl Info {
/// Returns the register block for this SPI instance.
pub fn regs(&self) -> &RegisterBlock {
unsafe { &*self.register_block }
fn reset_spi(&self) {
self.regs().slave().modify(|_, w| w.sync_reset().set_bit());
.slave()
.modify(|_, w| w.sync_reset().clear_bit());
self.regs().slave().modify(|_, w| w.soft_reset().set_bit());
.modify(|_, w| w.soft_reset().clear_bit());
fn prepare_length_and_lines(&self, rx_len: usize, tx_len: usize) {
.slv_rdbuf_dlen()
.write(|w| unsafe { w.bits((rx_len as u32 * 8).saturating_sub(1)) });
.slv_wrbuf_dlen()
.write(|w| unsafe { w.bits((tx_len as u32 * 8).saturating_sub(1)) });
// SPI Slave mode on ESP32 requires MOSI/MISO enable
self.regs().user().modify(|_, w| {
w.usr_mosi().bit(rx_len > 0);
w.usr_miso().bit(tx_len > 0)
/// Initializes for full-duplex 1 bit mode.
fn init(&self) {
#[cfg(soc_has_pcr)]
crate::peripherals::PCR::regs()
.spi2_clkm_conf()
.modify(|_, w| w.spi2_clkm_en().set_bit());
self.regs().clock().write(|w| unsafe { w.bits(0) });
self.regs().user().write(|w| unsafe { w.bits(0) });
self.regs().ctrl().write(|w| unsafe { w.bits(0) });
self.regs().slave().write(|w| {
w.slv_wr_rd_buf_en().set_bit();
w.mode().set_bit()
self.reset_spi();
w.doutdin().set_bit();
w.sio().clear_bit()
self.regs().misc().write(|w| unsafe { w.bits(0) });
fn set_data_mode(&self, data_mode: Mode, dma: bool) {
self.regs().pin().modify(|_, w| {
w.ck_idle_edge()
.bit(matches!(data_mode, Mode::_0 | Mode::_1))
.user()
.modify(|_, w| w.ck_i_edge().bit(matches!(data_mode, Mode::_1 | Mode::_2)));
self.regs().ctrl2().modify(|_, w| unsafe {
match data_mode {
Mode::_0 => {
w.miso_delay_mode().bits(0);
w.miso_delay_num().bits(0);
w.mosi_delay_mode().bits(2);
w.mosi_delay_num().bits(2)
Mode::_1 => {
w.miso_delay_mode().bits(2);
w.mosi_delay_mode().bits(0);
w.mosi_delay_num().bits(0)
Mode::_2 => {
w.mosi_delay_mode().bits(1);
Mode::_3 => {
w.miso_delay_mode().bits(1);
if dma {
assert!(
matches!(data_mode, Mode::_1 | Mode::_3),
"Mode {:?} is not supported with DMA",
data_mode
);
_ = dma;
w.tsck_i_edge()
.bit(matches!(data_mode, Mode::_1 | Mode::_2));
w.rsck_i_edge()
.bit(matches!(data_mode, Mode::_1 | Mode::_2))
esp32s2 => {
let ctrl1_reg = self.regs().ctrl1();
let ctrl1_reg = self.regs().slave();
ctrl1_reg.modify(|_, w| {
w.clk_mode_13()
.bit(matches!(data_mode, Mode::_1 | Mode::_3))
fn is_bus_busy(&self) -> bool {
let reg = cfg_select! {
any(esp32, esp32s2) => self.regs().slave(),
_ => self.regs().dma_int_raw(),
reg.read().trans_done().bit_is_clear()
// Clear the transaction-done interrupt flag so flush() can work properly.
fn setup_for_flush(&self) {
any(esp32, esp32s2) => {
.modify(|_, w| w.trans_done().clear_bit());
.dma_int_clr()
.write(|w| w.trans_done().clear_bit_by_one());
impl PartialEq for Info {
fn eq(&self, other: &Self) -> bool {
core::ptr::eq(self.register_block, other.register_block)
unsafe impl Sync for Info {}
for_each_spi_slave! {
($peri:ident, $sys:ident, $sclk:ident, $mosi:ident, $miso:ident, $cs:ident) => {
impl Instance for crate::peripherals::$peri<'_> {
#[inline(always)]
fn info(&self) -> &'static Info {
static INFO: Info = Info {
register_block: crate::peripherals::$peri::ptr(),
peripheral: crate::system::Peripheral::$sys,
sclk: InputSignal::$sclk,
mosi: InputSignal::$mosi,
miso: OutputSignal::$miso,
cs: InputSignal::$cs,
&INFO
crate::any_peripheral! {
/// Any SPI peripheral.
pub peripheral AnySpi<'d> {
#[cfg(spi_master_spi2)]
Spi2(crate::peripherals::SPI2<'d>),
#[cfg(spi_master_spi3)]
Spi3(crate::peripherals::SPI3<'d>),
($engine:tt, $any_ch:ident) => {
use crate::dma::DmaEligiblePeripheral;
impl<'d> DmaEligiblePeripheral<crate::dma::$any_ch<'d>> for AnySpi<'d> {
fn dma_peripheral(&self) -> crate::dma::DmaPeripheral {
any::delegate!(self, spi => { spi.dma_peripheral() })
impl Instance for AnySpi<'_> {
any::delegate!(self, spi => { spi.info() })