3

I have been gradually converting many devices from i2c to spi for various reasons. I noticed in tutorials like this from arduino.cc that SPI.beginTransaction is explicitly called out. Then they link to examples such as this and this where they don't use SPI.beginTransaction. In working through my own devices I found that I could often (almost always) forgo the use of SPI.beginTransaction. The only case where I found problems was when I had 2 devices that used different SPI settings.

I went to Google and other searches for a simple reason for SPI.beginTransactions and found discussions of interrupts and locks and settings. There was much discussions about users applications wrapping all calls with SPI.beginTransaction and SPI.endTransaction. There were bugs when libraries didn't wrap everywhere.

This leads me to the conclusion that making calls to SPI.beginTransaction are only needed when the SPI settings need to change, but in practice you should always do it. Is there documentation or examples that definitely state the purpose and proper (best practice) usage of it?

0
3

Although this question has an accepted answer, I'd like to provide some background and historical reasons to OP's question.

Transactional SPI using SPI.beginTransaction() was introduced in 2014, it was not available in the early version of the Arduinos or example codes published before that. As the results, there are still many codes out there on the Internet show the old way of setting up the SPI configuration, and this include the exampes provided by Arduino.cc on its library reference page even up to today, the authors of the library did not make any changes in those examples to show the usage of new API.

What the SPI.beginTransaction() does is to allow you to set unique SPI settings(through SPISettings object) for your application, even if other devices use different settings. SPI.beginTransaction() behave like a locking mechanism to gain the exclusive use of the SPI bus, and therefore requires SPI.endTransaction() to release the bus for others to access it. SPI.beginTransaction() provides better cross-device compatibility and solve software conflicts and allowing multiple SPI devices to properly share the SPI bus. You should therefore use the SPI.beginTransaction() in your SPI sketch. You should also using SPISettings object to configure your SPI communication interface, and not use the deprecated methods such as SPI.setBitOrder(), SPI.setClockDivider() and SPI.setDataMode() for setting up the SPI configuration.

Although rarely need today, there is a pre-defined macro SPI_HAS_TRANSACTION in SPI library for checking whether the firmware of the Arduino board supports SPI.beginTransaction(). It is useful for providing backward compatibility if you are writing an Arduino library.

#ifdef SPI_HAS_TRANSACTION
  SPI.beginTransaction(SPISettings(4000000, MSBFIRST, SPI_MODE0));
#elseif
  // use old Arduino SPI library syntax
#endif
3

It appears calling the method beginTransaction() prevents additional SPI transactions from interrupting the current transaction. Calling endTransaction() allows access to the SPI hardware again. This added complexity may sound odd. Especially in the simple Arduino sketch-writing paradigm. But as programs grow in complexity, more SPI devices are added and interrupts are used to quickly access SPI devices - the likely hood of SPI contention increases.

In the SPIClass source code, there is this comment immediately before the beginTransaction() method:

  // Before using SPI.transfer() or asserting chip select pins,
  // this function is used to gain exclusive access to the SPI bus
  // and configure the correct settings.

So, it may be, that if the sketch-code is particularly well behaved and does not need protection from accidentally using the SPI hardware from multiple concurrent transactions, that these two methods need not be used. But why take the chance.

0
2

Every modern Arduino library for a SPI device should use SPI library with beginTransaction to be able to coexists with libraries for SPI devices using different modes and speed.

Recently we had here a question about two libraries for SPI not working together. The devices required different modes. The libraries didn't use beginTransaction, they only used SPI.begin() in initialization. It is not possible to use these libraries together.

The STM32 core supports switching CS pin with enhanced beginTransaction and endTransaction but this is not so useful as it looks. Many devices use CS as 'commit' and the CS must be switched several times inside one 'transaction'.

If you write a library you don't know with what other SPI devices on bus it will be used so call beginTransaction as the library gets control and endTransaction as the public library function returns control out of the library.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.