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.

Read ADC samples for FFT

Hello everybody,

I am working on a project in which i would like to read samples from an adc with 40us, lets say 512 samples. After sampling i would like to calculate an fft with the DSPLib.

now I initialize an timer which is handling my adc read.

void init_timer(void)
{
		FPULazyStackingEnable();
		SysCtlClockSet(SYSCTL_SYSDIV_1 | SYSCTL_USE_OSC | SYSCTL_OSC_MAIN | SYSCTL_XTAL_16MHZ );
		UARTprintf("Timerinit\n");
		SysCtlPeripheralEnable(SYSCTL_PERIPH_TIMER0);
		TimerConfigure(TIMER0_BASE, TIMER_CFG_PERIODIC);
		TimerLoadSet(TIMER0_BASE, TIMER_A, SysCtlClockGet());
		IntEnable(INT_TIMER0A);
		SysCtlPeripheralEnable(SYSCTL_PERIPH_TIMER0);
		IntMasterEnable();
		TimerIntEnable(TIMER0_BASE, TIMER_TIMA_TIMEOUT);
		TimerEnable(TIMER0_BASE, TIMER_A);
}
void Timer0IntHandler(void)
{
    adc_val=adc_read();
    adc_flag=1;
}

unsigned long adc_read(void)
{

    SysCtlClockSet(SYSCTL_SYSDIV_5 | SYSCTL_USE_PLL | SYSCTL_OSC_MAIN | SYSCTL_XTAL_16MHZ);

    SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOE);
   
    GPIOPinTypeADC(GPIO_PORTE_BASE, GPIO_PIN_2);

    ADCSequenceConfigure(ADC0_BASE, 3, ADC_TRIGGER_PROCESSOR, 0);

    ADCSequenceStepConfigure(ADC0_BASE, 3, 0, ADC_CTL_CH1 | ADC_CTL_IE |
                             ADC_CTL_END);

    ADCSequenceEnable(ADC0_BASE, 3);
    ADCIntClear(ADC0_BASE, 3);
    ADCProcessorTrigger(ADC0_BASE, 3);
      while(!ADCIntStatus(ADC0_BASE, 3, false))
        {
        }

    ADCIntClear(ADC0_BASE, 3);
    // Read ADC Value.
    ADCSequenceDataGet(ADC0_BASE, 3, &ulADC0_Value);

    //}//while
    return ulADC0_Value;
}//adc_read

MAIN is working like that:

if(adc_flag)
		{
			Datenarray[cnt_fft]=adc_val;
		    Datenarray[cnt_fft+1]=0;

		    if(cnt_fft==samples*2-2)
		    	trig_fft=1;
		    else
		    	trig_fft=0;

		    cnt_fft=cnt_fft+2;
		    adc_flag=0;
		}
		if(trig_fft)
		{
			fft_calc(samples, Datenarray, fft_done);									//FFT berechnen
			trig_fft=0;
		}
		if(fft_done)
		{
		UARTprintf("Output_data_Datenarray\n");
		for(k = 0 ; k < samples*2 ; k=k+2)
			{
			if (Datenarray[k]>=100)
			UARTprintf("%d:F_Re = %4d\t\t%d:F_Im= %4d\n",k/2, Datenarray[k],k/2, Datenarray[k+1]);
			else
			UARTprintf("%d:F_Re = %4d\t\t%d:F_Im= %4d\n",k/2, 0,k/2, Datenarray[k+1]);
			}
		fft_done=0;
		}

while debugging everything is fine, but in release it is doing weird things.

Is there a better way to read the values, then calculate while reading and calulate again after the array is full again? how do i config the timer that my adc reads every 40 us?

