DMA Ping-Pong application

cancel
Showing results for 
Search instead for 
Did you mean: 

DMA Ping-Pong application

No ratings

DMA Ping-Pong application

Overview

         Ping-pong is a special case of a linked transfer which typically used more frequently than more complicated versions of linked transfers.

A ping-pong transfer usually uses at least two buffers. At any one time, one buffer is being loaded or unloaded by DMA operations. The other buffers have the opposite operation being handled by software, readying the buffer for use when the buffer currently being used by the DMA controller is full or empty. The Fig 1 illustrates an example of descriptors for ping-pong from a peripheral to two buffers in memory.

pastedImage_1.png

Fig 1

Implementation detail

        To continuous transfer the converted result of the ADC to RAM, I’m going to use four 4 DMA descriptors to work in Ping-Pong mode to

achieve this goal as the Fig 2 shows.

pastedImage_5.png

Fig 2 Data flow via Ping-Pong mode

Hardware introduction

  •         LPCXpressor54114 BoardOM13089

pastedImage_8.png

Fig 3 LPCXpressor54114 Board

Example code

       The code is based on the periph_adc demo, using the SCTimer output as the hardware trigger of ADC, meanwhile, the ADC converted value is transferred to the appointed area of RAM automatically.

#include "board.h"

#define SCT_PWM            LPC_SCT

#define NUM_BUFFERS 4
#define DMA_TRANSFER_SIZE 8
#define ADC_INPUT_CHANNEL 1

#define SCT_PWM_RATE   10000          /* PWM frequency 10 KHz */
#define SCT_PWM_PIN_OUT    7          /* COUT7 Generate square wave */
#define SCT_PWM_OUT        1          /* Index of OUT PWM */

uint16_t adcOut;

ALIGN(512) DMA_CHDESC_T ADC_TransferDescriptors[NUM_BUFFERS];


uint16_t CapturedData[32];

uint16_t DMA_Sum=0;

/**
 *
 * ADC IRQ not Used right now... Only for testing
 */
void ADC_SEQA_IRQHandler(void)
{
            /* If SeqA flags is set i.e. data in global register is valid then read it */
        Chip_GPIO_SetPinState(LPC_GPIO, 0, 6, true);
        //DEBUGOUT("ADC Output = %d\r\n", adcOut);
        Chip_GPIO_SetPinState(LPC_GPIO, 0, 6, false);
        Chip_ADC_ClearFlags(LPC_ADC,0xFFFFFFFF);
}


void DMA_IRQHandler(void)
{
        static uint16_t DMA_Sum=0;
        
        DMA_Sum++;
        
         if(DMA_Sum ==8)
         {
           DMA_Sum=4;
         }

       
     Chip_GPIO_SetPinState(LPC_GPIO, 0, 7,true);

     /* Rrror interrupt on channel 0? */
     if ((Chip_DMA_GetIntStatus(LPC_DMA) & DMA_INTSTAT_ACTIVEERRINT) != 0)
     {
          /* This shouldn't happen for this simple DMA example, so set the LED
             to indicate an error occurred. This is the correct method to clear
             an abort. */
          Chip_DMA_DisableChannel(LPC_DMA, DMA_CH0);
          while ((Chip_DMA_GetBusyChannels(LPC_DMA) & (1 << DMA_CH0)) != 0) {}
          Chip_DMA_AbortChannel(LPC_DMA, DMA_CH0);
          Chip_DMA_ClearErrorIntChannel(LPC_DMA, DMA_CH0);
          Chip_DMA_EnableChannel(LPC_DMA, DMA_CH0);
          Board_LED_Set(0, true);
     }

     Chip_GPIO_SetPinState(LPC_GPIO, 0,7,false);

     /* Clear DMA interrupt for the channel */
     LPC_DMA->DMACOMMON[0].INTA = 1;
}





     /***
      *      ____  __  __    _
      *     |  _ \|  \/  |  / \
      *     | | | | |\/| | / _ \
      *     | |_| | |  | |/ ___ \
      *     |____/|_|  |_/_/   \_\
      *     / ___|  ___| |_ _   _ _ __
      *     \___ \ / _ \ __| | | | '_ \
      *      ___) |  __/ |_| |_| | |_) |
      *     |____/ \___|\__|\__,_| .__/
      *                          |_|
      */
