Ping-Pong是一种特殊的已链接传输情况,由于与更复杂版本的已链接传输相比,它的使用通常更为频繁。
Ping-Pong传输通常至少使用两个缓冲区。不论何时,DMA操作都会载入或取消载入一个缓冲区。另一个缓冲区的操作与由软件控制的操作相反,这样在DMA控制器当前使用的缓冲区为满或空时,可将该缓冲区准备好以供使用。图1显示了从外设至存储器中两个缓冲区的Ping-Pong传输的描述符示例。
图1
为了将ADC转换结果连续传输到RAM,我将使用四个DMA描述符以Ping-Pong模式工作。
实现这一目标,如图2所示。
图 2 Ping-Pong 模式下的数据流
图3 LPCXpressor54114板
该代码基于periph_adc示例程序,使用SCTimer输出作为ADC的硬件触发信号,同时,ADC转换后的值会自动传输到指定的RAM区域。
#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)
{}
}
图4
4. 为了验证结果,我收集了多个数据组,并使用Excel将这些数据制成图表进行检查。图6是一个示例。
图5
图6
图7
图8
您好@jeremyzhou,
我遵循您的实现并进行了修改,以更新LPC54102上SCT块的匹配寄存器。我正在向该寄存器输入正弦波的振幅值以生成脉宽调制(SPWM)。
程序有些正常运行,但我注意到程序忽略了查找表(LUT)中的一些值。您对如何解决这个问题有什么建议吗?
我将添加部分代码。
/*****************************************CODE************************************************/
一个小问题。
该示例使用 SCT 触发 ADC。
对于我的代码,我使用了 CTIMER(match event)来触发 ADC 序列,这种方法似乎更简单,也更容易理解。
使用 SCT 有什么优势吗?
能否将描述符定义为常量,并在编译时进行设置,即放在闪存而不是RAM中?
Hi dingelen,
感谢您的回复。
根据上述描述,我仍然不清楚“struck”问题的实际情况,因此建议您进行更多测试并提供更详细的信息。
祝您有美好的一天。
BR,
Jeremy
Hi Jeremy,
感谢您的快速回复。我认为使用LPC55也可以实现相同的功能,但我在这一部分遇到了一些困难:
/* 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)所有这些功能似乎在LPC55 SDK中都不可用。SDK 中的示例也仅使用某种轮询式启动/停止方式,而非自动运行的设置。(请参见 lpcxpresso55s69_lpadc_dma 示例)
如果这最后一部分能够被移植,那就太好了。
谢谢!
Tom
Hi dingelen,
感谢您对演示的兴趣。
1) 是否有适用于 LPC55 系列的相同示例?
--在审查了RM后,我发现LPC55xx系列也支持Ping-Pong功能,因此该示例可以移植到该系列。
祝您有美好的一天。
BR,
Jeremy
超级示例。比SDK里的好多了... 谢谢!
LPC55系列是否有相同的示例可用?与LPC54系列相比,DMA的设置方式似乎有一些不同。