greetings

  • I see several issues here, no wonder it does "weird things" ...

    First, you need to configure the ADC only once. So everthing up to ADCSequenceEnable() is a waste of time in the read function.

    Second, you will never get equidistant sampling when busy-waiting for the end-of-conversion flag. Better setup an ADC interrupt, trigger a conversion in the timer interrupt, and gather the value in the ADC interrupt. A better (but slightly more complex) method is to configure a DMA that moves your results to a buffer, and gives you an interrupt when the buffer is full. This method becomes obligatory for high sampling frequencies, otherwise the ADC interrupts will eat up too much cycles.

    Third, move your buffer management code to the ADC interrupt. And once a sample buffer is full, copy it over to another buffer. A FFT calculation usually takes longer than a sampling period, so either will the ADC interrupt (or DMA) overwrite your buffer during your calculation, or your calculation holds off ADC sampling (which is happening now, thus the "weird behavior").

    I have exercised this task on another vendor's core, but not yet on a TIVA MCU. So in regard to TIVAware code examples, I must stave you off to other experts ...

  • f. m. said:
    A better (but slightly more complex) method is to configure a DMA that moves your results to a buffer, and gives you an interrupt when the buffer is full. This method becomes obligatory for high sampling frequencies, otherwise the ADC interrupts will eat up too much cycles.

    is there an example to do DMA from ADC to memory? There is one from tiva ware but its quite big and with memory to memory access.

  • is there an example to do DMA from ADC to memory

    Frankly said, I don't know. But I guess there are such examples.

    I'm using MCUs from several vendors, with an IDE which supports all (Crossworks for ARM). I rarely use TIVAware, and haven't done so in the last year. I rather grab the datasheet, and access the peripheral registers directly (or via CMSIS lib, which ist still kind of anathema for TIVA MCUs ...).

    You might get more detailed information from frequent TIVAware users. Since this forum is more US-centered/located, expect more answers in the late afternoon.

  • @f.m.,

    Simply great post - you provide user diagnosis and then - far "above/beyond" you detail the logical sequence & "nuts/bolts" of the ADC & DMA joint usage.

    Just like you - we too often use (other) cores - although (bug-free, StellarisWare) proves a great time/effort saver when this vendor's parts find their way to disty shelves & are priced competitively.  (movement really needed, here)

  • Hello Sebastian,

    You can first configure the timer to trigger the ADC conversion and uDMA to read the data out into a buffer. Yes, there is not an example in the TivaWare but the uart_dma example can be made to do the same. If you can begin with the code update, we can help you out complete it and get it to work.

    Also as you mentioned in the Release build it does not work. What was the issue you were seeing in the Release Build?

    Regards

    Amit

  • the problem was, that my program was working while debugging, i guess because i never got an interrupt during
    debugging this part of the code.
    So once the code was running in release, the µC was jumping to the ISR during calculating the fft or
    somewhere else. So in the end there was just chaos.
    Is it now the best to change the whole code to DMA, resave the data´s in another array for calculating while i am
    reading again, or is it also good to introduce a flag which allow the calculation of the fft(without dma)?

    for DMA, i made some changes:
    Am i right, that i have to change the timervalue to change the sampling rate for my fft then?
    void init_timer(void)
    {
    		FPULazyStackingEnable();
    		//Set to 20Mhz for ADC necessary
    		//SysCtlClockSet(SYSCTL_SYSDIV_1 | SYSCTL_USE_OSC | SYSCTL_OSC_MAIN | SYSCTL_XTAL_16MHZ );
    		UARTprintf("\nTimer_init\n");
    		SysCtlPeripheralEnable(SYSCTL_PERIPH_TIMER0);
    		TimerConfigure(TIMER0_BASE, TIMER_CFG_PERIODIC);
    		TimerLoadSet(TIMER0_BASE, TIMER_A, SysCtlClockGet()/25000);//equal to 40us
    		IntEnable(INT_TIMER0A);
    		SysCtlPeripheralEnable(SYSCTL_PERIPH_TIMER0);
    		IntMasterEnable();
    		//TimerIntRegister(TIMER0_BASE, TIMER_A, Timer0IntHandler);
    		TimerIntEnable(TIMER0_BASE, TIMER_TIMA_TIMEOUT);
    		//TimerEnable(TIMER0_BASE, TIMER_A);
    		//IntPrioritySet(INT_TIMER0A,0xE0);
    		//IntPrioritySet(INT_TIMER0B,0xE0);
    }

    void init_adc(void)
    {
    //********************************************************************************************
        //************************************Config start********************************************
    	UARTprintf("\nADC_init\n");
    	// Set the clocking to run at 20 MHz (200 MHz / 10) using the PLL.
    	// using the ADC, you must either use the PLL or supply a 16 MHz clock
    	// source.
        SysCtlClockSet(SYSCTL_SYSDIV_5 | SYSCTL_USE_PLL | SYSCTL_OSC_MAIN | SYSCTL_XTAL_16MHZ);
        //InitConsole();
        //*************************************************************
        // The ADC0 peripheral must be enabled for use.
        SysCtlPeripheralEnable(SYSCTL_PERIPH_ADC0);
        //GPIO_PORT_E Enable
        SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOE);
        //GPIO_E_PIN2 as ADC
        GPIOPinTypeADC(GPIO_PORTE_BASE, GPIO_PIN_2);
        // Enable sample sequence 3 with a processor signal trigger.  Sequence 3
        // will do a single sample when the processor sends a signal to start the
        // conversion.  Each ADC module has 4 programmable sequences, sequence 0
        // to sequence 3.  This example is arbitrarily using sequence 3.
        ADCSequenceConfigure(ADC0_BASE, 3, ADC_TRIGGER_TIMER, 0);
        // Configure step 0 on sequence 3.  Sample channel 0 (ADC_CTL_CH1) in
        // single-ended mode (default) and configure the interrupt flag
        // (ADC_CTL_IE) to be set when the sample is done.  Tell the ADC logic
        // that this is the last conversion on sequence 3 (ADC_CTL_END).
        ADCSequenceStepConfigure(ADC0_BASE, 3, 0, ADC_CTL_CH1 | ADC_CTL_IE |
                                 ADC_CTL_END);
    
    }



  • the problem was, that my program was working while debugging, i guess because i never got an interrupt during debugging this part of the code.



    Perhaps because you just debugged one cycle ? (sample gathering and FFT calculation).
    Before the first cycle, the sample buffer is empty, and you won't notice any problems.

    Is it now the best to change the whole code to DMA, resave the data´s in another array for calculating while i am
    reading again, or is it also good to introduce a flag which allow the calculation of the fft(without dma)?


    You do not necessarily need DMA, but it would reduce the CPU load. Alternatively, you can pick up the ADC data in an end-of-conversion interrupt. But you must move the sample data to a separate buffer. The FFT calculation takes some hundred microseconds for your buffer size, and for every frequency point as output data, every sample point as input data is used. Changing the sample data while the calculation is running will give you invalid results.

    Signaling the availibility of a new sample buffer with a flag would be o.k. And you can check this flag in the main loop, too. But the actual sample process should be free-running. Just initialize it once, pick up the sample data in the interrupt routine, and check the count there. If the buffer is full (i.e. 512 samples in your case), copy the data, and set the flag you are checking in the main loop. This way, sampling and FFT calculation are running quasi-parallel.

    And don't forget to reset your flag each time the calculation finished...

  • Hi,

    Several observations with the code, besides those posted by @f.m:

    a) the system clock is set at 40MHz ( although the comment say 20) but whatever it is, it is not good - since the ADC should work at 16MHz, unless specially configured to do so, which I did not observed. The best thing is to run the micro at 80MHz, nothing wrong, and the ADC will get the exact needed amount of frequency and the fft will be executed faster.

    b) since there is not specified if the fft will be real or complex in calculation, and the product will need real time processing or not, the best thing is to determine first the execution time of the fft routine, without the ADC, based only on  a software generator - it is easy to fill up a buffer with samples and then determine the execution time. Based on the requirements and the result of the execution time then the user can decide to use the DMA or not.

    c) it is unusual to have big behavioral differences between debug mode and release mode, except a little bit faster speed in release mode...

    Petrei

  • b) since there is not specified if the fft will be real or complex in calculation, and the product will need real time processing or not, the best thing is to determine first the execution time of the fft routine, without the ADC, based only on  a software generator - it is easy to fill up a buffer with samples and then determine the execution time. Based on the requirements and the result of the execution time then the user can decide to use the DMA or not.



    Yes, due diligence is for sure recommended. I left it out, because I had already done this for some M4 cores. Using a 2048 point FFT, I measured 2.5 ... 5 ms (for the CMSIS DSP lib functions), mainly dependant on the clock frequency. I would estimate about 0.5 ms for a 512 byte buffer, more than enough for a 20 ms sampling period.

    However, for higher sampling frequencies, use of DMA becomes almost compulsary. Otherwise, the 12+12 cycles for each interrupt will quickly eat away CPU performance.