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.
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.
I don't see anything obviously wrong but the writes to DMA0SA and DMA0DA are inefficient. Because the special function registers and the data array are in low memory, 20 bit addressing isn't needed. It takes a lot of work to shuffle the data around just to prepare for that write. Much simpler is: DMA0SAL = &ADC12MEM0. The upper bits of the source address get cleared on a word write so all is well.
Oh, I would probably use DMADT_0 so that the transfers stop. With the repeated single transfer mode, the DMAC will reload after DMASZ goes to zero and do it all over again. WIth DMADT_0, it will wait for you to fiddle with DMAEN again.
Thanks for the feedback. When I set DMA0SAL = &ADC12MEM0 and DMA0DAL = &ADC_array[0], CCS warns me about mismatched data types. The application runs, but does not transfer any data from the ADC.
I confirmed that replacing DMADT_4 with DMADT_0 and resetting DMAEN on every loop works. Setting it up this way was not clear from the system flow chart on the data sheet. I still have to use the 20 bit addressing to make the application work, but it appears I'm losing/missing/truncating data somewhere in the DMA pipeline.
Can confirm that 256 samples from the ADC are not being routed into the main program correctly when using DMA. Instead of seeing 256 well-behaved data points, the data comes through as a smaller set of very large, erroneous numbers at array indexes 0-97. The remaining array points are zero. On the next iteration, huge numbers are found at indexes 97-127 with the rest zero. This behavior repeats on alternate iterations. Clearly, there is a problem configuring data transfer using DMA. I have tried variations of __data20_write_long and __data20_write_short without success. Do I need to configure CCS to handle this?
Without disagreeing with David, I'll mention that (with CCS) I've only succeeded using something of the form:
> __data20_write_long((uintptr_t)&DMA0SA, (uintptr_t)&ADC12MEM0);
It may be that GCC deals more gracefully with these registers.
Are you still using DMADT_0? (I also recommend this.) Keep in mind that even after the DMA stops itself, the ADC is still running. It would probably be prudent to clear ADC12IFGR0=0 after setting DMAEN to avoid potential stalls.
You have a 512-byte array (pretty large) in the stack. Have you set a large enough stack size to accommodate this? As a quick experiment, try moving it outside main (global), and see if the symptom changes. As far as I know, you can DMA directly into LEA RAM.
GCC doesn't deal with those 20 bit registers well at all when you need 20 bits. (If that isn't going to be fixed then the compiler should at least issue a warning.) For addresses below 0x10000, (RAM, LEARAM, SFRs) writing to DMA0SAL works just fine for me.
Set DMA0SAL = &ADC12MEM0 and DMA0DAL = &ADC_array[0], which give 515-D warnings that I ignore. DMA0SZ = 256 and uint16_t ADC_array[256] placed as a global. Stepping through the code, the address of DMA0DA matches ADC_array[0] and there is memory allocated for 512 bytes. On the first iteration reasonable looking sample values are written to array elements 0--195. 196--255 are 0 and DMA0SZ = 60. On the next iteration, fill the entire array with zeroes and the DMA writes what appear to be valid ADC values only to indexes 196--255. DMA0SZ resets to 256 and this alternating process repeats. Same behavior using either DMADT_0 or DMADT_4. ADC12IFGR0 is cleared on every iteration and DMAEN is set just before ADC12ENC.
My guess is that I am running out of memory in the DMA, but if so, I can't figure out how to configure it for more. The fact that good data is making it into the main program -- although in incorrect chunks -- is a step in the right direction.
I appreciate the interest and assistance.
There is no way to get rid of those warnings completely. Casting into an int just gets me a warning that I am changing a pointer into an integer of a different size. (If -mlarge is used.) A cast to long does that as well.
A couple of things:
1) As a matter of style and just shear paranoia I dislike how you handle DMA0CTL. Sure it is supposed to be all zeros after a reset but there is not need to use |= to set bits in it.
2) The way that you are waiting on the DMA bothers me. You enter LPM0 and assume that on exit the DMA is complete. This might be the case but it might not. Certainly not in a more complex system. When using DMADT_0 DMAE should clear when DMA is complete. So check that bit and if it isn't clear, wait some more.
Suggestion 2) did the trick David. It was jumping out of LPM0 with an interrupt unrelated to DMA0IFG. The DMA0CTL bits were OK. In case others happen to find this thread, here are the code edits that got DMA working with ADC12 and LPM0:
DMA0SAL = &ADC12MEM0; //Ignore 515-D warning DMA0DAL = &ADC_array[0]; //Ignore 515-D warning ADC12IFGR0 = 0; DMA0CTL |= DMAEN; //Enable DMA for repeated single transfers ADC12CTL0 |= ADC12ENC; //Turn on the ADC LPM0; //Wait in LPM0 while (DMA0CTL & DMAEN); //Interrupt occurred. Make sure DMA transfer complete before disabling ADC ADC12CTL0 &= ~ADC12ENC; //Disable ADC // *** proceed with FFT *** #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); break; } }
I'd like to keep this thread open for a couple of days to make sure it's all working properly. I'll then mark the issue resolved and you can lock it if that's OK. Thanks very much.
**Attention** This is a public forum