SAM L10/L11 SERCOM Serial Peripheral Interface (SPI)

Last modified by Microchip on 2023/12/04 21:31

Overview

The Serial Peripheral Interface (SPI) is one of the available modes in the Serial Communication Interface (SERCOM). The SPI uses the SERCOM transmitter and receiver configured as shown in the block diagram below. Each side, Master and Slave, depicts a separate SPI containing a shift register, a transmit buffer and a two-level or four-level receive buffer. In addition, the SPI Master uses the SERCOM baud-rate generator, while the SPI Slave can use the SERCOM address match logic.

Features

SERCOM SPI includes the following features:

  • Full-duplex, four-wire interface (MISO, MOSI, SCK, /SS)
  • One-level transmit buffer, two-level, or four-level receive buffer
  • Supports all four SPI modes of operation
  • Single data direction operation allows alternate function on MISO or MOSI pin
  • Select-able LSB- or MSB-first data transfer
  • Can be used with Direct Memory Access (DMA)
  • Master operation:
    • Serial clock speed, fSCK=1/tSCK (For tSCK and tSSCK values, refer to SPI Timing Characteristics.)
    • 8-bit clock generator
    • Hardware-controlled SS

Back to top

Block Diagram

SAM L10 SERCOM SPI block diagram

Principle of Operation

In order to use the SERCOM’s I/O lines, the I/O pins must be configured using the IO Pin Controller (PORT). When the SERCOM is configured for SPI operation, the SERCOM controls the direction and value of the I/O pins. Both PORT control bits, PINCFGn.PULLEN and PINCFGn.DRVSTR, are still effective. If the receiver is disabled, the data input pin can be used for other purposes. In SPI Master mode, the Slave Select line (SS) is hardware-controlled when the Master Slave Select Enable bit in the Control B register (CTRLB.MSSEN) is 1.

The combined configuration of PORT, the Data In Pinout, and the Data Out Pinout bit groups in the Control A register, CTRLA.DIPO and CTRLA.DOPO, define the physical position of the SPI signals.

The SERCOM bus clock (CLK_SERCOMx_APB) can be enabled and disabled in the Main Clock Controller. A generic clock (GCLK_SERCOMx_CORE) is required to clock the SPI. This clock must be configured and enabled in the Generic Clock Controller before using the SPI. This generic clock is asynchronous to the bus clock (CLK_SERCOMx_APB), therefore, writes to certain registers will require synchronization to the clock domains.

The DMA request lines are connected to the DMA Controller (DMAC). In order to use DMA requests with this peripheral, the DMAC must be configured first. The interrupt request line is connected to the Interrupt Controller. In order to use interrupt requests of this peripheral, the Nested Vectored Interrupt Controller (NVIC) must be configured first.

The SPI is a high-speed synchronous data transfer interface. It allows high-speed communication between the device and peripheral devices. The SPI can operate as Master or Slave. As Master, the SPI initiates and controls all data transactions. The SPI is single-buffered for transmitting and double-buffered for receiving. When transmitting data, the Data register can be loaded with the next character to be transmitted during the current transmission. When receiving, the data is transferred to the two-level or four-level receive buffer, and the receiver is ready for a new character. The SPI transaction format is shown below. Each transaction can contain one or more characters. The character size is configurable and can be either eight or nine bits.

SAM L10 SERCOM SPI frame

The SPI Master must pull the Slave Select line (SS) of the desired Slave low to initiate a transaction. The Master and Slave prepare data to send via their respective shift registers, and the Master generates the serial clock on the SCK line. Data are always shifted from Master to Slave on the Master Output Slave Input line (MOSI); data is shifted from Slave to Master on the Master Input Slave Output line (MISO). Each time a character is shifted out from the Master, a character will be shifted out from the Slave simultaneously. To signal the end of a transaction, the Master will pull the SS line high.

Refer to the SERCOM SPI – SERCOM Serial Peripheral Interface chapter from the product datasheet for more details.

Back to top

Code Example

/***
 *** The example has no copyright and can be used by anyone.
 *** The following example is based on Device File Package
 *** required to compile the macro definitions used.
 *** The Device File Package is available by downloading Atmel Studio 7.
 ***/

 
/***
 *** In this example The SERCOM0 is configured as SPI to work with DMA in close Loop.
 *** the SPI Frequency (Baud Rate) will be OSC16M Freq/2
 *** Several functions are described to be reused in concrete example
 ***/

 
