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.

uDMA in ping pong mode cannot overwrite buffer

Hello,

I'm trying to use uDMA ping pong mode in order to fill a buffer with ADC samples.

The uint16_t buffer is of size 1024 * 50, and I'm filling it 1024 samples per transfer. So for instance:

  1. The primary control structure fills from 0 - 1023
  2. The alternate control structure fills from 1024 - 2047
  3. The primary control structure fills from 2048 - 3071
  4. And so on until 1024 * 50

The pointer that I am using in uDMAChannelTransferSet is going to be incremented by 2048 each time the control structure is reloaded, and when it reaches 50*1024 or more, it will go back to 0 and begin writing from there again.

I used memory browser and confirmed that the uDMA is filling the buffer from 0 to (50*1024 - 1), however;

My problem is when the pointer goes back to 0, it cannot overwrite the values that are already there. Is it possible to over write it? I would like to have a buffer that is constantly updated with the latest samples.

How do I fix this? Or is there a better alternative to what I am currently doing?

Please take a look at the code below, and if needed, the project attached in this post.
Any help would be much appreciated. Thank you in advance.

#include <stdint.h>
#include <stdbool.h>

/* Tivaware Header files */
#include "inc/hw_memmap.h"
#include "inc/hw_ints.h"
#include "inc/hw_adc.h"
#include "driverlib/timer.h"
#include "driverlib/interrupt.h"
#include "driverlib/sysctl.h"
#include "driverlib/gpio.h"
#include "driverlib/adc.h"
#include "driverlib/udma.h"

/* XDCtools Header files */
#include <xdc/std.h>
#include <xdc/runtime/System.h>

/* BIOS Header files */
#include <ti/sysbios/BIOS.h>
#include <ti/sysbios/knl/Task.h>

/* TI-RTOS Header files */
// #include <ti/drivers/EMAC.h>
#include <ti/drivers/GPIO.h>
// #include <ti/drivers/I2C.h>
// #include <ti/drivers/SDSPI.h>
// #include <ti/drivers/SPI.h>
// #include <ti/drivers/UART.h>
// #include <ti/drivers/USBMSCHFatFs.h>
// #include <ti/drivers/Watchdog.h>
// #include <ti/drivers/WiFi.h>

/* Board Header file */
#include "Board.h"

/* Defines */
#define TASK_STACK_SIZE	131072

/* Private variable declarations */

//
// Task object handles
//
Task_Struct ADCInitTask;

//
// Stacks for task objects
//
Char ADCInitTaskStack[TASK_STACK_SIZE];

#define ADC_BUF_SIZE 		1024
#define UDMA_NUM_TRANSFERS 	50
#define CLK_FREQ			30000000
#define SAMPLE_FREQ			1000000

uint8_t pui8DMAControlTable[1024];
uint16_t pui16Samples[ADC_BUF_SIZE * UDMA_NUM_TRANSFERS];

uint16_t *pui16APri = pui16Samples;
uint16_t *pui16AAlt = pui16Samples + ADC_BUF_SIZE;

uint32_t uDMATransferCount = 0;

/* =========================== Private functions ================================== */

void adc_irq_handler(void) {

// Clear the interrupt
	ADCIntClearEx(ADC0_BASE, ADC_INT_DMA_SS0);

	/* Check whether primary control has completed transfer */
	if (uDMAChannelModeGet(
	UDMA_CHANNEL_ADC0 | UDMA_PRI_SELECT) == UDMA_MODE_STOP) {

		// Calculate the base pointer to write to for the primary control structure
		pui16APri += ADC_BUF_SIZE * 2;
		if (pui16APri >= (pui16Samples + (ADC_BUF_SIZE * 10))) {
			pui16APri = pui16Samples;
		}

		// Reload the primary control structure
		uDMAChannelTransferSet(UDMA_CHANNEL_ADC0 | UDMA_PRI_SELECT,
		UDMA_MODE_PINGPONG, (void *) (ADC0_BASE + ADC_O_SSFIFO0), pui16APri,
		ADC_BUF_SIZE);
		uDMATransferCount++;

		return;
	}

	/* Check whether alternate control has completed transfer */
	if (uDMAChannelModeGet(
	UDMA_CHANNEL_ADC0 | UDMA_ALT_SELECT) == UDMA_MODE_STOP) {

		// Calculate base pointer to write to for the alternate control structure
		pui16AAlt += ADC_BUF_SIZE * 2;
		if (pui16AAlt >= (pui16Samples + (ADC_BUF_SIZE * 10))) {
			pui16AAlt = pui16Samples + ADC_BUF_SIZE;
		}

		// Reload the alternate control structure
		uDMAChannelTransferSet(UDMA_CHANNEL_ADC0 | UDMA_ALT_SELECT,
		UDMA_MODE_PINGPONG, (void *) (ADC0_BASE + ADC_O_SSFIFO0), pui16AAlt,
		ADC_BUF_SIZE);
		uDMATransferCount++;

		return;
	}

}

