Arduino SPI2 Not Working?

I’ve been using the Arduino standard SPI interface for a while now. There are 6 total SPI units on a Redboard though. A new project is using SPI2/IOM2. The strange part is that when I perform a write to SPI2, I don’t get any activity on IOM2 SCK or MOSI. I see my test program assert CS, then there is a blank spot where my data should be, and then CS gets deasserted. I do see the time it takes to perform the transfer, but nothing appears on SCK2 or MOSI2. I did an SPI2.begin(), and I am calling SPI2.transferOut() to send my data.

I must be missing something obvious here. My interpretation of the definition of Arduino SPI2 is that it uses the Apollo3 IOM2. This means that it would drive its clock on SCK2 (Redboard silkscreen D6, corresponding to Apollo3 pin 27). It would drive the data out on MOSI2 (Redboard silkscreen D7, corresponding to Apollo3 pin 28). I just see SCK2 and MOSI2 sit there at ‘0’. Any ideas?

OK, there are some issues here. Here is a trivial test program:

  Serial.println("At SPI.begin()");
  SPI.begin();
  Serial.println("At SPI1.begin()");
  SPI1.begin();
  Serial.println("At SPI2.begin()");
  SPI2.begin();
  Serial.println("At SPI3.begin()");
  SPI3.begin();
  Serial.println("At SPI4.begin()");
  SPI4.begin();
  Serial.println("At SPI5.begin()");
  SPI5.begin();

Strangely, this won’t even compile. I get errors that SPI4 and SPI5 are undefined. The Sparkfun SPI.cpp file shows that I have 6 SPI units declared.

Ignoring that weirdness, I deleted the references to SPI4 and SPI5 in the test. Then I instrumented the Sparkfun SPI.cpp code to tell me which pins it was initializing for each of the SPI units. That gave me this:

At SPI.begin()
Inside SPIClass::begin() for instance 0
Setting up SPI instance 0 pin 5 as SCK
Setting up SPI instance 0 pin 7 as MOSI
Setting up SPI instance 0 pin 6 as MISO
At SPI1.begin()
Inside SPIClass::begin() for instance 2
Setting up SPI instance 2 pin 27 as SCK
Setting up SPI instance 2 pin 28 as MOSI
Setting up SPI instance 2 pin 25 as MISO
At SPI2.begin()
Inside SPIClass::begin() for instance 1
Setting up SPI instance 1 pin 8 as SCK
Setting up SPI instance 1 pin 10 as MOSI
Setting up SPI instance 1 pin 9 as MISO
At SPI3.begin()
Inside SPIClass::begin() for instance 3
Setting up SPI instance 3 pin 42 as SCK
Setting up SPI instance 3 pin 38 as MOSI
Setting up SPI instance 3 pin 43 as MISO

There is the problem: IOM1 and IOM2 are swapped. SPI.begin properly set up IOM0, and SPI3.begin properly set up IOM3. But it explains why my data is not showing up on the bus: it must be going out on the wrong pins because it is using the wrong IOM.

Something is obviously funky. Maybe it is related to the fact that SPI4 and 5 do not exist when they obviously should exist. Any ideas out there?

OK, I found this in the definition of a Redboard:

// SPI Defines
#define SPI_INTERFACES_COUNT 4

#define AP3_SPI_IOM 0                   // Specify that SPI uses IOMaster 0
#define AP3_SPI_DUP ap3_spi_full_duplex // Specify that SPI is full-duplex (as opposed to ap3_spi_tx_only or ap3_spi_rx_only)

#define AP3_SPI1_IOM 2
#define AP3_SPI1_DUP ap3_spi_full_duplex

#define AP3_SPI2_IOM 1
#define AP3_SPI2_DUP ap3_spi_full_duplex

#define AP3_SPI3_IOM 3
#define AP3_SPI3_DUP ap3_spi_full_duplex

There it is: SPI1 is IOM2 and SPI2 is IOM1.

I can see why a Redboard Arduino variant would not support IOM5 and 6 because the Artemis pins don’t make it to the Redboard pins, but what was the theory behind swapping IOM1 and 2? That’s a totally unnecessary source of confusion.

Robin, which redboard definition is that in?

