Seeking guidance for Multiple SPIs

[Me: I have multiple decades with C and C++ programming, I'm deeply familiar with ARM in general and SAMD21 in particular, and I've written lots of sketches using existing libraries in Arduino, but I'm a complete newcomer to developing complex systems in the Arduino framework.]

I'm working on a project that requires three SPIs: One for SD/FATFS, one for an LCD display, one for a touch screen. My target processor is a SAMD21J18A (not a SAMD21G) on a SAMD21 XPRO development board.

Making any ONE of those SPI devices work appears not to be a problem: I'm using the board manager from here:

[Atmel SAMD21/W25/R21 Xplained Pro Arduino IDE Board Manager Module](https://github.com/AtmelUniversityFrance/atmel-samd21-xpro-boardmanagermodule) 

and it gets me partway there. I already have working code for the LCD display, I'm confident that the SD library would work with my setup, etc.

But where I'm stumped putting together an app that uses three SPIs simultaneously. I see that the SAMD21 XPRO variant.cpp file has support for multiple SERCOMs, but I don't understand how to instruct the mid-level libraries (i.e. SPI and SD) to use specific SERCOMs.

Do I create custom versions of those libraries with hardwired (or parameterized) pin numbers? Or is there some better way to do that?

SPI is a bus system, devices are selected by pulling their CS line low.
So as long as the devices are well-behaved - not driving bus lines when not selected -
you can run many of them on the same hardware SPI lines, each only needs an own CS pin.

In particular some SD modules are known to not play nice w/ the SPI bus.

Just a heads up.

a7

Isn't the SAMD21J18A 3.3v?

Then you could directly connect to the SD-card,
the bus squatters accomplish that often by misusing the 3.3V 5V level shifter.

@Whandall : I appreciate your replies. Two points:

  • I certainly know about using separate CS lines on a single SPI bus. The reason I asked about multiple SPIs is that -- due to external reasons -- I need multiple SPIs. Is that impossible in the Arduino world? (Seems hard to believe...)
  • "misusing the 3.3V 5V level shifter". What board are you talking about? I'm pretty sure the SAMD21 Xplained Pro board is 3.3V throughout.

@alto777 Thanks for the warning -- I'll try the SD example code sooner than later.

Can you enlighten us on the reasons ?

I don't know any AVRs with two SPIs, maybe the 328PB can use two.
ESP32s have two SPI, one driving the Flash and or RAM.

Seems hard to believe...

The OP's processor data sheet claims an ability for it to configure up to 6 SPI interfaces, so the original question, how to get a library to use a specific capability stands.

a7

That should be part of the Arduino core files then, or a missing part of it.

I have just been testing a setup that uses 3 SPI devices on the single SPI bus on a ATmega1284P, an SD card, a LoRa device and a OV2640 SPI camera.

The setup works just fine which is as expected since SPI is a bus system.

Am I doing something wrong ?

@everybody:

As most of you already know, the SAMD21 is an ARM core, not an AVR. Regular AVR stuff doesn't apply.

I want to use separate SPI buses (rather than multiplexing one bus with separate CS lines) because:

  • The SAMD21 XPRO dev board breaks out three separate SERCOMs on edge connectors (SERCOM0, 1, 5).
  • My SD card plugs into one of those three edge connectors.
  • I think the touch screen SPI and LCD SPI use different clock modes than the SD card (and maybe different speeds), so I'd have to reconfigure the SPI bus between each operation if I use a single SPI bus.
  • I'd like the option in the future for a direct DMA operation between the SD and the LCD.

Having said as much, I've figured out the mapping for the SERCOMs and the "native" Arduino pins, as defined in the SAMD21 XPRO variant.cpp file:

SAMD21 XPRO EXT1:

Function SAMD21 XPRO SAMD21J GPIO SERCOM[pad] Arduino Pin
CS_ EXT1.15 PA05 SERCOM0[1] 37
MOSI EXT1.16 PA06 SERCOM0[2] 34
MISO EXT1.17 PA04 SERCOM0[0] 35
SCK EXT1.18 PA07 SERCOM0[3] 36

SAMD21 XPRO EXT2:

Function SAMD21 XPRO SAMD21J GPIO SERCOM[pad] Arduino Pin
CS_ EXT2.15 PA17 SERCOM1[1] 42
MOSI EXT2.16 PA18 SERCOM1[2] 39
MISO EXT2.17 PA16 SERCOM1[0] 40
SCK EXT2.18 PA19 SERCOM1[3] 41

SAMD21 XPRO EXT3:

Function SAMD21 XPRO SAMD21J GPIO SERCOM[pad] Arduino Pin
CS_ EXT3.15 PB17 SERCOM5[1] 47
MOSI EXT3.16 PB22 SERCOM5[2] 44
MISO EXT3.17 PB16 SERCOM5[0] 45
SCK EXT3.18 PB23 SERCOM5[3] 46

So to get back to my original question: If I want the SD library to use Arduino Pins (for example) 44, 45, 46 for MOSI, MISO, SCK, what's the right way to do that? Do I need to modify the library? Or is there a way to tell the SD library to use the alternate pins?

There's an Adafruit topic called Creating a new SPI that talks about SERCOMs and which signals go to which "PADS". There are examples using SERCOM1 & SERCOM2, which may help you out.

I don't have a SAMD21J18A core installed,
in the ESP32 core that problem is solved via the begin function

bool begin(uint8_t ssPin=SS, SPIClass &spi=SPI, uint32_t frequency=4000000, const char * mountpoint="/sd", uint8_t max_files=5, bool format_if_empty=false);

@markd833 Thanks for that. I'd already read that article -- it's about how to create SERCOM1 and 2, and as I mentioned, I have the pins for multiple SERCOMs.

I'm probably not asking this the right way, but since I already have the pins defined for MOSI, MISO, SCK for several SPIs, how do I tell the SD module to use one of those alternate SPIs?? That's really what I'm stuck on.

What happened when you tried your devices on the same SPI bus ?

Digging deeper:

In my system's variant.h file, there's this:

/*
 * SPI Interfaces
 */
#define SPI_INTERFACES_COUNT 3

#define PIN_SPI_MOSI         (34ul)
#define PIN_SPI_MISO         (35ul)
#define PIN_SPI_SCK          (36ul)
#define PIN_SPI_SS           (37ul)
#define PERIPH_SPI           sercom0
#define PAD_SPI_TX           SPI_PAD_2_SCK_3
#define PAD_SPI_RX           SERCOM_RX_PAD_0

#define PIN_SPI1_MOSI        (39ul)
#define PIN_SPI1_MISO        (40ul)
#define PIN_SPI1_SCK         (41ul)
#define PIN_SPI1_SS          (42ul)
#define PERIPH_SPI1          sercom1
#define PAD_SPI1_TX          SPI_PAD_2_SCK_3
#define PAD_SPI1_RX          SERCOM_RX_PAD_0

#define PIN_SPI2_MOSI        (44ul)
#define PIN_SPI2_MISO        (45ul)
#define PIN_SPI2_SCK         (46ul)
#define PIN_SPI2_SS          (47ul)
#define PERIPH_SPI2          sercom5
#define PAD_SPI2_TX          SPI_PAD_2_SCK_3
#define PAD_SPI2_RX          SERCOM_RX_PAD_0

static const uint8_t SS	  = PIN_SPI_SS ;
static const uint8_t MOSI = PIN_SPI_MOSI ;
static const uint8_t MISO = PIN_SPI_MISO ;
static const uint8_t SCK  = PIN_SPI_SCK ;

If you trace through the SD library (i.e. in Sd2Card.cpp) it uses MOSI, MISO, and SCK for its SPI bus which (as you can see above) maps to SERCOM0.

If I want the SD library to use (say) SERCOM1, what's the idiomatically correct "Arduio Way" to make that happen? Some alternatives I see:

  • Modify variant.h to redefine MOSI, MISO and SCK to use PIN_SPI1_MOSI, etc.
  • Modify SPI to take parameters on its init function and make appropriate changes to Sd2Card to provide those parameters
  • Something else?

edit: I also suspect that if I use something other than SERCOM0, I need to make sure the module gets initialized. Or maybe that happens regardless -- any pointers appreciated.

@rdpoor, there are some examples at the very end of the web page I mentioned earlier. They show how to use the SPIClass and how to define the pins you want to use.

The datasheet for the SAMD21 family in the section called I/O Multiplexing & Considerations (section 7 in my datasheet) has a table showing the relationship between actual pins and the the various peripheral devices within the chip.

For SERCOM0 you are using the alternate pins (?), and SERCOM1 is using the normal pins.

I'm not sure about your mapping for SERCOM5. According to the datasheet, you are using the alternate pins for MOSI & SCK, but the normal pin for MISO. I think it has to be all normal or all alternate, but I could be wrong as i've just started playing with the SAMD21 devices.

On the SAMx family of processors, you can set the multiplexer for each pin separately.