Microchip® Advanced Software Framework

Quick Start Guide for Using DMA with TCC

The supported board list:

- SAM D21/R21/L21/L22/DA1/C21/HA1G16A Xplained Pro

In this use case, the TCC will be used to generate a PWM signal. Here the pulse width varies through the following values with the help of DMA transfer: one quarter of the period, half of the period, and three quarters of the period. The PWM output can be used to drive a LED. The waveform can also be viewed using an oscilloscope. The output signal is also fed back to another TCC channel by event system, the event stamps are captured and transferred to a buffer by DMA. SAMHA1G16A Xpro LED is PA00 which isn't connected out, use PA04 instead, so we can't see LED blink but only see the waveform from oscilloscope.

The PWM output is set up as follows:

Board Pin Connect to
SAM D21 Xpro PB30 LED0
SAM R21 Xpro PA19 LED0
SAM L21 Xpro PB10 LED0
SAM L22 Xpro PC27 LED0
SAM DA1 Xpro PB30 LED0
SAM C21 Xpro PA15 LED0
SAM HA1G16A Xpro PA04 NULL

The TCC module will be setup as follows:

  • GCLK generator 0 (GCLK main) clock source
  • Use double buffering write when set top, compare, or pattern through API
  • No dithering on the counter or compare
  • Prescaler is set to 1024
  • Single Slope PWM wave generation
  • GCLK reload action
  • Don't run in standby
  • No fault or waveform extensions
  • No inversion of waveform output
  • No capture enabled
  • Count upward
  • Don't perform one-shot operations
  • Counter starts on 0
  • Counter top set to 0x1000
  • Channel 0 (on SAM D21 Xpro) or 3 (on SAM R21 Xpro) is set to compare and match value 0x1000*3/4 and generate event
  • Channel 1 is set to capture on input event

The event resource of EVSYS module will be setup as follows:

  • TCC match capture channel 0 (on SAM D21 Xpro) or 3 (on SAM R21 Xpro) is selected as event generator
  • Event generation is synchronous, with rising edge detected
  • TCC match capture channel 1 is the event user

The DMA resource of DMAC module will be setup as follows:

  • Two DMA resources are used
  • Both DMA resources use peripheral trigger
  • Both DMA resources perform beat transfer on trigger
  • Both DMA resources use beat size of 16 bits
  • Both DMA resources are configured to transfer three beats and then repeat again in same buffer
  • On DMA resource which controls the compare value
    • TCC0 overflow triggers DMA transfer
    • The source address increment is enabled
    • The destination address is fixed to TCC channel 0 Compare/Capture register
  • On DMA resource which reads the captured value
    • TCC0 capture on channel 1 triggers DMA transfer
    • The source address is fixed to TCC channel 1 Compare/Capture register
    • The destination address increment is enabled
    • The captured value is transferred to an array in SRAM

Quick Start

Prerequisites

There are no prerequisites for this use case.

Code

