AnsweredAssumed Answered

LPC54114 DMA Linked Transfer Ping Pong - Circular Buffers

Question asked by Eli Hughes on Dec 28, 2016
Latest reply on Jan 16, 2017 by Rocky Song

Hello:

 

Introduction:

 

I am having a difficult problem with the DMA engine in the LPC54114.     I am trying to use the DMA in a continuously linked transfer described on page 160 of User Manual 1.4 UM10914.       Instead of a ping pong,  I am setting up 4 descriptors where the last one links back to the 1st.   I am transferring data from the ADC into some memory buffers.   To test,  I am feeding the ADC with a known frequency sine wave.

 

Observed Issue:

 

The 1st four transfers always work OK.   If I terminate the transfer at the 4th descriptor,  I can looked at the data and it is correct.     If I link the 4th descriptor back to the 1st,   data in my memory buffer is incorrect after the 1st four transfers.   It looks like it is ADC data BUT it is out of order.    I cannot find a pattern to the out of order data but it only appears to occur after the 1st set of 4 descriptors are executed. 

 

Also note,  that the DMA is continually operating.  It is doing the correct number if transfers,  it just is not moving to the correct destination addresses.   It does appear the DMA is moving data into the correct region,  but the actual ordering is messed up.    I am monitoring via a pin on an o-scope when I get a DMA IRQ as well as when the ADC is triggered via the SCT.   I get ADC & DMA pulses at the correct rate, etc.

Background:

 

Here is a description of of the DMA setup.

 

/*
      *  We need a total of 5 Descriptors
      *
      * 1 Descriptor that is in the main SRAM table to start
      *
      * 4 to maintain the continuous transfer.    (See Page 160 of User Manual 1.4 UM10914)
      *
      * There will always be 1 buffer getting new data while the other 3 are being processed.
      *
      *
      */


     /* DMA descriptor for memory to memory operation - note that addresses must
      *  be the END address for src and destination, not the starting address.
      *    DMA operations moves from end to start.
      *
      */


     /*
     *
     *         +------------+
     *        |   Initial  |       Next
     *        |            +------------+
     *        | Descriptor |            |
     *        +------------+            |
     *                                  |
     *        +------------+      +-----v------+      +------------+      +------------+
     *        |    ADC     | Next |    ADC     | Next |    ADC     | Next |    ADC     | Next
     *  +-----> Descriptor +------> Descriptor +------> Descriptor +------> Descriptor +-----+
     *  |     |     0      |      |     1      |      |     2      |      |     3      |     |
     *  |     +------------+      +------------+      +------------+      +------------+     |
     *  |                                                                                    |
     *  |                                                                                    |
     *  +------------------------------------------------------------------------------------+
     *
     */

  I am using the setup to get a set of 4 buffers such that I can have 4 at any time to process

 

/*

In the figure below,   B0,B1 and B2 form a complete buffer that
needs to be processed.   Since we are doing overlaps of 3,
we can wrap the pointer to the data exactly when we need to.


Buffer State      0               1              2              3

              +----------+   +----------+   +----------+   +----------+
              |          |   |          |   |          |   |          |
              |   Just   |   |          |   |          |   |  Next To |
              |  Filled  |   |          |   |          |   |    Fill  |
              |    B2    |   |    B1    |   |    B0    |   |          |
              +----------+   +----------+   +----------+   +----------+
              |          |   |          |   |          |   |          |
              | Next To  |   |   Just   |   |          |   |          |
              |   Fill   |   |  Filled  |   |          |   |          |
              |          |   |    B2    |   |    B1    |   |    B0    |
              +----------+   +----------+   +----------+   +----------+
              |          |   |          |   |          |   |          |
              |          |   | Next To  |   |   Just   |   |          |
              |          |   |   Fill   |   |  Filled  |   |          |
              |    B0    |   |          |   |    B2    |   |    B1    |
              +----------+   +----------+   +----------+   +----------+
              |          |   |          |   |          |   |          |
              |          |   |          |   | Next To  |   |   Just   |
              |          |   |          |   |   Fill   |   |  Filled  |
              |    B1    |   |    B0    |   |          |   |    B2    |
              +----------+   +----------+   +----------+   +----------+
  */

 

 

Here is the Setup routine:  

 

#define NUM_BUFFERS               4
#define DMA_TRANSFER_SIZE             8

__BSS(RAM2) ALIGN(512) DMA_CHDESC_T ADC_TransferDescriptors[4];

uint16_t CapturedData[NUM_BUFFERS * DMA_TRANSFER_SIZE];

