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.
Fig 1
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.
Fig 2 Data flow via Ping-Pong mode
Fig 3 LPCXpressor54114 Board
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)
{}
}
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.
Fig 5
Fig 6
Fig 7
Fig 8
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.
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 ?
Hello @jeremyzhou,
I followed your implementation and modified to update a match register on the SCT block on a LPC54102. I am feeding this register with the values of the amplitud of a sine wave to generate a SPWM.
The program is somewhat working but I noticed that the program is ignoring some values from the LUT. do you have any suggestions on how to address this ???
i will add part of the code.
/*****************************************CODE************************************************/