Add to the main application source file, before any functions, according to the kit used:

  • SAM D21 Xplained Pro
    #define CONF_PWM_MODULE LED_0_PWM4CTRL_MODULE
    #define CONF_PWM_CHANNEL LED_0_PWM4CTRL_CHANNEL
    #define CONF_PWM_OUTPUT LED_0_PWM4CTRL_OUTPUT
    #define CONF_PWM_OUT_PIN LED_0_PWM4CTRL_PIN
    #define CONF_PWM_OUT_MUX LED_0_PWM4CTRL_MUX
    #define CONF_TCC_CAPTURE_CHANNEL 1
    #define CONF_TCC_EVENT_GENERATOR EVSYS_ID_GEN_TCC0_MCX_0
    #define CONF_TCC_EVENT_USER EVSYS_ID_USER_TCC0_MC_1
    #define CONF_COMPARE_TRIGGER TCC0_DMAC_ID_OVF
    #define CONF_CAPTURE_TRIGGER TCC0_DMAC_ID_MC_1
  • SAM R21 Xplained Pro
    #define CONF_PWM_MODULE LED_0_PWM4CTRL_MODULE
    #define CONF_PWM_CHANNEL LED_0_PWM4CTRL_CHANNEL
    #define CONF_PWM_OUTPUT LED_0_PWM4CTRL_OUTPUT
    #define CONF_PWM_OUT_PIN LED_0_PWM4CTRL_PIN
    #define CONF_PWM_OUT_MUX LED_0_PWM4CTRL_MUX
    #define CONF_TCC_CAPTURE_CHANNEL 1
    #define CONF_TCC_EVENT_GENERATOR EVSYS_ID_GEN_TCC0_MCX_3
    #define CONF_TCC_EVENT_USER EVSYS_ID_USER_TCC0_MC_1
    #define CONF_COMPARE_TRIGGER TCC0_DMAC_ID_OVF
    #define CONF_CAPTURE_TRIGGER TCC0_DMAC_ID_MC_1
  • SAM L21 Xplained Pro
    #define CONF_PWM_MODULE LED_0_PWM4CTRL_MODULE
    #define CONF_PWM_CHANNEL LED_0_PWM4CTRL_CHANNEL
    #define CONF_PWM_OUTPUT LED_0_PWM4CTRL_OUTPUT
    #define CONF_PWM_OUT_PIN LED_0_PWM4CTRL_PIN
    #define CONF_PWM_OUT_MUX LED_0_PWM4CTRL_MUX
    #define CONF_TCC_CAPTURE_CHANNEL 1
    #define CONF_TCC_EVENT_GENERATOR EVSYS_ID_GEN_TCC0_MCX_0
    #define CONF_TCC_EVENT_USER EVSYS_ID_USER_TCC0_MC_1
    #define CONF_COMPARE_TRIGGER TCC0_DMAC_ID_OVF
  • SAM L22 Xplained Pro
    #define CONF_PWM_MODULE LED_0_PWM4CTRL_MODULE
    #define CONF_PWM_CHANNEL LED_0_PWM4CTRL_CHANNEL
    #define CONF_PWM_OUTPUT LED_0_PWM4CTRL_OUTPUT
    #define CONF_PWM_OUT_PIN LED_0_PWM4CTRL_PIN
    #define CONF_PWM_OUT_MUX LED_0_PWM4CTRL_MUX
    #define CONF_TCC_CAPTURE_CHANNEL 1
    #define CONF_TCC_EVENT_GENERATOR EVSYS_ID_GEN_TCC0_MCX_0
    #define CONF_TCC_EVENT_USER EVSYS_ID_USER_TCC0_MC_1
    #define CONF_COMPARE_TRIGGER TCC0_DMAC_ID_OVF
  • SAM DA1 Xplained Pro
    #define CONF_PWM_MODULE LED_0_PWM4CTRL_MODULE
    #define CONF_PWM_CHANNEL LED_0_PWM4CTRL_CHANNEL
    #define CONF_PWM_OUTPUT LED_0_PWM4CTRL_OUTPUT
    #define CONF_PWM_OUT_PIN LED_0_PWM4CTRL_PIN
    #define CONF_PWM_OUT_MUX LED_0_PWM4CTRL_MUX
    #define CONF_TCC_CAPTURE_CHANNEL 1
    #define CONF_TCC_EVENT_GENERATOR EVSYS_ID_GEN_TCC0_MCX_0
    #define CONF_TCC_EVENT_USER EVSYS_ID_USER_TCC0_MC_1
    #define CONF_COMPARE_TRIGGER TCC0_DMAC_ID_OVF
    #define CONF_CAPTURE_TRIGGER TCC0_DMAC_ID_MC_1
  • SAM C21 Xplained Pro
    #define CONF_PWM_MODULE LED_0_PWM4CTRL_MODULE
    #define CONF_PWM_CHANNEL LED_0_PWM4CTRL_CHANNEL
    #define CONF_PWM_OUTPUT LED_0_PWM4CTRL_OUTPUT
    #define CONF_PWM_OUT_PIN LED_0_PWM4CTRL_PIN
    #define CONF_PWM_OUT_MUX LED_0_PWM4CTRL_MUX
    #define CONF_TCC_CAPTURE_CHANNEL 1
    #define CONF_TCC_EVENT_GENERATOR EVSYS_ID_GEN_TCC0_MCX_0
    #define CONF_TCC_EVENT_USER EVSYS_ID_USER_TCC0_MC_1
    #define CONF_COMPARE_TRIGGER TCC0_DMAC_ID_OVF
    Add to the main application source file, outside of any functions:
    struct tcc_module tcc_instance;
    uint16_t capture_values[3] = {0, 0, 0};
    struct dma_resource capture_dma_resource;
    COMPILER_ALIGNED(16) DmacDescriptor capture_dma_descriptor SECTION_DMAC_DESCRIPTOR;
    struct events_resource capture_event_resource;
    uint16_t compare_values[3] = {
    (0x1000 / 4), (0x1000 * 2 / 4), (0x1000 * 3 / 4)
    };
    struct dma_resource compare_dma_resource;
    COMPILER_ALIGNED(16) DmacDescriptor compare_dma_descriptor SECTION_DMAC_DESCRIPTOR;
    Copy-paste the following setup code to your user application:
    static void config_event_for_capture(void)
    {
    struct events_config config;
    events_get_config_defaults(&config);
    config.generator = CONF_TCC_EVENT_GENERATOR;
    config.edge_detect = EVENTS_EDGE_DETECT_RISING;
    config.path = EVENTS_PATH_SYNCHRONOUS;
    config.clock_source = GCLK_GENERATOR_0;
    events_allocate(&capture_event_resource, &config);
    events_attach_user(&capture_event_resource, CONF_TCC_EVENT_USER);
    }
    static void config_dma_for_capture(void)
    {
    struct dma_resource_config config;
    dma_get_config_defaults(&config);
    config.trigger_action = DMA_TRIGGER_ACTION_BEAT;
    config.peripheral_trigger = CONF_CAPTURE_TRIGGER;
    dma_allocate(&capture_dma_resource, &config);
    struct dma_descriptor_config descriptor_config;
    dma_descriptor_get_config_defaults(&descriptor_config);
    descriptor_config.block_transfer_count = 3;
    descriptor_config.beat_size = DMA_BEAT_SIZE_HWORD;
    descriptor_config.step_selection = DMA_STEPSEL_SRC;
    descriptor_config.src_increment_enable = false;
    descriptor_config.source_address =
    (uint32_t)&CONF_PWM_MODULE->CC[CONF_TCC_CAPTURE_CHANNEL];
    descriptor_config.destination_address =
    (uint32_t)capture_values + sizeof(capture_values);
    dma_descriptor_create(&capture_dma_descriptor, &descriptor_config);
    dma_add_descriptor(&capture_dma_resource, &capture_dma_descriptor);
    dma_add_descriptor(&capture_dma_resource, &capture_dma_descriptor);
    dma_start_transfer_job(&capture_dma_resource);
    }
    static void config_dma_for_wave(void)
    {
    struct dma_resource_config config;
    dma_get_config_defaults(&config);
    config.trigger_action = DMA_TRIGGER_ACTION_BEAT;
    config.peripheral_trigger = CONF_COMPARE_TRIGGER;
    dma_allocate(&compare_dma_resource, &config);
    struct dma_descriptor_config descriptor_config;
    dma_descriptor_get_config_defaults(&descriptor_config);
    descriptor_config.block_transfer_count = 3;
    descriptor_config.beat_size = DMA_BEAT_SIZE_HWORD;
    descriptor_config.dst_increment_enable = false;
    descriptor_config.source_address =
    (uint32_t)compare_values + sizeof(compare_values);
    #if (SAMR21) || (SAMD21) || (SAMDA1) || (SAMHA1)
    descriptor_config.destination_address =
    (uint32_t)&CONF_PWM_MODULE->CC[CONF_PWM_CHANNEL];
    #else
    descriptor_config.destination_address =
    (uint32_t)&CONF_PWM_MODULE->CCBUF[CONF_PWM_CHANNEL];
    #endif
    dma_descriptor_create(&compare_dma_descriptor, &descriptor_config);
    dma_add_descriptor(&compare_dma_resource, &compare_dma_descriptor);
    dma_add_descriptor(&compare_dma_resource, &compare_dma_descriptor);
    dma_start_transfer_job(&compare_dma_resource);
    }
    static void configure_tcc(void)
    {
    struct tcc_config config_tcc;
    tcc_get_config_defaults(&config_tcc, CONF_PWM_MODULE);
    config_tcc.counter.clock_prescaler = TCC_CLOCK_PRESCALER_DIV1024;
    config_tcc.counter.period = 0x1000;
    config_tcc.compare.channel_function[CONF_TCC_CAPTURE_CHANNEL] =
    config_tcc.compare.wave_generation = TCC_WAVE_GENERATION_SINGLE_SLOPE_PWM;
    config_tcc.compare.wave_polarity[CONF_PWM_CHANNEL] = TCC_WAVE_POLARITY_0;
    config_tcc.compare.match[CONF_PWM_CHANNEL] = compare_values[2];
    config_tcc.pins.enable_wave_out_pin[CONF_PWM_OUTPUT] = true;
    config_tcc.pins.wave_out_pin[CONF_PWM_OUTPUT] = CONF_PWM_OUT_PIN;
    config_tcc.pins.wave_out_pin_mux[CONF_PWM_OUTPUT] = CONF_PWM_OUT_MUX;
    tcc_init(&tcc_instance, CONF_PWM_MODULE, &config_tcc);
    struct tcc_events events_tcc = {
    .input_config[1].modify_action = false,
    .output_config.modify_generation_selection = false,
    .generate_event_on_channel[CONF_PWM_CHANNEL] = true,
    .on_event_perform_channel_action[CONF_TCC_CAPTURE_CHANNEL] = true
    };
    tcc_enable_events(&tcc_instance, &events_tcc);
    config_event_for_capture();
    config_dma_for_capture();
    config_dma_for_wave();
    tcc_enable(&tcc_instance);
    }
    Add to user application initialization (typically the start of main()):
    configure_tcc();

