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.
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
}