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.

DMA Memory locations for ADCs

I am trying to put together a scenario where I trigger an M4 ADC on a timer and use an interrupt handler to process the data that's returned. When the ADC finishes taking a sample I would like it to DMA it into a ring buffer in memory, so I can then get an interrupt and process the data.

it looks like pretty much all the example code out there is for either software-based transfers or UART, which seem to operate differently. In theory, I shouldn't need a ping-pong buffer to handle this since I should only ever be capturing one ADC sequence at a time. My question is related to this piece of code from the udma_demo in the boards/ek-lm4f232 folder:

ROM_uDMAChannelTransferSet(UDMA_CHANNEL_UART0RX | UDMA_PRI_SELECT,
UDMA_MODE_PINGPONG,
(void *)(UART0_BASE + UART_O_DR),
g_ucRxBufA, sizeof(g_ucRxBufA));

They seem to use UART0_BASE + UART_0_DR as their offset into memory where the data is being read from, however I don't see anywhere in driverlib/adc.h or inc/hw_memmap.h where a similar offset into the ADC is given for ADC sequences. If I were using, say, ADC Sequence 3 to get my data, what do I put in for a memory location where I could read the data out of? 

I am also assuming I need to select the proper channel and change my mode from MODE_PINGPONG to MODE_AUTO, but those I can find reference materials to let me know what I should change - this is a piece I can't find any documentation on.

  • The register you want is the ADC FIFO, for instance ADCSSFIFO0.  I don't have the stellaris library code in front of me, but I would guess the address is going to be something like ADC0_BASE + ADC_O_SSFIFO0.  You can always look in the ADC read functions in the stellaris library, and see what address they read from.

    You will probably want to use the ADC UDMA Channel, with UDMA_MODE_AUTO.  Don't use UDMA_MODE_BASIC with peripherals.  See the M4 manual on the DMA operation of the ADC channel for some of the peculiarities.

    I hope that helps.  I have used the uDMA a lot, with a number of peripherals, but never the ADC.  If you need any help setting up the uDMA, just let me know.

  • @Dustin,

    What a great addition you are to this forum!  Unselfish, skilled and crisp/clear - you embody the goals of any forum/grouping.

    Thanks...

  • Wow, thanks @cb1_mobile

  • I'm sorry I don't have a complete example for you, but here is an excerpt from some test code that uses DMA with the ADC:

        //
        // Configure control parameters for DMA channel
        //
        MAP_uDMAChannelControlSet(g_pTestCase->ulChan | UDMA_PRI_SELECT,
                                  UDMA_SIZE_32 | UDMA_SRC_INC_NONE |
                                  UDMA_DST_INC_32 | ulArbSize);

        //
        // Set up the transfer for the DMA channel
        //
        MAP_uDMAChannelTransferSet(g_pTestCase->ulChan | UDMA_PRI_SELECT,
                                   UDMA_MODE_BASIC,
                                   (void *)(g_pADC->ulBase + ADC_O_SSFIFO0 + (0x20 * g_pTestCase->ulSeq)),
                                   g_ulAdcBuf, ADC_DSTBUF_SIZE);

    This example sets up to transfer 256 samples from the ADC sequencer to a buffer in memory. When using uDMA with ADC, you must configure the ADC sequencer to use ADC_CTL_END | ADC_CTL_IE, on a power of 2 location in the sequence.  For example, 1, 2, or 4.  And the ARB_SIZE must match that value.  The way the DMA works, you will get an interrupt when the entire transfer is complete.

    It is not clear to me if you expect to get an interrupt for every sample or only after a certain amount of data has been collected.

    I should also correct something that was mentioned above ... you should use BASIC mode (or one of the more complex modes) with peripherals, and AUTO mode for memory transfers.  With AUTO mode, once a channel is triggered, it will perform the entire transfer without looking again at the request line.  This is usually used for performing memory-to-memory transfers.  BASIC mode will perform a transfer when a DMA request is made by the peripheral.  In the case of ADC, its request line is hooked up to the "burst" request, which means when the DMA controller sees the request line, it will transfer the number of items specified in ARB_SIZE and then stop and wait for the next request, until the entire number of items have been transferred, at which time it will issue an interrupt.

  • Thanks for correcting me about BASIC and AUTO, I got mixed up when I was writing.  I always use BASIC (or PINGPONG) for peripherals.  Sorry for adding confusion.

  • @Stellaris Joe - This is really most helpful - thanks much - appreciated.

    Have a question, " power of 2 location in the sequence.  For example, 1, 2, or 4."  If we use Sequence0 (as we seek 8 channel conversion) is 8 (2**3) to be excluded?

    Level of detail you've provided will save countless time/effort - again thank you...

  • You can use 8.

    I was doing this in my test

            MAP_ADCSequenceStepConfigure(ulBase, ulSeq, 0, ADC_CTL_CH1);
            MAP_ADCSequenceStepConfigure(ulBase, ulSeq, 1, ADC_CTL_CH2);
            MAP_ADCSequenceStepConfigure(ulBase, ulSeq, 2, ADC_CTL_CH3);
            MAP_ADCSequenceStepConfigure(ulBase, ulSeq, 3, ADC_CTL_CH4 | ADC_CTL_IE);
            MAP_ADCSequenceStepConfigure(ulBase, ulSeq, 4, ADC_CTL_CH5);
            MAP_ADCSequenceStepConfigure(ulBase, ulSeq, 5, ADC_CTL_CH6);
            MAP_ADCSequenceStepConfigure(ulBase, ulSeq, 6, ADC_CTL_CH7);
            MAP_ADCSequenceStepConfigure(ulBase, ulSeq, 7, ADC_CTL_CH8 | ADC_CTL_IE |
                                                       ADC_CTL_END);
            ulArbSize = UDMA_ARB_4;
     

    but you can also leave out the "IE" at entry 3 and set your ARB size to 8.  The reason I did it this way is that the DMA can begin transferring data before the entire sequence has been captured.

  • Terrific - just what our group sought - and expect will be of value to many.  Especially appreciate your clarity wrt ARB_4 vs. ARB_8.  (surely would have gotten me...)

    Thanks much.

  • Hi Joe,

    I agree with cb1, this looks really valuable as example code. Thanks for posting it and clarifying the use of BASIC and AUTO with regard to peripherals.

    Currently I am using ADC Sequencer 3, which only has a fifo depth of 1, so I do plan to get an interrupt after every sample. I need to guarantee a specific sample rate, so I have a timer set up to trigger the ADC at a defined interval, capture a sample, and DMA it, and then interrupt me when it's done. I would be thrilled if there were a way to guarantee a specific sample rate between ADC steps so I could use something more like your sequence step listing, but I think using the timer is the only way to do this, correct?

    One question I still had, I know the 'ulBase' that I have is ADC0_BASE, so since I am only looking for the first sample out of the ADC should my register I am looking at be "(void*)(ADC0_BASE+ADC_O_SSFIFO3)"? This seems correct looking at the hw_adc header plus the example you just gave.

  • Benjamin Fitzpatrick said:

    Currently I am using ADC Sequencer 3, which only has a fifo depth of 1, so I do plan to get an interrupt after every sample. I need to guarantee a specific sample rate, so I have a timer set up to trigger the ADC at a defined interval, capture a sample, and DMA it, and then interrupt me when it's done.

    You are setting up a DMA transfer for one item, from the ADC, and getting an interrupt for each sample?  How is that better than getting the interrupt directly from the ADC when it has acquired the sample, and then transferring it within your ADC interrupt handler?  There is a certain amount of overhead associated with using the uDMA and for small transfers like that, it is not really worth it.  Now, if you are talking about setting up a buffer worth of samples, for example 256 samples, then you could set up the DMA and it transfers one sample at each time period, and after 256 have been collected you generate an interrupt.  I think that is a good use of the DMA.

    Benjamin Fitzpatrick said:

    I would be thrilled if there were a way to guarantee a specific sample rate between ADC steps so I could use something more like your sequence step listing, but I think using the timer is the only way to do this, correct?

    There is a fundamental sample rate control for the ADC, but it ranges from 1Msample/sec to 125ksample/sec.  So if you need slower than that (likely) then you have to use a timer.

    Benjamin Fitzpatrick said:

    One question I still had, I know the 'ulBase' that I have is ADC0_BASE, so since I am only looking for the first sample out of the ADC should my register I am looking at be "(void*)(ADC0_BASE+ADC_O_SSFIFO3)"? This seems correct looking at the hw_adc header plus the example you just gave.

    Yes, that is right.

  • Joe - good questions! I'll try and answer.

    Stellaris Joe said:
    You are setting up a DMA transfer for one item, from the ADC, and getting an interrupt for each sample?  How is that better than getting the interrupt directly from the ADC when it has acquired the sample, and then transferring it within your ADC interrupt handler? 

    I was still hopeful that I would be able to set up a multi-sample acquisition directly in the ADC, in which case I'd be able to use this DMA code to pull all the samples out at once. I only currently had it set up to do the one sample because I'm doing the timer interrupt thing.

    Stellaris Joe said:
    There is a fundamental sample rate control for the ADC, but it ranges from 1Msample/sec to 125ksample/sec.  So if you need slower than that (likely) then you have to use a timer.

    Yes, we are slower than that. Since it looks like the timer is the way to go, we will only be capturing one sample at a time - which means your comment about DMA is spot-on, and we should just use an interrupt and read the sample from the fifo directly. 

    Thanks for all the information!

  • hi,

      I have a doubt in configuring the DMA for ADC. I am using TIVA tm4c1294 launch pad.I have added my code

    /*  DMA configuration

    */

    SysCtlPeripheralEnable(SYSCTL_PERIPH_UDMA);
        SysCtlPeripheralReset(SYSCTL_PERIPH_UDMA);
        uDMAEnable();
        uDMAControlBaseSet(pui8ControlTable);

        uDMAChannelAssign(UDMA_CH14_ADC0_0);

        uDMAChannelAttributeDisable(UDMA_CHANNEL_ADC0,UDMA_ATTR_ALL);

        uDMAChannelControlSet((UDMA_CHANNEL_ADC0|UDMA_PRI_SELECT),(UDMA_ARB_8|UDMA_SRC_INC_NONE|UDMA_DST_INC_32|UDMA_SIZE_32));

        uDMAChannelTransferSet((UDMA_CHANNEL_ADC0|UDMA_PRI_SELECT),UDMA_MODE_BASIC, (void*)(ADC0_BASE+ADC0_SSFIFO0),&value,8);


        uDMAChannelAttributeEnable(UDMA_CHANNEL_ADC0,UDMA_ATTR_USEBURST);

         uDMAChannelEnable(UDMA_CHANNEL_ADC0);

    /*  ADC configuration

    */

        SysCtlPeripheralEnable(SYSCTL_PERIPH_ADC0);
        SysCtlPeripheralReset(SYSCTL_PERIPH_ADC0);
        SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOE);
        GPIOPinTypeADC(GPIO_PORTE_BASE,GPIO_PIN_0);
        ADCClockConfigSet(ADC0_BASE,ADC_CLOCK_SRC_PLL,ADC_CLOCK_RATE_FULL);
        ADCSequenceConfigure(ADC0_BASE,0,ADC_TRIGGER_TIMER,0);
        ADCSequenceStepConfigure(ADC0_BASE,0,0,ADC_CTL_CH3|ADC_CTL_END|ADC_CTL_IE);
        ADCSequenceDMAEnable(ADC0_BASE,0);
        ADCSequenceEnable(ADC0_BASE,0);
        ADCIntEnableEx(ADC0_BASE,ADC_INT_DMA_SS0);
        IntEnable(INT_ADC0SS0);

    The problem is I am getting the ADC_DMA interrupt but the data are not being transfered.