void DMA_Steup(void)
{
        DMA_CHDESC_T Initial_DMA_Descriptor;
        
        ADC_TransferDescriptors[0].source = (uint32_t)&LPC_ADC->SEQ_GDAT[0];
     ADC_TransferDescriptors[1].source = (uint32_t)&LPC_ADC->SEQ_GDAT[0];
     ADC_TransferDescriptors[2].source = (uint32_t)&LPC_ADC->SEQ_GDAT[0];
     ADC_TransferDescriptors[3].source = (uint32_t)&LPC_ADC->SEQ_GDAT[0];


     ADC_TransferDescriptors[0].dest = (uint32_t)&CapturedData[(0+1)*DMA_TRANSFER_SIZE-1];
     ADC_TransferDescriptors[1].dest = (uint32_t)&CapturedData[(1+1)*DMA_TRANSFER_SIZE-1];
     ADC_TransferDescriptors[2].dest = (uint32_t)&CapturedData[(2+1)*DMA_TRANSFER_SIZE-1];
     ADC_TransferDescriptors[3].dest = (uint32_t)&CapturedData[(3+1)*DMA_TRANSFER_SIZE-1];

     //The initial DMA desciptor is the same as the 1st transfer descriptor.   It
     //Will link into the 2nd of the main descriptors.

     ADC_TransferDescriptors[0].next = (uint32_t)&ADC_TransferDescriptors[1];
     ADC_TransferDescriptors[1].next = (uint32_t)&ADC_TransferDescriptors[2];
     ADC_TransferDescriptors[2].next = (uint32_t)&ADC_TransferDescriptors[3];

     //Link back to the 1st descriptor
     ADC_TransferDescriptors[3].next = (uint32_t)&ADC_TransferDescriptors[0];

     //For a test,  stop the transfers here.   The sine wave will look fine.
     //ADC_TransferDescriptors[3].next = 0;

     ADC_TransferDescriptors[0].xfercfg = (DMA_XFERCFG_CFGVALID |
                               DMA_XFERCFG_RELOAD  |
                               DMA_XFERCFG_SETINTA |
                               DMA_XFERCFG_WIDTH_16 |
                               DMA_XFERCFG_SRCINC_0 |
                               DMA_XFERCFG_DSTINC_1 |
                               DMA_XFERCFG_XFERCOUNT(DMA_TRANSFER_SIZE));

     ADC_TransferDescriptors[1].xfercfg = ADC_TransferDescriptors[0].xfercfg;
     ADC_TransferDescriptors[2].xfercfg = ADC_TransferDescriptors[0].xfercfg;

     ADC_TransferDescriptors[3].xfercfg = (DMA_XFERCFG_CFGVALID |
                               DMA_XFERCFG_RELOAD  |
                               DMA_XFERCFG_SETINTA |
                              DMA_XFERCFG_WIDTH_16 |
                              DMA_XFERCFG_SRCINC_0 |
                              DMA_XFERCFG_DSTINC_1 |
                              DMA_XFERCFG_XFERCOUNT(DMA_TRANSFER_SIZE));

     Initial_DMA_Descriptor.source = ADC_TransferDescriptors[0].source;
     Initial_DMA_Descriptor.dest =   ADC_TransferDescriptors[0].dest;
     Initial_DMA_Descriptor.next =  (uint32_t)&ADC_TransferDescriptors[1];
     Initial_DMA_Descriptor.xfercfg = ADC_TransferDescriptors[0].xfercfg;

     /* DMA initialization - enable DMA clocking and reset DMA if needed */
     Chip_DMA_Init(LPC_DMA);

     /* Enable DMA controller and use driver provided DMA table for current descriptors */
     Chip_DMA_Enable(LPC_DMA);
     Chip_DMA_SetSRAMBase(LPC_DMA, DMA_ADDR(Chip_DMA_Table));

     /* Setup channel 0 for the following configuration:
        - High channel priority
        - Interrupt A fires on descriptor completion */
     Chip_DMA_EnableChannel(LPC_DMA, DMA_CH0);
     Chip_DMA_EnableIntChannel(LPC_DMA, DMA_CH0);
     Chip_DMA_SetupChannelConfig(LPC_DMA, DMA_CH0,     //(DMA_CFG_PERIPHREQEN     |
                                   (DMA_CFG_HWTRIGEN        |
                                    DMA_CFG_TRIGBURST_BURST |
                                                         DMA_CFG_TRIGTYPE_EDGE   |
                                       DMA_CFG_TRIGPOL_LOW    |    //DMA_CFG_TRIGPOL_HIGH
                                       DMA_CFG_BURSTPOWER_1    |
                                    DMA_CFG_CHPRIORITY(0)
                                         )
                                       );


     //make sure ADC Sequence A interrupts is selected for for a DMA trigger
     LPC_INMUX->DMA_ITRIG_INMUX[0] = 0;

     /* Enable DMA interrupt */
     NVIC_EnableIRQ(DMA_IRQn);

     // The 1st descriptor is set up through the registers.

     /* Setup transfer descriptor and validate it */
     Chip_DMA_SetupTranChannel(LPC_DMA, DMA_CH0, &Initial_DMA_Descriptor);

     //Use the transfer configuration for our 4 main descriptors
     Chip_DMA_SetupChannelTransfer(LPC_DMA, DMA_CH0,     ADC_TransferDescriptors[0].xfercfg);
     Chip_DMA_SetValidChannel(LPC_DMA, DMA_CH0);      
}

