Hello:
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.
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.
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.
Hello Rocky:
In your last comment, you are correct
Initial_DMA_Descriptor.next = (uint32_t)&ADC_TransferDescriptors[1];
I was playing with the settings to see if something would change but forgot to change it back.
I tried 1-4. None of them had an effect by themselves BUT if I combined them, I got different behavior. if I set the descriptors for ping pong like in 2.), Set the DMA transfer size to 16, Set the Match reload to 1000 AND the the ADCCLKDIV to 15, the data looked better. It still isn't correct as the waveform is being chopped in unexpected locations.
Notice the discontinuity at sample 22. I would expect a discontinuity at 16 or 32.....
With this same setup, If I use 4 descriptors, it would break again.
Also, if I change the DMA transfer size (say to 32 or 64), the data is messed up like before. there seems like a strange interplay between the ADC and DMA controller.
Let me know if there are any other tests I can run here. I have tried many permutations and I think there is some sort of synchronization bug in the chip.
Any help would be greatly appreciated.
Hi Eli Hughes,
I've adapted the adc demo (coming from the LPCOpen library ) to integrate with the DMA feature which is based on your sample code, and please have a try.
Hope it helps.
Have a great day,
Ping
-----------------------------------------------------------------------------------------------------------------------
Note: If this post answers your question, please click the Correct Answer button. Thank you!
-----------------------------------------------------------------------------------------------------------------------
Hi Eli Hughes,
Thank you for your interest in NXP Semiconductor products and the opportunity to serve you.
I was wondering if you can share the sample code, then I can replicate the issue when I run the code on the board.
I'm looking forward to your reply.
Have a great day,
Ping
Have a great day,
Ping
-----------------------------------------------------------------------------------------------------------------------
Note: If this post answers your question, please click the Correct Answer button. Thank you!
-----------------------------------------------------------------------------------------------------------------------
Hello:
I have zipped my project Files. There are 6 Projects to be imported. It is a dual core setup for the LPC54114 so I included the M4, M0 Projects as well as the chip and board support libraries. The "M4" project is where all the code is at. I am using LPCXpresso 8.2.2
Now, I have some details about the tests I ran to illustrate the behavior I am observing.
For my setup, I am using am LPCXpresso 54114 board with the OLED shield. (The OLED shied really isn't used but it is plugged on top). I am feeding header Pin A0 (which is ADC Channel 1) with a 30KHz sine wave. The DC Offset is 1.5v and the amplitude is 1v.
In the M4 Project, uncomment line 189 and comment out Line 186:
//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;
In this test case, the DMA will capture four blocks of 8 samples from the ADC. There is a 32-element array called "CaptureData" that the 4 descriptors point to. Here we are terminating the DMA oepration by setting the "next" field to "null" in the final descriptor.
Download and run the code. If you pause the program and look at capture data in the watch window, you can get data that looks like this: (I plotted in Excel)
The waveform looks correct. CaptureData is a 32-element array of 16 bit unsigned values (the upper 12 are the ADC Codes). The mean value looks correct as does the amplitude. Pretty much a text book data capture.
In the M4 Project, uncomment line 186 and comment out Line 189:
//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 fill look fine.
//ADC_TransferDescriptors[3].next = 0;
All I am doing is linking the last descriptor back to the 1st for continuous operation. If you run this code, I would expect that I could pause the program at any time and see a nice sine wave in 24 / 32 of the samples. (There are always 8 that are being filled in by the active descriptor)
When I pause and look at the "CaptureData" array, here is what I get:
Notice that mean value looks correct but the data is out of order.
I am pretty sure this is a synchronization issue between the ADC and the DMA.
A.) If I use an ADC Interrupt routine to fill a buffer, the data always looks correct
B.) If I have the DMA transfer a variable of known value as the source address (instead of using the ADC as the source), the data always looks correct.
C.) If you change the capture data to be 32-bit and update the descriptors to be 32-bit you can see the other ADC flag bits. In all cases, the codes transferred from the ADC are have valid data. I.E. the DataValid bit is set, etc. It is just the result portion is screwy.
I hope this helps in being able to recreate the problem on your end. I have not found any examples in the LPCOpen package that using a Ping-Pong style transfer as described on page 160 of User Manual 1.4 UM10914. My fear is that this feature has never been tested with the ADC!