Model citizen SPI device library - HOW?

Status
Not open for further replies.

Markk

Well-known member
Hi

I'm trying to find the right way to write a "model citizen" SPI device library, maybe even a reusable base class for such devices.

Looking at diverse libraries I'm now a bit confused. These are my findings:

  1. For portability you would have to go with SPI.h
  2. Strangely there is no separate SPI::write() method for buffers, the transfer() method both sends and receives at the same time, overwriting your buffer, so you usually have to copy it, which obviously is wasteful both on CPU and RAM resources. Many libs just don't use buffers (therefore?) and instead feed byte for byte to the SPI lib, even if a buffer is present. Both the unclear direction of data flow and the byte-wise operation therefore precludes reasonable optimization within the SPI lib itself (such as FIFO and DMA). If I got these things right, I think the SPI.h interface is very badly designed.
  3. For speed you would instead have to go with spi4teensy3.h [where BTW you get your separate send() method]
  4. Or even DmaSpi?
  5. Alternatively you can go half SPI.h + half native as the ILI9341_t3 lib does
  6. Using an #ifdef fest you could probably get both, portability and speed, using a simplified ILI9341_t3 lib pattern one way (in a reusable SPIDevice base class this could be justified)
  7. For in-interrupt SPI processing (and if you're using libs that do) you would have to use SPI Transactions (which can be combined with spi4teensy3 as seen in the USB_Host_Shield_Library_2.0)
  8. Contrary to the K20 Sub-Family Reference Manual, Rev. 1.1, Dec 2012, p. 128 the Teensy 3.1 has only one SPI module and only one is exposed in SPI.h
  9. Strangely however, the LC has two, both are exposed in SPI.h albeit in separate classes SPI and SPI1 respectively, so you'd actually have to write a template class for a versatile SPI device lib (ugh!)
  10. For two SPI busses using one SPI module [switching back and forth using SPI.setMOSI(pin) etc.] it seems that SPI Transactions / interrputs won't work safely because the SPISettings do not store the SPI pins i.e. SPI.beginTransaction() will not reconfigure the pins when switching from a bus A to a bus B device

Have I got things right?

Can I use SPI.setMOSI(pin) etc. after SPI.beginTransaction() and restore primary pins before SPI.endTransaction() so the pin swapping is guarded inside the transaction?

BTW, why is the SPI pin set not part of SPISettings? Is it that costly to test and reset each time a bus pin changes?
(if I'm not mistaken it'd be comparing three bits and setting three 32bit words if a change is seen. See SPCRemulation::enable_pins() in avr_emulation.h)

BTW, why is the chip select not part of SPISettings and why do the SPI.beginTransaction() and SPI.endTransaction() not assert and deassert the CS?
(with an option perhaps, not to set the CS and still doing it manually for quirky devices etc.)

Should my SPI device lib use a constructor that takes and stores an SPISettings reference? Or a copy?
Or should I take the classical clock dividers and SPI mode params and construct the SPISettings in the constructor?

Thanks for all advice,
-Markk
 
I've only just started to learn about SPI and have been confused regarding the different implementations. I'm very interested in a recommended path to take in the teensy world. This sounds like the beginnings of a future wiki on SPI for the teensy. Thanks for starting the discussion.
 
Whatever it is, it should'nt have more overhead than the existing functions.
Spi is -in most of the cases here (RAM/FLASH/DISPLAY...)- speedcritical. The fifo helps a bit, but with SPI at 30 MHz (fbus/2) is not much time...
 
Last edited:
be sure to understand why there is an SPI Transactions library and how it applies to tiime-sharing an SPI port or ports among different drivers and devices. If time-sharing is feasible...
And understand the issues when there is one too few SPI interfaces in hardware... such as for SPI devices that have perishable data and are time critical - such as wireless radios that receive packets and must interrupt the MCU and get the packet's bytes transferred NOW, not after the (sole) SPI port is released by another function that was doing a slow thing like a big flash transfer that's not perishable data.
And so on.
 
be sure to understand why there is an SPI Transactions library and how it applies to tiime-sharing an SPI port or ports among different drivers and devices. If time-sharing is feasible...
And understand the issues when there is one too few SPI interfaces in hardware... such as for SPI devices that have perishable data and are time critical - such as wireless radios that receive packets and must interrupt the MCU and get the packet's bytes transferred NOW, not after the (sole) SPI port is released by another function that was doing a slow thing like a big flash transfer that's not perishable data.
And so on.

Yes, I think I understand SPI Transactions reasonably well and I know it's just an incremental consensus solution, not wanting to break the Arduino "way of life". It is clear that many problems can't be solved adequately with the SPI Transactions' still simplistic approach. One would need queues, priorities, semaphores and a dedicated SPI transceiver thread per physical SPI module i.e. a real time OS (at least for reasonably understandable programming). However I believe programming against a real time OS is a completely different commitment for [mostly] a completely different audience. There have been many discussions in this forum.

So I'm a aware of the shortcomings of the Ardiuno "way of life" and willing to accept its conceptual limits. However inside that envelope I think there is much room for improvement and consolidation for the SPI device developer.

While it is clear that software can hardly resolve hardware shortcomings such as "one too few SPI interfaces in hardware" you have pointed out important issues. The scenario you describe can't be fully solved because SPI Transaction just can't be blindly preempted once they've started. However there are possible approaches to reduce the problem:
  1. If it is foreseeable when and how long a time critical SPI interaction will occur, one could block all the other devices for the duration (the SPI device base class could perhaps provide this by withholding SPI.endTransation() across these multiple transmissions, effectively bracketing these together)
  2. However if the time critical SPI interaction is not foreseeable (or too long or the duty factor too high), there's simply no real solution, I believe (second MCU?). The following workarounds and/or mitigations come to mind:
  3. One could condemn all the other devices to software/bit banging SPI on separate pins (if a SPI device base class were a template class it could also accommodate a software SPI implementation)
  4. The "big flash transfer" should be broken up into several transactions so the time critical stuff can go in between. So the SPI device base class should be prepared to do that inside large transfers if another device is requesting it on the SPI module. The device would have to explicitly override this behavior if it really needed uninterrupted transfers.
  5. With multiple SPI modules one would also have to make sure that SPI Transactions just block the interrupts associated with their module and not the others (I don't know if this is currently the case with the LC's SPI vs. SPI1).

Thanks for the interesting input.
--Markk
 
Whatever it is, it should'nt have more overhead than the existing functions.
Spi is -in most of the cases here (RAM/FLASH/DISPLAY...)- speedcritical. The fifo helps a bit, but with SPI at 30 MHz (fbus/2) is not much time...

That's one reason I'm currently favoring the "half SPI.h + half native as the ILI9341_t3 lib does" approach "Using an #ifdef fest" to "get both, portability and speed, using a simplified ILI9341_t3 lib pattern one way".

The demo by Paul is really impressive!

Also, only a template class is acceptable for using different SPI classes. So all method calls can be early bound and therefore be inlined (=zero overhead).

_Markk
 
Most all ARM mid-range and up MCUs packages have 2+ SPI ports and the next Teensy board probably will as well.
I was naively surprised to find that the MCU die for a family have all the peripherals in silicon, but according the the pin count of the package, many are present but not connected (wire-bonded).
Naivete' was I thought they omitted some peripherals on-chip to reduce the packaged chip price.

Learned all this working now on a project were all ICs are dies on the PCB. MCU, data mux, etc.
 
Last edited:
Status
Not open for further replies.
Back
Top