void SCT_PWM_Generate(void)
{
         /* Initialize the SCT as PWM and set frequency */
     Chip_SCTPWM_Init(SCT_PWM);
     Chip_SCTPWM_SetRate(SCT_PWM, SCT_PWM_RATE);

     /* Setup Board specific output pin */
     Chip_IOCON_PinMuxSet(LPC_IOCON, 1, 14, IOCON_FUNC3 | IOCON_MODE_INACT | IOCON_DIGITAL_EN | IOCON_INPFILT_OFF);
     /* Use SCT0_OUT7 pin */
     Chip_SCTPWM_SetOutPin(SCT_PWM, SCT_PWM_OUT, SCT_PWM_PIN_OUT);

        
     /* Start with 50% duty cycle */
     Chip_SCTPWM_SetDutyCycle(SCT_PWM, SCT_PWM_OUT, Chip_SCTPWM_PercentageToTicks(SCT_PWM, 10));
     Chip_SCTPWM_Start(SCT_PWM);    
}


     /***
           *         _    ____   ____
           *        / \  |  _ \ / ___|
           *       / _ \ | | | | |
           *      / ___ \| |_| | |___
           *     /_/__ \_\____/ \____|
           *     / ___|  ___| |_ _   _ _ __
           *     \___ \ / _ \ __| | | | '_ \
           *      ___) |  __/ |_| |_| | |_) |
           *     |____/ \___|\__|\__,_| .__/
           *                          |_|
           */
void ADC_Steup(void)
{
    /*Set Asynch Clock to the Main clock*/
    LPC_SYSCON->ADCCLKSEL = 0;
    //Set the divider to 1 and enable.  note,  the HALT bit (30) and RESET (29) are not in the manual
    LPC_SYSCON->ADCCLKDIV = 0;
     /* Initialization ADC to 12 bit and set clock divide to 1 to operate synchronously at System clock */
    Chip_ADC_Init(LPC_ADC, ADC_CR_RESOL(3) | ADC_CR_CLKDIV(0)| ADC_CR_ASYNC_MODE);   
    //select ADC Channel 1 as input
    Chip_IOCON_PinMuxSet(LPC_IOCON, 0, 30, IOCON_FUNC0 | IOCON_ANALOG_EN| IOCON_INPFILT_OFF);   
    LPC_ADC->INSEL = 0x01;
    Chip_ADC_SetupSequencer(LPC_ADC,ADC_SEQA_IDX,                                            
                              ADC_SEQ_CTRL_SEQ_ENA |
                              ADC_SEQ_CTRL_CHANNEL_EN(ADC_INPUT_CHANNEL) |
                                                ADC_SEQ_CTRL_TRIGGER(2) |
                              ADC_SEQ_CTRL_HWTRIG_POLPOS |
                                                ADC_SEQ_CTRL_HWTRIG_SYNCBYPASS |
                              ADC_SEQ_CTRL_MODE_EOS |
                                                ADC_SEQ_CTRL_SEQ_ENA);
    /* Enable Sequence A interrupt */
    Chip_ADC_EnableInt(LPC_ADC, ADC_INTEN_SEQA_ENABLE);
    
    /* Calibrate ADC */
    if(Chip_ADC_Calibration(LPC_ADC) == LPC_OK) {
        /* Enable ADC SeqA Interrupt */
        NVIC_EnableIRQ(ADC_SEQA_IRQn);
    }
    else {
        DEBUGSTR("ADC Calibration Failed \r\n");
        return ;
    }
}