/*** Define SPI Channels ***/
#define SPI_TX_CHANNEL      1
#define SPI_RX_CHANNEL      0
 
/*** Create DMA descriptor in global variable (128-bit aligned) ***/
volatile __attribute__((__aligned__(128))) DmacDescriptor descriptor_SPI[2];
volatile __attribute__((__aligned__(128))) DmacDescriptor write_back_descriptor_SPI[2];
 
/*** SPI Output Data Buffer ***/
 
const uint8_t spi_output_data[SPI_DATA_SIZE+1] = 
{
 0xd5, 0xed, 0x50, 0x1c, 0x9e, 0xf0, 0x65, 0xb6,
 0xed, 0x85, 0xf2, 0x36, 0x8e, 0x3e, 0x11, 0x12,
 0x3d, 0x34, 0xe9, 0x66, 0x17, 0x50, 0x46, 0x62,
 0xad, 0xfd, 0xe3, 0x43, 0x40, 0x85, 0x0d, 0x13,
 0xbe, 0xd3, 0xf5, 0xb9, 0x8f, 0x18, 0x5b, 0xe5,
 0xd3, 0xed, 0x6e, 0xc3, 0x7e, 0xe6, 0x3c, 0x2b,
 0x89, 0xb1, 0x50, 0x36, 0xae, 0x9b, 0x2b, 0x11,
 0x49, 0xe3, 0xf4, 0x50, 0x55, 0xfe, 0x90, 0xae,
 0x7f, 0xb3, 0x17, 0x2a, 0xdc, 0x6d, 0x08, 0xe9,
 0xf7, 0x9a, 0x57, 0xf9, 0x78, 0xa2, 0x9c, 0xd8,
 0x5a, 0x30, 0xc7, 0x9d, 0x31, 0xb9, 0xce, 0x85,
 0x14, 0x98, 0x2c, 0x8d, 0xb8, 0x04, 0xf5, 0x0d,
 0x16, 0xf1, 0x26, 0xea, 0x15, 0x5e, 0x83, 0xd1,
 0x07, 0x35, 0x60, 0x5b, 0x87, 0xde, 0x8b, 0x33,
 0x6f, 0x73, 0x53, 0xbf, 0xa3, 0x7a, 0xd7, 0x2f,
 0xf5, 0x81, 0xf8, 0x10, 0xcd, 0xdd, 0xc2, 0xb6,
 0x00
};
 
/*** SPI RX Buffer ***/
extern uint8_t spi_rx_buffer[SPI_DATA_SIZE];
 
/*************************************************/
/*** DMA RX Descriptor Initialization step by step
 *** - Validate the Descriptor
 *** - No Event out generated by the DMA
 *** - Channel will be disabled if it is the last block 
 ***   transfer in the transaction and block interrupt
 *** - 8-bit bus transfer (BYTE)
 *** - No increment from the source address (SRCINC=0)
 *** - Enable Increment from the destination address (DSTINC=1)
 *** - Step size settings apply to the Destination address
 *** - Step Size => Next ADDR = ADDR + (BEATSIZE+1) * 1
 *** - DMA_BITCOUNT_VALUE SPI_DATA_SIZE transfer before waking up the core
 ***/

void DMA_RX_descriptor_init(void)
{
 descriptor_SPI[SPI_RX_CHANNEL].BTCTRL.bit.VALID = 1;
 descriptor_SPI[SPI_RX_CHANNEL].BTCTRL.bit.EVOSEL = DMAC_BTCTRL_EVOSEL_DISABLE_Val;
 descriptor_SPI[SPI_RX_CHANNEL].BTCTRL.bit.BLOCKACT = DMAC_BTCTRL_BLOCKACT_INT_Val;    
 descriptor_SPI[SPI_RX_CHANNEL].BTCTRL.bit.BEATSIZE = DMAC_BTCTRL_BEATSIZE_BYTE_Val;    
 descriptor_SPI[SPI_RX_CHANNEL].BTCTRL.bit.SRCINC = 0;                        
 descriptor_SPI[SPI_RX_CHANNEL].BTCTRL.bit.DSTINC = 1;
 descriptor_SPI[SPI_RX_CHANNEL].BTCTRL.bit.STEPSEL = DMAC_BTCTRL_STEPSEL_DST_Val;
 descriptor_SPI[SPI_RX_CHANNEL].BTCTRL.bit.STEPSIZE = DMAC_BTCTRL_STEPSIZE_X1_Val;
 descriptor_SPI[SPI_RX_CHANNEL].BTCNT.reg = SPI_DATA_SIZE+1;
 
 /*** Set source address and destination address 
  *** source address is the SPI Data register (SERCOM0->SPI.DATA)
  *** destination address in the RAM is the rx buffer (spi_rx_buffer[SPI_DATA_SIZE])
  ***/

 descriptor_SPI[SPI_RX_CHANNEL].SRCADDR.reg = (uint32_t) (&(SERCOM0->SPI.DATA));
 descriptor_SPI[SPI_RX_CHANNEL].DSTADDR.reg = (uint32_t) (&(spi_rx_buffer[SPI_DATA_SIZE]));
 
 /*** Set next transfer descriptor_SPI_TX address ***/
 descriptor_SPI[SPI_RX_CHANNEL].DESCADDR.reg = (uint32_t) (&descriptor_SPI[SPI_TX_CHANNEL]);
}
 
