Part Number: MSP430FR5994
I have developed an application on the MSP430FR5994 that uses the 12-bit ADC and FFT LEA. It is working splendidly. The next step is to incorporate DMA to move ADC data to RAM and this is where I am encountering some difficulty. There aren't any relevant code examples in MSPWare and the family user's guide is somewhat opaque. I was able to put something together after searching the forums, but it doesn't appear to be working correctly. The modified code runs, but performance is substantially degraded compared to the no DMA application.
The ADC gathers 256 time-domain samples at 33 ks/sec in a timed while loop. After acquisition, the LEA performs a real FFT with fixed scaling. I want to emphasize that my issue is only how to correctly implement DMA. CPU mediated data transfer works fine.
Code snippets (lots of cluttering lines omitted) for the relevant sections of the correctly working application follow:
// ADC sampling using Timer_A1 sourced to SMCLK at 8 MHz
TA1CTL = TASSEL__SMCLK | MC__UP; //SMCLK, up mode
TA1CCR0 = SAMPLING_PERIOD; // PWM Period = 30 us or 33.333 kHz sampling frequency
TA1CCTL1 = OUTMOD_3; // Set/reset
TA1CCR1 = SAMPLE_TIME; // PWM Duty Cycle
ADC12CTL0 |= ADC12ON; //ADC12 on
/* ADC triggered by Timer_A1 at 33.333 kHz; ADC clock is SMCLK at 8 MHz; see 5994 data sheet
TA1.1 output selected with ADC12SHS_4; SMCLK; single-channel repeat */
ADC12CTL1 = ADC12SHS_4 | ADC12SSEL_3 | ADC12CONSEQ_2;
ADC12CTL2 = ADC12RES_2; //12-bit conversion
ADC12MCTL0 |= ADC12INCH_4; //Input on A4 (P1.4)
ADC12MCTL0 |= ADC12VRSEL_1; //ADC range between Vref and ground
ADC12IER0 |= ADC12IE0; // Enable ADC conversion-complete interrupt
while(1)
{
int16_t ADC_array[SAMPLES],*ADC;
ADC12CTL0 |= ADC12ENC;
for(k=0; k<SAMPLES; k++) //Acquire the time-sampled data
{
LPM0; //Wait for the ADC interrupt
ADC_array[k] = ADC_Output;
}
// Perform real FFT with fixed scaling on ADC data
ADC = ADC_array; //Point to first element of ADC_array
for (k=0;k<SAMPLES;k++) input[k] = *ADC++; //Copy time data to input array
status = MAP_msp_fft_fixed_q15(&fftParams, input);
msp_checkStatus(status);
for (k=0;k<SAMPLES;k++) fft_array[k] = input[k]; //Copy the complex FFT data
*** etc ***
}
#pragma vector = ADC12_B_VECTOR
__interrupt void ADC12_ISR(void)
{
switch(__even_in_range(ADC12IV, ADC12IV__ADC12RDYIFG))
{
case ADC12IV__ADC12IFG0: // Vector 12: ADC12MEM0 Interrupt
ADC_Output = ADC12MEM0;
__bic_SR_register_on_exit(LPM0_bits); // Clear CPUOFF bit from LPM0
break;
}
}
In my attempt to use DMA, I eliminate the ADC interrupt (line 14 above) and its interrupt vector ISR. Additions to the code:
DMACTL0 = DMA0TSEL_26; // Channel 0 trigger is ADC12 end of conversion
/* Configure Ch 0. Repeated single transfer, increment destination address,
source address unchanged, enable DMA interrupt to signal that all conversions
are complete and let DMA ISR escape from LPM0 */
DMA0CTL |= DMADT_4 + DMADSTINCR_3 + DMAIE;
DMACTL4 = DMARMWDIS; // Read-modify-write disable. Makes no difference
DMA0SZ = SAMPLES; //Allocate space for 256 ADC samples
while(1){
uint16_t ADC_array[SAMPLES],*ADC;
//Assign the DMA source and destination addresses
__data20_write_long((uintptr_t) &DMA0SA,(uintptr_t) &ADC12MEM0);
__data20_write_long((uintptr_t) &DMA0DA,(uintptr_t) &ADC_array[0]);
DMA0CTL |= DMAEN; //Enable DMA for repeated single transfers
ADC12CTL0 |= ADC12ENC; //Turn on the ADC
LPM0; //Wait for the DMA interrupt that occurs at the completion of SAMPLES number of transfers
// Perform real FFT with fixed scaling on ADC data
ADC = ADC_array; //Point to first element of ADC_array
for (k=0;k<SAMPLES;k++) input[k] = *ADC++; //Copy time data to input array
*** etc, etc ***
}
#pragma vector=DMA_VECTOR
__interrupt void DMA_ISR(void)
{
switch(__even_in_range(DMAIV,16))
{
case 0: break;
case 2: // DMA0IFG = DMA Channel 0
__bic_SR_register_on_exit(LPM0_bits);
DMA0CTL &= ~DMAIFG; //Including this line doesn't make an difference
break;
}
}
I'm hoping someone can spot a mistake before I have to pull out the oscilloscope.