void timer_init(void) {

	uint32_t ui32ClockFreq;

// Set clock frequency
	ui32ClockFreq = SysCtlClockFreqSet(SYSCTL_XTAL_25MHZ | SYSCTL_OSC_MAIN |
	SYSCTL_USE_PLL | SYSCTL_CFG_VCO_480, CLK_FREQ);

// Enable timer peripheral clock
	SysCtlPeripheralEnable(SYSCTL_PERIPH_TIMER0);
	SysCtlPeripheralReset(SYSCTL_PERIPH_TIMER0);
	while (!SysCtlPeripheralReady(SYSCTL_PERIPH_TIMER0)) {
	}

// Configure the timer
	TimerConfigure(TIMER0_BASE, TIMER_CFG_A_PERIODIC_UP);
	TimerLoadSet(TIMER0_BASE, TIMER_A, (ui32ClockFreq / SAMPLE_FREQ));
// Enable timer to trigger ADC
	TimerControlTrigger(TIMER0_BASE, TIMER_A, true);
	// Enable event to trigger ADC
	TimerADCEventSet(TIMER0_BASE, TIMER_ADC_TIMEOUT_A);
// Enable timer
	TimerEnable(TIMER0_BASE, TIMER_A);
}

void ADC_init(void) {

// Enable GPIO peripheral clock for the ADC input
	SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOE);
	SysCtlPeripheralReset(SYSCTL_PERIPH_GPIOE);
	while (!SysCtlPeripheralReady(SYSCTL_PERIPH_GPIOE)) {
	}

// Enable ADC peripheral clock
	SysCtlPeripheralEnable(SYSCTL_PERIPH_ADC0);
	SysCtlPeripheralReset(SYSCTL_PERIPH_ADC0);
	while (!SysCtlPeripheralReady(SYSCTL_PERIPH_ADC0)) {
	}

// Set PE5 to be ADC input
	GPIOPinTypeADC(GPIO_PORTE_BASE, GPIO_PIN_5);

// Disable before configuring
	ADCSequenceDisable(ADC0_BASE, 0);

// Configure ADC0 sequencer 0:
// Triggered by timer, highest priority (0)
	ADCSequenceConfigure(ADC0_BASE, 0, ADC_TRIGGER_TIMER, 0);

// Configure ADC0 sequencer 0 step 0:
// Channel 8, last step insequence, causes interrupt when step is complete
	ADCSequenceStepConfigure(ADC0_BASE, 0, 0,
	ADC_CTL_CH8 | ADC_CTL_END | ADC_CTL_IE);

	ADCReferenceSet(ADC0_BASE, ADC_REF_INT);

// Register and enable ADC interrupt
	ADCIntEnableEx(ADC0_BASE, ADC_INT_DMA_SS0);
	IntRegister(INT_ADC0SS0, adc_irq_handler);
	IntEnable(INT_ADC0SS0);

// Enable ADC to use uDMA
	ADCSequenceDMAEnable(ADC0_BASE, 0);

// Enable ADC
	ADCSequenceEnable(ADC0_BASE, 0);
}

