Microchip® Advanced Software Framework

Quick Start Guide for Using DMA with ADC/DAC

The supported board list:

  • SAM D21 Xplained Pro
  • SAM D11 Xplained Pro
  • SAM L21 Xplained Pro
  • SAM DA1 Xplained Pro
  • SAM C21 Xplained Pro
  • SAM HA1G16A Xplained Pro

This quick start will convert an analog input signal from AIN4 and output the converted value to DAC on PA2. The data between ADC and DAC with be transferred through DMA instead of a CPU intervene.

The ADC will be configured with the following settings:

  • 1/2 VDDANA
  • Div 16 clock prescaler
  • 10-bit resolution
  • Window monitor disabled
  • No gain
  • Positive input on ADC AIN4
  • Averaging disabled
  • Oversampling disabled
  • Right adjust data
  • Single-ended mode
  • Free running enable
  • All events (input and generation) disabled
  • Sleep operation disabled
  • No reference compensation
  • No gain/offset correction
  • No added sampling time
  • Pin scan mode disabled

The DAC will be configured with the following settings:

  • Analog VCC as reference
  • Internal output disabled
  • Drive the DAC output to PA2
  • Right adjust data
  • The output buffer is disabled when the chip enters STANDBY sleep mode

The DMA will be configured with the following settings:

  • Move data from peripheral to peripheral
  • Using ADC result ready trigger
  • Using DMA priority level 0
  • Beat transfer will be triggered on each trigger
  • Loopback descriptor for DAC conversion

Setup

Prerequisites

There are no special setup requirements for this use-case.

Code

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

struct dac_module dac_instance;
struct adc_module adc_instance;
struct dma_resource example_resource;
DmacDescriptor example_descriptor SECTION_DMAC_DESCRIPTOR;

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

void configure_adc(void)
{
struct adc_config config_adc;
#if !(SAML21)
#if !(SAMC21)
config_adc.gain_factor = ADC_GAIN_FACTOR_DIV2;
#endif
config_adc.resolution = ADC_RESOLUTION_10BIT;
#endif
config_adc.clock_prescaler = ADC_CLOCK_PRESCALER_DIV16;
config_adc.reference = ADC_REFERENCE_INTVCC1;
config_adc.positive_input = ADC_POSITIVE_INPUT_PIN4;
config_adc.freerunning = true;
config_adc.left_adjust = false;
#if (SAMC21)
adc_init(&adc_instance, ADC1, &config_adc);
#else
adc_init(&adc_instance, ADC, &config_adc);
#endif
adc_enable(&adc_instance);
}
void configure_dac(void)
{
struct dac_config config_dac;
#if (SAML21)
config_dac.reference = DAC_REFERENCE_INTREF;
#else
config_dac.reference = DAC_REFERENCE_AVCC;
#endif
dac_init(&dac_instance, DAC, &config_dac);
}
void configure_dac_channel(void)
{
struct dac_chan_config config_dac_chan;
dac_chan_get_config_defaults(&config_dac_chan);
dac_chan_set_config(&dac_instance, DAC_CHANNEL_0, &config_dac_chan);
dac_chan_enable(&dac_instance, DAC_CHANNEL_0);
}
void configure_dma_resource(struct dma_resource *resource)
{
struct dma_resource_config config;
#if (SAMC21)
config.peripheral_trigger = ADC1_DMAC_ID_RESRDY;
#else
config.peripheral_trigger = ADC_DMAC_ID_RESRDY;
#endif
config.trigger_action = DMA_TRIGGER_ACTION_BEAT;
dma_allocate(resource, &config);
}
void setup_transfer_descriptor(DmacDescriptor *descriptor)
{
struct dma_descriptor_config descriptor_config;
descriptor_config.beat_size = DMA_BEAT_SIZE_HWORD;
descriptor_config.dst_increment_enable = false;
descriptor_config.src_increment_enable = false;
descriptor_config.block_transfer_count = 1000;
descriptor_config.source_address = (uint32_t)(&adc_instance.hw->RESULT.reg);
#if (SAML21)
descriptor_config.destination_address = (uint32_t)(&dac_instance.hw->DATA[DAC_CHANNEL_0].reg);
#else
descriptor_config.destination_address = (uint32_t)(&dac_instance.hw->DATA.reg);
#endif
descriptor_config.next_descriptor_address = (uint32_t)descriptor;
dma_descriptor_create(descriptor, &descriptor_config);
}

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

configure_adc();
configure_dac();
configure_dac_channel();
dac_enable(&dac_instance);
configure_dma_resource(&example_resource);
setup_transfer_descriptor(&example_descriptor);
dma_add_descriptor(&example_resource, &example_descriptor);

Workflow