void InitADC()
{

     DMA_CHDESC_T Initial_DMA_Descriptor;



     /***
      *      ____                        _
      *     / ___|  __ _ _ __ ___  _ __ | | ___
      *     \___ \ / _` | '_ ` _ \| '_ \| |/ _ \
      *      ___) | (_| | | | | | | |_) | |  __/
      *     |____/ \__,_|_| |_| |_| .__/|_|\___|
      *     |  _ \ __ _| |_ ___   |_|
      *     | |_) / _` | __/ _ \
      *     |  _ < (_| | ||  __/
      *     |_|_\_\__,_|\__\___|
      *     / ___|  ___| |_ _   _ _ __
      *     \___ \ / _ \ __| | | | '_ \
      *      ___) |  __/ |_| |_| | |_) |
      *     |____/ \___|\__|\__,_| .__/
      *                          |_|
      */


     /*
      *
      * Configure SCT0 channel 7.   This is hardwired in the LPC54114 to
      * trigger the ADC
      *
      */


      /* Initialize the SCT as PWM and set frequency */

     Chip_SCTPWM_Init(LPC_SCT);

     /* Stop the SCT before configuration */
     Chip_SCTPWM_Stop(LPC_SCT);

     /* Set MATCH0 for max limit */
     LPC_SCT->REGMODE_L = 0;
     LPC_SCT->REGMODE_H = 0;
     Chip_SCT_SetMatchCount(LPC_SCT, SCT_MATCH_0, 0);

     //At the high frequencies,  we have rough control over frequency
     //Starting with a 640Khz Fs
     //A value of 150 maps evenly to 640Khz.   (96MHz/150)
     Chip_SCT_SetMatchReload(LPC_SCT, SCT_MATCH_0, 960);
     LPC_SCT->EVENT[0].CTRL = 1 << 12;
     LPC_SCT->EVENT[0].STATE = 1;

     /* Set SCT Counter to count 32-bits and reset to 0 after reaching MATCH0 */
     Chip_SCT_Config(LPC_SCT, SCT_CONFIG_32BIT_COUNTER | SCT_CONFIG_AUTOLIMIT_L);

     /* Use SCT0_OUT7 pin to monitor the ADC trigger signal*/
     Chip_IOCON_PinMuxSet(LPC_IOCON, 1, 14, IOCON_FUNC3 | IOCON_MODE_INACT | IOCON_DIGITAL_EN | IOCON_INPFILT_OFF);

     Chip_SCTPWM_SetOutPin(LPC_SCT, SCT_PWM_OUT, SCT_PWM_PIN_OUT);

     //Use 1tick of,  the other ticks off for a nice pulse
     Chip_SCTPWM_SetDutyCycle(LPC_SCT, SCT_PWM_OUT, 1);

     Chip_SCTPWM_Start(LPC_SCT);

     /***
      *      ____  __  __    _
      *     |  _ \|  \/  |  / \
      *     | | | | |\/| | / _ \
      *     | |_| | |  | |/ ___ \
      *     |____/|_|  |_/_/   \_\
      *     / ___|  ___| |_ _   _ _ __
      *     \___ \ / _ \ __| | | | '_ \
      *      ___) |  __/ |_| |_| | |_) |
      *     |____/ \___|\__|\__,_| .__/
      *                          |_|
      */




     /*
      *  We need a total of 5 Descriptors
      *
      * 1 Descriptor that is in the main SRAM table to start
      *
      * 4 to maintain the continuous transfer.    (See Page 160 of User Manual 1.4 UM10914)
      *
      * There will always be 1 buffer getting new data while the other 3 are being processed.
      *
      *
      */


     /* DMA descriptor for memory to memory operation - note that addresses must
      *  be the END address for src and destination, not the starting address.
      *    DMA operations moves from end to start.
      *
      */


     /*
     *
     *         +------------+
     *        |   Initial  |       Next
     *        |            +------------+
     *        | Descriptor |            |
     *        +------------+            |
     *                                  |
     *        +------------+      +-----v------+      +------------+      +------------+
     *        |    ADC     | Next |    ADC     | Next |    ADC     | Next |    ADC     | Next
     *  +-----> Descriptor +------> Descriptor +------> Descriptor +------> Descriptor +-----+
     *  |     |     0      |      |     1      |      |     2      |      |     3      |     |
     *  |     +------------+      +------------+      +------------+      +------------+     |
     *  |                                                                                    |
     *  |                                                                                    |
     *  +------------------------------------------------------------------------------------+
     *
     */



     ADC_TransferDescriptors[0].source = (uint32_t)&LPC_ADC->DAT[1];
     ADC_TransferDescriptors[1].source = (uint32_t)&LPC_ADC->DAT[1];
     ADC_TransferDescriptors[2].source = (uint32_t)&LPC_ADC->DAT[1];
     ADC_TransferDescriptors[3].source = (uint32_t)&LPC_ADC->DAT[1];


     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];
     ADC_TransferDescriptors[3].next = (uint32_t)&ADC_TransferDescriptors[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_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);


     /***
           *         _    ____   ____
           *        / \  |  _ \ / ___|
           *       / _ \ | | | | |
           *      / ___ \| |_| | |___
           *     /_/__ \_\____/ \____|
           *     / ___|  ___| |_ _   _ _ __
           *     \___ \ / _ \ __| | | | '_ \
           *      ___) |  __/ |_| |_| | |_) |
           *     |____/ \___|\__|\__,_| .__/
           *                          |_|
           */


             ADC_BufferState = 0;

             /* Initialization ADC to 12 bit and set clock divide to 2 to operate synchronously at System clock/2 */
              Chip_ADC_Init(LPC_ADC, ADC_CR_RESOL(3) | ADC_CR_CLKDIV(0));

              //select ADC Channel 1 as input
              Chip_IOCON_PinMuxSet(LPC_IOCON, 0, 30, IOCON_FUNC0 | IOCON_ANALOG_EN| IOCON_INPFILT_OFF);
              /* Enable Channel 1 conversion in Sequence A */

              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_MODE_EOS);


              /* 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);
                  /* Enable SysTick Timer */
                  //SysTick_Config(SystemCoreClock / 100);

              }
              else {
                  DEBUGSTR("ADC Calibration Failed \r\n");
                  Board_LED_Set(0, true);
                  return 0;
              }

}

 

The DMA handler:

 

void DMA_IRQHandler(void)
{


     Chip_GPIO_SetPinState(LPC_GPIO,TEST_PORT,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);
     }

     ADC_BufferState++;
     ADC_BufferState &= 0x03;

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

 

I can upload my entire source code if needed.   The only other pieces I have not shown is the main routine which calls the InitADC() function.

 

I will certainly post the results if I solve the issue.   Hopefully someone else has seen this behavior.

Outcomes