void DMA_init(void) {

	/* Enable uDMA clock */
	SysCtlPeripheralEnable(SYSCTL_PERIPH_UDMA);
	while (!SysCtlPeripheralReady(SYSCTL_PERIPH_UDMA))
		;

	/* Enable uDMA */
	uDMAEnable();
	/* Set the control table for uDMA */
	uDMAControlBaseSet(&pui8DMAControlTable[0]);

	/* Disable unneeded attributes of the uDMA channels */
	uDMAChannelAttributeDisable(UDMA_CHANNEL_ADC0, UDMA_ATTR_ALL);

	// Channel A udma control set
	uDMAChannelControlSet(UDMA_CHANNEL_ADC0 | UDMA_PRI_SELECT,
	UDMA_SIZE_16 |
	UDMA_SRC_INC_NONE |
	UDMA_DST_INC_16 |
	UDMA_ARB_1);

	uDMAChannelControlSet(UDMA_CHANNEL_ADC0 | UDMA_ALT_SELECT,
	UDMA_SIZE_16 |
	UDMA_SRC_INC_NONE |
	UDMA_DST_INC_16 |
	UDMA_ARB_1);

	// Channel A transfer set
	uDMAChannelTransferSet(UDMA_CHANNEL_ADC0 | UDMA_PRI_SELECT,
	UDMA_MODE_PINGPONG, (void *) (ADC0_BASE + ADC_O_SSFIFO0), pui16APri,
	ADC_BUF_SIZE);

	uDMAChannelTransferSet(UDMA_CHANNEL_ADC0 | UDMA_ALT_SELECT,
	UDMA_MODE_PINGPONG, (void *) (ADC0_BASE + ADC_O_SSFIFO0), pui16AAlt,
	ADC_BUF_SIZE);

	/* Enable the channels */
	uDMAChannelEnable(UDMA_CHANNEL_ADC0);
}

Void ADC_init_task(UArg arg0, UArg arg1) {
	DMA_init();
	ADC_init();
	timer_init();
}

/*
 *  ======== main ========
 */
int main(void) {
	Task_Params taskParams;
	/* Call board init functions */
	Board_initGeneral();

// Construct adc create task thread
	Task_Params_init(&taskParams);
	taskParams.stackSize = TASK_STACK_SIZE;
	taskParams.stack = &ADCInitTaskStack;
	Task_construct(&ADCInitTask, (Task_FuncPtr) ADC_init_task, &taskParams,
	NULL);

	/* Start BIOS */
	BIOS_start();

	return (0);
}

