embassy-rs / embassy

Modern embedded framework, using Rust and async.
https://embassy.dev
Apache License 2.0
5.15k stars 713 forks source link

SPI data lines floating after write (STM32) #1946

Open petegibson opened 11 months ago

petegibson commented 11 months ago

I'm using SPI to drive some WS2812 RGB LEDs (only using MOSI). On stm32, SPI::write/read calls finish_dma after the transaction, which disables the peripheral. That appears to leave MOSI & CLK floating on my board (STM32F412) which is making them susceptible to interference.

https://github.com/embassy-rs/embassy/blob/2543bcafafbe56f925c39c37b72f634674ccba65/embassy-stm32/src/spi/mod.rs#L816

blocking_write doesn't have the same issue, so as a workaround, I'm sending an empty list after the async write

        unwrap!(rgb_spi.write(&data).await); // send the WS2812 waveform
        _ = rgb_spi.blocking_write(&[0u8; 0]); // dummy write to reenable SPI
Artur-Romaniuk commented 8 months ago

Commenting on this issue, because it is relevant to me as well. DMA transfers lead to an incorrect behavior compared to a blocking SPI transfer. Disabling of SPI peripheral is crucial information for many applications, as many devices will stop working/loose synchronization when it happens. When it is done implicitly at the end of every DMA transaction, it breaks asynchronous code, compared to the same implementation for synchronous code which doesn't disable the peripheral.

Commenting this single expression in finish_dma(:858) function leads to a correct behavior.

    // Disable the spi peripheral
    regs.cr1().modify(|w| {
         w.set_spe(false);
    });
petegibson commented 8 months ago

There is some relevant discussion in Matrix if you search "set_spe".

The RM seems to suggest that you need to set_spe(false) before disabling DMA "to close communication", and @Dirbaio commented that SPI can hang if this is messed with. So maybe it needs to be disabled, DMA stopped, then SPI re-enabled?

Some chips have the ability to keep the lines driven when SPI is disabled but I don't think that's the case for my device

Artur-Romaniuk commented 8 months ago

Ref man for STM32f446xx image

You are correct in that sense that in order to close communication, one has to:

  1. disable DMA
  2. disable SPI
  3. clear DMA buffers.

So the problem boils down to the concept what is DMA communication. Current implementation suggests that each transaction is a separate communication sequence, which means DMA is enabled/disabled after each transaction. This assumption may be wrong for certain applications which require persistent line state, like mine device driver does.

Additionally, this teardown procedure is also not free, as it takes a not insignificant amount of time after each transaction, which means that DMA results being more CPU intensive than a regular blocking transfer (or at least very similar).

That alone would be acceptable, but as an author of a generic device driver using embedded-hal I need to have a possibility to allow my code to run on any implementation, including embassy and current behavior is a major blocker.

I would love to see some configuration parameter, which would allow for disabling of this automatic shutdown of a peripheral, as an opt-in feature.