M0 SERCOM4 SPI Slave Setup Issue

Hi All,

I am attempting to use an M0 as an SPI slave on the default SPI bus (SERCOM4). I believe that all the registers are being set correctly, but when I set the enable register with this line, the program freezes:

SERCOM4->SPI.CTRLA.bit.ENABLE = 0x1;

I've also tried it like this:

REG_SERCOM4_SPI_CTRLA |= (1 << 1);

Any ideas why writing to this register might be causing it to freeze? Also, if you know an easier way to get the M0 to work as an SPI slave, I'm open to suggestions. Thanks.

Here is the full setup code:

void SPI_Slave_Setup()
{
	SerialUSB.println("Entering Slave Setup");
	
	// Enable Peripheral Multiplexing for SERCOM4 SPI
	PORT->Group[PORTB].PINCFG[9].bit.PMUXEN = 0x1; // SS   on PB09 (SERCOM4.1 ALT)
	PORT->Group[PORTB].PINCFG[10].bit.PMUXEN = 0x1; // MOSI on PB10 (SERCOM4.2 ALT)
	PORT->Group[PORTB].PINCFG[11].bit.PMUXEN = 0x1; // SCK  on PB11 (SERCOM4.3 ALT)
	PORT->Group[PORTA].PINCFG[12].bit.PMUXEN = 0x1; // MISO on PA12 (SERCOM4.0 ALT)

	// Setup MUX for peripheral use of this pad. 0x3 selects ALT pad
	PORT->Group[PORTB].PMUX[4].bit.PMUXO = 0x3; // Odd mux rounddown(pin#/2)
	PORT->Group[PORTB].PMUX[5].bit.PMUXE = 0x3;	// Even mux rounddown(pin#/2)
	PORT->Group[PORTB].PMUX[5].bit.PMUXO = 0x3;
	PORT->Group[PORTA].PMUX[6].bit.PMUXE = 0x3;
	
	//Disable SPI
	SERCOM4->SPI.CTRLA.bit.ENABLE = 0;
	while(SERCOM4->SPI.SYNCBUSY.bit.ENABLE);
	
	//Reset SPI
	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 synchronization
	
	//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 = 0x2;	 // PAD[2]: MOSI
	SERCOM4->SPI.CTRLA.bit.DOPO = 0x3;	 // PAD[0]: MISO , PAD[3]: SCK, PAD[1]: SS
	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.SSDE = 0x1; //Slave Select Detection Enabled
	SERCOM4->SPI.CTRLB.bit.CHSIZE = 0; //character size 8 Bit
	//SERCOM4->SPI.CTRLB.bit.PLOADEN = 0x1; //Enable Preload Data Register
	
	//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 = 0x1; // <<<<<<<<<<<< Causes code to freeze
	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
	
	SerialUSB.println("SPI Setup Complete");
}

The problem turned out to be some serial outputs in the SERCOM4 interrupt handler. Removing them allowed the SPI port to be enabled.

Here is the working code for anyone who is interested:

#include "wiring_private.h"

enum spi_transfer_mode
{
	SERCOM_SPI_TRANSFER_MODE_0 = 0,
	SERCOM_SPI_TRANSFER_MODE_1 = SERCOM_SPI_CTRLA_CPHA,
	SERCOM_SPI_TRANSFER_MODE_2 = SERCOM_SPI_CTRLA_CPOL,
	SERCOM_SPI_TRANSFER_MODE_3 = SERCOM_SPI_CTRLA_CPHA | SERCOM_SPI_CTRLA_CPOL,
};

enum spi_frame_format
{
	SERCOM_SPI_FRAME_FORMAT_SPI_FRAME      = SERCOM_SPI_CTRLA_FORM(0),
	SERCOM_SPI_FRAME_FORMAT_SPI_FRAME_ADDR = SERCOM_SPI_CTRLA_FORM(2),
};

enum spi_character_size
{
	SERCOM_SPI_CHARACTER_SIZE_8BIT = SERCOM_SPI_CTRLB_CHSIZE(0),
	SERCOM_SPI_CHARACTER_SIZE_9BIT = SERCOM_SPI_CTRLB_CHSIZE(1),
};

enum spi_data_order
{
	SERCOM_SPI_DATA_ORDER_LSB	= SERCOM_SPI_CTRLA_DORD,
	SERCOM_SPI_DATA_ORDER_MSB   = 0,
};

volatile uint8_t data = 0;
volatile uint8_t dataCount = 52;

void setup()
{
	SerialUSB.begin(115200);
	SPI_Slave_Setup();
}

void loop()
{
}