Workflow

Configure the TCC

  1. Create a module software instance structure for the TCC module to store the TCC driver state while it is in use.
    struct tcc_module tcc_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. Create a TCC module configuration struct, which can be filled out to adjust the configuration of a physical TCC peripheral.
    struct tcc_config config_tcc;
  3. Initialize the TCC configuration struct with the module's default values.
    tcc_get_config_defaults(&config_tcc, CONF_PWM_MODULE);
    Note
    This should always be performed before using the configuration struct to ensure that all values are initialized to known default settings.
  4. Alter the TCC settings to configure the counter width, wave generation mode, and the compare channel 0 value.
    config_tcc.counter.clock_prescaler = TCC_CLOCK_PRESCALER_DIV1024;
    config_tcc.counter.period = 0x1000;
    config_tcc.compare.channel_function[CONF_TCC_CAPTURE_CHANNEL] =
    config_tcc.compare.wave_generation = TCC_WAVE_GENERATION_SINGLE_SLOPE_PWM;
    config_tcc.compare.wave_polarity[CONF_PWM_CHANNEL] = TCC_WAVE_POLARITY_0;
    config_tcc.compare.match[CONF_PWM_CHANNEL] = compare_values[2];
  5. Alter the TCC settings to configure the PWM output on a physical device pin.
    config_tcc.pins.enable_wave_out_pin[CONF_PWM_OUTPUT] = true;
    config_tcc.pins.wave_out_pin[CONF_PWM_OUTPUT] = CONF_PWM_OUT_PIN;
    config_tcc.pins.wave_out_pin_mux[CONF_PWM_OUTPUT] = CONF_PWM_OUT_MUX;
  6. Configure the TCC module with the desired settings.
    tcc_init(&tcc_instance, CONF_PWM_MODULE, &config_tcc);
  7. Configure and enable the desired events for the TCC module.
    struct tcc_events events_tcc = {
    .input_config[1].modify_action = false,
    .output_config.modify_generation_selection = false,
    .generate_event_on_channel[CONF_PWM_CHANNEL] = true,
    .on_event_perform_channel_action[CONF_TCC_CAPTURE_CHANNEL] = true
    };
    tcc_enable_events(&tcc_instance, &events_tcc);