Configure the ADC

  1. Create a module software instance structure for the ADC module to store the ADC driver state while it is in use.
    struct adc_module adc_instance;
    Note
    This should never go out of scope as long as the module is in use. In most cases, this should be global.
  2. Configure the ADC module.
    1. Create an ADC module configuration struct, which can be filled out to adjust the configuration of a physical ADC peripheral.
      struct adc_config config_adc;
    2. Initialize the ADC 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.
    3. Set extra configurations.
      #if !(SAML21)
      #if !(SAMC21)
      config_adc.gain_factor = ADC_GAIN_FACTOR_DIV2;
      #endif
      config_adc.resolution = ADC_RESOLUTION_10BIT;
      #endif
      config_adc.clock_prescaler = ADC_CLOCK_PRESCALER_DIV16;
      config_adc.reference = ADC_REFERENCE_INTVCC1;
      config_adc.positive_input = ADC_POSITIVE_INPUT_PIN4;
      config_adc.freerunning = true;
      config_adc.left_adjust = false;
    4. Set ADC configurations.
      #if (SAMC21)
      adc_init(&adc_instance, ADC1, &config_adc);
      #else
      adc_init(&adc_instance, ADC, &config_adc);
      #endif
    5. Enable the ADC module so that conversions can be made.
      adc_enable(&adc_instance);

Configure the DAC

  1. Create a module software instance structure for the DAC module to store the DAC driver state while it is in use.
    struct dac_module dac_instance;
    Note
    This should never go out of scope as long as the module is in use. In most cases, this should be global.
  2. Configure the DAC module.
    1. Create a DAC module configuration struct, which can be filled out to adjust the configuration of a physical DAC peripheral.
      struct dac_config config_dac;
    2. Initialize the DAC 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.
    3. Set extra DAC configurations.
      #if (SAML21)
      config_dac.reference = DAC_REFERENCE_INTREF;
      #else
      config_dac.reference = DAC_REFERENCE_AVCC;
      #endif
    4. Set DAC configurations to DAC instance.
      dac_init(&dac_instance, DAC, &config_dac);
    5. Enable the DAC module so that channels can be configured.
      dac_enable(&dac_instance);
  3. Configure the DAC channel.
    1. Create a DAC channel configuration struct, which can be filled out to adjust the configuration of a physical DAC output channel.
      struct dac_chan_config config_dac_chan;
    2. Initialize the DAC channel configuration struct with the module's default values.
      dac_chan_get_config_defaults(&config_dac_chan);
      Note
      This should always be performed before using the configuration struct to ensure that all values are initialized to known default settings.
    3. Configure the DAC channel with the desired channel settings.
      dac_chan_set_config(&dac_instance, DAC_CHANNEL_0, &config_dac_chan);
    4. Enable the DAC channel so that it can output a voltage.
      dac_chan_enable(&dac_instance, DAC_CHANNEL_0);

Configure the DMA

  1. Create a DMA resource configuration structure, which can be filled out to adjust the configuration of a single DMA transfer.
    struct dma_resource_config config;
  2. 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.
  3. Set extra configurations for the DMA resource. ADC_DMAC_ID_RESRDY trigger causes a beat transfer in this example.
    #if (SAMC21)
    config.peripheral_trigger = ADC1_DMAC_ID_RESRDY;
    #else
    config.peripheral_trigger = ADC_DMAC_ID_RESRDY;
    #endif
    config.trigger_action = DMA_TRIGGER_ACTION_BEAT;
  4. Allocate a DMA resource with the configurations.
    dma_allocate(resource, &config);
  5. 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 descriptor_config;
  6. Initialize the DMA transfer descriptor 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.
  7. Set the specific parameters for a DMA transfer with transfer size, source address, and destination address.
    descriptor_config.beat_size = DMA_BEAT_SIZE_HWORD;
    descriptor_config.dst_increment_enable = false;
    descriptor_config.src_increment_enable = false;
    descriptor_config.block_transfer_count = 1000;
    descriptor_config.source_address = (uint32_t)(&adc_instance.hw->RESULT.reg);
    #if (SAML21)
    descriptor_config.destination_address = (uint32_t)(&dac_instance.hw->DATA[DAC_CHANNEL_0].reg);
    #else
    descriptor_config.destination_address = (uint32_t)(&dac_instance.hw->DATA.reg);
    #endif
    descriptor_config.next_descriptor_address = (uint32_t)descriptor;
  8. Create the DMA transfer descriptor.
    dma_descriptor_create(descriptor, &descriptor_config);
  9. Add DMA descriptor to DMA resource.
    dma_add_descriptor(&example_resource, &example_descriptor);

Use Case

Code

Copy-paste the following code to your user application:

adc_start_conversion(&adc_instance);
dma_start_transfer_job(&example_resource);
while (true) {
}

Workflow

  1. Start ADC conversion.
    adc_start_conversion(&adc_instance);
  2. Start the transfer job.
    dma_start_transfer_job(&example_resource);
  3. Enter endless loop.
    while (true) {
    }