AnHardt / Marlin

Reprap FW with look ahead. SDcard and LCD support. It works on Gen6, Ultimaker, RAMPS and Sanguinololu

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Some Remarks about SPI

AnHardt opened this issue · comments

There seems to be some confusion about what SPI is, does, how it works, has to be handled. Here some remarks.

SPI Buss:
The common lines of a SPI-buss are the 3 lines: a clock (SCLK), a line where the Master is sending and the Slave is receiving (MasterOutSlaveIn) and a line where the Master is receiving and the Slave is sending (MasterInSlaveOut).
Normally having one SPI-Buss for all Slaves is enough. There are four reasons for having more.
a.) There are to many slaves on the buss. Even when usually a Slave is switching its inputs to high impedance when not selected the current providable by a Masters output pin may not be enough to produce a clean signal.
b.) The data volume may exceed the possible amount on one buss. (summ_of(bits_to_transfer_per_second_to_a_slave/baudrate_of_slave)over all the slaves > 1)
c. One (or more) Slaves are ignoring or interpreting the SS-Signal falsely - like the ST7920 driving the REPRAP_DISCOUNT_FULL_GRAPHIC_SMART_CONTROLLER receiving random data even when SS is high.
d. Having SPI-Transfers in an interrupt. A SPI_Master strictly communicates to only one Slave at a time, the one with the SS at low. Driving more then one SS low is only allowed in send only configurations, when all slaves have to receive the exact same data and are not sending back any data (MISO disconnected at the slaves).