/*************************************************/
/*** DMA RX Descriptor Initialization step by step
 *** - Validate the Descriptor
 *** - No Event out generated by the DMA
 *** - Channel will be disabled if it is the last block 
 ***   transfer in the transaction and block interrupt
 *** - 8-bit bus transfer (BYTE)
 *** - Enable Increment from the  the source address (SRCINC=1)
 *** - No increment from the destination address (DSTINC=0)
 *** - Step size settings apply to the Source address
 *** - Step Size => Next ADDR = ADDR + (BEATSIZE+1) * 1
 *** - DMA_BITCOUNT_VALUE SPI_DATA_SIZE transfer before waking up the core
 ***/

void DMA_TX_descriptor_init(void)
{
 descriptor_SPI[SPI_TX_CHANNEL].BTCTRL.bit.VALID = 1;
 descriptor_SPI[SPI_TX_CHANNEL].BTCTRL.bit.EVOSEL = DMAC_BTCTRL_EVOSEL_DISABLE_Val;
 descriptor_SPI[SPI_TX_CHANNEL].BTCTRL.bit.BLOCKACT = DMAC_BTCTRL_BLOCKACT_INT_Val;
 descriptor_SPI[SPI_TX_CHANNEL].BTCTRL.bit.BEATSIZE = DMAC_BTCTRL_BEATSIZE_BYTE_Val;
 descriptor_SPI[SPI_TX_CHANNEL].BTCTRL.bit.SRCINC = 1;
 descriptor_SPI[SPI_TX_CHANNEL].BTCTRL.bit.DSTINC = 0;
 descriptor_SPI[SPI_TX_CHANNEL].BTCTRL.bit.STEPSEL = DMAC_BTCTRL_STEPSEL_SRC_Val;    
 descriptor_SPI[SPI_TX_CHANNEL].BTCTRL.bit.STEPSIZE = DMAC_BTCTRL_STEPSIZE_X1_Val;
 descriptor_SPI[SPI_TX_CHANNEL].BTCNT.reg = SPI_DATA_SIZE+1;
 
 /*** Set source address and destination address 
  *** Source address in the RAM is the spi Output buffer (spi_output_data[SPI_DATA_SIZE])
  *** Destination address is the SPI Data register (SERCOM0->SPI.DATA)
  ***/

 descriptor_SPI[SPI_TX_CHANNEL].SRCADDR.reg = (uint32_t) (&(spi_output_data[SPI_DATA_SIZE]));
 descriptor_SPI[SPI_TX_CHANNEL].DSTADDR.reg = (uint32_t) (&(SERCOM0->SPI.DATA));
 
 /*** Set next transfer address to NULL ***/
 descriptor_SPI[SPI_TX_CHANNEL].DESCADDR.reg = NULL;
}
 
