DannySwarzman:
- A data transfer cannot be initiated from an interrupt handler.
I don't know if this is done in other libraries. Is it a vital feature? Why is it needed?
Actually, very few libraries do that to work properly. The only example I can give you is TMRpcm; this library reads data from a SD card inside an ISR, that reading eventually ends up in doing SPI transactions... INSIDE AN INTERRUPT!
In fact, is not a good practice doing I/O operations inside an interrupt routine (ISR); specially the blocking ones.
pylon:
- you store the pins of MOSI, MISO and SCKL although this is just wasted memory
DannySwarzman:
You could say the same about the register addresses for SPCR, SPDR and SPSR as well as SS. Seven wasted bytes. I guess I should confess a hidden motivation. I'm planning on expanding the library to the Atmega 328PB which has two SPI ports.
In that case, you still don't need that many variables because hardware peripherals are hard-wired to specfic pins; you can't change them by software unless it's a bitbanged SPI port.
At most you need one variable, to indicate which SPI controller the instance (object) should manipulate.
DannySwarzman:
handles slave mode.
How?
Remember that SPI is designed to always transfer data in both directions and at the same time (at least as the master is concerned); so the slave Arduino somehow has to always have data to send as soon as the master begins to pulse the clock line. Only in I2C isn't that time-critical.
DannySwarzman:
The other answer is a little more complex. Consider this:
Serial.print ( ... );
next line of code
The call to Serial.print() puts something into a buffer and exits. The next line of code is executed immediately. Whereas SPI.transfer() has a wait loop. The next line isn't executed until the transfer is complete.
Yup, that's the difference between blocking (synchronous) I/O and non-blocking (asynchronous) I/O.
DannySwarzman:
The SPI peripheral has some distinct idiosyncrasies. The two data streams are simultaneous. But only one end of the wire determines when transfers are to occur. SPIAVR expects both master and slave to be prepared with data to send.
So I guess your library deals with that, because that's the caveat of the SPI slave I've mentioned before.
DannySwarzman:
There is a dataIsReady() function called when the buffer is full.
Callback or it has to be checked in the main program?
Since libraries are supposed to be coded for general usage; how you determine when "data is ready" without being used in a specific application?
DannySwarzman:
But SPI ports don't fit into Stream classes so comfortably.
Why not? Any data stream with some sort of buffer works perfectly with the Stream class (or Print if it's only for output).
DannySwarzman:
I suppose there are a myriad of applications for which SPI is more suitable than UART. SPI is in common use for chips that act as servers in slave mode.
Consider this:
loop() {
if ( SPIAVR.dataIsReady() )
processCommand();
runTheMachine();
}
In this case almost all the processor time is spent in runTheMachine(). The command is processed only when the entire command has been received.
Pretty much like Serial works; being dataIsReady() somewhat a counterpart of available().
So there's no doubt you have created an interrupt-driven version of the SPI library.
I would like to see something similar with analogRead(); since this is another "wait until it's done" function.
AVR ADCs work by successive approximation, a process that takes a while to complete. This is why an interrupt flag for this purpose exists; and analogRead() sets some registers to initiate a conversion, but it stays there until that interrupt triggers to finally give the analog reading. analogRead() enables the required interrupt; so you'll never see it getting stuck when called by another ISR.
As you can see, analogRead() has the exact same issue of SPI.transfer(); and both can be addressed in a similar way indeed.
DannySwarzman:
Sketches that don't require efficient use of the processor should use SPIClass. Arduino libraries aren't written to maximize the use of the processor. Most of the time, you don't care. But if you do and you are willing to be a little more careful, there are great rewards to the more venturesome in the power of these processors.
On low-resources environments, this is a well-known trade-off: you have either convenience or performance, but never both. A very famous example is digitalWrite() vs PORTx.
PD1: ironically, successive approximation ADCs require a DAC, which is what supposedly AVRs lack (excluding PWM).
PD2: maybe the reason why transfer() is blocking it's because of how long it takes to shift out a byte in the "full speed". At such speed, 8 bits takes up to 16 clock cycles (1 microsecond at 16 MHz); an asynchronous data retrieval will obviously delay the transfer of multiple bytes because there's no way the program would attend the completion at the exact moment.
Making transfer() non-blocking when the SPI controller operates at full speed may be actually worse, 16 clock cycles hardly makes up any meaningful processing. If the SPI operates at half speed or less, then it's a big deal; because even 32 clock cycles are enough for some meaningful processing.