Configure the Event System

Configure the EVSYS module to wire channel 0 event to channel 1.

  1. Create an event resource instance.
    struct events_resource capture_event_resource;
    Note
    This should never go out of scope as long as the resource is in use. In most cases, this should be global.
  2. Create an event resource configuration struct.
    struct events_config config;
  3. Initialize the event resource configuration struct with default values.
    events_get_config_defaults(&config);
    Note
    This should always be performed before using the configuration struct to ensure that all values are initialized to known default settings.
  4. Adjust the event resource configuration to desired values.
    config.generator = CONF_TCC_EVENT_GENERATOR;
    config.edge_detect = EVENTS_EDGE_DETECT_RISING;
    config.path = EVENTS_PATH_SYNCHRONOUS;
    config.clock_source = GCLK_GENERATOR_0;
  5. Allocate and configure the resource using the configuration structure.
    events_allocate(&capture_event_resource, &config);
  6. Attach a user to the resource.
    events_attach_user(&capture_event_resource, CONF_TCC_EVENT_USER);

Configure the DMA for Capture TCC Channel 1

Configure the DMAC module to obtain captured value from TCC channel 1.

  1. Create a DMA resource instance.
    struct dma_resource capture_dma_resource;
    Note
    This should never go out of scope as long as the resource is in use. In most cases, this should be global.
  2. Create a DMA resource configuration struct.
    struct dma_resource_config config;
  3. Initialize the DMA resource configuration struct with default values.
    dma_get_config_defaults(&config);
    Note
    This should always be performed before using the configuration struct to ensure that all values are initialized to known default settings.
  4. Adjust the DMA resource configurations.
    config.trigger_action = DMA_TRIGGER_ACTION_BEAT;
    config.peripheral_trigger = CONF_CAPTURE_TRIGGER;
  5. Allocate a DMA resource with the configurations.
    dma_allocate(&capture_dma_resource, &config);
  6. Prepare DMA transfer descriptor.
    1. Create a DMA transfer descriptor.
      COMPILER_ALIGNED(16) DmacDescriptor capture_dma_descriptor SECTION_DMAC_DESCRIPTOR;
      Note
      When multiple descriptors are linked, the linked item should never go out of scope before it is loaded (to DMA Write-Back memory section). In most cases, if more than one descriptors are used, they should be global except the very first one.
    2. Create a DMA transfer descriptor struct.
    3. 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;
    4. Initialize the DMA transfer descriptor configuration struct with default values.
      dma_descriptor_get_config_defaults(&descriptor_config);
      Note
      This should always be performed before using the configuration struct to ensure that all values are initialized to known default settings.
    5. Adjust the DMA transfer descriptor configurations.
      descriptor_config.block_transfer_count = 3;
      descriptor_config.beat_size = DMA_BEAT_SIZE_HWORD;
      descriptor_config.step_selection = DMA_STEPSEL_SRC;
      descriptor_config.src_increment_enable = false;
      descriptor_config.source_address =
      (uint32_t)&CONF_PWM_MODULE->CC[CONF_TCC_CAPTURE_CHANNEL];
      descriptor_config.destination_address =
      (uint32_t)capture_values + sizeof(capture_values);
    6. Create the DMA transfer descriptor with the given configuration.
      dma_descriptor_create(&capture_dma_descriptor, &descriptor_config);
  7. Start DMA transfer job with prepared descriptor.
    1. Add the DMA transfer descriptor to the allocated DMA resource.
      dma_add_descriptor(&capture_dma_resource, &capture_dma_descriptor);
      dma_add_descriptor(&capture_dma_resource, &capture_dma_descriptor);
      Note
      When adding multiple descriptors, the last one added is linked at the end of the descriptor queue. If ringed list is needed, just add the first descriptor again to build the circle.
    2. Start the DMA transfer job with the allocated DMA resource and transfer descriptor.
      dma_start_transfer_job(&capture_dma_resource);

