Atmel Software Framework

Quick Start Guide for Using DMA with SERCOM SPI

The supported board list:

  • SAMD21 Xplained Pro
  • SAMR21 Xplained Pro

This quick start will transmit a buffer data from master to slave through DMA. In this use case the SPI master will be configured with the following settings on SAMD21 Xplained Pro:

  • Master Mode enabled
  • MSB of the data is transmitted first
  • Transfer mode 0
  • SPI MUX Setting E
    • MOSI on pad 2, extension header 2, pin 16
    • MISO on pad 0, extension header 2, pin 17
    • SCK on pad 3, extension header 2, pin 18
    • SS on extension header 2, pin 15
  • 8-bit character size
  • Not enabled in sleep mode
  • Baudrate 100000
  • GLCK generator 0

The SPI slave will be configured with the following settings:

  • Slave mode enabled
  • Preloading of shift register enabled
  • MSB of the data is transmitted first
  • Transfer mode 0
  • SPI MUX Setting E
    • MISO on pad 2, extension header 1, pin 16
    • MOSI on pad 0, extension header 1, pin 17
    • SCK on pad 3, extension header 1, pin 18
    • SS on pad 1, extension header 1, pin 15
  • 8-bit character size
  • Not enabled in sleep mode
  • GLCK generator 0

Note that the pinouts on other boards may different, see next sector for details.

Setup

Prerequisites

The following connections has to be made using wires:

  • SAM D21 Xplained Pro
    • SS_0: EXT1 PIN15 (PA05) <–> EXT2 PIN15 (PA17)
    • DO/DI: EXT1 PIN16 (PA06) <–> EXT2 PIN17 (PA16)
    • DI/DO: EXT1 PIN17 (PA04) <–> EXT2 PIN16 (PA18)
    • SCK: EXT1 PIN18 (PA07) <–> EXT2 PIN18 (PA19)
  • SAM R21 Xplained Pro
    • SS_0: EXT1 PIN15 (PB03) <–> EXT1 PIN10 (PA23)
    • DO/DI: EXT1 PIN16 (PB22) <–> EXT1 PIN9 (PA22)
    • DI/DO: EXT1 PIN17 (PB02) <–> EXT1 PIN7 (PA18)
    • SCK: EXT1 PIN18 (PB23) <–> EXT1 PIN8 (PA19)

Code

Add to the main application source file, before user definitions and functions according to your board:

For SAMD21 Xplained Pro

#define CONF_MASTER_SPI_MODULE EXT2_SPI_MODULE
#define CONF_MASTER_SS_PIN EXT2_PIN_SPI_SS_0
#define CONF_MASTER_MUX_SETTING EXT2_SPI_SERCOM_MUX_SETTING
#define CONF_MASTER_PINMUX_PAD0 EXT2_SPI_SERCOM_PINMUX_PAD0
#define CONF_MASTER_PINMUX_PAD1 PINMUX_UNUSED
#define CONF_MASTER_PINMUX_PAD2 EXT2_SPI_SERCOM_PINMUX_PAD2
#define CONF_MASTER_PINMUX_PAD3 EXT2_SPI_SERCOM_PINMUX_PAD3
#define CONF_SLAVE_SPI_MODULE EXT1_SPI_MODULE
#define CONF_SLAVE_MUX_SETTING EXT1_SPI_SERCOM_MUX_SETTING
#define CONF_SLAVE_PINMUX_PAD0 EXT1_SPI_SERCOM_PINMUX_PAD0
#define CONF_SLAVE_PINMUX_PAD1 EXT1_SPI_SERCOM_PINMUX_PAD1
#define CONF_SLAVE_PINMUX_PAD2 EXT1_SPI_SERCOM_PINMUX_PAD2
#define CONF_SLAVE_PINMUX_PAD3 EXT1_SPI_SERCOM_PINMUX_PAD3
#define CONF_PERIPHERAL_TRIGGER_TX SERCOM1_DMAC_ID_TX
#define CONF_PERIPHERAL_TRIGGER_RX SERCOM0_DMAC_ID_RX

