Finally got the XMEGA DMA controller on an ATxmega128A3 working. There is not much example code for it and the datasheet is not very clear, so I wrote this.
The datasheet and examples don’t really explain the terminology well. A transaction is the complete cycle of the DMA controller. To start a transaction you load up the control registers and enable the DMA channel. It will then respond to triggers. The transaction ends when TRFCNT * REPCNT bytes have been transferred.
A block is a way of dividing the transaction up to fit your buffer size. If you just want to fill one buffer from start to finish you can set the block size in TRFCNT to the full size of your buffer. There are not many situations where you would want to use blocks smaller than one whole transaction, so for simplicity you can often just ignore them and look at transactions only.
The burst length is the number of bytes copied in one go by the DMA controller and is set with the BURSTLEN bits in CTRLA. Often it is used to read/write 16 bit SFRs such as the ADC result or the DAC output level. You always want to access those 16 bit words in one go rather than as two 8 bit bytes and the burst length lets you do that.
Now comes the poorly documented bit. First we have the single shot mode bit in CTRLA (bit 2 SINGLE). If set every time the DMA channel is triggered it transfers a single burst of data, e.g. 2 bytes if you set BURSTLEN to 2BYTE. If it isn’t set the DMA channel goes into free running mode and simply transfers bursts as fast as possible until the transaction ends. So if you want to save ADC readings into a buffer like I am you want single shot mode triggered by the ADC.
Next let’s look at repeat mode. In repeat mode the DMA controller transfers REPCNT blocks of data. To keep it simple let’s say you only have one buffer and intend to fill it in one go, so you set the block size to the buffer size. If you are not in repeat mode the buffer will be filled once and then the transaction will end and the DMA channel will be disabled. If you are in repeat mode the DMA channel will perform REPCNT block transfers, and since you set your block size to the buffer size that means it is the number of times the buffer will be filled consecutively. If you are in repeat mode and you set REPCNT to zero the DMA channel will repeat forever.
Now keep in mind that even with repeat mode enabled the DMA controller still has to be triggered. In normal mode one trigger transfers an entire buffer’s worth of data, and in single shot mode each trigger transfers one burst of data.
Finally we have double buffering mode. Double buffering basically alternates between filling two buffers. When one transaction is complete the DMA channel automatically stops and its partner starts. This action is independent of repeat mode, and will set the transaction complete flag so that you can detect when it happens.
Note 1. You need to look at the transaction complete flags, not the DMA channel active or pending flags, if you want to know when the channel has finished and the buffer is ready for processing.
Note 2. If you want to use a timer to trigger a DMA channel then you must enable that timer’s interrupt. You can give it a null interrupt handler (e.g. ISR(TCC1_OVF_vect){} or just RETI in assembler) but it does have to actually be enabled and triggering, otherwise the DMA channel will not be triggered either. That doesn’t apply to other peripherals such as the ADC, it seems to be just the timers.
PORTA.DIRCLR = PIN7_bm; PORTA.OUTCLR = PIN7_bm; ADCA.CH0.CTRL = ADC_CH_INPUTMODE_SINGLEENDED_gc | ADC_CH_GAIN_1X_gc; ADCA.CH0.MUXCTRL = ADC_CH_MUXPOS_PIN7_gc; adc_wait_8mhz(&ADCA); ADCA.EVCTRL = ADC_SWEEP_0_gc | ADC_EVSEL_0123_gc | ADC_EVACT_CH0_gc; // event channel 0 triggers ADC channel 0 ADCA.CTRLA |= ADC_ENABLE_bm; // set TCC1 to 11024Hz overflow, actually 11019.2838Hz (-0.052% error) TCC1.CTRLA = 0; // stop if running TCC1.CNT = 0; TCC1.PER = 0x02D5; EVSYS.CH0MUX = EVSYS_CHMUX_TCC1_OVF_gc; // trigger on timer overflow // reset DMA controller DMA.CTRL = 0; DMA.CTRL = DMA_RESET_bm; while ((DMA.CTRL & DMA_RESET_bm) != 0) ; // configure DMA controller DMA.CTRL = DMA_CH_ENABLE_bm | DMA_DBUFMODE_CH01_gc; // double buffered with channels 0 and 1 // channel 0 // **** TODO: reset dma channels DMA.CH0.REPCNT = 0; DMA.CH0.CTRLA = DMA_CH_BURSTLEN_2BYTE_gc | DMA_CH_SINGLE_bm | DMA_CH_REPEAT_bm; // ADC result is 2 byte 12 bit word DMA.CH0.ADDRCTRL = DMA_CH_SRCRELOAD_BURST_gc | DMA_CH_SRCDIR_INC_gc | // reload source after every burst DMA_CH_DESTRELOAD_TRANSACTION_gc | DMA_CH_DESTDIR_INC_gc; // reload dest after every transaction DMA.CH0.TRIGSRC = DMA_CH_TRIGSRC_ADCA_CH0_gc; DMA.CH0.TRFCNT = 2048; // always the number of bytes, even if burst length > 1 DMA.CH0.DESTADDR0 = (( (uint16_t) buffer_a) >> 0) & 0xFF; DMA.CH0.DESTADDR1 = (( (uint16_t) buffer_a) >> 8) & 0xFF; DMA.CH0.DESTADDR2 = 0; DMA.CH0.SRCADDR0 = (( (uint16_t) &ADCA.CH0.RES) >> 0) & 0xFF; DMA.CH0.SRCADDR1 = (( (uint16_t) &ADCA.CH0.RES) >> 8) & 0xFF; DMA.CH0.SRCADDR2 = 0; // channel 1 DMA.CH1.REPCNT = 0; DMA.CH1.CTRLA = DMA_CH_BURSTLEN_2BYTE_gc | DMA_CH_SINGLE_bm | DMA_CH_REPEAT_bm; // ADC result is 2 byte 12 bit word DMA.CH1.ADDRCTRL = DMA_CH_SRCRELOAD_BURST_gc | DMA_CH_SRCDIR_INC_gc | // reload source after every burst DMA_CH_DESTRELOAD_TRANSACTION_gc | DMA_CH_DESTDIR_INC_gc; // reload dest after every transaction DMA.CH1.TRIGSRC = DMA_CH_TRIGSRC_ADCA_CH0_gc; DMA.CH1.TRFCNT = 2048; DMA.CH1.DESTADDR0 = (( (uint16_t) buffer_b) >> 0) & 0xFF; DMA.CH1.DESTADDR1 = (( (uint16_t) buffer_b) >> 8) & 0xFF; DMA.CH1.DESTADDR2 = 0; DMA.CH1.SRCADDR0 = (( (uint16_t) &ADCA.CH0.RES) >> 0) & 0xFF; DMA.CH1.SRCADDR1 = (( (uint16_t) &ADCA.CH0.RES) >> 8) & 0xFF; DMA.CH1.SRCADDR2 = 0; DMA.CH0.CTRLA |= DMA_CH_ENABLE_bm; TCC1.CTRLA = TC_CLKSEL_DIV1_gc; // start timer, and in turn ADC for (i = 0; i < 20; i++) { while (!(DMA.INTFLAGS & DMA_CH0TRNIF_bm)); DMA.INTFLAGS = DMA_CH0TRNIF_bm; TERM_tx_char('A'); while (!(DMA.INTFLAGS & DMA_CH1TRNIF_bm)); DMA.INTFLAGS = DMA_CH1TRNIF_bm; TERM_tx_char('B'); }
In the example the code outputs ‘A’ or ‘B’ when each buffer is full.