Configure the DMA for Compare TCC Channel 0

Configure the DMAC module to update TCC channel 0 compare value. The flow is similar to last DMA configure step for capture.

  1. Allocate and configure the DMA resource.
    struct dma_resource compare_dma_resource;
    struct dma_resource_config config;
    dma_get_config_defaults(&config);
    config.trigger_action = DMA_TRIGGER_ACTION_BEAT;
    config.peripheral_trigger = CONF_COMPARE_TRIGGER;
    dma_allocate(&compare_dma_resource, &config);
  2. Prepare DMA transfer descriptor.
    COMPILER_ALIGNED(16) DmacDescriptor compare_dma_descriptor SECTION_DMAC_DESCRIPTOR;
    struct dma_descriptor_config descriptor_config;
    dma_descriptor_get_config_defaults(&descriptor_config);
    descriptor_config.block_transfer_count = 3;
    descriptor_config.beat_size = DMA_BEAT_SIZE_HWORD;
    descriptor_config.dst_increment_enable = false;
    descriptor_config.source_address =
    (uint32_t)compare_values + sizeof(compare_values);
    #if (SAMR21) || (SAMD21) || (SAMDA1) || (SAMHA1)
    descriptor_config.destination_address =
    (uint32_t)&CONF_PWM_MODULE->CC[CONF_PWM_CHANNEL];
    #else
    descriptor_config.destination_address =
    (uint32_t)&CONF_PWM_MODULE->CCBUF[CONF_PWM_CHANNEL];
    #endif
    dma_descriptor_create(&compare_dma_descriptor, &descriptor_config);
  3. Start DMA transfer job with prepared descriptor.
    dma_add_descriptor(&compare_dma_resource, &compare_dma_descriptor);
    dma_add_descriptor(&compare_dma_resource, &compare_dma_descriptor);
    dma_start_transfer_job(&compare_dma_resource);
  4. Enable the TCC module to start the timer and begin PWM signal generation.
    tcc_enable(&tcc_instance);

Use Case

Code

Copy-paste the following code to your user application:

while (true) {
/* Infinite loop */
}

Workflow

  1. Enter an infinite loop while the PWM wave is generated via the TCC module.
    while (true) {
    /* Infinite loop */
    }