For SAMR21 Xplained Pro

#define CONF_MASTER_SPI_MODULE SERCOM3
#define CONF_MASTER_SS_PIN EXT1_PIN_10
#define CONF_MASTER_MUX_SETTING SPI_SIGNAL_MUX_SETTING_E
#define CONF_MASTER_PINMUX_PAD0 PINMUX_PA22C_SERCOM3_PAD0
#define CONF_MASTER_PINMUX_PAD1 PINMUX_UNUSED
#define CONF_MASTER_PINMUX_PAD2 PINMUX_PA18D_SERCOM3_PAD2
#define CONF_MASTER_PINMUX_PAD3 PINMUX_PA19D_SERCOM3_PAD3
#define CONF_SLAVE_SPI_MODULE EXT1_SPI_MODULE
#define CONF_SLAVE_MUX_SETTING EXT1_SPI_SERCOM_MUX_SETTING
#define CONF_SLAVE_PINMUX_PAD0 EXT1_SPI_SERCOM_PINMUX_PAD0
#define CONF_SLAVE_PINMUX_PAD1 EXT1_SPI_SERCOM_PINMUX_PAD1
#define CONF_SLAVE_PINMUX_PAD2 EXT1_SPI_SERCOM_PINMUX_PAD2
#define CONF_SLAVE_PINMUX_PAD3 EXT1_SPI_SERCOM_PINMUX_PAD3
#define CONF_PERIPHERAL_TRIGGER_TX SERCOM3_DMAC_ID_TX
#define CONF_PERIPHERAL_TRIGGER_RX SERCOM5_DMAC_ID_RX

Add to the main application source file, outside of any functions:

#define BUF_LENGTH 20
#define TEST_SPI_BAUDRATE 1000000UL
#define SLAVE_SELECT_PIN CONF_MASTER_SS_PIN
static const uint8_t buffer_tx[BUF_LENGTH] = {
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A,
0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14,
};
static uint8_t buffer_rx[BUF_LENGTH];
static volatile bool transfer_tx_is_done = false;
static volatile bool transfer_rx_is_done = false;

Copy-paste the following setup code to your user application:

