edarc / ssd1322

Rust embedded-hal driver for SSD1322 OLED displays
7 stars 10 forks source link

`spi::SpiInterface::send_command` hangs since SSD1322 SPI doesn't support reading #1

Open kalj opened 4 years ago

kalj commented 4 years ago

When trying out the basic example code (adapted for a raspberry pi), the program hangs at the call to init. Looking further, it seems every call to send_command hangs in the while loop preceded by this comment:

        // The SPI device has FIFOs that we must ensure are drained before the bus will
        // quiesce. This must happen before asserting DC for a command.

The explanation can be found in the data sheet (e.g. http://www.newhavendisplay.com/specs/NHD-3.12-25664UCY2.pdf page 11): "Note: Read is not available in serial mode."

By commenting out the while loop, everything seems to work fine. I don't really know what the solution is, since that seems to non-standard SPI behavior. Perhaps it needs to implement another, more restricted SPI interface with no send function?

edarc commented 4 years ago

Sorry it's not working! There's been some discussion over on the embedded_hal repo about how the spi::FullDuplex trait is not very well defined with respect to hardware FIFOs, and so sometimes there are interoperability problems. I haven't checked if there's been any progress on that. I may need to just make a blocking interface (and possibly remove the async support) so it works more reliably with other HAL crates.

Were you able to tell if it's just spinning in that loop, or if the .read() call is blocked? The latter should not happen because it's supposed to be async, but anything is possible :)

Which crate are you using to get an implementation of embedded_hal::spi::FullDuplex on your RPi? I mostly tested this on STM32, so it would be nice to get it working elsewhere.

kalj commented 3 years ago

Sorry for the bad response back.

It is spinning in the loop; the read() call is not blocked.

I am using rppal and I am using a Raspberry Pi Zero W.

Can I give you any other information?

edarc commented 3 years ago

I think this might be caused by mismatched assumptions between what ssd1322 expects from a FullDuplex, and what the rppal implementation provides. In the absence of guidance from the embedded_hal definition/documentation, I am unfortunately not sure which libraries need fixing.

The rrpal impl unconditionally returns Ok(v) where v is the last byte shifted in on MISO, even if v has already been returned before, a.k.a. even if no new transfer has occurred. It will never return nb::Error::WouldBlock when there is no more data to read, which is what that while-let in send_command is expecting.

Despite the fact that the SSD1322 does not have a MISO line to return data back to the master, the FullDuplex trait is documented as requiring one read call for every send call, or the hardware may overflow the receive buffer (in this case, with garbage from a floating or tied MISO line). As the comment alludes, the loop on read is intended to prevent this and also to detect that all FIFOs on the SPI master are empty before changing the state of the D/C line. Without some way to synchronize this, the driver can toggle the D/C line while the hardware is still shifting one or more bytes out on MOSI, which violates the timing requirements for the SSD1322.

As I mentioned previously, there has been some debate about whether FullDuplex trait is well-specified enough to be interoperable, and I think this is another example of that: the documentation for FullDuplex::read actually doesn't say that rppal's implementation is incorrect, because it doesn't require implementors to provide the behavior I'm relying on -- namely that it should only return each incoming word once, and that it should return nb::Error::WouldBlock if the receive FIFO is empty.

There is a tracking issue for FullDuplex trait's problems already; let me try to catch up on any developments there and figure out what to do.

edarc commented 3 years ago

Tracking bugs on FullDuplex that I know of:

kalj commented 3 years ago

Thanks for your answers. Unfortunately, the complex interplay between the rust type system and the full duplex SPI protocol makes this a bit too complicated for me to fully grasp. If I understand you correctly, there is no simple workaround for this issue when using rppal / embedded_hal? I found the alternative SPI crate https://github.com/rust-embedded/rust-spidev -- can I use that somehow or do I need something which implements the embedded_hal traits?

kalj commented 3 years ago

Another problem I am experiencing is that each spi.send inside send_data_async takes way too long time and cpu resources. Checking with an oscilloscope, data is in fact sent out at the configured 10MHz, but it takes 40µs between each byte sent to the display, time which is spent busy inside spi.send. Perhaps this is completely unrelated, or due to other problems in rppal, but do you have any feeling for what might be going on there?