/*************************************************/
/*** DMA SPI TX and RX Channels initialization ***/
void DMA_init_SPI(void)
{
 /***
  *** Setup descriptor base address 
  *** and write back section base address 
  ***/

 DMAC->BASEADDR.reg = (uint32_t)descriptor_SPI;
 DMAC->WRBADDR.reg = (uint32_t)write_back_descriptor_SPI;
 
 /***
  *** Disable DMAC 
  *** Enable all priority level at the same time 
  *** Round Robin Arbitrer Scheme selected
  ***/

 DMAC->CTRL.reg &= ~(DMAC_CTRL_DMAENABLE);
 DMAC->CTRL.reg |= DMAC_CTRL_LVLEN(0xf);    
 DMAC->PRICTRL0.bit.RRLVLEN0 = 1;
 
 /***
  *** Configure The TX SPI DMA Channel:
  *** DMAC Channel 1 is used
  *** Disable the channel before configuring it
  ***/

 DMAC->CHID.reg = DMAC_CHID_ID(SPI_TX_CHANNEL);
 DMAC->CHCTRLA.reg &= ~DMAC_CHCTRLA_ENABLE;
 
 /***
  *** Configure TX SPI DMA Channel: (continue...)
  *** DMA is used in STANDBY, therefore needs RUNSTDBY 
  *** Channel Priority Level = 0
  *** SERCOM0 (SPI) is used as source to trigger the DMA TX
  *** The trigger action is done for every DMA BEAT
  ***/

 DMAC->CHCTRLA.reg = DMAC_CHCTRLA_RUNSTDBY;
 DMAC->CHCTRLB.reg = (DMAC_CHCTRLB_LVL(0x0)|    
                      DMAC_CHCTRLB_TRIGSRC(SERCOM0_DMAC_ID_TX)| 
                      DMAC_CHCTRLB_TRIGACT_BEAT);
 
 /*** Enabling DMA channel TX ***/
 DMAC->CHCTRLA.reg |= DMAC_CHCTRLA_ENABLE;
 while(DMAC->CHSTATUS.bit.BUSY);  
 
 /***
  *** Configure RX SPI DMA Channel:
  *** DMAC Channel 0 is used
  *** Disable the channel before configuring it
  ***/

 DMAC->CHID.reg = DMAC_CHID_ID(SPI_RX_CHANNEL);    
 DMAC->CHCTRLA.reg &= ~DMAC_CHCTRLA_ENABLE;    
 
 /***
  *** Configure RX SPI DMA Channel: (continue...)
  *** DMA is used in STANDBY, therefore needs RUNSTDBY 
  *** Channel Priority Level = 0
  *** SERCOM0 (SPI) is used as source to trigger the DMA RX
  *** The trigger action is done for every DMA BEAT
  ***/

  DMAC->CHCTRLA.reg = DMAC_CHCTRLA_RUNSTDBY;
  DMAC->CHCTRLB.reg = ( DMAC_CHCTRLB_LVL(0x0)|    
                        DMAC_CHCTRLB_TRIGSRC(SERCOM0_DMAC_ID_RX)|
                        DMAC_CHCTRLB_TRIGACT_BEAT);
 
 /*** enabling DMA interrupt on RX transfer completed ***/
 DMAC->CHINTENSET.reg = DMAC_CHINTENSET_TCMPL;        
 
 /*** Enabling DMA channel RX ***/
 DMAC->CHCTRLA.reg |= DMAC_CHCTRLA_ENABLE;
 while(DMAC->CHSTATUS.bit.BUSY);  
 
 /*** Enabling interrupt at core side ***/
 NVIC_EnableIRQ(DMAC_0_IRQn);
 
 /*** Finally Enable the DMA 
  *** (Enable Protected)
  ***/

 DMAC->CTRL.reg |= (DMAC_CTRL_DMAENABLE) ;    
}
 