static void transfer_tx_done( const struct dma_resource* const resource )
{
transfer_tx_is_done = true;
}
static void transfer_rx_done( const struct dma_resource* const resource )
{
transfer_rx_is_done = true;
}
static void configure_dma_resource_tx(struct dma_resource *tx_resource)
{
struct dma_resource_config tx_config;
tx_config.peripheral_trigger = CONF_PERIPHERAL_TRIGGER_TX;
tx_config.trigger_action = DMA_TRIGGER_ACTON_BEAT;
dma_allocate(tx_resource, &tx_config);
}
static void configure_dma_resource_rx(struct dma_resource *rx_resource)
{
struct dma_resource_config rx_config;
rx_config.peripheral_trigger = CONF_PERIPHERAL_TRIGGER_RX;
rx_config.trigger_action = DMA_TRIGGER_ACTON_BEAT;
dma_allocate(rx_resource, &rx_config);
}
static void setup_transfer_descriptor_tx(DmacDescriptor *tx_descriptor)
{
struct dma_descriptor_config tx_descriptor_config;
dma_descriptor_get_config_defaults(&tx_descriptor_config);
tx_descriptor_config.beat_size = DMA_BEAT_SIZE_BYTE;
tx_descriptor_config.dst_increment_enable = false;
tx_descriptor_config.block_transfer_count = sizeof(buffer_tx)/sizeof(uint8_t);
tx_descriptor_config.source_address = (uint32_t)buffer_tx + sizeof(buffer_tx);
tx_descriptor_config.destination_address =
(uint32_t)(&spi_master_instance.hw->SPI.DATA.reg);
dma_descriptor_create(tx_descriptor, &tx_descriptor_config);
}
static void setup_transfer_descriptor_rx(DmacDescriptor *rx_descriptor)
{
struct dma_descriptor_config rx_descriptor_config;
dma_descriptor_get_config_defaults(&rx_descriptor_config);
rx_descriptor_config.beat_size = DMA_BEAT_SIZE_BYTE;
rx_descriptor_config.src_increment_enable = false;
rx_descriptor_config.block_transfer_count = sizeof(buffer_rx)/sizeof(uint8_t);
rx_descriptor_config.source_address =
(uint32_t)(&spi_slave_instance.hw->SPI.DATA.reg);
rx_descriptor_config.destination_address =
(uint32_t)buffer_rx + sizeof(buffer_rx);
dma_descriptor_create(rx_descriptor, &rx_descriptor_config);
}
static void configure_spi_master(void)
{
struct spi_config config_spi_master;
struct spi_slave_inst_config slave_dev_config;
/* Configure and initialize software device instance of peripheral slave */
slave_dev_config.ss_pin = SLAVE_SELECT_PIN;
spi_attach_slave(&slave, &slave_dev_config);
/* Configure, initialize and enable SERCOM SPI module */
spi_get_config_defaults(&config_spi_master);
config_spi_master.mode_specific.master.baudrate = TEST_SPI_BAUDRATE;
config_spi_master.mux_setting = CONF_MASTER_MUX_SETTING;
/* Configure pad 0 for data in */
config_spi_master.pinmux_pad0 = CONF_MASTER_PINMUX_PAD0;
/* Configure pad 1 as unused */
config_spi_master.pinmux_pad1 = CONF_MASTER_PINMUX_PAD1;
/* Configure pad 2 for data out */
config_spi_master.pinmux_pad2 = CONF_MASTER_PINMUX_PAD2;
/* Configure pad 3 for SCK */
config_spi_master.pinmux_pad3 = CONF_MASTER_PINMUX_PAD3;
spi_init(&spi_master_instance, CONF_MASTER_SPI_MODULE, &config_spi_master);
}
static void configure_spi_slave(void)
{
struct spi_config config_spi_slave;
/* Configure, initialize and enable SERCOM SPI module */
spi_get_config_defaults(&config_spi_slave);
config_spi_slave.mode = SPI_MODE_SLAVE;
config_spi_slave.mode_specific.slave.preload_enable = true;
config_spi_slave.mode_specific.slave.frame_format = SPI_FRAME_FORMAT_SPI_FRAME;
config_spi_slave.mux_setting = CONF_SLAVE_MUX_SETTING;
/* Configure pad 0 for data in */
config_spi_slave.pinmux_pad0 = CONF_SLAVE_PINMUX_PAD0;
/* Configure pad 1 as unused */
config_spi_slave.pinmux_pad1 = CONF_SLAVE_PINMUX_PAD1;
/* Configure pad 2 for data out */
config_spi_slave.pinmux_pad2 = CONF_SLAVE_PINMUX_PAD2;
/* Configure pad 3 for SCK */
config_spi_slave.pinmux_pad3 = CONF_SLAVE_PINMUX_PAD3;
spi_init(&spi_slave_instance, CONF_SLAVE_SPI_MODULE, &config_spi_slave);
}

Add to user application initialization (typically the start of main()):

