While I like the idea of remembering settings for different SPI devices on the AVR devices, why does it need another API with begin/end etc?
The extended SPI API for the Due already does this pretty neatly... just pass the desired SS pin # as a reference with setBitOrder, setDataMode and setClockDivider, and store the settings. Then pass the same SS again with each transfer. A quick check in each transfer to see whether the last SS used is the same as the current avoids having to unnecessarily set/reset settings each time if the same device is being used. Then use SPI_CONTINUE and SPI_LAST flags to decide whether to toggle the SS. This way it keeps compatibility with existing libraries too.
C
--
You received this message because you are subscribed to the Google Groups "Developers" group.
To unsubscribe from this group and stop receiving emails from it, send an email to developers+unsubscribe@arduino.cc.
C
--
You received this message because you are subscribed to the Google Groups "Developers" group.
To unsubscribe from this group and stop receiving emails from it, send an email to developers+...@arduino.cc.
--
You received this message because you are subscribed to the Google Groups "Developers" group.
To unsubscribe from this group and stop receiving emails from it, send an email to developers+unsubscribe@arduino.cc.
Thanks for explaining. I thought I was missing something!
Thinking about it more, one thing that bothers me is that this requires the SPI library to know about interrupts (even if not very much), which seems opposed to modularity. For example, you could imagine wanting similar functionality for many other libraries. Also, it makes the SPI library dependent on the interrupt API, e.g. if we added attachPinChangeInterrupt(), we'd also need to add SPI.usingPinChangeInterrupt(), etc. (Yes, you could do attachInterrupt(PINCHANGE0) instead, but this is same problem in reverse: the interrupt API is then being constrained by the fact that the SPI library depends on it.) Of course, it's good to solve the problems that people are actually running into (and I believe you, Paul, that these are mostly with SPI) but it would be good to do it in a more general way.
One idea would be for the SPI library to set a flag when it's in a transaction, so that you could code your ISR to refrain from using SPI if there's a transaction in progress, e.g.
void myInterruptHandler(){if (SPI.isTransmitting()) return;
// ...}
Or do the actual use cases require the interrupt to be buffered until the current SPI transaction completes?
Another thought is that the typical way for one module to do something (in a flexible way) based on actions in another module is using events. So, for example, a library that uses SPI inside an ISR could do something like:
void BLE::begin(){attachInterrupt(0, bleReceive);SPI.onBeginTransaction(disableInterrupt0);SPI.onEndTransaction(enableInterrupt0);// ...}
void disableInterrupt0() { disableInterrupt(0); }void enableInterrupt0() { enableInterrupt(0); }
I know this is more verbose than simply calling SPI.usingInterrupt(0), but it seems much more flexible and modular.
Anyway, just throwing some ideas out there. What do you think?
David
On Tue, Apr 15, 2014 at 3:55 PM, Paul Stoffregen <pa...@pjrc.com> wrote:
On 04/15/2014 10:06 AM, Cristian Maglie wrote:Yes, a perfect summary of the interrupt awareness feature!
Exactly, the idea is that Library B decalares to the SPI library that he uses
SPI bus inside ISRx, so it's SPI lib that disable ISRx every time a
transcation is started (by A or B or anyone else), to guarantee that the
transaction itself is not corrupted.
With this feature, and always establishing the correct setting at the beginning of each transaction, my hope is to make all SPI-using libraries "just work" together. Of course, there's a caveat that an extremely long transaction could delay an interrupt, but I believe this approach will give the maximum possible compatibility between existing libraries.
This conversation clearly shows there's a great desire to add more features to the SPI library. I'm not opposed to adding features, but I'm personally not very interested in doing so. My main interest is making the many library combinations that today fail work together as seamlessly as possible. I believe limiting the scope of a software project greatly improves the odds of success, so my hope is to focus only on this compatibility problem. More features can always be added as a separate effort.
These SPI sharing failures are driving people away from the Arduino platform, to Linux and complex RTOS systems (or just giving up), for increasingly common needs, such as using SD card storage and Bluetooth LE together in the same project. I really want to solve this problem, and start submitting patches to the main SPI-based libraries, so Arduino can be a viable platform for these projects that use multiple SPI devices with libraries from different 3rd party developers.
--
You received this message because you are subscribed to the Google Groups "Developers" group.
To unsubscribe from this group and stop receiving emails from it, send an email to developers+...@arduino.cc.
--
You received this message because you are subscribed to the Google Groups "Developers" group.
To unsubscribe from this group and stop receiving emails from it, send an email to developers+...@arduino.cc.
Paul,
I just want to throw in my support for your proposal. It's becoming an increasingly urgent problem we are facing as we try and integrate more SPI peripherals.
Cheers,
Vic
--
You received this message because you are subscribed to the Google Groups "Developers" group.
To unsubscribe from this group and stop receiving emails from it, send an email to developers+...@arduino.cc.
Thanks Paul for the encouraging words.On 04/18/2014 10:01 PM, Paul Stoffregen wrote:
Mikael, your Cosa project is truly beautiful. I've read source code from many projects and nearly all widely used Arduino libraries. Rarely have I see APIs and implementations with elegance even approaching Cosa.
I really like how you've provided a room() function. Arduino really needs something like that!
Over the last few days I've put quite a bit of time into analyzing how several existing libraries really use SPI. The more I look into this, the more I believe SPI chip select should have its own API rather than being rolled into the transaction level.
For example, Ethernet involves many small transfers that require separate assertions of SS. Even just reading a 16 bit register on the W5100 chip requires two sets of 4 byte transfers. There are places where a 16 bit register is read repeatedly until 2 consecutive readings agree, to guard against reading the MSB and LSB from different values. Maybe these should be single transactions, even though they involve asserting the SS pin several times?
Really, I'm asking here. I'm still studying how several libraries really work and where indivisible SPI transactions really make sense.
The chip select pin is a valid part of the SPI::Driver base class so it is fine for the driver to assert the pin as many times as needed within the transaction block.
BW: The W5100 Ethernet chip is a marvel of inefficient SPI ;-).
Yet another example is the shift register LCD adapter which requires a pulse at the end of the transaction. The eight bit write to the LCD even has a pulse in the "middle" of the transaction.
https://github.com/mikaelpatel/Cosa/blob/master/cores/cosa/Cosa/LCD/Driver/HD44780_IO_SR3WSPI.cpp#L50
/** Chip select pulse width;* 0 for active low logic during the transaction,* 1 for active high logic,* 2 pulse on end of transaction.*/uint8_t m_pulse;
I have followed this discussion and it is really a good example of the sharing of ideas within the Arduino community. Great inspiration!Really, I'm asking here. I'm still studying how several libraries really work and where indivisible SPI transactions really make sense.
I do have one other question on the begin() implementation. Shouldn't the interrupt disable happen before asserting the config and chip select? Seems like an interrupt could occur during that very brief time, causing 2 simultaneous chip selects and leaving the settings for another instance.
C
To elaborate a bit on the modularity concern... I worry that if add a usingInterrupt() function to the SPI library, we're soon going to have:
SPI.usingInterrupt()SPI.usingPinChangeInterrupt() // once we add attachPinChangeInterrupt() or similarSPI.usingTimerInterrupt()
Wire.usingInterrupt()Wire.usingPinChangeInterrupt()Wire.usingTimerInterrupt()
Serial.usingInterrupt()
It seems like it would be good to avoid having SPI and possibly other libraries depend some intimately on interrupts, both from an implementation maintenance perspective and from an API usability perspective.
I realize an event-based API would add overhead, but it seems like it might be worth the tradeoff. Do you have a sense of what it would or wouldn't allow?
In general, I don't think the goal of Arduino is to provide a maximally efficient API: it intentionally trades off some amount of performance and size for ease-of-use. Of course, performance is also a goal; there's a point at which trade-off becomes too big -- but it's not clear to me that this is one of those cases.
On 04/15/2014 01:28 AM, Cristian Maglie wrote:
> Moreover, i would expect that beginTransaction toggle also the SS
> pin (that is now deferred to the user).
What to do about the SS pin, or whether to do anything at all, is the
part of this proposal I've struggled with the most.
Teensy3 has hardware support for generating the SS signal (like Arduino
Due), so I'm certainly tempted to propose including the SS signal, since
that might greatly benefit my own products.
On Monday, 28 April 2014 10:33:02 UTC-3, c.maglie wrote:In data domenica 27 aprile 2014 00:08:30, Cristian Maglie ha scritto:
> To move forward, I'd like to benchmark the new SPI lib, are you already
> running some tests, or have suggestions? I guess that the Robot RGB display
> may be a good performance check.
Hi,
I've just tried an alternative event-based API, here:
https://github.com/cmaglie/Arduino/commits/spi-transactions
now I'd like to compare all these variants, so I used the followin test to
measure the peak raw performance:
https://gist.github.com/cmaglie/11366715
After a first check it seems that the event-based version is faster than the
interrupt-based, but this strongly goes against my expectation (I'm expecting
the interrupt-based version being faster), can someone help me understand
what's going on?
interrupt-based (git revision 6fa292):
Plain SPI: 4923077.00
Transaction API (interrupts not used): (4) 2738944.50
Transaction API (interrupts not used): (2) 3503649.75
Transaction API (using interrupts): (4) 2291169.50
Transaction API (using interrupts): (2) 3121951.25
event-based (git revision f1d73e0):
Plain SPI: 4923077.00
Transaction API (interrupts not used): (4) 3350785.50
Transaction API (interrupts not used): (2) 3966942.00
Transaction API (using interrupts): (4) 2823529.25
Transaction API (using interrupts): (2) 3588785.00
C
I couldn't find the interrupt version, but I looked at the event version https://github.com/cmaglie/Arduino/blob/e5e8a7e62a856cc2bd636820ccff430dfc567e60/hardware/arduino/avr/libraries/SPI/SPI.cppinline static byte transfer(byte data) {SPDR = data;while (!(SPSR & _BV(SPIF))) ; // waitreturn SPDR;}
This should compile an out instruction followed by sbis/rjmp. Any code that uses an interrupt handler is going to have much more overhead - typically > 20 cycles.The only way I can think of to get any faster than the above code would be overlapped transfers (perhaps what Paul was referring to). You'd get the byte returned from the first transfer when you call it the second time. i.e.:inline static byte transfer(byte data) {while (!(SPSR & _BV(SPIF))) ; // waitSPDR = data;return SPDR;}
I've just tried an alternative event-based API, here: https://github.com/cmaglie/Arduino/commits/spi-transactions now I'd like to compare all these variants, so I used the followin test to measure the peak raw performance: https://gist.github.com/cmaglie/11366715 After a first check it seems that the event-based version is faster than the interrupt-based, but this strongly goes against my expectation (I'm expecting the interrupt-based version being faster), can someone help me understand what's going on?
interrupt-based (git revision 6fa292): Plain SPI: 4923077.00 Transaction API (interrupts not used): (4) 2738944.50 Transaction API (interrupts not used): (2) 3503649.75 Transaction API (using interrupts): (4) 2291169.50 Transaction API (using interrupts): (2) 3121951.25 event-based (git revision f1d73e0): Plain SPI: 4923077.00 Transaction API (interrupts not used): (4) 3350785.50 Transaction API (interrupts not used): (2) 3966942.00 Transaction API (using interrupts): (4) 2823529.25 Transaction API (using interrupts): (2) 3588785.00
I couldn't find the interrupt version, but I looked at the event version https://github.com/cmaglie/Arduino/blob/e5e8a7e62a856cc2bd636820ccff430dfc567e60/hardware/arduino/avr/libraries/SPI/SPI.cppinline static byte transfer(byte data) {SPDR = data;while (!(SPSR & _BV(SPIF))) ; // waitreturn SPDR;}
This should compile an out instruction followed by sbis/rjmp. Any code that uses an interrupt handler is going to have much more overhead - typically > 20 cycles.
Ralph, we're not talking about replacing SPI.transfer() with the SPI interrupt. The casual use of "interrupt version" is misleading.
Really, it's the "interrupt masking version", not the "interrupt version".
This conversation is about different ways to temporarily disable interrupts during a "transaction" composed of several SPI.transfer() and other stuff like manipulating the chip select. We're not talking about always disabling all interrupts. These are alternate approaches to do so in a smart way, so interrupts are only disabled when another library is known to be using SPI.transfer() inside an interrupt, and if it's an external pin interrupt (the most common case), only that specific interrupt is temporarily disabled. Timer, serial and other urgent interrupts won't get disabled in the case where external pin interrupts are disabled.
Implementing such smart interrupt disable adds code. We're looking at the overhead this will add before and after those SPI transfers. The language is about the speed change, which seems like talking about changing transfer(), but it's really about the overall impact relative to not having this extra code. The actual SPI.transfer() function is not changing, despite the confusing language in this conversation. Hopefully this explanation helps make things clearer?
---------------------------------------------------------------------------------------------------------------------------------------------------
// Snippet from Cosa/SPI
void transfer_start(uint8_t data) __attribute__((always_inline))
{
SPDR = data;
}
uint8_t transfer_await() __attribute__((always_inline))
{
loop_until_bit_is_set(SPSR, SPIF);
return (SPDR);
}
void
SPI::transfer(void* buf, size_t count)
{
if (count == 0) return;
uint8_t* dp = (uint8_t*) buf;
uint8_t data = *dp;
while (1) {
transfer_start(data);
if (--count) break;
uint8_t* tp = dp + 1;
data = *tp;
*dp = transfer_await();
dp = tp;
}
*dp = transfer_await();
}
--
You received this message because you are subscribed to the Google Groups "Developers" group.
To unsubscribe from this group and stop receiving emails from it, send an email to developers+unsubscribe@arduino.cc.
One small (I hope) style point: it seems inconsistent with the style of most existing Arduino API's to use a separate settings objects rather than simply passing the relevant parameters directly to the appropriate function (i.e. beginTransaction() here).
For example, Serial.begin() directly accepts the baud rate and other configuration. Was there an optimization advantage to doing so? How significant is it?
(I looked over the previous discussion briefly; apologies if this has been covered and I missed it.) If it's just a question of style (i.e. avoiding the duplication of parameters), I'd suggest going with passing the parameters directly to SPI.beginTransaction().
To unsubscribe from this group and stop receiving emails from it, send an email to developers+...@arduino.cc.
--
You received this message because you are subscribed to the Google Groups "Developers" group.
To unsubscribe from this group and stop receiving emails from it, send an email to developers+...@arduino.cc.
--
You received this message because you are subscribed to the Google Groups "Developers" group.
To unsubscribe from this group and stop receiving emails from it, send an email to developers+...@arduino.cc.
> an email to developers+unsubscribe@arduino.cc
> <mailto:developers+unsub...@arduino.cc>.
--
Cristian Maglie <c.ma...@arduino.cc>
It appears to be completely non-portable; I see no way of writing a single version of an Arduino that uses SPI so that it will work on both AVR and SAM.
What you end up with is library code that is hacked up with a bunch of #ifdef lines to cobble together two different code bases into one library.
On the SAM, from reading the code, it looks like spi.transfer will assert BOARD_SPI_DEFAULT_SS(pin 4?) during the transaction. So if there's another SPI device using pin 4 for SS, it will likely corrupt the data I'm getting from my device using pin 3 for SS.
On 02/26/2015 06:46 AM, Ralph Doncaster wrote:
It appears to be completely non-portable; I see no way of writing a single version of an Arduino that uses SPI so that it will work on both AVR and SAM.
You are half correct.
Indeed, the "Extended SPI library usage with the Due" stuff, as documented here, only works on Arduino Due.
The simplest way is to just not use the "Extended SPI library usage with the Due" functions. Just use the regular way that works on all boards, where you use digitalWrite() or a write to the register from portOutputRegister(), to control the chip select pin.
With that regular, works-on-all-boards approach, you use just SPI.beginTransaction(settings). That way does indeed work on all boards. It's been very throughly tested.
But perhaps you don't believe me? Maybe you're looking at the code inside hardware/arduino/sam/libraries/SPI.h and seeing that SPI.beginTransaction(settings) actually uses the other one with some default number. Maybe that leads you to incorrectly conclude SPI.beginTransaction(settings) can't work on regular AVR boards? Of course, whether or not SPI.beginTransaction(settings), digitalWrite(), regular SPI.transfer(data), and SPI.endTransaction() actually work properly on AVR boards is a simple matter of fact you can prove or disprove by simply compiling and running code. I have personally done this many times. You might have too, without realizing, when using the SD or Ethernet libraries, which do use SPI transactions and which do work properly on both AVR and SAM architecture.
On 02/26/2015 08:05 AM, Ralph Doncaster wrote:
On the SAM, from reading the code, it looks like spi.transfer will assert BOARD_SPI_DEFAULT_SS(pin 4?) during the transaction. So if there's another SPI device using pin 4 for SS, it will likely corrupt the data I'm getting from my device using pin 3 for SS.
No, you are mistaken.
If you actually test on an Arduino Due, or read the code more carefully, you'll discover SPI.beginTransaction(settings) does NOT cause pin 4 to assert low.
According to the following page, pin 78 is A0 on the due.