SPI is a high speed, 3-wire protocol that can be clocked at up to 12MHz on the ATSAMD21 and can also take advantage of DMA (that's for another tutorial)
Each SPI SERCOM has 3 pins - Serial Clock line (SCK), Microcontroller Out/Serial In (MOSI) and Microcontroller In/Serial Out (MISO). In these examples we'll assume the ATSAMD21 is acting as the Microcontroller, since that's the by-far-most-common example. In this way the chip can control wifi modules, SD cards, DotStar LEDs, various sensors and actuators, etc.
So you might be wondering "Why does she keep bringing up that SPI uses only 3 pins?" It's cuz SERCOM's have 4 pads, but we only need three. There is one unused one that can be used as a normal GPIO (or whatever) pin
How SPI is Created Now
Luckily, Atmel & Arduino did a really great job with structuring SERCOMs so you can create and assign new ones. You can find the actual code that is used to create the "SPI" SERCOM in SPI.cpp
SPIClass SPI (&PERIPH_SPI, PIN_SPI_MISO, PIN_SPI_SCK, PIN_SPI_MOSI, PAD_SPI_TX, PAD_SPI_RX);
The macros are see in variants.h
#define PIN_SPI_MISO (22u) #define PIN_SPI_MOSI (23u) #define PIN_SPI_SCK (24u) #define PERIPH_SPI sercom4 #define PAD_SPI_TX SPI_PAD_2_SCK_3 #define PAD_SPI_RX SERCOM_RX_PAD_0
You can see here that SPI is of type SPIClass and if you expand the macros:
SPIClass SPI (&sercom4, 22, 24, 23, SPI_PAD_2_SCK_3, SERCOM_RX_PAD_0);
We can match this up with the pins we know are used for SPI:
PA12 D22 / MISO SERCOM2.0 SERCOM4.0 PB10 D23 / MOSI SERCOM4.2 PB11 D24 / SCK SERCOM4.3
You can see that the first argument (&sercom4) does in fact match our observation that SPI is on SERCOM4. The second, third and fourth arguments match with the MISO/SCK/MOSI pins.
The last two args are what actually define the pin muxing. We can see these definitions in SERCOM.h
typedef enum { SPI_PAD_0_SCK_1 = 0, SPI_PAD_2_SCK_3, SPI_PAD_3_SCK_1, SPI_PAD_0_SCK_3 } SercomSpiTXPad;
and
typedef enum { SERCOM_RX_PAD_0 = 0, SERCOM_RX_PAD_1, SERCOM_RX_PAD_2, SERCOM_RX_PAD_3 } SercomRXPad;
The two together let us define the muxing. For example:
- SPI_PAD_0_SCK_1 means MOSI is on SERCOMn.0 and SCK is on SERCOMn.1
- SPI_PAD_2_SCK_3 means MOSI is on SERCOMn.2 and SCK is on SERCOMn.3
- SPI_PAD_3_SCK_1 means MOSI is on SERCOMn.3 and SCK is on SERCOMn.1
- SPI_PAD_0_SCK_3 means MOSI is on SERCOMn.0 and SCK is on SERCOMn.3
Then we can define which pin is for MISO
- SERCOM_RX_PAD_0 means MISO on SERCOMn.0
- SERCOM_RX_PAD_1 means MISO on SERCOMn.1
- SERCOM_RX_PAD_2 means MISO on SERCOMn.2
- SERCOM_RX_PAD_3 means MISO on SERCOMn.3
As you can tell, you can't quite have every possibility, for example you cannot have SCK on pad 0 or pad 2. And you cant have MOSI on pad 1. But you do have a lot of options!
SAMD51 SPI Pin Pads
The SAMD51 is more restrictive than the SAMD21. While MISO can still be on any pad, There's only two possibilities for SCK & MOSI:
SCK can only be on PAD 1 and MOSI can only be on PAD 0 or PAD 3!
OK so let's make a new SPI SERCOM already
ok ok so you are getting impatient! Lets try creating a new SERCOM and testing it out
Let's make a SPI device that 'mimics' the ATmega328 SPI pin numbering on Digital 11, 12, and 13:
- SCK on Digital 13
- MISO on Digital 12
- MOSI on Digital 11
Looking at our SERCOMs from the Mux table, we should go for SERCOM1
Pin Arduino 'Pin' SERCOM SERCOM alt ----------------------------------------- PA18 D10 SERCOM1.2 SERCOM3.2 PA16 D11 SERCOM1.0 SERCOM3.0 PA19 D12 SERCOM1.3 SERCOM3.3 PA17 D13 SERCOM1.1 SERCOM3.1
To put SCK signal on D13 (SERCOM1.1) and MOSI on D11 (SERCOM1.0) we want to use SPI_PAD_0_SCK_1 for the TX mux. That leaves on MISO to do, on D12 (SERCOM1.3), which is SERCOM_RX_PAD_3
So something like this:
SPIClass SPI1 (&sercom1, 12, 13, 11, SPI_PAD_0_SCK_1, SERCOM_RX_PAD_3);
Lets test it out!
#include <SPI.h> SPIClass mySPI (&sercom1, 12, 13, 11, SPI_PAD_0_SCK_1, SERCOM_RX_PAD_3); void setup() { Serial.begin(115200); mySPI.begin(); } uint8_t i=0; void loop() { Serial.println(i); mySPI.beginTransaction(SPISettings(8000000, MSBFIRST, SPI_MODE0)); mySPI.transfer(i++); mySPI.endTransaction(); }
OK ... that wont work. Why? Good question! Check out those pin definitions in the variants.cpp file:
const PinDescription g_APinDescription[]= ... { PORTA, 16, PIO_TIMER, (PIN_ATTR_DIGITAL|PIN_ATTR_PWM|PIN_ATTR_TIMER), No_ADC_Channel, PWM2_CH0, TCC2_CH0, EXTERNAL_INT_0 }, // TCC2/WO[0] { PORTA, 19, PIO_TIMER_ALT, (PIN_ATTR_DIGITAL|PIN_ATTR_PWM|PIN_ATTR_TIMER_ALT), No_ADC_Channel, PWM0_CH3, TCC0_CH3, EXTERNAL_INT_3 }, // TCC0/WO[3] // 13 (LED) { PORTA, 17, PIO_PWM, (PIN_ATTR_DIGITAL|PIN_ATTR_PWM), No_ADC_Channel, PWM2_CH1, NOT_ON_TIMER, EXTERNAL_INT_1 }, // TCC2/WO[1]
The first argument is the port (e.g. PORTA or PORTB), the second is the port's pin number, e.g. PORTA, 16 -> PA16. The third argument 'sets' the type of pinmux we're going to use.
In this case, these are set to be used as PIO_TIMER, PIO_TIMER_ALT and PIO_PWM
You can do that by calling pinPeripheral(pinnumber, function) which is an internal function but hey we're being hardk0re
#include <SPI.h> #include "wiring_private.h" // pinPeripheral() function SPIClass mySPI (&sercom1, 12, 13, 11, SPI_PAD_0_SCK_1, SERCOM_RX_PAD_3); void setup() { Serial.begin(115200); // do this first, for Reasons mySPI.begin(); // Assign pins 11, 12, 13 to SERCOM functionality pinPeripheral(11, PIO_SERCOM); pinPeripheral(12, PIO_SERCOM); pinPeripheral(13, PIO_SERCOM); } uint8_t i=0; void loop() { Serial.println(i); mySPI.beginTransaction(SPISettings(8000000, MSBFIRST, SPI_MODE0)); mySPI.transfer(i++); mySPI.endTransaction(); }
You can use your oscilloscope to see the data traces
We can also try SERCOM2:
Pin Arduino 'Pin' SERCOM SERCOM alt ----------------------------------------- PA14 D2 SERCOM2.2 SERCOM4.2 PA09 D3 SERCOM0.1 SERCOM2.1 PA08 D4 SERCOM0.0 SERCOM2.0 PA15 D5 SERCOM2.3 SERCOM4.3
Lets put SCK on D5 (SERCOM2.3), MISO on D3 (SERCOM2.1) and MOSI on D4 (SERCOM2.0)
#include <SPI.h> #include "wiring_private.h" // pinPeripheral() function SPIClass mySPI (&sercom2, 3, 5, 4, SPI_PAD_0_SCK_3, SERCOM_RX_PAD_1); void setup() { Serial.begin(115200); // do this first, for Reasons mySPI.begin(); // Assign pins 3, 4, 5 to SERCOM & SERCOM_ALT pinPeripheral(3, PIO_SERCOM_ALT); pinPeripheral(4, PIO_SERCOM_ALT); pinPeripheral(5, PIO_SERCOM); } uint8_t i=0; void loop() { Serial.println(i); mySPI.beginTransaction(SPISettings(8000000, MSBFIRST, SPI_MODE0)); mySPI.transfer(i++); mySPI.endTransaction(); }
Note that since pin 3 and 4 use a SERCOM alt mux, we have to pass in PIO_SERCOM_ALT rather than PIO_SERCOM when we call pinPeripheral()
Page last edited March 08, 2024
Text editor powered by tinymce.