test_adc.rar

  • Sorry, in the code:

    if (pui16APri >= (pui16Samples + (ADC_BUF_SIZE * 10))) {
    pui16APri = pui16Samples;
    }

    Replace "10" with "UDMA_NUM_TRANSFERS".
  • The DMA engine doesn't "Know" that you already wrote to the bottom of the memory area.   It can't refuse.   That means its your code :-(

    I don't think this is right:

            pui16APri += ADC_BUF_SIZE * 2;
    

    The compiler knows that it needs to multiply ADC_BUF_SIZE by 2.

    The other potential hazard here is that you call 

    uDMAChannelModeGet() 

    Twice, with some work in between.   That creates a hazard.   You want this:

    status1 = uDMAChannelModeGet(PRI);
    status2 = uDMAChannelModeGet(ALT);
    
    if ( status1 == stopped && status2 != stopped ) {}
    
    etc

    Ping-pong DMA is a little tricky.   You have to get the math right.   The simplest way that I've found is to simply keep a pointer to the next spot in the destination buffer.  Keeping two pointers just makes your life harder.   I suggest starting by getting rid of your pui16alt pointer.

  • Thanks R Sexton,

    I've used a single pointer, and called uDMAChannelModeGet twice with no work in between.

    It also turns out that for some reason I needed to make my buffer slightly larger. Before, at 49 transfers, the primary mode goes to 4, which is a bit strange. When I made the buffer slightly larger, the error stopped. It was probably some memory calculation errors.

    Anyway, here is the working code if anybody is interested.

    #include <stdint.h>
    #include <stdbool.h>
    
    /* Tivaware Header files */
    #include "inc/hw_memmap.h"
    #include "inc/hw_ints.h"
    #include "inc/hw_adc.h"
    #include "driverlib/timer.h"
    #include "driverlib/interrupt.h"
    #include "driverlib/sysctl.h"
    #include "driverlib/gpio.h"
    #include "driverlib/adc.h"
    #include "driverlib/udma.h"
    
    /* XDCtools Header files */
    #include <xdc/std.h>
    #include <xdc/runtime/System.h>
    
    /* BIOS Header files */
    #include <ti/sysbios/BIOS.h>
    #include <ti/sysbios/knl/Task.h>
    
    /* TI-RTOS Header files */
    // #include <ti/drivers/EMAC.h>
    #include <ti/drivers/GPIO.h>
    // #include <ti/drivers/I2C.h>
    // #include <ti/drivers/SDSPI.h>
    // #include <ti/drivers/SPI.h>
    // #include <ti/drivers/UART.h>
    // #include <ti/drivers/USBMSCHFatFs.h>
    // #include <ti/drivers/Watchdog.h>
    // #include <ti/drivers/WiFi.h>
    
    /* Board Header file */
    #include "Board.h"
    
    /* Defines */
    #define TASK_STACK_SIZE	131072
    
    /* Private variable declarations */
    
    //
    // Task object handles
    //
    Task_Struct ADCInitTask;
    
    //
    // Stacks for task objects
    //
    Char ADCInitTaskStack[TASK_STACK_SIZE];
    
    #define ADC_BUF_SIZE 		1024
    #define UDMA_NUM_TRANSFERS 	50
    #define CLK_FREQ			30000000
    #define SAMPLE_FREQ			1000000
    
    uint8_t pui8DMAControlTable[1024];
    uint16_t pui16Samples[ADC_BUF_SIZE * (UDMA_NUM_TRANSFERS + 1)];
    
    uint16_t *p = pui16Samples;
    
    uint32_t uDMATransferCount = 0;
    
    /* =========================== Private functions ================================== */
    
    void adc_irq_handler(void) {
    
    	uint32_t modePrimary;
    	uint32_t modeAlternate;
    
    // Clear the interrupt
    	ADCIntClearEx(ADC0_BASE, ADC_INT_DMA_SS0);
    
    // Get the mode statuses of primary and alternate control structures
    	modePrimary = uDMAChannelModeGet(UDMA_CHANNEL_ADC0 | UDMA_PRI_SELECT);
    	modeAlternate = uDMAChannelModeGet(UDMA_CHANNEL_ADC0 | UDMA_ALT_SELECT);
    
    // Reload the control structures
    	if ((modePrimary == UDMA_MODE_STOP) && (modeAlternate != UDMA_MODE_STOP)) {
    		// Need to reload primary control structure
    		p += ADC_BUF_SIZE * 2;
    		if (p >= (pui16Samples + (ADC_BUF_SIZE * UDMA_NUM_TRANSFERS))) {
    			p = pui16Samples;
    		}
    
    		uDMAChannelTransferSet(UDMA_CHANNEL_ADC0 | UDMA_PRI_SELECT,
    		UDMA_MODE_PINGPONG, (void *) (ADC0_BASE + ADC_O_SSFIFO0), p,
    		ADC_BUF_SIZE);
    		uDMATransferCount++;
    
    	} else if ((modePrimary != UDMA_MODE_STOP)
    			&& (modeAlternate == UDMA_MODE_STOP)) {
    		// Need to reload alternate control structure
    
    		uDMAChannelTransferSet(UDMA_CHANNEL_ADC0 | UDMA_ALT_SELECT,
    		UDMA_MODE_PINGPONG, (void *) (ADC0_BASE + ADC_O_SSFIFO0),
    				(p + ADC_BUF_SIZE),
    				ADC_BUF_SIZE);
    		uDMATransferCount++;
    
    	} else {
    		// Either both still not stopped, or both stopped. This is an error
    		System_printf("Error in uDMA control structure modes\n");
    		System_printf("uDMATransferCount: %d\n", uDMATransferCount);
    		System_printf("Primary mode: %d\nAlternate mode: %d\n", modePrimary,
    				modeAlternate);
    		System_printf(
    				"For reference: Stop:%d, Basic:%d, Auto:%d, PingPong:%d, MemScatter:%d, PerScatter:%d\n",
    				UDMA_MODE_STOP, UDMA_MODE_BASIC, UDMA_MODE_AUTO,
    				UDMA_MODE_PINGPONG, UDMA_MODE_MEM_SCATTER_GATHER,
    				UDMA_MODE_PER_SCATTER_GATHER);
    		System_flush();
    	}
    }
    
    void timer_init(void) {
    
    	uint32_t ui32ClockFreq;
    
    // Set clock frequency
    	ui32ClockFreq = SysCtlClockFreqSet(SYSCTL_XTAL_25MHZ | SYSCTL_OSC_MAIN |
    	SYSCTL_USE_PLL | SYSCTL_CFG_VCO_480, CLK_FREQ);
    
    // Enable timer peripheral clock
    	SysCtlPeripheralEnable(SYSCTL_PERIPH_TIMER0);
    	SysCtlPeripheralReset(SYSCTL_PERIPH_TIMER0);
    	while (!SysCtlPeripheralReady(SYSCTL_PERIPH_TIMER0)) {
    	}
    
    // Configure the timer
    	TimerConfigure(TIMER0_BASE, TIMER_CFG_A_PERIODIC_UP);
    	TimerLoadSet(TIMER0_BASE, TIMER_A, (ui32ClockFreq / SAMPLE_FREQ));
    // Enable timer to trigger ADC
    	TimerControlTrigger(TIMER0_BASE, TIMER_A, true);
    	// Enable event to trigger ADC
    	TimerADCEventSet(TIMER0_BASE, TIMER_ADC_TIMEOUT_A);
    // Enable timer
    	TimerEnable(TIMER0_BASE, TIMER_A);
    }
    
    void ADC_init(void) {
    
    // Enable GPIO peripheral clock for the ADC input
    	SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOE);
    	SysCtlPeripheralReset(SYSCTL_PERIPH_GPIOE);
    	while (!SysCtlPeripheralReady(SYSCTL_PERIPH_GPIOE)) {
    	}
    
    // Enable ADC peripheral clock
    	SysCtlPeripheralEnable(SYSCTL_PERIPH_ADC0);
    	SysCtlPeripheralReset(SYSCTL_PERIPH_ADC0);
    	while (!SysCtlPeripheralReady(SYSCTL_PERIPH_ADC0)) {
    	}
    
    // Set PE5 to be ADC input
    	GPIOPinTypeADC(GPIO_PORTE_BASE, GPIO_PIN_5);
    
    // Disable before configuring
    	ADCSequenceDisable(ADC0_BASE, 0);
    
    // Configure ADC0 sequencer 0:
    // Triggered by timer, highest priority (0)
    	ADCSequenceConfigure(ADC0_BASE, 0, ADC_TRIGGER_TIMER, 0);
    
    // Configure ADC0 sequencer 0 step 0:
    // Channel 8, last step insequence, causes interrupt when step is complete
    	ADCSequenceStepConfigure(ADC0_BASE, 0, 0,
    	ADC_CTL_CH8 | ADC_CTL_END | ADC_CTL_IE);
    
    	ADCReferenceSet(ADC0_BASE, ADC_REF_INT);
    
    // Register and enable ADC interrupt
    	ADCIntEnableEx(ADC0_BASE, ADC_INT_DMA_SS0);
    	IntRegister(INT_ADC0SS0, adc_irq_handler);
    	IntEnable(INT_ADC0SS0);
    
    // Enable ADC to use uDMA
    	ADCSequenceDMAEnable(ADC0_BASE, 0);
    
    // Enable ADC
    	ADCSequenceEnable(ADC0_BASE, 0);
    }
    
    void DMA_init(void) {
    
    	/* Enable uDMA clock */
    	SysCtlPeripheralEnable(SYSCTL_PERIPH_UDMA);
    	while (!SysCtlPeripheralReady(SYSCTL_PERIPH_UDMA))
    		;
    
    	/* Enable uDMA */
    	uDMAEnable();
    	/* Set the control table for uDMA */
    	uDMAControlBaseSet(&pui8DMAControlTable[0]);
    
    	/* Disable unneeded attributes of the uDMA channels */
    	uDMAChannelAttributeDisable(UDMA_CHANNEL_ADC0, UDMA_ATTR_ALL);
    
    	// Channel A udma control set
    	uDMAChannelControlSet(UDMA_CHANNEL_ADC0 | UDMA_PRI_SELECT,
    	UDMA_SIZE_16 |
    	UDMA_SRC_INC_NONE |
    	UDMA_DST_INC_16 |
    	UDMA_ARB_1);
    
    	uDMAChannelControlSet(UDMA_CHANNEL_ADC0 | UDMA_ALT_SELECT,
    	UDMA_SIZE_16 |
    	UDMA_SRC_INC_NONE |
    	UDMA_DST_INC_16 |
    	UDMA_ARB_1);
    
    	// Channel A transfer set
    	uDMAChannelTransferSet(UDMA_CHANNEL_ADC0 | UDMA_PRI_SELECT,
    	UDMA_MODE_PINGPONG, (void *) (ADC0_BASE + ADC_O_SSFIFO0), p,
    	ADC_BUF_SIZE);
    
    	uDMAChannelTransferSet(UDMA_CHANNEL_ADC0 | UDMA_ALT_SELECT,
    	UDMA_MODE_PINGPONG, (void *) (ADC0_BASE + ADC_O_SSFIFO0),
    			(p + ADC_BUF_SIZE),
    			ADC_BUF_SIZE);
    
    	/* Enable the channels */
    	uDMAChannelEnable(UDMA_CHANNEL_ADC0);
    }
    
    Void ADC_init_task(UArg arg0, UArg arg1) {
    	DMA_init();
    	ADC_init();
    	timer_init();
    }
    
    /*
     *  ======== main ========
     */
    int main(void) {
    	Task_Params taskParams;
    	/* Call board init functions */
    	Board_initGeneral();
    
    // Construct adc create task thread
    	Task_Params_init(&taskParams);
    	taskParams.stackSize = TASK_STACK_SIZE;
    	taskParams.stack = &ADCInitTaskStack;
    	Task_construct(&ADCInitTask, (Task_FuncPtr) ADC_init_task, &taskParams,
    	NULL);
    
    	/* Start BIOS */
    	BIOS_start();
    
    	return (0);
    }
    

  • There are edge cases here that you must watch out for --

    You need both of these:


    if ((modePrimary == UDMA_MODE_STOP) && (modeAlternate != UDMA_MODE_STOP)) {


    and:

    if ((modePrimary != UDMA_MODE_STOP) && (modeAlternate == UDMA_MODE_STOP)) {

    Its entirely possible that both of them can be running at once or both be stopped at the same time. Be careful. That can happen.
  • Aha. Failure to read. You did that. Sorry.
  • Aha. You only advance p+reset p when the first condition applies. Thats an edge case that can bite you. Its easy to get this wrong.

    Ideally, p would always point to the next bit of memory for DMA:


    // Channel A transfer set
    uDMAChannelTransferSet(UDMA_CHANNEL_ADC0 | UDMA_PRI_SELECT,
    UDMA_MODE_PINGPONG, (void *) (ADC0_BASE + ADC_O_SSFIFO0), p,
    ADC_BUF_SIZE);
    p+= ADC_BUF_SIZE;

    uDMAChannelTransferSet(UDMA_CHANNEL_ADC0 | UDMA_ALT_SELECT,
    UDMA_MODE_PINGPONG, (void *) (ADC0_BASE + ADC_O_SSFIFO0),
    p, ADC_BUF_SIZE);
    p+= ADC_BUF_SIZE;



    You want something like this:

    static void advance_p() {
    p += ADC_BUF_SIZE;
    if (p >= (pui16Samples + (ADC_BUF_SIZE * UDMA_NUM_TRANSFERS))) {
    p = pui16Samples;

    }


    if ((modePrimary == UDMA_MODE_STOP) && (modeAlternate != UDMA_MODE_STOP)) {
    // Need to reload primary control structure
    }
    advance_p();
    uDMAChannelTransferSet(UDMA_CHANNEL_ADC0 | UDMA_PRI_SELECT,
    UDMA_MODE_PINGPONG, (void *) (ADC0_BASE + ADC_O_SSFIFO0), p,
    ADC_BUF_SIZE);
    uDMATransferCount++;

    } else if ((modePrimary != UDMA_MODE_STOP)
    && (modeAlternate == UDMA_MODE_STOP)) {
    // Need to reload alternate control structure
    advance_p();
    uDMAChannelTransferSet(UDMA_CHANNEL_ADC0 | UDMA_ALT_SELECT,
    UDMA_MODE_PINGPONG, (void *) (ADC0_BASE + ADC_O_SSFIFO0), p,
    ADC_BUF_SIZE);
    uDMATransferCount++;
  • And thats slightly wrong, too. p needs to always be ready for use, or always require update prior to use, so you should always advance it -after- calling uDMAChannelTransferSet().