SPI SD Library on Arduino Zero

On the Arduino Zero, the traditional SPI pins are on the ICSP connector. This [I think] means that most libraries which use SPI, and almost all the shields that use SPI will not work because SPI is expected to be on pins 13 (SCK), 12 (MISO) and 11 (MOSI).

I wanted to use the Arduino SD library with a Zero, but was unable to use it without a “hack” where I had to patch out a line of code in the SD library because it kept re-configuring the overridden SPI channel.

It seems that many have had similar problems with SPI on the Zero, and nobody has a solution.

I don’t know if I’m allowed to add links in this forum, but the full details of my solution [my horrible hack] can be found in my [almost unknown] blog:

But surely there must be a better way. Improved solutions greatly appreciated!

Thanks in advance, Matt, muman.ch

the Arduino SD library works fine with the Zero, because it uses defined constants.

https://github.com/arduino-libraries/SD/blob/master/src/utility/Sd2PinMap.h

https://github.com/arduino/ArduinoCore-samd/blob/993398cb7a23a4e0f821a73501ae98053773165b/variants/arduino_zero/variant.h#L142

Hi Juraj,

Thanks for your response - I would have thought that too. But it seems (to me) that the defined pins are on the ICP connector, in variant.h …

You will probably get a better response if you post the details of your self described "horrible hack" here rather than expecting people to visit your blog.

Hi @swissmatt.

Any competently designed shield makes the SPI connections via the ICSP header. Any shield that is designed under the assumption that the SPI pins are on 11-13 would be incompatible with the even more popular Mega and Leonardo boards.

Ok, here’s the text from my website, but you really need to read the full article (and what’s wrong with having it on my own web page, eh? :-)

The problem is that the Zero’s SAMD architecture requires a new SERCOM channel to be defined if you want to use the traditional Uno SPI pins. I was not able to find out how to do this properly with the SD library, hence my “horrible hack”, described below…

*****

Using SPI on pins 11/12/13 of the Arduino Zero (updated 2025.11.21)

On the Arduino Zero (SAMD21 MCU), the default SPI channel is wired to the ICSP connector, not to the standard SPI pins 11/12/13. I not sure this was a wise design decision because (I think) it means that most sketches which use SPI will not run on the Zero, and none of the SPI shields will work. A quick internet search shows that many people have had this problem, and I did not find any solutions. Some just abandoned the Zero altogether, or used the ICSP connector instead, but I can't do that because my shield prototypes use the traditional pins.

This also means that the circuits shown above will not work unless you define and assign a new hardware SPI channel which uses pins 11, 12, and 13. This can be done, but it causes another problem - the Arduino SD library does not [seem to] support this.

But... I found a workaround which requires patching out one line of code in the Arduino SD library.

The Nucleo-64 STM32 boards use the standard SPI pins, so this problem does not occur. I have not tried it with any other Uno-style boards yet.

In the code below (SDCard.h), if it's an Arduino Zero _VARIANT_ARDUINO_ZERO_, then a new SPI channel is created (sercom1) which uses pins 11, 12, and 13:

#ifdef _VARIANT_ARDUINO_ZERO_
SPIClassSAMD mySPI(&sercom1, 12, 13, 11, SPI_PAD_0_SCK_1, SERCOM_RX_PAD_3);
#endif

Here's a nice article from Adafruit which describes how to define new SPI (and other) channels on the SAMD MCUs:
https://learn.adafruit.com/using-atsamd21-sercom-to-add-more-spi-i2c-serial-ports/creating-a-new-spi

The new channel is assigned to the standard 'SPI', and it needs pinPeripheral(..., PIO_SERCOM) calls to make it work.

     // Initialise SPI
    void SDCard::begin(uint chipSelectPin, uint sdDetectPin)
    {
        csPin = chipSelectPin;
        sdDetPin = sdDetectPin;
        if (sdDetPin)
            pinMode(sdDetPin, INPUT_PULLUP);

    #ifdef _VARIANT_ARDUINO_ZERO_
        // override SPI so we can use the standard SPI pins 11/12/13
        // (the Arduino Zero has the default SPI on the ICSP connector)
        mySPI.begin();
        SPI = mySPI;

        // assign pins 11, 12, 13 to SERCOM functionality
        pinPeripheral(11, PIO_SERCOM);

        pinPeripheral(12, PIO_SERCOM);
        pinPeripheral(13, PIO_SERCOM);
    #endif
    }

This allows the SD library to use the new 'sercom1' SPI channel on the standard pins. But there's one problem - the SD library code keeps re-initializing it which [seems to] override the pinPeripheral(..., PIO_SERCOM) settings. I'm probably not understanding what's going on, and maybe the SD library has a better way to do this, but I didn't find it. Please let me know if there is better/correct way to do this, maybe with USE_SPI_LIB or something - but that didn't work for me.

My horrible hack "solution" is to patch out the offending line which re-initializes SPI. Then everything works beautifully.

Find the SD library file Sd2Card.cpp (C:\Users\\Documents\Arduino\libraries\SD\src\utility\Sd2Card.cpp) and patch out the line SDCARD_SPI.begin(). Line 283 in my version of the library.

This code is not finished, and may have other problems, but it should be enough to get you started. I'll update it when I develop a real application.

:black_large_square: SDCard.h

#pragma once