int main(void)
{
  
    SystemCoreClockUpdate();
    Board_Init();
    
    DMA_Steup();
    ADC_Steup();
    SCT_PWM_Generate();

    
    while(1)
    {}
    
}
‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍

 Verification

  1.      Building the project, then click the  pastedImage_25.pngto debug;
  2.        Generate the sine wave: 1 KHz, 幅度:0~2 V,feed the wave the ADC via the J9_1(P0_30-ADC1);
  3.         Setting the breakpoint (Fig 4) to observe the ADC converted value CapturedData[32];

pastedImage_26.png

Fig 4

                       4. To verifying the result, I collect several group of data and use the Excel to make these data graphical for checking. Fig 6 is an example.

pastedImage_32.png

Fig 5

pastedImage_33.png

Fig 6

pastedImage_34.png

Fig 7

pastedImage_35.png

Fig 8

Labels (2)
Tags (2)
Comments

Super example. Much better than the ones in the SDK... Thanks!

Is there a same example available for the LPC55-series? There seem to be some differences in how to set-up the DMA compared to the LPC54 series.

Hi dingelen,

Thanks for your interest in the demo.
1) Is there a same example available for the LPC55-series?
-- After reviewing the RM, I find that the LPC55xx series also support the Ping-Pong feature, so this sample is able to be ported to the series.

pastedImage_1.png

Have a great day.

BR,

Jeremy

Hi Jeremy,

thanks for the fast reply. I figured the same was possible with the LPC55 but I'm somewhat stuck with this part:

     /* DMA initialization - enable DMA clocking and reset DMA if needed */
     Chip_DMA_Init(LPC_DMA);

     /* Enable DMA controller and use driver provided DMA table for current descriptors */
     Chip_DMA_Enable(LPC_DMA);
     Chip_DMA_SetSRAMBase(LPC_DMA, DMA_ADDR(Chip_DMA_Table));

     /* Setup channel 0 for the following configuration:
        - High channel priority
        - Interrupt A fires on descriptor completion */

     Chip_DMA_EnableChannel(LPC_DMA, DMA_CH0);
     Chip_DMA_EnableIntChannel(LPC_DMA, DMA_CH0);
     Chip_DMA_SetupChannelConfig(LPC_DMA, DMA_CH0,     //(DMA_CFG_PERIPHREQEN     |
                                   (DMA_CFG_HWTRIGEN        |
                                    DMA_CFG_TRIGBURST_BURST |
                                                         DMA_CFG_TRIGTYPE_EDGE   |
                                       DMA_CFG_TRIGPOL_LOW    |    //DMA_CFG_TRIGPOL_HIGH
                                       DMA_CFG_BURSTPOWER_1    |
                                    DMA_CFG_CHPRIORITY(0)
                                         )
                                       );


     //make sure ADC Sequence A interrupts is selected for for a DMA trigger
     LPC_INMUX->DMA_ITRIG_INMUX[0] = 0;

     /* Enable DMA interrupt */
     NVIC_EnableIRQ(DMA_IRQn);

     // The 1st descriptor is set up through the registers.

     /* Setup transfer descriptor and validate it */
     Chip_DMA_SetupTranChannel(LPC_DMA, DMA_CH0, &Initial_DMA_Descriptor);

     //Use the transfer configuration for our 4 main descriptors
     Chip_DMA_SetupChannelTransfer(LPC_DMA, DMA_CH0,     ADC_TransferDescriptors[0].xfercfg);
     Chip_DMA_SetValidChannel(LPC_DMA, DMA_CH0)

All these functions don't seem to be available in the LPC55 SDK. The examples in the SDK also only use some kind of polled start-stop, not an automaticly running setup. (See lpcxpresso55s69_lpadc_dma example)

It would be great if this last part could get ported.

Thanks!

Tom

Hi dingelen,

Thanks for your reply.
According to the above description, I still have no idea with the 'struck' issue actually, so I'd like to suggest that you'd better do more testing and provide more detailed information about the issue.

Have a great day.

BR,

Jeremy

Can the descriptors be defined as constants and setup at compile time, i.e. they'd be in FLASH not RAM?

A question to a minor point.

The example uses SCT to trigger the ADC.

For my code, I used a CTIMER (match event) to trigger the ADC sequence, which seems simpler, and easier to comprehend.

Is there an advantage of using the SCT ?

Version history
Revision #:
2 of 2
Last update:
‎09-10-2020 02:49 AM
Updated by:
 
Contributors