I wanted to try SAMD21 in slave mode no particular reason, just wanted to know how it works.
My message was badly formatted I used a SPI slave library (for SAMD21) here is how the slave is implemented:
/*
Copyright (C) 2021 lenvm
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
For the GNU General Public License see https://www.gnu.org/licenses/
Contact Information
-------------------
lenvm
GitHub : https://github.com/lenvm
*/
/*
Example code for the SercomSPISlave library.
This code initializes a Sercom1 SPI Slave and prints the data received.
Written 2020 July 15
by lenvm
Updated 2021 June 8
by lenvm
*/
#include <SercomSPISlave.h>
SercomSPISlave SPISlave;
#define DEBUG // comment this line out to not print debug data on the serial bus
// initialize variables
byte buf[1];
void setup()
{
Serial.begin(115200);
Serial.println("Serial started");
SPISlave.Sercom1init();
Serial.println("Sercom1 SPI slave initialized");
}
void loop()
{
Serial.println(buf[0]);
delay(1);
}
void SERCOM1_Handler() // 25.7 Register Summary, page 454 atmel 42181, samd21
{
#ifdef DEBUG
Serial.println("In SPI Interrupt");
#endif
uint8_t data = 0;
uint8_t interrupts = SERCOM1->SPI.INTFLAG.reg; //Read SPI interrupt register
#ifdef DEBUG
Serial.print("Interrupt: "); Serial.println(interrupts);
#endif
if(interrupts & (1<<3)) // 8 = 1000 = SSL
{
#ifdef DEBUG
Serial.println("SPI SSL Interupt");
#endif
SERCOM1->SPI.INTFLAG.bit.SSL = 1; //clear slave select interrupt
//data = SERCOM1->SPI.DATA.reg; //Read data register
#ifdef DEBUG
Serial.print("DATA: "); Serial.println(data);
#endif
//SERCOM1->SPI.INTFLAG.bit.RXC = 1; //clear receive complete interrupt
}
// This is where data is received, and is written to a buffer, which is used in the main loop
if(interrupts & (1<<2)) // 4 = 0100 = RXC
{
#ifdef DEBUG
Serial.println("SPI Data Received Complete Interrupt");
#endif
data = SERCOM1->SPI.DATA.reg; //Read data register
buf[0] = data; // copy data to buffer
#ifdef DEBUG
Serial.print("DATA: ");
Serial.println(data);
#endif
SERCOM1->SPI.INTFLAG.bit.RXC = 1; //clear receive complete interrupt
}
if(interrupts & (1<<1)) // 2 = 0010 = TXC
{
#ifdef DEBUG
Serial.println("SPI Data Transmit Complete Interrupt");
#endif
SERCOM1->SPI.INTFLAG.bit.TXC = 1; //clear receive complete interrupt
}
if(interrupts & (1<<0)) // 1 = 0001 = DRE
{
#ifdef DEBUG
Serial.println("SPI Data Register Empty Interrupt");
#endif
SERCOM1->SPI.DATA.reg = 0xAA;
//SERCOM1->SPI.INTFLAG.bit.DRE = 1;
}
#ifdef DEBUG
Serial.println("----------");
#endif
}
And this is the setup for the SPI on the slave:
/*
Copyright (C) 2021 lenvm
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
For the GNU General Public License see https://www.gnu.org/licenses/
Contact Information
-------------------
lenvm
GitHub : https://github.com/lenvm
*/
#include "SercomSPISlave.h"
// Constructors //
SercomSPISlave::SercomSPISlave()
{
}
// Public Methods //
void SercomSPISlave::Sercom0init()
{
//Configure SERCOM0 SPI PINS PAD
//Set PA08 as input (MOSI) 00
//Set PA09 as input (SCK) 01
//Set PA10 as input (SS) 02
//Set PA11 as output (MISO) 03
PORT->Group[PORTA].PINCFG[8].bit.PMUXEN = 0x1; //Enable Peripheral Multiplexing for SERCOM0 SPI PA08
PORT->Group[PORTA].PMUX[4].bit.PMUXE = 0x2; //SERCOM 0 is selected for peripheral use of this pad (0x2 selects peripheral function C: SERCOM)
PORT->Group[PORTA].PINCFG[9].bit.PMUXEN = 0x1; //Enable Peripheral Multiplexing for SERCOM0 SPI PA09
PORT->Group[PORTA].PMUX[4].bit.PMUXO = 0x2; //SERCOM 0 is selected for peripheral use of this pad (0x2 selects peripheral function C: SERCOM)
PORT->Group[PORTA].PINCFG[10].bit.PMUXEN = 0x1; //Enable Peripheral Multiplexing for SERCOM0 SPI PA10
PORT->Group[PORTA].PMUX[5].bit.PMUXE = 0x2; //SERCOM 0 is selected for peripheral use of this pad (0x2 selects peripheral function C: SERCOM)
PORT->Group[PORTA].PINCFG[11].bit.PMUXEN = 0x1; //Enable Peripheral Multiplexing for SERCOM0 SPI PA11
PORT->Group[PORTA].PMUX[5].bit.PMUXO = 0x2; //SERCOM 0 is selected for peripheral use of this pad (0x2 selects peripheral function C: SERCOM)
/*
Explanation:
PMUXEN stands for Peripheral Multiplexing Enable
PMUXE stands for Even bits in the Peripheral Multiplexing register
PMUXO stands for Odd bits in the Peripheral Multiplexing register
The selection of peripheral function A to H is done by writing to the Peripheral Multiplexing Odd and Even bits in the Peripheral Multiplexing register (PMUXn.PMUXE/O) in the PORT.
Reference: Atmel-42181G-SAM-D21_Datasheet section 6.1 on page 21
PA08 corresponds to: PORTA, PMUX[4] Even
PA09 corresponds to: PORTA, PMUX[4] Odd
PA10 corresponds to: PORTA, PMUX[5] Even
PA11 corresponds to: PORTA, PMUX[5] Odd
In general:
Px(2n+0/1) corresponds to Portx, PMUX[n] Even=0/Odd=1
*/
//Disable SPI 1
SERCOM0->SPI.CTRLA.bit.ENABLE =0;
while(SERCOM0->SPI.SYNCBUSY.bit.ENABLE);
//Reset SPI 1
SERCOM0->SPI.CTRLA.bit.SWRST = 1;
while(SERCOM0->SPI.CTRLA.bit.SWRST || SERCOM0->SPI.SYNCBUSY.bit.SWRST);
//Setting up NVIC
NVIC_EnableIRQ(SERCOM0_IRQn);
NVIC_SetPriority(SERCOM0_IRQn,2);
//Setting Generic Clock Controller!!!!
GCLK->CLKCTRL.reg = GCLK_CLKCTRL_ID(GCM_SERCOM0_CORE) | //Generic Clock 0
GCLK_CLKCTRL_GEN_GCLK0 | // Generic Clock Generator 0 is the source
GCLK_CLKCTRL_CLKEN; // Enable Generic Clock Generator
while(GCLK->STATUS.reg & GCLK_STATUS_SYNCBUSY); //Wait for synchronisation
//Set up SPI Control A Register
SERCOM0->SPI.CTRLA.bit.DORD = 0; //MSB first
SERCOM0->SPI.CTRLA.bit.CPOL = 0; //SCK is low when idle, leading edge is rising edge
SERCOM0->SPI.CTRLA.bit.CPHA = 0; //data sampled on leading sck edge and changed on a trailing sck edge
SERCOM0->SPI.CTRLA.bit.FORM = 0x0; //Frame format = SPI
SERCOM0->SPI.CTRLA.bit.DIPO = 0x0; //DATA PAD 00 MOSI is used as slave input (slave mode) // page 492
SERCOM0->SPI.CTRLA.bit.DOPO = 0x2; //DATA PAD 03 MISO is used as slave output
SERCOM0->SPI.CTRLA.bit.MODE = 0x2; //SPI in Slave mode
SERCOM0->SPI.CTRLA.bit.IBON = 0x1; //Buffer Overflow notification
SERCOM0->SPI.CTRLA.bit.RUNSTDBY = 1; //wake on receiver complete
//Set up SPI control B register
SERCOM0->SPI.CTRLB.bit.SSDE = 0x1; //Slave Selecte Detection Enabled
SERCOM0->SPI.CTRLB.bit.CHSIZE = 0; //character size 8 Bit
//Set up SPI interrupts
SERCOM0->SPI.INTENSET.bit.SSL = 0x1; //Enable Slave Select low interrupt
SERCOM0->SPI.INTENSET.bit.RXC = 0x1; //Receive complete interrupt
SERCOM0->SPI.INTENSET.bit.TXC = 0x1; //Receive complete interrupt
SERCOM0->SPI.INTENSET.bit.ERROR = 0x1; //Receive complete interrupt
SERCOM0->SPI.INTENSET.bit.DRE = 0x1; //Data Register Empty interrupt
//Enable SPI
SERCOM0->SPI.CTRLA.bit.ENABLE = 1;
while(SERCOM0->SPI.SYNCBUSY.bit.ENABLE);
SERCOM0->SPI.CTRLB.bit.RXEN = 0x1; //Enable Receiver, this is done here due to errate issue
while(SERCOM0->SPI.SYNCBUSY.bit.CTRLB); //wait until receiver is enabled
}
void SercomSPISlave::Sercom1init()
{
//Configure SERCOM1 SPI PINS
//Set PA16 as input (MOSI)
//Set PA17 as input (SCK)
//Set PA18 as input (SS)
//Set PA19 as output (MISO)
PORT->Group[PORTA].PINCFG[16].bit.PMUXEN = 0x1; //Enable Peripheral Multiplexing for SERCOM1 SPI PA16 Arduino PIN11
PORT->Group[PORTA].PMUX[8].bit.PMUXE = 0x2; //SERCOM 1 is selected for peripheral use of this pad (0x2 selects peripheral function C: SERCOM)
PORT->Group[PORTA].PINCFG[17].bit.PMUXEN = 0x1; //Enable Peripheral Multiplexing for SERCOM1 SPI PA17 Arduino PIN13
PORT->Group[PORTA].PMUX[8].bit.PMUXO = 0x2; //SERCOM 1 is selected for peripheral use of this pad (0x2 selects peripheral function C: SERCOM)
PORT->Group[PORTA].PINCFG[18].bit.PMUXEN = 0x1; //Enable Peripheral Multiplexing for SERCOM1 SPI PA18 Arduino PIN10
PORT->Group[PORTA].PMUX[9].bit.PMUXE = 0x2; //SERCOM 1 is selected for peripheral use of this pad (0x2 selects peripheral function C: SERCOM)
PORT->Group[PORTA].PINCFG[19].bit.PMUXEN = 0x1; //Enable Peripheral Multiplexing for SERCOM1 SPI PA19 Arduino PIN12
PORT->Group[PORTA].PMUX[9].bit.PMUXO = 0x2; //SERCOM 1 is selected for peripheral use of this pad (0x2 selects peripheral function C: SERCOM)
/*
Explanation:
PMUXEN stands for Peripheral Multiplexing Enable
PMUXE stands for Even bits in the Peripheral Multiplexing register
PMUXO stands for Odd bits in the Peripheral Multiplexing register
The selection of peripheral function A to H is done by writing to the Peripheral Multiplexing Odd and Even bits in the Peripheral Multiplexing register (PMUXn.PMUXE/O) in the PORT.
Reference: Atmel-42181G-SAM-D21_Datasheet section 6.1 on page 21
PA16 corresponds to: PORTA, PMUX[8] Even
PA17 corresponds to: PORTA, PMUX[8] Odd
PA18 corresponds to: PORTA, PMUX[9] Even
PA19 corresponds to: PORTA, PMUX[9] Odd
In general:
Px(2n+0/1) corresponds to Portx, PMUX[n] Even=0/Odd=1
*/
//Disable SPI 1
SERCOM1->SPI.CTRLA.bit.ENABLE =0;
while(SERCOM1->SPI.SYNCBUSY.bit.ENABLE);
//Reset SPI 1
SERCOM1->SPI.CTRLA.bit.SWRST = 1;
while(SERCOM1->SPI.CTRLA.bit.SWRST || SERCOM1->SPI.SYNCBUSY.bit.SWRST);
//Setting up NVIC
NVIC_EnableIRQ(SERCOM1_IRQn);
NVIC_SetPriority(SERCOM1_IRQn,2);
//Setting Generic Clock Controller!!!!
GCLK->CLKCTRL.reg = GCLK_CLKCTRL_ID(GCM_SERCOM1_CORE) | //Generic Clock 0
GCLK_CLKCTRL_GEN_GCLK0 | // Generic Clock Generator 0 is the source
GCLK_CLKCTRL_CLKEN; // Enable Generic Clock Generator
while(GCLK->STATUS.reg & GCLK_STATUS_SYNCBUSY); //Wait for synchronisation
//Set up SPI Control A Register
SERCOM1->SPI.CTRLA.bit.DORD = 0; //MSB first
SERCOM1->SPI.CTRLA.bit.CPOL = 0; //SCK is low when idle, leading edge is rising edge
SERCOM1->SPI.CTRLA.bit.CPHA = 0; //data sampled on leading sck edge and changed on a trailing sck edge
SERCOM1->SPI.CTRLA.bit.FORM = 0x0; //Frame format = SPI
SERCOM1->SPI.CTRLA.bit.DIPO = 0x0; //DATA PAD0 MOSI is used as slave input (slave mode) // page 492
SERCOM1->SPI.CTRLA.bit.DOPO = 0x2; //DATA PAD2 MISO is used as slave output
SERCOM1->SPI.CTRLA.bit.MODE = 0x2; //SPI in Slave mode
SERCOM1->SPI.CTRLA.bit.IBON = 0x1; //Buffer Overflow notification
SERCOM1->SPI.CTRLA.bit.RUNSTDBY = 1; //wake on receiver complete
//Set up SPI control B register
//SERCOM1->SPI.CTRLB.bit.RXEN = 0x1; //Enable Receiver
SERCOM1->SPI.CTRLB.bit.SSDE = 0x1; //Slave Selecte Detection Enabled
SERCOM1->SPI.CTRLB.bit.CHSIZE = 0; //character size 8 Bit
//SERCOM1->SPI.CTRLB.bit.PLOADEN = 0x1; //Enable Preload Data Register
//while (SERCOM1->SPI.SYNCBUSY.bit.CTRLB);
//Set up SPI interrupts
SERCOM1->SPI.INTENSET.bit.SSL = 0x1; //Enable Slave Select low interrupt
SERCOM1->SPI.INTENSET.bit.RXC = 0x1; //Receive complete interrupt
SERCOM1->SPI.INTENSET.bit.TXC = 0x1; //Receive complete interrupt
SERCOM1->SPI.INTENSET.bit.ERROR = 0x1; //Receive complete interrupt
SERCOM1->SPI.INTENSET.bit.DRE = 0x1; //Data Register Empty interrupt
//init SPI CLK
//SERCOM1->SPI.BAUD.reg = SERCOM_FREQ_REF / (2*4000000u)-1;
//Enable SPI
SERCOM1->SPI.CTRLA.bit.ENABLE = 1;
while(SERCOM1->SPI.SYNCBUSY.bit.ENABLE);
SERCOM1->SPI.CTRLB.bit.RXEN = 0x1; //Enable Receiver, this is done here due to errate issue
while(SERCOM1->SPI.SYNCBUSY.bit.CTRLB); //wait until receiver is enabled
}
void SercomSPISlave::Sercom4init()
{
//Configure SERCOM4 SPI PINS
//Set PA12 as input (MOSI) - on D22 / MISO
//Set PB09 as input (SCK) - on A2
//Set PB10 as input (SS) - on D23 / MOSI
//Set PB11 as output (MISO) - on D24 / SCK
PORT->Group[PORTA].PINCFG[12].bit.PMUXEN = 0x1; //Enable Peripheral Multiplexing for SERCOM4 SPI PA12 Arduino PIN22
PORT->Group[PORTA].PMUX[6].bit.PMUXE = 0x3; //SERCOM 4 is selected for peripheral use of this pad (0x3 selects peripheral function D: SERCOM-ALT)
PORT->Group[PORTB].PINCFG[9].bit.PMUXEN = 0x1; //Enable Peripheral Multiplexing for SERCOM4 SPI PB09 Arduino PIN16
PORT->Group[PORTB].PMUX[4].bit.PMUXO = 0x3; //SERCOM 4 is selected for peripheral use of this pad (0x3 selects peripheral function D: SERCOM-ALT)
PORT->Group[PORTB].PINCFG[10].bit.PMUXEN = 0x1; //Enable Peripheral Multiplexing for SERCOM4 SPI PB10 Arduino PIN23
PORT->Group[PORTB].PMUX[5].bit.PMUXE = 0x3; //SERCOM 4 is selected for peripheral use of this pad (0x3 selects peripheral function D: SERCOM-ALT)
PORT->Group[PORTB].PINCFG[11].bit.PMUXEN = 0x1; //Enable Peripheral Multiplexing for SERCOM4 SPI PB11 Arduino PIN24
PORT->Group[PORTB].PMUX[5].bit.PMUXO = 0x3; //SERCOM 4 is selected for peripheral use of this pad (0x3 selects peripheral function D: SERCOM-ALT)
/*
Explanation:
PMUXEN stands for Peripheral Multiplexing Enable
PMUXE stands for Even bits in the Peripheral Multiplexing register
PMUXO stands for Odd bits in the Peripheral Multiplexing register
The selection of peripheral function A to H is done by writing to the Peripheral Multiplexing Odd and Even bits in the Peripheral Multiplexing register (PMUXn.PMUXE/O) in the PORT.
Reference: Atmel-42181G-SAM-D21_Datasheet section 6.1 on page 21
PA12 corresponds to: PORTA, PMUX[6] Even
PB09 corresponds to: PORTB, PMUX[4] Odd
PB10 corresponds to: PORTB, PMUX[5] Even
PB11 corresponds to: PORTB, PMUX[5] Odd
In general:
Px(2n+0/1) corresponds to Portx, PMUX[n] Even=0/Odd=1
*/
//Disable SPI 1
SERCOM4->SPI.CTRLA.bit.ENABLE =0;
while(SERCOM4->SPI.SYNCBUSY.bit.ENABLE);
//Reset SPI 1
SERCOM4->SPI.CTRLA.bit.SWRST = 1;
while(SERCOM4->SPI.CTRLA.bit.SWRST || SERCOM4->SPI.SYNCBUSY.bit.SWRST);
//Setting up NVIC
NVIC_EnableIRQ(SERCOM4_IRQn);
NVIC_SetPriority(SERCOM4_IRQn,2);
//Setting Generic Clock Controller!!!!
GCLK->CLKCTRL.reg = GCLK_CLKCTRL_ID(GCM_SERCOM4_CORE) | //Generic Clock 0
GCLK_CLKCTRL_GEN_GCLK0 | // Generic Clock Generator 0 is the source
GCLK_CLKCTRL_CLKEN; // Enable Generic Clock Generator
while(GCLK->STATUS.reg & GCLK_STATUS_SYNCBUSY); //Wait for synchronisation
//Set up SPI Control A Register
SERCOM4->SPI.CTRLA.bit.DORD = 0; //MSB first
SERCOM4->SPI.CTRLA.bit.CPOL = 0; //SCK is low when idle, leading edge is rising edge
SERCOM4->SPI.CTRLA.bit.CPHA = 0; //data sampled on leading sck edge and changed on a trailing sck edge
SERCOM4->SPI.CTRLA.bit.FORM = 0x0; //Frame format = SPI
SERCOM4->SPI.CTRLA.bit.DIPO = 0x0; //DATA PAD0 MOSI is used as slave input (slave mode) // page 492
SERCOM4->SPI.CTRLA.bit.DOPO = 0x2; //DATA PAD2 MISO is used as slave output
SERCOM4->SPI.CTRLA.bit.MODE = 0x2; //SPI in Slave mode
SERCOM4->SPI.CTRLA.bit.IBON = 0x1; //Buffer Overflow notification
SERCOM4->SPI.CTRLA.bit.RUNSTDBY = 1; //wake on receiver complete
//Set up SPI control B register
//SERCOM4->SPI.CTRLB.bit.RXEN = 0x1; //Enable Receiver
SERCOM4->SPI.CTRLB.bit.SSDE = 0x1; //Slave Selecte Detection Enabled
SERCOM4->SPI.CTRLB.bit.CHSIZE = 0; //character size 8 Bit
//SERCOM4->SPI.CTRLB.bit.PLOADEN = 0x1; //Enable Preload Data Register
//while (SERCOM4->SPI.SYNCBUSY.bit.CTRLB);
//Set up SPI interrupts
SERCOM4->SPI.INTENSET.bit.SSL = 0x1; //Enable Slave Select low interrupt
SERCOM4->SPI.INTENSET.bit.RXC = 0x1; //Receive complete interrupt
SERCOM4->SPI.INTENSET.bit.TXC = 0x1; //Receive complete interrupt
SERCOM4->SPI.INTENSET.bit.ERROR = 0x1; //Receive complete interrupt
SERCOM4->SPI.INTENSET.bit.DRE = 0x1; //Data Register Empty interrupt
//init SPI CLK
//SERCOM4->SPI.BAUD.reg = SERCOM_FREQ_REF / (2*4000000u)-1;
//Enable SPI
SERCOM4->SPI.CTRLA.bit.ENABLE = 1;
while(SERCOM4->SPI.SYNCBUSY.bit.ENABLE);
SERCOM4->SPI.CTRLB.bit.RXEN = 0x1; //Enable Receiver, this is done here due to errate issue
while(SERCOM4->SPI.SYNCBUSY.bit.CTRLB); //wait until receiver is enabled
}
And the code I use for arduino as master is in the message above, I'll paste it here just so everything is in one place.
#include <SPI.h>
#include<Arduino.h>
void setup (void)
{
digitalWrite(SS, HIGH); // ensure SS stays high
SPI.begin ();
SPI.setClockDivider(SPI_CLOCK_DIV4); //Sets clock for SPI communication at 8 (16/8=2Mhz)
Serial.begin(115200);
} // end of setup
void loop (void)
{
byte c;
// enable Slave Select
digitalWrite(SS, LOW); // SS is pin 10
// send test string
for (const char * p = "Fab" ; c = *p; p++)
SPI.transfer (c);
// disable Slave Select
digitalWrite(SS, HIGH);
delay (100);
} // end of loop
As far as I can tell the connections should remain the same.