// A wrapper for the Arduino SD Card Library
// muman.ch, 2025.11.21
/*
For details see
https://muman.ch/muman/index.htm?muman-reusing-3d-printer-displays.htm

There's a problem with SPI on the Arduino Zero - it does not use the standard
SPI pins 11/12/13, so most SPI libraries and shields do not work.
But there's a solution...  
https://muman.ch/muman/index.htm?muman-reusing-3d-printer-displays.htm#muman-spi-on-the-arduino-zero

Arduino SD card library
https://github.com/arduino-libraries/SD

The SD Library is itself a wrapper for the SDFat library by Bill Greiman
https://github.com/greiman/SdFat

// SAMD21 stuff
https://learn.adafruit.com/using-atsamd21-sercom-to-add-more-spi-i2c-serial-ports/overview


https://forum.arduino.cc/t/clear-explanation-about-how-sd-cards-works-and-how-sd-library-works/1176912/10

NOTES
The SD Card uses the hardware SPI, so it cannot be used at the same time as the 
ICSP Atmel-ICE. The built-in LED is on the same output pin as the SPI SCLK signal, so you can't use the LED and SPI at the same time.
*/


#include <SPI.h>
#include "wiring_private.h"  // for pinPeripheral() function


//problem 'extern SDClass SD;' is publicly defined, not good for class inheritance
#include <SD.h>

// New SPI channel for the Arduino Zero
#ifdef _VARIANT_ARDUINO_ZERO_
SPIClassSAMD mySPI(&sercom1, 12, 13, 11, SPI_PAD_0_SCK_1, SERCOM_RX_PAD_3);
#endif


class SDCard : public SDClass
{
private:
	uint csPin;
	uint sdDetPin;
	
public:
	void begin(uint chipSelectPin, uint sdDetectPin = 0);
	bool begin();

	// Check the SD_DET signal, if wired
	bool cardInSlot() { return sdDetPin ? digitalRead(sdDetPin) == 0 : false; }

	bool test();
};


// Initialise SPI
void SDCard::begin(uint chipSelectPin, uint sdDetectPin)
{
	csPin = chipSelectPin;
	sdDetPin = sdDetectPin;
	if (sdDetPin) {
		// if INPUT_PULLUP is not supported, add a 10..100K pull-up resistor
		pinMode(sdDetPin, INPUT_PULLUP);
	}

#ifdef _VARIANT_ARDUINO_ZERO_
	// override SPI so we can use the standard SPI pins 11/12/13
	// (the Arduino Zero has the default SPI on the ICSP connector)
	mySPI.begin();
	SPI = mySPI;

	// assign pins 11, 12, 13 to SERCOM functionality
	pinPeripheral(11, PIO_SERCOM);
	pinPeripheral(12, PIO_SERCOM);
	pinPeripheral(13, PIO_SERCOM);
#endif
}

Hi ptillisch,

I get that, but none of the Uno-style prototype shields have the ICSP connections. And, because of the 1.25mm spacing of the right-hand connectors, we can’t use 2.54mm prototype boards to make our own shields. <there’s and obvious gap in the market there, you could make a fortune - ed>

Sure they do. You just need to make a stronger effort to search and you'll surely find quite a few. For example:

Because when you want free help, you are more likely to get it when you put in fewer obstacles. "Look at this screen image of code." "Watch this YouTube video." "Go to my website." They all tend to make people go to the next topic.

So to sum up, you use the same method as Adafruit described to create a new SPI instance and comment out the library's begin call, and you're wondering if there's a better way. Turns out I didn't need to read the whole article.

And the majority of the Uno prototype shields I have on hand here have the ICSP connections. As did 3 of the top 4 results returned when I searched on Amazon for "Arduino Uno prototype shield".

I'm with @ptillisch on this one. Expecting SPI to always be on pins 11-13 on all Uno footprint board types is proceeding from a false premise, as would expecting I2C to always be on A4 and A5. Using the ICSP connector is a better solution for SPI across multiple boards.

Good luck with your project.

You are right! They do have ICSP connections :face_with_spiral_eyes: I think I went too far down a rabbit hole and list sight of reality <no, it was probably that glass of wine you had - ed>. I must buy some new prototype boards…

Normally I use the Nucleo-64 STM32 boards. These don’t have the ISCP connector at all, but they do have built-in STLINK debugging via USB, which is great.

The pinout picture for the Zero shows SPI on the “standard” (I mean, the old) pins. Perhaps this should be updated. This was what confused me.

The Arduino libraries use the default SPI, which does not connect to the default pins on the Zero. So it’s useful to be able to define a new channel, but that doesn’t always work with the Arduino libraries, hence my hack.

You are right. For the Leonardo, it is clear, and the pinout diagram does not show the SPI signals are on the digital pins.

Leonardo:

  • SPI: on the ICSP header. These pins support SPI communication using the SPI library. Note that the SPI pins are not connected to any of the digital I/O pins as they are on the Uno, They are only available on the ICSP connector. This means that if you have a shield that uses SPI, but does NOT have a 6-pin ICSP connector that connects to the Leonardo's 6-pin ICSP header, the shield will not work.

But this is not made clear for the Zero, and the Zero’s pinout diagram shows the SPI signals on the old digital pins, hence my confusion.

Thanks for bringing this to our attention. I submitted a formal report on your behalf to the team at Arduino responsible for this documentation.

I apologize for any confusion caused by this error.

Thanks. I learned a lot, and ordered some new prototype shields :-)

A suggestion for SPI library developers… A pointer to the HardwareSPI or SoftwareSPI object could be passed to begin() or to the constructor, instead of the csPin/speed/spi configuration. The SPI object is created and initialized outside the library. Then the libraries could be used easily with any SPI port, and it’s easier to share the SPI port with other devices and other libraries. Do the same for I2C libraries too.

Matt

An update…

My Arduino shield boards arrived. With the ICSP connector. But…

The ICSP connector pins are wired directly to the Arduino pins 11/12/13 by tracks on the shield board. I had to cut the tracks from the ICSP connector to resolve the conflict.

I wonder how many other Arduino prototype shields are like this? …