Typically the variant definitions are only exposing “dedicated” (labelled) interfaces. It is left up to the user to create their own additional SPIClass objects using the constructor “SPIClass mySPI(IOM#, DUPLEX_SETTING)”

On the regular redboard we’ve made SPI use the IOM peripheral that was most convenient to route to the equivalent Arduino Uno pins (also considering pin capability priorities)

I’m unfamiliar with which board has SPI_INTERFACES_COUNT set to 4 (excluding SPI5 and SPI6 by default)… did you modify anything in your local core?

I did not modify my local core. The Redboard is defined here: <…>\AppData\Local\Arduino15\packages\SparkFun\hardware\apollo3\1.1.1\variants\redboard_artemis\config

The compile commands during the arduino build show that this is the file being used:

… “-IC:\Users\robin\AppData\Local\Arduino15\packages\SparkFun\hardware\apollo3\1.1.1\variants\redboard_artemis/config” …

It is left up to the user to create their own additional SPIClass objects using the constructor “SPIClass mySPI(IOM#, DUPLEX_SETTING)”

I searched all over, but I can’t find that documented anywhere. If it is, my apologies, and can you point me at what I should be reading?

No worries, it isn’t really documented anywhere except in the source code right now.

Regarding variant SPI definitions

https://github.com/sparkfun/Arduino_Apo … es/SPI/src

https://github.com/sparkfun/Arduino_Apo … #L149-L166

https://github.com/sparkfun/Arduino_Apo … #L339-L356

https://github.com/sparkfun/Arduino_Apo … .h#L43-L47

SPIClass constructor

https://github.com/sparkfun/Arduino_Apo … pp#L33-L41

I don’t get it. How come my variants.h file for a Redboard is so different than yours? My board manager says it is at revision 1.1.1, which is the most recent.

Yours:

// SPI Defines
#define SPI_INTERFACES_COUNT 1

#define AP3_SPI_IOM 0                   // Specify that SPI uses IOMaster 0
#define AP3_SPI_DUP ap3_spi_full_duplex // Specify that SPI is full-duplex (as opposed to ap3_spi_tx_only or ap3_spi_rx_only)

Mine:

// SPI Defines
#define SPI_INTERFACES_COUNT 4

#define AP3_SPI_IOM 0                   // Specify that SPI uses IOMaster 0
#define AP3_SPI_DUP ap3_spi_full_duplex // Specify that SPI is full-duplex (as opposed to ap3_spi_tx_only or ap3_spi_rx_only)

#define AP3_SPI1_IOM 2
#define AP3_SPI1_DUP ap3_spi_full_duplex

#define AP3_SPI2_IOM 1
#define AP3_SPI2_DUP ap3_spi_full_duplex

#define AP3_SPI3_IOM 3
#define AP3_SPI3_DUP ap3_spi_full_duplex

Ahh, I apologize. I gave you the link to the RedBoard Artemis Nano configuration file.

The URL for the RedBoard Artemis (Uno clone) takes you to a file that matches what you are seeing:

https://github.com/sparkfun/Arduino_Apo … /variant.h

To sum it all up for anyone googling this thread:

  • - In the Artemis Arduino world, there is no relationship to be expected between the 'n' of predefined SPI objects and the IOM unit 'n' that is used by those objects. It was just happenstance that SPI0 mapped to IOM0 and SPI3 mapped to IOM3 on a Redboard.
  • - It would be wise for a software developer to assume that the different Artemis-based Arduino boards may perform the SPI->IOM mappings as they see fit. So far, the basic Artemis Arduino SPI class always uses IOM0, but beyond that, it would be safest for a software developer to make no further assumptions regarding the SPI->IOM mappings.
  • - If you need to perform SPI using a particular IOM, instead of trying to figure out the SPI->IOM mappings for your specific Arduino board, just create your own SPI class that explicitly defines the relationship: ``` SPIClass mySPI(IOM# [, DUPLEX_SETTING]) ``` where:
  • - IOM# ranges from [0..5]
  • - The optional duplex setting can be:
  • - ap3_spi_tx_only
  • - ap3_spi_rx_only
  • - ap3_spi_full_duplex (the default, performs R/W transfers)
  • Creating an SPI interface in this fashion ensures that it will continue to function as expected if you move your project to a different Artemis board that may implement different SPI->IOM mappings.

    Great summary Robin, thank you.

    The only thing I would change is to say that people should not even rely on SPI mapping to IOM0. That is merely happenstance. The method you described above is the preferred way of specifying SPI ports.

    Also, it is OK to declare SPI ports using IOM modules that are already associated with another SPI object. Say SPI maps to IOM0, but you create your own SPIClass object “mySPI” that also uses IOM0. This is OK as long as only one object gets used (either SPI or mySPI but not both) because the IOM module is actually configured in the “.begin()” method.