void SPI_Slave_Setup()
{
	// Set pin modes
	pinPeripheral(MISO, PIO_SERCOM_ALT);
	pinPeripheral(MOSI, PIO_SERCOM_ALT);
	pinPeripheral(SCK,  PIO_SERCOM_ALT);
	pinPeripheral(A2,   PIO_SERCOM_ALT);
	
	sercom4.disableSPI();
	sercom4.resetSPI();

	// Setting up NVIC
	NVIC_EnableIRQ(SERCOM4_IRQn);
	NVIC_SetPriority(SERCOM4_IRQn, (1 << __NVIC_PRIO_BITS) - 1);
	
	// Setting the CTRLA register
	//  - MISO: PAD[0], PA12, D22
	//  - MOSI: PAD[2], PB10, D23
	//  - SCK:  PAD[3], PB11, D24
	//  - SS:   PAD[1], PB09, A2
	SERCOM4->SPI.CTRLA.reg = SERCOM_SPI_CTRLA_MODE_SPI_SLAVE        |   // Set as slave
							 SERCOM_SPI_CTRLA_DIPO(SERCOM_RX_PAD_2) |   // Set input register
							 SERCOM_SPI_CTRLA_DOPO(SPI_PAD_0_SCK_3) |   // Set output register
							 SERCOM_SPI_TRANSFER_MODE_0             |   // Use SPI Mode 0
							 SERCOM_SPI_FRAME_FORMAT_SPI_FRAME      |   // Disable Address message format
							 SERCOM_SPI_DATA_ORDER_MSB              ;   // Most significant bit first
	
	// Setting the CTRLB register 
	SERCOM4->SPI.CTRLB.reg = SERCOM_SPI_CTRLB_CHSIZE(SPI_CHAR_SIZE_8_BITS) |   // 8 bit message size
							 SERCOM_SPI_CTRLB_SSDE						   |   // Slave Select Low Detection Enable
							 SERCOM_SPI_CTRLB_RXEN                         |   // Active the SPI receiver
							 SERCOM_SPI_CHARACTER_SIZE_8BIT                ;   // 8bit message size

	// Set up SPI interrupts
	SERCOM4->SPI.INTENSET.reg = SERCOM_SPI_INTFLAG_SSL   |   //Enable Slave Select low interrupt
							    SERCOM_SPI_INTFLAG_RXC   |   //Receive complete interrupt
							    SERCOM_SPI_INTFLAG_TXC   |
							    SERCOM_SPI_INTFLAG_ERROR |
							    SERCOM_SPI_INTFLAG_DRE   ;
	
	// Enable SPI
	sercom4.enableSPI();
}

void SERCOM4_Handler(void)
{
	/*
	*  1. ERROR
	*	  Occurs when the SPI receiver has one or more errors.
	*/
	if (SERCOM4->SPI.INTFLAG.bit.ERROR)
	{
		SERCOM4->SPI.INTFLAG.bit.ERROR = 1;
	}
	
	
	/*
	*  2. SSL: Slave Select Low
	*	  Occurs when SS goes low
	*/
	if (SERCOM4->SPI.INTFLAG.bit.SSL)
	{
		OnTransmissionStart();
		SERCOM4->SPI.INTFLAG.bit.SSL = 1;
	}
	

	/*
	*  3. TXC: Transmission Complete
	*	  Occurs when SS goes high. The transmission is complete.
	*/
	if (SERCOM4->SPI.INTFLAG.bit.TXC)
	{
		SERCOM4->SPI.INTFLAG.bit.TXC = 1;
	}
	
	
	/*
	*  4. RXC: Receive Complete
	*	  Occurs after a character has been full stored in the data buffer. It can now be retrieved from DATA.
	*/
	if (SERCOM4->SPI.INTFLAG.bit.RXC)
	{
		uint8_t readData = (uint8_t)SERCOM4->SPI.DATA.reg;
		SERCOM4->SPI.INTFLAG.bit.RXC = 1;
	}
	
	
	/*
	*  5. DRE: Data Register Empty
	*	  Occurs when data register is empty
	*/
	if (SERCOM4->SPI.INTFLAG.bit.DRE)
	{
		// Write some test data to buffer. Master will shift out 'dataCount' increments of 'data'
		if (data < dataCount)
		{
			SERCOM4->SPI.DATA.reg = ++data;
		}
		else
		{
			SERCOM4->SPI.DATA.reg = 0;
		}
		
		
		SERCOM4->SPI.INTFLAG.bit.DRE = 1;
	}
}

void OnTransmissionStart()
{
	data = 0;
}

Thanks Matthew, I will try it for SERCOM1 too, trying to connect a SAMD21 and a Pi Zero.

Actually I already solved it some time ago but forgot about it:

My latest problem was that I had a CS0 / CS1 option on my board and incorrectly soldered it to CS1 setting thinking it was the CS0 jumper between SAMD21 and Pi. After putting a Analog Discovery 2 from Digilent to the SPI pins I realized the signal came correctly from both sides but the SAMD21 did not detect the changes for it's SS and did not issue interrupts.

I actually also got the Python script working now.

I desperately need help to Slave 2 SAMD21 chips. I am new in general to slaving 2 Micro-controller.

MatthewHoworko has posted code to SPI Slave a SAMD21 board but I still cant get it to work.

At the bottom of the code MatthewHoworko posted there is a void SERCOM4_Handler(void) but its never called, so maybe that is my problem

I have been looking everywhere on how to slave one SAMD21 to another with no successes. Apparently a lot of other people have the same problem.
What is promising is jopiek was able to use this code to slave to a Pi


I continued to work on it and can get it to work with jopiek's Slave code and no SERCOM editing on the Master!!!!!!!!!! Thank you jopiek
Although i dont know what the Bit Shifting is for, at-least it works