Adding More SERCOM Ports for SAMD Boards
Adding an SPI
1.) Figure Out Which Pins to Use.
Let's start by checking out the SERCOM.h file in Arduino’s SAMD21 core files. Specifically we are looking at lines 73-79 (yep, we looked at those when we added the UART) and lines 102-108. You should see the code listed below. But what does it mean? There are 2 parts, the first part defines which pads you can use for an RX (MISO) pad. It looks like you can use pads 0
, 1
, 2
, and 3
which is all of them. Next, it defines which pads you can use for TX (MOSI, and SCK) and in what configuration. So, we'll need to keep that in mind when we select our pins.
language:c
typedef enum
{
SERCOM_RX_PAD_0 = 0,
SERCOM_RX_PAD_1,
SERCOM_RX_PAD_2,
SERCOM_RX_PAD_3
} SercomRXPad;
//...
typedef enum
{
SPI_PAD_0_SCK_1 = 0,
SPI_PAD_2_SCK_3,
SPI_PAD_3_SCK_1,
SPI_PAD_0_SCK_3
} SercomSpiTXPad;
Since none of the boards I've come across use SERCOM 2 for anything we're going to use it for our examples. Let's start with our outputs since those pins are limited compared to the MISO pins. It looks like SCK can only be on pad 1
or 3
, and MOSI can be on 0
, 2
, or 3
depending on the configuration. So, looking at our table, let's start by removing the pins that are not broken out to our board or already in use. That removes MISO, D38, D1/TX, and D0/RX, leaving use with D4, D3, D2, and D5. Any of those look like good options, but lets go with D3 for MISO, D5 for SCK, and D4 for MOSI. Again, we'll need to dig into our fancy charts to figure out what is where.
2.) Add Your Code.
Next lets figure out what code we need. Let's take a look at the SPI.cpp file again. All the way near the bottom on line 261, you'll see the following line. This is what we are trying to duplicate in our code. The variant.h file defines all those macros, but their names give us a good idea of what should go there.
language:c
SPIClass SPI (&PERIPH_SPI, PIN_SPI_MISO, PIN_SPI_SCK, PIN_SPI_MOSI, PAD_SPI_TX, PAD_SPI_RX);
Let's start with the definition. Let's pick a name. "SPI2
" sounds good. We also know we are going to use SERCOM 2, that we are going to use D3 for MISO, D5 for SCK, and D4 for MOSI. So, we'll add the following to our code.
language:c
SPIClass SPI2 (&sercom2, 3, 5, 4, SPI_PAD_0_SCK_3, SERCOM_RX_PAD_1);
3.) Update the Pin Definitions Based on the Pin Mux.
Next, we need to set up the mux. Right now, the pins are defined as general I/O pins. We want them to act as SERCOM pins. The first thing we need to do is to add the pin peripheral library. Then we use the pinPeripheral
command to set up the pin defintion.
language:c
#include "wiring_private.h" // pinPeripheral() function
SPI2.begin();
pinPeripheral(3, PIO_SERCOM_ALT);
pinPeripheral(4, PIO_SERCOM_ALT);
pinPeripheral(5, PIO_SERCOM);
You'll notice that one uses the argument PIO_SEROM
and the others PIO_SERCOM_ALT
. If you look on the datasheet, you'll notice that pins can have a SERCOM port listed under SERCOM or SERCOM-ALT. For D5, we are using the SERCOM port in the SERCOM column. For D3 and D4, we are using the SERCOM port in the SERCOM-ALT column.
4.) Putting It All Together.
Step 4 is running the code, testing, and troubleshooting. For this example, we are going to grab a Serial LCD screen and connect it to our new SPI port. Make sure that you have soldered headers to the LCD if you have not already.
The next step is to test out the SPI port with the code listed below. The code is pretty bare bones and shows you where all your new code should go. The code also includes a pin definition for the CSPIN
. Copy and paste the code in your Arduino IDE. Select your board, COM port, and hit upload.
language:c
/*********************************************************************
Sample code for setting up additional Serial ports on a SamD21 board
In this example the Redboard Turbo is used with the 16x2 SerLCD display
For more information on the SerLCD code check out the github repo
https://github.com/sparkfun/OpenLCD
https://www.sparkfun.com/products/14812
https://www.sparkfun.com/products/14072
By: Michelle Shorter - SparkFun Electronics
License: This code is public domain but you buy me a burger
if you use this and we meet someday (Beefware license).
**********************************************************************/
#include <SPI.h>
#include "wiring_private.h" // pinPeripheral() function
#define CSPIN 6
#define Time 25
//D3-MISO, D4-MOSI, D5-SCK
SPIClass SPI2 (&sercom2, 3, 5, 4, SPI_PAD_0_SCK_3, SERCOM_RX_PAD_1);
int i = 0;
void setup() {
// put your setup code here, to run once:
//Get all pins and SPI ports setup
SPI2.begin();
pinPeripheral(3, PIO_SERCOM_ALT);
pinPeripheral(4, PIO_SERCOM_ALT);
pinPeripheral(5, PIO_SERCOM);
pinMode(CSPIN, OUTPUT);
digitalWrite(CSPIN, HIGH); //make sure it is high to start
SPI2.setClockDivider(SPI_CLOCK_DIV128); //Slow down the master a bit
//Reset the screen, set backlight, etc.
digitalWrite(CSPIN, LOW);// Select the screen before sending
SPI2.transfer('|');//Setting character
SPI2.transfer('-');//Clear display
SPI2.transfer('|');//Put LCD into setting mode
SPI2.transfer(158 + 0); //Set green backlight amount to 0%
SPI2.transfer('|');//Put LCD into setting mode
SPI2.transfer(188 + 10); //Set blue backlight amount to 0%
SPI2.transfer('|');//Put LCD into setting mode
SPI2.transfer(128 + 5); //Set white/red backlight amount to (15=51% 100%=+29)
delay(Time);
digitalWrite(CSPIN, HIGH);// Deselect the screen after sending
delay(1000);//Each setting change prints an output, this delay allows them to be printed before trying to keep going
//Send Welcome Text
char tempString[50]; //Needs to be large enough to hold the entire string with up to 5 digits
sprintf(tempString, "Welcome ");
spiSendString(tempString);
delay(1500);
}
void loop() {
// put your main code here, to run repeatedly:
//Clear the screen, then send the Counting string
digitalWrite(CSPIN, LOW);// Select the screen before sending
SPI2.transfer('|');//Setting character
SPI2.transfer('-');//Clear display
delay(Time);
digitalWrite(CSPIN, HIGH);// Deselect the screen after sending
char tempString[50]; //Needs to be large enough to hold the entire string with up to 5 digits
sprintf(tempString,"Counting: %d ", i);
spiSendString(tempString);
i++;
delay(1000-Time);
}
//Sends a string over SPI
void spiSendString(char* data)
{
digitalWrite(CSPIN, LOW); //Drive the CS pin low to select OpenLCD
for(byte x = 0 ; data[x] != '\0' ; x++) //Send chars until we hit the end of the string
SPI2.transfer(data[x]);
digitalWrite(CSPIN, HIGH); //Release the CS pin to de-select OpenLCD
}