Buffer multiple ADC channel results using DMA?

Tip / Sign in to post questions, reply, level up, and achieve exciting badges. Know more

cross mob
lock attach
Attachments are accessible only for community members.
KyTr_1955226
Level 6
Level 6
250 sign-ins 10 likes given 50 solutions authored

Hi all,

I've got some questions regarding a specific ADC+DMA setup I'd like to accomplish, but I'm not sure how to make it happen (I'm rather new to using DMA).

I'd like to, through DMA, have the SAR ADC cycle between 3 channels and maintain 3 separate ADC results buffers.  The idea being that I could at any time read the last 8 (or really any arbitrary number) readings for any of the inputs and quickly generate an average.

My schematic looks like this:

pastedImage_0.png

The idea is that the PWM module provides a short (about 1.2ms) pulse from the PWM module.  The rising edge starts the ADC Conversion, the EOC signal triggers the DMA to transfer the reading to SRAM, and the interrupt on the falling edge of the PWM increments the mux(es).

The PWM Period is 40ms, so there should be more than enough time to switch the analog mux for the next reading (I plan on tightening this time up once I get the general system working the way I want).  The Timer and UART are just for serial reporting.  I've confirmed the EOC signal is coming out of the ADC well before the falling edge of my PWM.

What's getting me stuck is configuring the DMA to properly increment the destination address and reset to the first address after 8 DMA transactions.  My idea was to count the number of transfers in their respective ISRs and reset the DMA when it reaches the end of it's respective buffer.  I.E.

void DMA0_Reset(void){

    ADC_CH0_DMA_DmaRelease();

    DMA_Ch0_Init();

}

void ISR_DMA0_DONE_Interrupt_InterruptCallback(void){

    static uint8_t count = 0;

    ISR_DMA0_DONE_ClearPending();

    /*DMA is done transferring reading to SRAM*/

    if (count++ >= ADC_BUFF_SIZE-1){

        /*End of buffer, reset DMA to initial state*/

        DMA0_Reset();

        count = 0;

    }

    DMADone = true;

}

I am initializing the DMA though the autogenerated code of the DMA wizard.  An init function like this one is being called for all 3 DMA channels before the main loop, as well as being used to reinitialize the channel in the Reset function:

void DMA_Ch0_Init(void){

    /* DMA Configuration for ADC_CH0_DMA */

    ADC_CH0_DMA_Chan = ADC_CH0_DMA_DmaInitialize(ADC_CH0_DMA_BYTES_PER_BURST, ADC_CH0_DMA_REQUEST_PER_BURST,

        HI16(ADC_CH0_DMA_SRC_BASE), HI16(ADC_CH0_DMA_DST_BASE));

    ADC_CH0_DMA_TD[0] = CyDmaTdAllocate();

    CyDmaTdSetConfiguration(ADC_CH0_DMA_TD[0], 2, CY_DMA_DISABLE_TD, ADC_CH0_DMA__TD_TERMOUT_EN | CY_DMA_TD_INC_DST_ADR );

    CyDmaTdSetAddress(ADC_CH0_DMA_TD[0], LO16((uint32)ADC_SAR_SAR_WRK0_PTR), LO16((uint32)&ADC_Data1[0]));

    CyDmaChSetInitialTd(ADC_CH0_DMA_Chan, ADC_CH0_DMA_TD[0]);

}

My PWM TC (falling edge) ISR is as follows:

void ISR_PWM_Interrupt_InterruptCallback(void){

    /*Falling edge of PWM, increment the mux input*/

    ADC_PWM_ReadStatusRegister();

    ISR_PWM_ClearPending();

   

    if (DMADone){

        DMADone = false;

       

        switch (ADChannel){

            case 0:

                CyDmaChEnable(ADC_CH1_DMA_Chan, 0);

                ADChannel++;

            break;

           

            case 1:

                CyDmaChEnable(ADC_CH2_DMA_Chan, 0);

                ADChannel++;

            break;

           

            case 2:

                CyDmaChEnable(ADC_CH0_DMA_Chan, 0);

                ADChannel = 0;

            break;

           

        }

        MuxCtrl_Write(ADChannel);

    }

}

What I'm seeing with this project is that the first ADC input readings just seem to overrun into all 3 of my ADC buffers, incrementing the destination address, but never incrementing the muxes and switching to the next channel.  I have attached the project, any suggestions to where I'm going wrong would be appreciated (I'm sure there's probably a better way to accomplish this).

So as far as specific questions go:

1) How should I go about reinitializing a DMA channel back to the starting address?

2) Would I maybe be better off using a single DMA component to dump all my readings into a single buffer, then pulling out the values I want for my average with some pointer math?

3) I notice that when I set preserveTD to 1 when I enable a DMA channel, my mux switching logic and switching of buffers seems to work, but I never fill anything other than the first index of the buffer for each channel.  When preserveTD is 0, the first channel just overruns all the buffers.  Which of these settings should I actually be using in this case?

Thanks in advance for any assistance!

I have attached the project if someone wants to have a deeper look.  I'm just trying to prototype this on a CY8CKIT-050.

0 Likes
1 Solution

I have had the exact same issue before with a design where I needed multiplexed inputs to the ADC but the sequencing SAR was too complex in terms of UDB.

My solution was to essentially create my own smaller sequencing SAR ADC and it worked quite well.

An overview of what I did that might help you:

I didn't service anything in interrupt routines. I handled the start of conversion, end of conversion, dma transaction chaining and channel switching logic in hardware.

The general process here is you use a digital clock to make your sample requests, at your desired sample rate. When you get a clock edge, or an eoc occurs from the previous channel, you switch to your next channel and then trigger a delay to occur (about 2us or so) that will then send a signal to your soc of the ADC. Essentially this provides a small buffer of time between the actual channel switching and the start of conversions, which is needed for the switch to be settled otherwise your results are noisy. I do the delay with PWM setup for a hardware trigger (from the mux switch signal) that has about a 2us period and the cmp being such that a small pulse is generated at the end of the period.

Then you just need your logic for the switching channels. I handled it by creating two custom components, one that managed the sequencing for my mux (can be lookup table based, or digital logic, or verilog), the other to manage the timings between channel switching, start of conversions, and end of conversions.

View solution in original post

12 Replies