/***********************************************************/
/*** Function That initializes the SERCOM to work in SPI ***/
void Spi_Init(void) 
{
 /*** port & clock to be configured and enable first 
  *** Considering that OSC16M is enabled by defaut running 
  *** @4MHz, the following lines configure the Generic Clock
  *** Generator 4 (GCLK4) to provide a clock to the SERCOM0.
 ***/

 GCLK->GENCTRL[4].reg = GCLK_GENCTRL_DIV(PWM_HIGH_GCLK_RATIO)|GCLK_GENCTRL_SRC_OSC16M|GCLK_GENCTRL_GENEN;
 while((GCLK->SYNCBUSY.reg & GCLK_SYNCBUSY_GENCTRL4));
 GCLK->PCHCTRL[GCLK_SERCOM0_CORE].reg = (GCLK_PCHCTRL_CHEN|GCLK_PCHCTRL_GEN_GCLK4);
 
 /*** configure SERCOM0 I/Os (SPI) 
  *** PA04: SERCOM0 PAD[0]: SPI MISO
  *** PA14: SERCOM0 PAD[2]: SPI MOSI
  *** PA05: SERCOM0 PAD[1]: SPI SS
  *** PA15: SERCOM0 PAD[3]: SPI SCK
  ***/

 PORT->Group[0].WRCONFIG.reg = (uint32_t)(PORT_WRCONFIG_WRPINCFG|
                                          PORT_WRCONFIG_WRPMUX|
                                          PORT_WRCONFIG_PINMASK(1<<5|1<<4|1<<15|1<<14)|
                                          PORT_WRCONFIG_PMUXEN|PORT_WRCONFIG_PMUX(3));    
 
 /***    
  *** Reset the SPI Mode prior any configuration this will also disable the SERCOM
  *** Set SPI mode to master
  *** Set SPI transfer mode to CPOL=0,CPHA=0
  *** Set SPI frame format (std. SPI Frame)
  *** Set Data out pinout
  *** Set Data In pinout
  *** Set Data Order to MSB first
  ***/

 SERCOM0->SPI.CTRLA.bit.SWRST = 1;
 while(SERCOM0->SPI.SYNCBUSY.bit.SWRST);
 SERCOM0->SPI.CTRLA.reg = ( SERCOM_SPI_CTRLA_MODE(0x03)|
                            SERCOM_SPI_CTRLA_FORM(0x00)|
                            SERCOM_SPI_CTRLA_DIPO(0x00)|
                            SERCOM_SPI_CTRLA_DOPO(0x01));
 
 /*** Set SPI Character size to 8-bit
  *** Enable the receiver
  *** Set the baud rate to 0 that ensures to have Fbaud = 2MHZ
  *** knowing that Fbaud is Fref/2 (OSC16M running @ 4MHz)
  ***/

 SERCOM0->SPI.CTRLB.reg = ( SERCOM_SPI_CTRLB_CHSIZE(0x00)|
                            SERCOM_SPI_CTRLB_RXEN);
 SERCOM0->SPI.BAUD.reg = 0;
 
 /*** Disable SPI ***/
 SERCOM0->SPI.CTRLA.bit.ENABLE = 0 ;
 while(SERCOM0->SPI.SYNCBUSY.bit.ENABLE);
 
}
 
/*************************************************/
/*** Start Transfer ***/
void Spi_TxData(uint8_t data)
{
 /*** REconfigure SERCOM0 I/Os (SPI)
  *** PA04: SERCOM0 PAD[0]: SPI MISO
  *** PA14: SERCOM0 PAD[2]: SPI MOSI
  *** PA05: SERCOM0 PAD[1]: SPI SS
  *** PA15: SERCOM0 PAD[3]: SPI SCK
  ***/

 PORT->Group[0].WRCONFIG.reg = (uint32_t)(PORT_WRCONFIG_WRPINCFG|
                                          PORT_WRCONFIG_WRPMUX|
                                          PORT_WRCONFIG_PINMASK(1<<5|1<<4|1<<15|1<<14)|
                                          PORT_WRCONFIG_PMUXEN|PORT_WRCONFIG_PMUX(3));    
 
 /*** 
  *** ReInitialize the DMA descriptors and Cionfigure
  *** and enable the channel
  *** DMAC Channel Description 
  ***/

 DMA_RX_descriptor_init();
 DMA_TX_descriptor_init();
 DMA_init_SPI();
 
 /*** Enable The SERCOM SPI to start the transfer ***/
 SERCOM0->SPI.CTRLA.bit.ENABLE = 1 ;
 while(SERCOM0->SPI.SYNCBUSY.bit.ENABLE);
}
 
/*************************************************/
/***
 *** Shut down the SPI, if requred; called after all bytes sent
 *** Port are also put in tristate to prevent over consumption (if required)
 ***/

void Spi_Done(void) 
{
    SERCOM0->SPI.CTRLA.bit.ENABLE = 0 ;
    while(SERCOM0->SPI.SYNCBUSY.bit.ENABLE);       
 
 /*** disable DMAC ***/
 DMAC->CTRL.reg    &= ~(DMAC_CTRL_DMAENABLE) ;
 
 /*** port & clock to be disabled ***/
 PORT->Group[0].WRCONFIG.reg = (uint32_t)(PORT_WRCONFIG_WRPINCFG|
                                          PORT_WRCONFIG_WRPMUX|
                                          PORT_WRCONFIG_PINMASK(1<<5|1<<4|1<<15|1<<14)|
                                          PORT_WRCONFIG_PMUX(0));    
}

Back to top