SPI Slave:
A SPI-Slave is a device a SPI-Master wants to communicate with. Usually it has 4 pins related to SPI-Communication: a input called SlaveSelect (or CE, or ...) drawn low by the Master to select this specific Slave when the Master wants to talk to this Slave (may be fixed to low if only one SPI-Slave is connected to the SPI-Buss), a input called SerialCL*oK** driven by the SPI-Master and at least one of the following two: MasterInSlaveOut and/or MasterOutSlaveIn, depending on if only data is received (only MOSI on for example some display controllers) or send (only MISO for example MAX6675 thermometer) or both (for example an SPI-SD-Card-reader/writer). There can be multiple Slaves connected to one SPI-Buss.
There are 4 things a SPI-Master has to know about a SPI-Slave: What SPI-Bus is it connected to; What is the pin to set for the right Select of the Slave; What is the maximum speed the Slave can handle; And finally the SPI-Mode the Slave expects (Clock polarity and edge of the clock when to fetch the data) and the bit-order LSBFIRST/BSBFIRST (That's not the Byte-order described by the "Endianness" (big-endian/little-endian) when sending multi-byte data like int16_t or int32_t - what is simply ignored by transfer16()).
Being a SPI-Slave is not handled by the Arduino-SPI-API.
In Marlin the CPU Marlin is running on is never a SPI-Slave.

SPI Master:
Each SPI_Bus must have one (and only ONE at a time) SPI-Master. That is the one driving the SCLK-line. Usually it has to drive SCLK, MOSI and the SS-Lines for the different Slaves and to read from the MISO line. (In special cases all but the SCLK_Line may be omitted.)(In SPI-MultiMasterMode the chips connected to a Buss may change their Master/Slave-roles - but there is always only one Master at a time.) In Marlin the CPU Marlin is running on is always the SPI-Master. One SPI-Master is driving one SPI-Buss. One CPU can have multiple SPI-Masters - so drive multiple SPI-Busses.

Hardware SPI:
All CPUs Marlin is currently running on, have at least one Hardware-SPI peripheral integrated into the processor. One of this set of registers and pins is driving one SPI-buss. In general you can select one of a set of pins used for driving a SPI-Signal_Line but the Arduino-SPI-library selects always a predefined set of pins - the selection is not changeable by the Arduino-SPI-API. So SPI, SPI3, SPI6, ... always us the same pins - except we work around the Arduino-SPI-API and use a deeper layer.
Usually the data send register is feed and picked up byte by byte but on some processors even block transfers via DMA are possible.
All Hardware_SPI processor peripherals have a dedicated SS input pin (maybe selectable from a set) for being a slave. That is switching, when drawn to low, automatically the MISO and the SCLK pins from high impedance to inputs. On the AVRs, for being a Master this pin has no special function. On Arduino boards this is usually the one labeled "SS" (if labeled). When being a master this pin may be used as one of the SS-outputs, but any otherwise unused pin is ok for that.

Software SPI:
Software SPI is possible with any set of not otherwise used pins. The main disadvantage of using Software-SPI is speed. Here the processor has to send the data bit by bit. This limits the achievable SPI transfer speed - keeping the CPU busy for a longer time when sending the same amount of data.
In Slave-Mode it would be nice if the SS-input and the SCLK could cause an interrupt. Otherwise the CPU will be busy in polling this pins and cant' do much else. (Being a Software-SPI-Slave is not a good idea.)

Hardware SPI vs. Software SPI
On any system Hardware-SPI can transfer data much faster, with a lower processor load than with Software-SPI. If that higher speed is usable depends on:
Is the Slave capable of receiving faster than the Software-SPI-Master can send?
How granular is the SPI-Speed adjustable? In the old Arduino API there is only a "full speed devising factor" adjustable in potentes of two (1, 1/2, 1/4, 1/8, ...). If there is one factor just right to produce a SPI-data-rate a bit too fast for the Slave, then the next lower data-rate will only make use of a bit more than half of the possible data-rate of the Slave. If the achievable data-rate for the Software-SPI is higher than that, like at Marlins fine tunable Software-SPI device for the ST7920_RRD in U8G and AVRs, than that is preferable (Must be on its own SPI-Buss anyway because of the broken SS - or you have to use tricks to avoid as much visible garbage on the screen as possible).
Otherwise (than duration of the transfer) at Marlin it makes not much of a difference if a Soft- or Hardware-SPI is used. Marlin always waits until the transfer was completed before doing something else. Marlin does not have any SPI-transfers in interrupts. Even SPI-DMA does not make the transfers much faster, only the gaps between sending the bytes are shorter. Marlin busy waits until all data is sent and/or received back.
Making DMA-SPI-Transfers really useful, avoiding the busy waits, making that time available for more useful things, would result in a completely different structure of Marlins SPI concept (a mayor rewrite).

Old Arduino SPI API:
As far as i can remember the old original Arduino SPI API was made with only the AVRs in mind. You had to do about everything by yourselves.
The one SPI is already define. SPI.begin() had no parameters.
The mode of SS,pin(s) had to be set.

When selecting a slave the sequence setClockDivider(a); setDataMode(b), setBitOrder(c); digitalWrite(SSPin, LOW); has to be sent. Note: All the set commands having only one parameter. If there was any chance an other Slave was still selected you had to check all other SSPins before - and wait.
Then you could transfer the data with recdata = SPI.transfer(sendata);. Note: having only one parameter when transferring one byte only and having two parameters when sending an array.
After transfer to one Slave you had to deselect it by a digitalWrite(SSPin, HIGH);

The intermediate DueExtendedSPI:
When the DUE came up the SPI API was extended. SPI.begin(pin) could now have a pin parameter from a small set of capable pins for the SS-pin. setClockDivider(pin, a); setDataMode(pin, b), setBitOrder(pin, c);. recdata = SPI.transfer(sendata); got two new parameters; the pin and a flag if the transfer was a final one or not. For that the digitalWrite(SSPin, HIGH|LOW); before and after the transfer can be omitted.

The current Arduino SPI API:
Was mend to simplify the change of the slaves. The data for ClockDivider, DataMode, BitOrder went into a structure (class) named "SPI Settings". This struct is used in SPI.beginTransaction() to switch all three parameters at once. For whatever reason the SSPin is not part of the "SPI Settings" so handling of that is still as before. Also SPI.beginTransaction() does not give back any data like if that call was successful or not because maybe an other Slave on that buss was still in use.

Adafruit BussIO:
Adafruit BussIO here especially Adafruit_SPIDevice is a game changer. A Adafruit_SPIDevice for the first time describes a SPI-Slave completely, adds a relatively fast Software-SPI implementation. The higher level read(), write() and write_then_read() functions do handle the SS-Pin and start/end-transaction automatically, while you have with transfer() the full flexibility to do all by yourselves.