There is a problem transferring data from ADBUF0/1 to a RAM location using DMA. The DMA engine generates an overflow error (DMAINTnbits.OVRUNIF) on each transfer, beginning with the very first transfer. This problem appears to be caused by the triggering method used. The impact is the excessive overhead caused by generating too many ISRs.
I'm using a dsPIC33CK64MP205.
The system has 2 different methods of DMA transfer. Method #1 transfers data from a RAM buffer to a SPI port. Method #2 attempts to transfer from 2 ADC channels to 2 RAM buffers. Method #1 performs as expected and only generates 1 interrupt after all data have been transferred. Method #2 transfers the data as required but also generates an interrupt after each individual data is transferred. The methods are almost identical with the exception of the triggering mechanism.
Here are detailed descriptions of both methods. Hopefully an error can be found in Method #2.
The DMA is initially configured by turning the DMA engine ON, then setting the upper and lower bounds where DMA RAM access is to be performed:
DMACON |= 0x8000;
DMAL = 0x1A5A;
DMAH = 0x22E6;
Method #1 transfers data from the uppermost region of the DMA-accessible space, beginning at 0x225A, to the transmit register of SPI1. The initiating trigger is the SPI TX event:
SPI1 TX event -> DMA Ch. 2 -> RAM Buffer[] -> SPI1BUFL
The DMA is setup to increment the source address, transfering to a fixed destination address, transfers 16 bit data using the “Repeat one-shot” transfer mode. The “Repeat one-shot” mode receives multiple triggers which cause the DMA engine to transfers one data element at a time. After the DMACNT2 has decremented to 0 the DMAINT2bits.DONEIF interrupt is generated, ending the process.
The transfer is initiated every 500 msecs from a timer operating in a foreground loop.
Here is the setup code for the DMA:
DMACH2bits.CHEN = 0;
DMACH2bits.SAMODE = 1; // Source increment
DMACH2bits.DAMODE = 0; // Destination fixed
DMACH2bits.SIZE = 0; // 16-bit transfers
DMACH2bits.TRMODE = 1; // Repeat one-shot
DMASRC2 = 0x225A;
DMADST2 = (U16)&SPI1BUFL;
DMAINT2bits.CHSEL = 0x03; // SPI1 Transmitter
DMACNT2 = 72;
DMACH2bits.CHEN = 1;
Here is the callback from the _DMA2Interrupt ISR. Note a debug pin is toggled upon entry to the ISR:
void
DMACallbackLEDChannel()
{
_LATB11 ^= 1; // Toggle debug pin
if(DMAINT2bits.DONEIF)
{
// End the transfer
DMACH2bits.CHEN = 0;
// Clear the flag
DMAINT2bits.DONEIF = 0;
}
}
The process is initiated every 500 msecs. 72 16-bit data are transferred out using a SPI clock of 3.3MHz, or 0.3 µsec/clock, 4.8 µsec/16-bit DMA transfer; the entire transfer takes ≈ 350 µsec. The debug I/O pin only toggles once per transfer, demonstrating that the ISR only fires once when the DMAINT2bits.DONEIF event occurs:
Here is an expanded view of the SPI transfer. Note the SPI clocks continue after the DMAINT2bits.DONEIF event occurs:
This is because the DMA is filling the SPI FIFO so SPI operations continue after the data is completely transferred out of RAM:
Method #2 is almost identical. In Method #2 the objective is to transfer data from ADC channels 0 & 1 at a rate of 100 µsec/sample (10kHz) to the RAM space beginning at 0x1A5A.
There are 3 differences between the 2 methods:
1. The source is fixed
2. The destination is incremented
3. The process is initiated from an SCCP trigger
The SCCP is set up to generate an event every 100 µsec. The process is intended to operate as follows:
SSCP event -> ADC Ch. 0/1 -> DMA 0/1 -> RAM Buffer 0/1
In this case the SCCP triggers the ADC channels which in turn trigger the DMA transfers.
There may to be a problem with constructing the SCCP/ADC/DMA triggering algorithm.
Here is the setup code for the SCCP:
CCP1CON1Lbits.CCSEL = 0; // Output compare
CCP1CON1Lbits.CCPMOD = 0b0011; // Single edge, toggle
CCP1CON1Hbits.OPSRC = 0b1; // Special event
CCP1CON2Hbits.AUXOUT = 0b10; // Output defined by op mode
CCP1PRL = 3125; // 100usecs
Note the SCCP is not started until after the ADC & DMA setup procedures are executed.
Here is the setup code for the ADC:
// ADSIDL disabled;
// ADON disabled;
ADCON1L = 0x0000;
// FORM Integer;
// SHRRES 12-bit resolution
ADCON1H = 0x0060;
// SHREN disabled;
// C1EN disabled;
// C0EN disabled;
// CLKDIV 1;
// CLKSEL FOSC/2;
ADCON3H = 0x0000;
// IE0 enabled;
// IE1 enabled;
ADIEL = 0x0003;
// Early interrupts disabled
ADEIEL = 0x0000;
// RES 12-bit resolution;
// EISEL Early interrupt generated 1 TADCORE prior to data ready;
// ADCS 2;
ADCORE0H = 0x0300;
ADCORE1H = 0x0300;
// C0CIE disabled;
// C1CIE disabled;
// SHRCIE disabled;
// WARMTIME 32768 Source Clock Periods;
ADCON5H = (0x0F00 & 0xF0FF); //Disabling WARMTIME bit
ADTRIG0L = 0x1414; // ADC0/ADC1 trigger off SCCP1
// Setting WARMTIME bit
ADCON5Hbits.WARMTIME = 0xF;
// Enabling ADC Module
ADCON1Lbits.ADON = 0x1;
// Enabling power, Core 0
ADCON5Lbits.C0PWR = 1;
while(ADCON5Lbits.C0RDY == 0) {}
ADCON3Hbits.C0EN = 1;
// Enabling power, Core 1
ADCON5Lbits.C1PWR = 1;
while(ADCON5Lbits.C1RDY == 0) {}
ADCON3Hbits.C1EN = 1;
Here is the setup code for the DMA. Note the only difference is the setting of the RELOAD bit (DMACHnbits.RELOAD). This enables the process to be re-started in the ISR without having to re-install the SAMODE/DAMODE/SIZE/TRMODE configuration:
DMACH0bits.CHEN = 0;
DMACH1bits.CHEN = 0;
DMACH0bits.SAMODE = 0; // Source no change
DMACH0bits.DAMODE = 1; // Destination increment
DMACH0bits.SIZE = 0; // 16-bit transfers
DMACH0bits.TRMODE = 1; // Repeat one-shot
DMACH0bits.RELOAD = 1; // Reload
DMASRC0 = (U16)&ADCBUF0;
DMADST0 = 0x1A5A;
DMAINT0bits.CHSEL = 0x28; // ADC Done AN0
DMACNT0 = 512;
DMACH1bits.SAMODE = 0; // Source no change
DMACH1bits.DAMODE = 1; // Destination increment
DMACH1bits.SIZE = 0; // 16-bit transfers
DMACH1bits.TRMODE = 1; // Repeat one-shot
DMACH1bits.RELOAD = 1; // Reload
DMASRC1 = (U16)&ADCBUF1;
DMADST1 = 0x1E5A;
DMAINT1bits.CHSEL = 0x29; // ADC Done AN1
DMACNT1 = 512;
IFS0bits.DMA0IF = 0;
IFS0bits.DMA1IF = 0;
IEC0bits.DMA0IE = 1;
IEC0bits.DMA1IE = 1;
DMACH0bits.CHEN = 1;
DMACH1bits.CHEN = 1;
Once all setups are complete the SCCP can be stared:
CCP1CON1Lbits.CCPON = 1;
Here is the callback from the _DMA0Interrupt ISR. Note that:
1. A debug pin is toggled upon entry to the ISR
2. The DONE and HALF events allow the ISR to signal that the upper and lower halves of the buffer are full
3. The interrupt for ADC0 is shown; that for ADC1 is identical except that it does not toggle the I/O pin.
void
DMACallbackADCChannel0()
{
_LATB11 ^= 1; // Toggle debug pin
if(DMAINT0bits.DONEIF)
{
// Signal upper half of the buffer is full
SetBufferReadyFlag(ADC0_UPPERBUFFER);
// Restart the process
DMACH0bits.CHEN = 1;
// Clear the DONEIF flag
DMAINT0bits.DONEIF = 0;
}
if(DMAINT0bits.HALFIF)
{
// Signal lower half of the buffer is full
SetBufferReadyFlag(ADC0_LOWERBUFFER);
// Clear the HALFIF flag
DMAINT0bits.HALFIF = 0;
}
if(DMAINT0bits.OVRUNIF)
{
// Clear the OVERUNIF flag
DMAINT0bits.OVRUNIF = 0;
}
}
The problem is that unlike Method #1, when this code runs it generates an overflow error on every single transfer:
In Method #1 only 1 interrupt is generated at the end of the transfer sequence.
In Method #2 an overrun error is generated for each transfer.
The only difference is the triggering mechanism.
Note that Method #2 successfully transfers the data from ADC0/1 to Buffer[]0/1. The problem is the overhead incurred by firing 2 ISRs (DMA0/1) every 100 µsec.
The definition of the overrun error is:
“The DMA channel is triggered while it is still completing the operation based on the previous trigger”
One assumption was that the ADC flag remained set, causing the DMA to re-trigger while still in the process of completing a transfer. Another was that the ADBUFn register needed to be read for the same reason, i.e. clear the ‘Ready’ state of the channel. Trying to enable the ADC channels to generate an interrupt that would both read the data and clear the flag did not help. Increasing the priority of the ADC interrupts in an attempt to clear the IFS flag/Data Ready before the DMA interrupt occurred did not help either.
There may to be a problem with constructing the SCCP/ADC/DMA triggering algorithm.