This thread has been locked.

If you have a related question, please click the "Ask a related question" button in the top right corner. The newly created question will be automatically linked to this question.

RTOS/CC2640R2F: Using DMA channel (UDMA_CHAN_TIMER0_A) conflicts with BLE Stack

Part Number: CC2640R2F

Tool/software: TI-RTOS

Hi

I have been working on CC2640R2f LP based on simple_peripheral example, and my SDK version is simplelink_cc2640r2_sdk_2_30_00_28.

What I am trying to do is using GPT timer driver to generate pwm signals and trigger the DMA to transfer data while running BLE Stack.Obviously, I have to configure DMA channels for timer by using uDMAChannelControlSet( UDMA0_BASE, UDMA_CHAN_TIMER0_A, but after this I found the peripheral cannot be connected since the advertisement has stopped.

I have tried to change UDMA0_BASE, UDMA_CHAN_TIMER0_A to UDMA_CHAN_UART0_RX and it turned out BLE can work. So I was wondering if there are some conflicts about DMA channel, and if so, is there any chance to avoid this.

wish all help.

Alex

  • Hi Alex,

    It looks like you're not using the PWM and DMA together correctly. Can you take a look at the small example on how to use the DMA and PWM driver together to update period and duty cycles?

    While the particular use case might not be as exciting, I think the four (4) examples contained in this file could be helpful in most situations related to using the uDMA module and its different modes: Basic, Ping-pong and scatter-gather.

    /*
     * Copyright (c) 2015-2017, Texas Instruments Incorporated
     * All rights reserved.
     *
     * Redistribution and use in source and binary forms, with or without
     * modification, are permitted provided that the following conditions
     * are met:
     *
     * *  Redistributions of source code must retain the above copyright
     *    notice, this list of conditions and the following disclaimer.
     *
     * *  Redistributions in binary form must reproduce the above copyright
     *    notice, this list of conditions and the following disclaimer in the
     *    documentation and/or other materials provided with the distribution.
     *
     * *  Neither the name of Texas Instruments Incorporated nor the names of
     *    its contributors may be used to endorse or promote products derived
     *    from this software without specific prior written permission.
     *
     * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
     * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
     * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
     * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
     * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
     * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
     * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
     * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
     * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
     * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
     * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     */
    
    /*
     *  ======== empty.c ========
     */
    
    /* For usleep() */
    #include <unistd.h>
    #include <stdint.h>
    #include <stddef.h>
    
    /* Driver Header files */
    #include <ti/drivers/GPIO.h>
    #include <ti/drivers/PWM.h>
    #include <ti/drivers/timer/GPTimerCC26XX.h>
    #include <ti/drivers/Power.h>
    
    // #include <ti/drivers/UART.h>
    // #include <ti/drivers/Watchdog.h>
    
    #include <ti/drivers/dma/UDMACC26XX.h>
    
    #include <ti/devices/DeviceFamily.h>
    #include DeviceFamily_constructPath(inc/hw_memmap.h)
    #include DeviceFamily_constructPath(inc/hw_gpt.h)
    #include DeviceFamily_constructPath(inc/hw_event.h)
    #include DeviceFamily_constructPath(driverlib/udma.h)
    
    /* Allocate the DMA software 0 table */
    ALLOCATE_CONTROL_TABLE_ENTRY(dmaSoftwareDmaPri, UDMA_CHAN_SW_EVT0);
    ALLOCATE_CONTROL_TABLE_ENTRY(dmaSoftwareDmaAlt, (UDMA_CHAN_SW_EVT0 + UDMA_ALT_SELECT));
    
    /* Allocate the DMA GPTimerA1 tables */
    ALLOCATE_CONTROL_TABLE_ENTRY(dmaTimerDmaPri, (UDMA_CHAN_TIMER1_A + UDMA_PRI_SELECT));
    ALLOCATE_CONTROL_TABLE_ENTRY(dmaTimerDmaAlt, (UDMA_CHAN_TIMER1_A + UDMA_ALT_SELECT));
    
    /* Board Header file */
    #include "Board.h"
    
    /* Shared global variables */
    UDMACC26XX_Handle dmaHandle;
    static uint8_t counter = 0;
    static uint8_t reloadCounter = 0;
    
    /* ================== Used in step (1) ================== */
    uint16_t mySoftwareTable[5] = {1, 2, 3, 4, 5};
    
    /* ================== Used in step (2) and (3) ================== */
    /* Note that no value is larger then the max 16-bit value, this means we don't need to handle the pre-scaler using the DMA */
    uint16_t myPwmDutyInCounts[5] = {
                                        /* For 1000 Hz */
                                        4800,    /* 10% duty */
                                        9600,    /* 20% duty */
                                        14400,   /* 30% duty */
                                        19200,   /* 40% duty */
                                        24000,   /* 50% duty */
                                    };
    
    void timerCallback(GPTimerCC26XX_Handle handle, GPTimerCC26XX_IntMask interruptMask) {
        /* We need to re-load the previous DMA table for the ping-pong to work.
         * As every table takes 5 timer interrupts to complete, we only reset
         * every fifth interrupt we get. */
        counter++;
        reloadCounter++;
        if ((reloadCounter == 4) && (counter < 30)) {
            reloadCounter = 0;
            /* If currently on the "Alternative table", we reload the primary */
            uint32_t resetControl = UDMA_MODE_PINGPONG |                       // Basic Ping-Pong
                                    UDMA_SIZE_16 |                             // 16-bit transfers
                                    UDMA_SRC_INC_16 |                          // Increment source address as 16-bit
                                    UDMA_DST_INC_NONE |                        // Do not increment destination address
                                    UDMA_ARB_1 |                               // Arbitrate system bus for 1 transfer at a time
                                    UDMACC26XX_SET_TRANSFER_SIZE(5);           // Setup number of transfers (in frames of 16-bit)
    
            if (HWREG(UDMA0_BASE + UDMA_O_SETCHNLPRIALT) & (1 << UDMA_CHAN_TIMER1_A)) {
                dmaTimerDmaPri.ui32Control = resetControl;
                dmaTimerDmaPri.pvSrcEndAddr = (void *) (myPwmDutyInCounts + 4);         // Set source end address (mind the pointer arithmetic)
                dmaTimerDmaPri.pvDstEndAddr = (void *) (GPT0_BASE + GPT_O_TAMATCHR);    // Set destination end address to GPTimerA0 match register
    
            }
            else {
                dmaTimerDmaAlt.ui32Control = resetControl;
                dmaTimerDmaAlt.pvSrcEndAddr = (void *) (myPwmDutyInCounts + 4);         // Set source end address (mind the pointer arithmetic)
                dmaTimerDmaAlt.pvDstEndAddr = (void *) (GPT0_BASE + GPT_O_TAMATCHR);    // Set destination end address to GPTimerA0 match register
            }
        }
    }
    
    /* ================== Used in step (4) ================== */
    uint32_t myPwmPeriodInCountsScatter[25] = {
                                         48000,     /* 1000 Hz */
                                         48000,     /* 1000 Hz */
                                         48000,     /* 1000 Hz */
                                         48000,     /* 1000 Hz */
                                         48000,     /* 1000 Hz */
                                         4800,      /* 10000 Hz */
                                         4800,      /* 10000 Hz */
                                         4800,      /* 10000 Hz */
                                         4800,      /* 10000 Hz */
                                         4800,      /* 10000 Hz */
                                         960,       /* 50000 Hz */
                                         960,       /* 50000 Hz */
                                         960,       /* 50000 Hz */
                                         960,       /* 50000 Hz */
                                         960,       /* 50000 Hz */
                                         600,       /* 80000 Hz */
                                         600,       /* 80000 Hz */
                                         600,       /* 80000 Hz */
                                         600,       /* 80000 Hz */
                                         600,       /* 80000 Hz */
                                         480,       /* 100000 Hz */
                                         480,       /* 100000 Hz */
                                         480,       /* 100000 Hz */
                                         480,       /* 100000 Hz */
                                         480,       /* 100000 Hz */
                                    };
    
    /* Note that no value is larger then the max 16-bit value, this means we don't need to handle the pre-scaler using the DMA */
    uint16_t myPwmDutyInCountsScatter[25] = {
                                        /* For 1000 Hz */
                                        4800,    /* 10% duty */
                                        9600,    /* 20% duty */
                                        14400,   /* 30% duty */
                                        19200,   /* 40% duty */
                                        24000,   /* 50% duty */
                                        /* For 10000 Hz */
                                        480,    /* 10% duty */
                                        960,    /* 20% duty */
                                        1440,   /* 30% duty */
                                        1920,   /* 40% duty */
                                        2400,   /* 50% duty */
                                        /* For 50000 Hz */
                                        96,    /* 10% duty */
                                        192,   /* 20% duty */
                                        288,   /* 30% duty */
                                        384,   /* 40% duty */
                                        480,   /* 50% duty */
                                        /* For 80000 Hz */
                                        60,    /* 10% duty */
                                        120,   /* 20% duty */
                                        180,   /* 30% duty */
                                        240,   /* 40% duty */
                                        300,   /* 50% duty */
                                        /* For 100000 Hz */
                                        48,    /* 10% duty */
                                        96,    /* 20% duty */
                                        144,   /* 30% duty */
                                        192,   /* 40% duty */
                                        240,   /* 50% duty */
                                    };
    
    /* DMA Control tables */
    tDMAControlTable MyTaskList[3];
    tDMAControlTable updatePeriodTask;
    tDMAControlTable updateDutyTask;
    
    void timerCallbackMemoryScatter(GPTimerCC26XX_Handle handle, GPTimerCC26XX_IntMask interruptMask) {
    
        if (counter < 24) {
            while(uDMAGetStatus(UDMA0_BASE) & 0xF0);
            /* Update the period and duty task source pointers to traverse the array */
            MyTaskList[0].pvSrcEndAddr = (void *) (((uint32_t *)MyTaskList[0].pvSrcEndAddr) + 1);
            MyTaskList[1].pvSrcEndAddr = (void *) (((uint16_t *)MyTaskList[1].pvSrcEndAddr) + 1);
    
            /* Once the scatter-gather completes, the channel is disabled. We need to re-arm the transfer again */
            uDMAChannelScatterGatherSet(UDMA0_BASE, UDMA_CHAN_TIMER1_A, 3, MyTaskList, UDMA_MODE_PER_SCATTER_GATHER);
            UDMACC26XX_channelEnable(dmaHandle, 1 << UDMA_CHAN_TIMER1_A);
        }
    
        counter++;
    }
    /*
     *  ======== mainThread ========
     */
    void *mainThread(void *arg0)
    {
        /* Call driver init functions */
        PWM_init();
    
        /* Open the uDMA driver */
        dmaHandle = UDMACC26XX_open();
        while(!dmaHandle){};
    
        /* Initialize the PWM parameters */
        PWM_Params pwmParams;
        PWM_Params_init(&pwmParams);
        pwmParams.idleLevel = PWM_IDLE_LOW;         /* Output low when PWM is not running */
        pwmParams.periodUnits = PWM_PERIOD_COUNTS;  /* Period is in counts */
        pwmParams.periodValue = 1;                  /* 0 MHz period (for now) */
        pwmParams.dutyUnits = PWM_DUTY_COUNTS;      /* Duty is in counts */
        pwmParams.dutyValue = 0;                    /* 0% initial duty cycle */
    
        /* Open PWM driver, Board_PWM0 (note that this typically use GPTimerA0) */
        PWM_Handle pwm = PWM_open(Board_PWM0, &pwmParams);
        while(!pwm){};
    
        /* Open GPTimer driver */
        GPTimerCC26XX_Params params;
        GPTimerCC26XX_Params_init(&params);
        params.width          = GPT_CONFIG_32BIT;
        params.mode           = GPT_MODE_PERIODIC_UP;
        params.debugStallMode = GPTimerCC26XX_DEBUG_STALL_OFF;
        GPTimerCC26XX_Handle hTimer = GPTimerCC26XX_open(Board_GPTIMER1A, &params);
        while(!hTimer){};
    
        /* 1. ================= Software triggered DMA from table to single variable ================= */
    
        /* Dummy variable to write to */
        uint16_t dummy = 0;
    
        /* Setup DMA transfer table */
        dmaSoftwareDmaPri.ui32Control =  UDMA_MODE_BASIC |                                                             // Basic mode
                                         UDMA_SIZE_16 |                                                                // 16-bit transfers
                                         UDMA_SRC_INC_16 |                                                             // Increment source address as 16-bit
                                         UDMA_DST_INC_NONE |                                                           // Do not increment destination address
                                         UDMA_ARB_1 |                                                                  // Arbitrate system bus for 1 transfer at a time
                                         UDMACC26XX_SET_TRANSFER_SIZE(sizeof(mySoftwareTable) / sizeof(uint16_t));     // Setup number of transfers (in frames of 16-bit)
        dmaSoftwareDmaPri.pvSrcEndAddr = (void *)(mySoftwareTable + (sizeof(mySoftwareTable) / sizeof(uint16_t)) - 1); // Set source end address (mind the pointer arithmetic)
        dmaSoftwareDmaPri.pvDstEndAddr = &dummy;                                                                       // Set destination end address
    
        /* Enable DMA software channel 0 */
        UDMACC26XX_channelEnable(dmaHandle, 1 << UDMA_CHAN_SW_EVT0);
    
        /* Trigger SW DMA transfer event */
        uDMAChannelRequest(UDMA0_BASE, UDMA_CHAN_SW_EVT0);
        while(uDMAGetStatus(UDMA0_BASE) & 0xF0);
        /* Dummy is now 1 */
        uDMAChannelRequest(UDMA0_BASE, UDMA_CHAN_SW_EVT0);
        while(uDMAGetStatus(UDMA0_BASE) & 0xF0);
        /* Dummy is now 2 */
        uDMAChannelRequest(UDMA0_BASE, UDMA_CHAN_SW_EVT0);
        while(uDMAGetStatus(UDMA0_BASE) & 0xF0);
        /* Dummy is now 3 */
        uDMAChannelRequest(UDMA0_BASE, UDMA_CHAN_SW_EVT0);
        while(uDMAGetStatus(UDMA0_BASE) & 0xF0);
        /* Dummy is now 4 */
        uDMAChannelRequest(UDMA0_BASE, UDMA_CHAN_SW_EVT0);
        while(uDMAGetStatus(UDMA0_BASE) & 0xF0);
        /* Dummy is now 5 */
    
        /* Disable DMA software channel 0 */
        UDMACC26XX_channelDisable(dmaHandle, 1 << UDMA_CHAN_SW_EVT0);
    
        /* =================  2. Update PWM duty using SW triggered DMA ================= */
    
        /* Setup DMA transfer table */
        dmaSoftwareDmaPri.ui32Control =  UDMA_MODE_BASIC |                      // Basic mode
                                         UDMA_SIZE_16 |                         // 16-bit transfers
                                         UDMA_SRC_INC_16 |                      // Increment source address as 16-bit
                                         UDMA_DST_INC_NONE |                    // Do not increment destination address
                                         UDMA_ARB_1 |                           // Arbitrate system bus for 1 transfer at a time
                                         UDMACC26XX_SET_TRANSFER_SIZE(5);       // Setup number of transfers (in frames of 16-bit)
    
        dmaSoftwareDmaPri.pvSrcEndAddr = (void *) (myPwmDutyInCounts + 4);      // Set source end address (mind the pointer arithmetic)
        dmaSoftwareDmaPri.pvDstEndAddr = (void *) (GPT0_BASE + GPT_O_TAMATCHR); // Set destination end address to GPTimerA0 match register
    
        /* Use 1000 Hz as period */
        PWM_setPeriod(pwm, 48000);
        PWM_start(pwm);
    
        /* Enable DMA software channel 0 */
        UDMACC26XX_channelEnable(dmaHandle, 1 << UDMA_CHAN_SW_EVT0);
    
        /* Trigger SW DMA transfer event */
        uDMAChannelRequest(UDMA0_BASE, UDMA_CHAN_SW_EVT0);
        while(uDMAGetStatus(UDMA0_BASE) & 0xF0);
        /* Duty is now 10% */
        uDMAChannelRequest(UDMA0_BASE, UDMA_CHAN_SW_EVT0);
        while(uDMAGetStatus(UDMA0_BASE) & 0xF0);
        /* Duty is now 20% */
        uDMAChannelRequest(UDMA0_BASE, UDMA_CHAN_SW_EVT0);
        while(uDMAGetStatus(UDMA0_BASE) & 0xF0);
        /* Duty is now 30% */
        uDMAChannelRequest(UDMA0_BASE, UDMA_CHAN_SW_EVT0);
        while(uDMAGetStatus(UDMA0_BASE) & 0xF0);
        /* Duty is now 40% */
        uDMAChannelRequest(UDMA0_BASE, UDMA_CHAN_SW_EVT0);
        while(uDMAGetStatus(UDMA0_BASE) & 0xF0);
        /* Duty is now 50% */
    
        /* Disable DMA software channel 0 */
        UDMACC26XX_channelDisable(dmaHandle, 1 << UDMA_CHAN_SW_EVT0);
    
        /* Disable PWM */
        PWM_stop(pwm);
    
        /* ================= 3. Update duty on Timer triggered DMA (ping-pong loop)  ================= */
    
        /* Setup the trigger timer at 50 ms */
        GPTimerCC26XX_Value loadVal = 48000000 / 20 - 1;
        GPTimerCC26XX_setLoadValue(hTimer, loadVal);
        /* Setup callback */
        GPTimerCC26XX_registerInterrupt(hTimer, timerCallback, GPT_INT_TIMEOUT);
        GPTimerCC26XX_enableInterrupt(hTimer, GPT_INT_TIMEOUT);
    
        /* Setup DMA transfer tables, ping-pong between tables to be able to "loop" around */
        dmaTimerDmaPri.ui32Control = UDMA_MODE_PINGPONG |                       // Basic Ping-Pong
                                     UDMA_SIZE_16 |                             // 16-bit transfers
                                     UDMA_SRC_INC_16 |                          // Increment source address as 16-bit
                                     UDMA_DST_INC_NONE |                        // Do not increment destination address
                                     UDMA_ARB_1 |                               // Arbitrate system bus for 1 transfer at a time
                                     UDMACC26XX_SET_TRANSFER_SIZE(5);           // Setup number of transfers (in frames of 16-bit)
    
        dmaTimerDmaPri.pvSrcEndAddr = (void *) (myPwmDutyInCounts + 4);         // Set source end address (mind the pointer arithmetic)
        dmaTimerDmaPri.pvDstEndAddr = (void *) (GPT0_BASE + GPT_O_TAMATCHR);    // Set destination end address to GPTimerA0 match register
    
        /* Setup the alternative table, shifted to after the primary table completes*/
        dmaTimerDmaAlt.ui32Control = UDMA_MODE_PINGPONG |                       // Basic Ping-Pong
                                     UDMA_SIZE_16 |                             // 16-bit transfers
                                     UDMA_SRC_INC_16 |                          // Increment source address as 16-bit
                                     UDMA_DST_INC_NONE |                        // Do not increment destination address
                                     UDMA_ARB_1 |                               // Arbitrate system bus for 1 transfer at a time
                                     UDMACC26XX_SET_TRANSFER_SIZE(5);           // Setup number of transfers (in frames of 16-bit)
    
        dmaTimerDmaAlt.pvSrcEndAddr = (void *) (myPwmDutyInCounts + 4);         // Set source end address (mind the pointer arithmetic)
        dmaTimerDmaAlt.pvDstEndAddr = (void *) (GPT0_BASE + GPT_O_TAMATCHR);    // Set destination end address
    
        /* Enable GPTimer DMA channel */
        UDMACC26XX_channelEnable(dmaHandle, 1 << UDMA_CHAN_TIMER1_A);
    
        /* Enable event signal */
        HWREG(GPT1_BASE + GPT_O_DMAEV) = GPT_DMAEV_TATODMAEN;
    
        /* Start with 123 counts, easy to identify */
        PWM_setPeriod(pwm, 128);
        PWM_start(pwm);
    
        /* Start the update timer */
        GPTimerCC26XX_start(hTimer);
    
        /* Wait for 30 runs in the interrupt (6 DMA re-loads) */
        while (counter != 30) {};
    
        /* Clean up */
        PWM_stop(pwm);
        GPTimerCC26XX_stop(hTimer);
        UDMACC26XX_channelDisable(dmaHandle, 1 << UDMA_CHAN_TIMER1_A);
    
        /* ================= X. (advance edition) Update PWM duty and period using
                                Timer triggered DMA and memory scatter-gathering (all entries)  =================*/
    
        /* Start with 123 counts, easy to identify */
        PWM_setPeriod(pwm, 123);
        PWM_start(pwm);
    
        /* Setup the trigger timer at 50 ms */
        loadVal = 48000000 / 20 - 1;
        GPTimerCC26XX_setLoadValue(hTimer, loadVal);
        /* Setup callback */
        GPTimerCC26XX_registerInterrupt(hTimer, timerCallbackMemoryScatter, GPT_INT_TIMEOUT);
        GPTimerCC26XX_enableInterrupt(hTimer, GPT_INT_TIMEOUT);
    
        /* Creating the DMA task list */
    
        /*
         * "scatter-gather" mode is more complex to use but can be very powerful.
         * In short it means that we can provide a list of "task" that the DMA should
         * perform on the trigger instead of only performing a single task. This gives
         * a way to update both duty and period on a single DMA trigger.
         *
         * As it works by "copying" the task from the task list into the alternative
         * DMA control table and then runs it, there is no state being kept in the
         * control tables we provide in the task list. This means that if we want to
         * change any values between runs, we need to do so ourself.
         *
         * In this example there is three tasks, one to update period, one to update duty
         * and a third dummy task used as an end of the list. To make this easy,
         * we create two arrays for duty and period with matching values we want to
         * run.
         *
         * We still need to do some "manual" work in the timer callback once
         * the DMA completes. This as we need to update the source pointer of our first
         * two tasks to point to the next duty/period location in arrays. We also use the timer
         * interrupt to re-arm the next transfer as the channel is disabled following completion
         * of the task list.
         */
    
        updateDutyTask.ui32Control = UDMA_MODE_PER_SCATTER_GATHER |             // Peripheral scatter-gather mode
                                     UDMA_SIZE_16 |                             // 16-bit transfers
                                     UDMA_SRC_INC_16 |                          // Increment source address as 16-bit
                                     UDMA_DST_INC_NONE |                        // Do not increment destination address
                                     UDMA_ARB_1 |                               // Arbitrate system bus for 1 transfer at a time
                                     UDMACC26XX_SET_TRANSFER_SIZE(1);           // Setup number of transfers (in frames of 16-bit)
        updateDutyTask.pvSrcEndAddr = (void *) (myPwmDutyInCountsScatter);      // Source is the table containing the duty values
        updateDutyTask.pvDstEndAddr = (void *) (GPT0_BASE + GPT_O_TAMATCHR);    // Destination is GPT Match register
    
        updatePeriodTask.ui32Control = UDMA_MODE_PER_SCATTER_GATHER |           // Peripheral scatter-gather mode
                                       UDMA_SIZE_32 |                           // 32-bit transfers
                                       UDMA_SRC_INC_32 |                        // Increment source address as 32-bit
                                       UDMA_DST_INC_NONE |                      // Do not increment destination address
                                       UDMA_ARB_1 |                             // Arbitrate system bus for 1 transfer at a time
                                       UDMACC26XX_SET_TRANSFER_SIZE(1);         // Setup number of transfers (in frames of 16-bit)
        updatePeriodTask.pvSrcEndAddr = (void *) (myPwmPeriodInCountsScatter);  // Source is the table containing the matching period values
        updatePeriodTask.pvDstEndAddr = (void *) (GPT0_BASE + GPT_O_TAILR);     // Destination is GPT load register
    
        /* Populate DMA task list */
        MyTaskList[0] = updatePeriodTask;
        MyTaskList[1] = updateDutyTask;
    
        /* Use last task as a dummy "end" task */
        MyTaskList[2].ui32Control = UDMA_MODE_STOP |                 // Stop mode (marks end of list)
                                    UDMA_SIZE_16 |                    // 16-bit transfers
                                    UDMA_DST_INC_NONE |               // Increment source address as 16-bit
                                    UDMA_DST_INC_NONE |               // Do not increment destination address
                                    UDMA_ARB_1 |                      // Arbitrate system bus for 1 transfer at a time
                                    UDMACC26XX_SET_TRANSFER_SIZE(0);  // Setup number of transfers (in frames of 16-bit)
        MyTaskList[2].pvDstEndAddr = &dummy;
        MyTaskList[2].pvSrcEndAddr = &dummy;
    
        /* Configure DMA to use scatter-gather mode */
        uDMAChannelScatterGatherSet(UDMA0_BASE, UDMA_CHAN_TIMER1_A, 3, MyTaskList, UDMA_MODE_PER_SCATTER_GATHER);
    
        /* Enable GPTimer DMA channel */
        UDMACC26XX_channelEnable(dmaHandle, 1 << UDMA_CHAN_TIMER1_A);
    
        /* Enable event signal */
        HWREG(GPT1_BASE + GPT_O_DMAEV) = GPT_DMAEV_TATODMAEN;
    
        /* Clear/set global counter variables */
        counter = 0;
    
        /* Start the DMA trigger timer */
        GPTimerCC26XX_start(hTimer);
    
        /* Wait for 26 runs in the Timer interrupt (25 of these we update the DMA task list: 5 periods * 5 duty cycles) */
        while (counter != 26) {};
    
        /* Clean up */
        PWM_stop(pwm);
        GPTimerCC26XX_stop(hTimer);
        UDMACC26XX_channelDisable(dmaHandle, 1 << UDMA_CHAN_TIMER1_A);
    
        /* DONE */
        while (1) {
            sleep(5);
        }
    }