-
Notifications
You must be signed in to change notification settings - Fork 211
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Contract of spi::FullDuplex in presence of hardware FIFOs? #130
Comments
FYI: This is what #120 tries to tackle (I think) |
Another question: in full-duplex SPI mode each And, please, please, add half duplex (send only and receive only) traits too. |
Returning values unfortunately doesn't work out that well. You can't use it at all with fifos at all and even without it's kind of strange & non-deterministic and only really makes sense in combination with block:
I've opened a pr for send-only traits as well (#121), but it depends on #120. If you need this, you might want to bring it up to the hal team in the weekly meetings. |
Then probably a deferred read will do? Let
or something like this. Anyway, on the hardware level a successful write of one byte guarantees exactly one incoming byte regardless of the FIFO, and this should be somehow reflected in the API. Also, I believe the I know at least three important cases for send-only traits: driving shift-registers, driving WS2812 LEDs and driving the ST7565 character display. Especially in the WS2812 case due to hard real-time requirements calling |
My example code was a bit unclear, I didn't mean an error condition but a blocking "Error", sorry 'bout that, updated it. The
While it's flexible, it only seems realistic to use it when you have hardware knowledge. Otherwise, you can (hopefully) only rely on the most recent byte, for not much additional gain compared to just adding the restrictions proposed by me in #120. Adding device-specific methods to be able to use the rx fifo efficiently seems like the way to go, but out of the scope of It also seems to add a lot of complexity to the hal. I agree on the need for more "standardized" hal errors & recovery, for something universal like crc or similar things, but haven't come up with great yet. If you have anything I'm sure the hal team would be receptive. For my ws2812 implementation, I'm just doing a non-blocking read and it worked on anything i tested it on (anything i've seen has larger rx than tx fifos, so even extended pauses shouldn't be an issue in that regard), but I agree it's not great. |
@david-sawatzke The implementation I'm talking about is exactly yours. And it does not work properly if combined with external delays on certain microcontrollers. Even in the simple blink example on a STM32F103 it result in FIFO overrun. What's happening is, the |
@david-sawatzke You've mentioned a couple of times that you don't think it's possible to have It is really discouraging as a driver writer to hear "well if you want to use FIFOs, drop To be clear, I do not think the
|
@edarc The problem we're facing is, many crates that use SPI via
|
@agalakhov Unfortunately I haven't used the stm32f103 yet, but that does sound bad. @edarc The TxFifo is fine, but the RxFifo isn't in my opinion. You can do the TxFifo potentially transparently, but not the RxFifo. Both of your options have flaws: Your first option doesn't work (at least easily), because "insane" hardware like that is pretty widely spread.
I think there might also be race conditions in there for "normal" hardware, but I'm not that sure. Your second option forces drivers to assume the worst case and we're back to the good ol' |
@david-sawatzke The only reliable option for now is to do blocking reads sometimes. But this may interfere with the realtime nature of WS2812. For now I just hacked the SPI implementation to ignore FIFO overruns, but this is not an option for long-term use. We really need something like #120 or, better, an API that has no implicit assumptions and does everything using the type system. Or, even better, the write-only SPI mode. |
A good example of how things could be done is the command set of FTDI's MPSSE engine. It has following commands:
The fourth one is not needed unless SPI is abused for tasks other than serial data transfer, so probably nobody needs this in traits. First three ones correspond to read-only, write-only and read-write access. I imagine something like this:
but it is hard to combine with both FIFO and non-blocking at the same time. |
Argh, that is unfortunate. I really should learn to not underestimate how user-hostile hardware can be :-)
I agree it's not great, I was just trying to think of an option that doesn't involve breaking changes to the trait. You're probably right in that it may race if there is an interrupt between
Sorry, maybe I'm being thick.. what assumptions must be made if let mut maybe_tx = iter.next();
loop {
if let Some(tx) = maybe_tx {
match spi.send(tx) {
Err(WouldBlock) => {
spi.read().map(|rx| my_buffer.push(rx)); // or whatever
}
Ok(()) => { word = iter.next(); }
}
} else {
break;
}
} If RXFIFO doesn't have space to accept the MOSI word for this I think splitting the trait does make sense, I was just trying to avoid it because it's a breaking change, but maybe the current API is not salvageable without breaking it or making many implementations non-conforming (which #120 does AFAICT). I maintain both a write-only driver and a read-write driver, and it would be useful in the former case to have the HAL worry about throwing away the MISO data for me so I don't have to deal with it. But I'd definitely want that to be opt-in behavior; having You'd need to think carefully about how devices switch from one of these modes to the other if the API remains non-blocking, otherwise it becomes extremely hard or impossible to do bus sharing correctly. My only other observation would be that |
@edarc My personal opinion is, taking into account the amount of errors I saw, the current API is not salvageable. I also believe that a breaking change here is even desirable in order to force both HAL implementors and HAL users to fix the behaviour. Looks like virtually nobody (including me) does this correctly. I'd rather deprecate the existing trait and introduce new ones.
It's about wiring, not about periphery. In some cases there is no MOSI at all, and the corresponding pin is configured as GPIO. The precise meaning of traits should probably be "Has MOSI", "Has MISO" and "Has both". |
Agree that it is extremely difficult to use the current trait correctly in any arrangement that couldn't just be served by the current blocking I'm not strongly opinionated on how it gets fixed, other than I'd obviously be sad if we ruled out FIFO support :-) ... I'm even fine if FIFO access is exposed through separate traits so that users who don't want to worry about ordering and overrun issues can just use the non-FIFO traits.
Ah, yes that makes sense. |
Strictly speaking, there is no SPI without FIFO. At least one byte is always buffered. "No FIFO" means FIFO of size 1. |
Can this be closed since #120 has been merged? |
3 years later... yes. |
Background: I'm using a SPI device on an STM32F303, with the
stm32f30x-hal
crate. In this crate, theFullDuplex::send
implementation does the following check (edited below for clarity):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 shouldnb::WouldBlock
. That would have the effect that ifsend
accepts your write, then as soon asread
returns a value (and notnb::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:
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 onread
. 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:-- STM32F303 reference manual, §30.5.9 (warning: PDF)
So this means that my match block can succeed at calling
send
multiple times beforeread
ever returnsOk
, and so I cannot really know how many values I need toread
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 queuessend
s in a FIFO is conforming or not.stm32f30x-hal
: it should really check BSY, or perhaps FTLVL, not TXE. In fact, I noticed that a commented-out manual impl ofblocking::spi
traits does spin on BSY, so it would have actually behaved differently.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 ofread
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 callread
repeatedly until itnb::WouldBlock
, but for the reasons just mentioned I believe this is still technically incorrect. (Edit: I suppose you can manually keep track of whetherread
has returnedOk
exactly as many times assend
returnedOk
, 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.
The text was updated successfully, but these errors were encountered: