Skip to content

Contract of spi::FullDuplex in presence of hardware FIFOs? #130

@edarc

Description

@edarc

Background: I'm using a SPI device on an STM32F303, with the stm32f30x-hal crate. In this crate, the FullDuplex::send implementation does the following check (edited below for clarity):

if sr.txe().bit_is_set() {  // <-- note this register
    unsafe { ptr::write_volatile(&self.spi.dr as *const _ as *mut u8, byte) }
    Ok(())
} else {
    Err(nb::Error::WouldBlock)
}

I am guessing, based on the way FullDuplex is documented, that the intention here is to make sure the TXFIFO is empty before accepting a write, otherwise it should nb::WouldBlock. That would have the effect that if send accepts your write, then as soon as read returns a value (and not nb::WouldBlock) exactly once, you know that the SPI bus has quiesced.

In a driver I maintain, there is a string of blind writes (discarding data from MISO) to the device it owns through the SPI impl, and it tries to pipeline the writes by repeatedly doing the following:

match self.spi.send(word) {
    Ok(()) => {
        let _ = self.spi.read();
        Ok(())
    }
    Err(nb::Error::Other(_)) => Err(nb::Error::Other(())),
    Err(nb::Error::WouldBlock) => Err(nb::Error::WouldBlock),
}

Under the assumption I described above about read's behavior, I was originally synchronizing at the end of this string of blind writes by blocking once on read. However this sometimes would return before the bus quiesces. In trying to understand this, I discovered that the TXE bit of the STM32F303's SPI control register, despite its name, does not in fact indicate the TXFIFO is empty, it indicates it is no more than half full:

[...] write access of a data frame to be transmitted is managed by the TXE event. This event is triggered when the TXFIFO level is less than or equal to half of its capacity. Otherwise TXE is cleared and the TXFIFO is considered as full.

-- STM32F303 reference manual, §30.5.9 (warning: PDF)

So this means that my match block can succeed at calling send multiple times before read ever returns Ok, and so I cannot really know how many values I need to read before the bus is guaranteed to be quiescent.

I was trying to determine how to fix my problem, but I am not sure how to proceed, because embedded-hal FullDuplex trait does not document whether an impl that queues sends in a FIFO is conforming or not.

  • If queueing is non-conforming, then I suppose it is a bug in stm32f30x-hal: it should really check BSY, or perhaps FTLVL, not TXE. In fact, I noticed that a commented-out manual impl of blocking::spi traits does spin on BSY, so it would have actually behaved differently.
  • If allowing to queue writes is conforming, then FullDuplex needs to provide a way to detect when the SPI bus is quiescent, otherwise it seems to me as though it isn't possible to correctly synchronize with the SPI device e.g. for managing a CS line. Trying to infer it from the behavior of read is not sufficient, because although you can empty the RXFIFO by doing this, the TXFIFO may still have items in it, which will sometime later cause RXFIFO to again be non-empty, and there is no way to detect this. I "fixed" my driver by having it call read repeatedly until it nb::WouldBlock, but for the reasons just mentioned I believe this is still technically incorrect. (Edit: I suppose you can manually keep track of whether read has returned Ok exactly as many times as send returned Ok, but it seems like it would be far more ergonomic to just query the driver)

I guess my request here is to clarify the contract of FullDuplex with regard to FIFOs or other queueing behavior, so it's obvious what to do about this.

If it matters at all, I'd slightly prefer declaring that a queuing impl is conforming and then providing a way to detect quiescence. While digging around about this problem, I also found japaric/stm32f30x-hal/issues/28, in which it appears the reporter believes, as I originally did, that this impl is not using the FIFOs and requesting that it do so.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions