/*** *** 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)); }