Workflow

  1. Create a module software instance structure for the SPI module to store the SPI driver state while it is in use.
    Note
    This should never go out of scope as long as the module is in use. In most cases, this should be global.
  2. Create a module software instance structure for DMA resource to store the DMA resource state while it is in use.
    Note
    This should never go out of scope as long as the module is in use. In most cases, this should be global.
  3. Create transfer done flag to indication DMA transfer done
    static volatile bool transfer_tx_is_done = false;
    static volatile bool transfer_rx_is_done = false;
  4. Define the buffer length for tx/rx
    #define BUF_LENGTH 20
  5. Create buffer to store the data to be transferred
    static const uint8_t buffer_tx[BUF_LENGTH] = {
    0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A,
    0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14,
    };
    static uint8_t buffer_rx[BUF_LENGTH];
  6. Create SPI module configuration struct, which can be filled out to adjust the configuration of a physical SPI peripheral.
    struct spi_config config_spi_master;
    struct spi_config config_spi_slave;
  7. Initialize the SPI configuration struct with the module's default values.
    spi_get_config_defaults(&config_spi_master);
    spi_get_config_defaults(&config_spi_slave);
    Note
    This should always be performed before using the configuration struct to ensure that all values are initialized to known default settings.
  8. Alter the SPI settings to configure the physical pinout, baud rate and other relevant parameters.
    config_spi_master.mux_setting = CONF_MASTER_MUX_SETTING;
    config_spi_slave.mux_setting = CONF_SLAVE_MUX_SETTING;
  9. Configure the SPI module with the desired settings, retrying while the driver is busy until the configuration is stressfully set.
    spi_init(&spi_master_instance, CONF_MASTER_SPI_MODULE, &config_spi_master);
    spi_init(&spi_slave_instance, CONF_SLAVE_SPI_MODULE, &config_spi_slave);
  10. Enable the SPI module.
  11. Create DMA resource configuration structure, which can be filled out to adjust the configuration of a single DMA transfer.
    struct dma_resource_config tx_config;
    struct dma_resource_config rx_config;
  12. Initialize the DMA resource configuration struct with the module's default values.
    Note
    This should always be performed before using the configuration struct to ensure that all values are initialized to known default settings.
  13. Set extra configurations for the DMA resource. It is using peripheral trigger, SERCOM Tx empty and RX complete trigger causes a beat transfer in this example.
    tx_config.peripheral_trigger = CONF_PERIPHERAL_TRIGGER_TX;
    tx_config.trigger_action = DMA_TRIGGER_ACTON_BEAT;
    rx_config.peripheral_trigger = CONF_PERIPHERAL_TRIGGER_RX;
    rx_config.trigger_action = DMA_TRIGGER_ACTON_BEAT;
  14. Allocate a DMA resource with the configurations.
    dma_allocate(tx_resource, &tx_config);
    dma_allocate(rx_resource, &rx_config);
  15. Create a DMA transfer descriptor configuration structure, which can be filled out to adjust the configuration of a single DMA transfer.
    struct dma_descriptor_config tx_descriptor_config;
    struct dma_descriptor_config rx_descriptor_config;
  16. Initialize the DMA transfer descriptor configuration struct with the module's default values.
    dma_descriptor_get_config_defaults(&tx_descriptor_config);
    dma_descriptor_get_config_defaults(&rx_descriptor_config);
    Note
    This should always be performed before using the configuration struct to ensure that all values are initialized to known default settings.
  17. Set the specific parameters for a DMA transfer with transfer size, source address, destination address.
    tx_descriptor_config.beat_size = DMA_BEAT_SIZE_BYTE;
    tx_descriptor_config.dst_increment_enable = false;
    tx_descriptor_config.block_transfer_count = sizeof(buffer_tx)/sizeof(uint8_t);
    tx_descriptor_config.source_address = (uint32_t)buffer_tx + sizeof(buffer_tx);
    tx_descriptor_config.destination_address =
    (uint32_t)(&spi_master_instance.hw->SPI.DATA.reg);
    rx_descriptor_config.beat_size = DMA_BEAT_SIZE_BYTE;
    rx_descriptor_config.src_increment_enable = false;
    rx_descriptor_config.block_transfer_count = sizeof(buffer_rx)/sizeof(uint8_t);
    rx_descriptor_config.source_address =
    (uint32_t)(&spi_slave_instance.hw->SPI.DATA.reg);
    rx_descriptor_config.destination_address =
    (uint32_t)buffer_rx + sizeof(buffer_rx);
  18. Create the DMA transfer descriptor.
    dma_descriptor_create(tx_descriptor, &tx_descriptor_config);
    dma_descriptor_create(rx_descriptor, &rx_descriptor_config);

Use Case

Code

Copy-paste the following code to your user application:

while (!transfer_rx_is_done) {
/* Wait for transfer done */
}
while (true) {
}

Workflow

  1. Select the slave.
  2. Start the transfer job.
  3. Wait for transfer done.
    while (!transfer_rx_is_done) {
    /* Wait for transfer done */
    }
  4. Deselect the slave.
  5. enter endless loop
    while (true) {
    }